-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a0aa42c
commit b460041
Showing
5 changed files
with
289 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import json | ||
import base64 | ||
import io | ||
import time | ||
from PIL import Image | ||
import boto3 | ||
from botocore.exceptions import ClientError | ||
|
||
def lambda_handler(event, context): | ||
### get the meme id from path parameters | ||
meme_id = event["pathParameters"]["id"] | ||
|
||
### get entry from the database | ||
dynamodb = boto3.resource("dynamodb") | ||
table = dynamodb.Table("im-memes") | ||
db_meme = table.get_item(Key={"id": meme_id}) | ||
|
||
### return an error code if the meme's not in the database | ||
meme = db_meme.get("Item") | ||
|
||
if not meme: | ||
return { | ||
"statusCode": 404, | ||
"body": json.dumps("no such meme") | ||
} | ||
|
||
# create an in-memory file | ||
with io.BytesIO() as in_mem_file: | ||
### download the image data into the in-memory | ||
### file. return error code if it doesn't exist | ||
s3 = boto3.resource("s3") | ||
bucket = s3.Bucket("<username>-quantic-im-memes") | ||
|
||
try: | ||
bucket.download_fileobj(f"/memes/{meme_id}", in_mem_file) | ||
except ClientError as error: | ||
if error.response["Error"]["Code"] == "404": | ||
return { | ||
"statusCode": 404, | ||
"body": json.dumps("no such meme") | ||
} | ||
else: | ||
raise error | ||
|
||
# load the meme as an image to get its type | ||
image = Image.open(in_mem_file) | ||
|
||
time_now = int(time.time()) | ||
|
||
### build the object to return | ||
return_data = { | ||
"imageUrl": (f"data:image/{image.format};base64," | ||
+ base64.b64encode(in_mem_file.getvalue()).decode("utf-8")), | ||
"id": meme_id, | ||
"userName": meme["userName"], | ||
"timePosted": int(meme["timePosted"]), | ||
"timeToLive": int(meme["timeToDie"]) - time_now, | ||
"likes": meme.get("likes", []) | ||
} | ||
|
||
return { | ||
"statusCode": 200, | ||
"body": json.dumps(return_data) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import json | ||
import boto3 | ||
import base64 | ||
import io | ||
import time | ||
from botocore.exceptions import ClientError | ||
|
||
def lambda_handler(event, context): | ||
### get the S3 service resource | ||
s3 = boto3.resource("s3") | ||
bucket = s3.Bucket("<username>-quantic-im-memes") | ||
|
||
### get all meme entries from the database | ||
dynamodb = boto3.resource("dynamodb") | ||
table = dynamodb.Table("im-memes") | ||
db_memes = table.scan() | ||
memes = db_memes["Items"] | ||
|
||
# build the response for each entry. id, userName, and timePosted | ||
# simply pass through. we compute timeToLive from timeToDie and | ||
# generate the data URL using PIL and base64 | ||
time_now = int(time.time()) | ||
thumbnails = [] | ||
|
||
for meme in memes: | ||
# skip this thumbnail if it's past its time to die | ||
if time_now > int(meme["timeToDie"]): | ||
continue | ||
|
||
# create a thumbnail item with metadata and image data | ||
thumbnail = { | ||
"timeToLive": int(meme["timeToDie"]) - time_now, | ||
"timePosted": int(meme["timePosted"]), | ||
"userName": meme["userName"], | ||
"id": meme["id"] | ||
} | ||
|
||
# load the image into an in-memory file object | ||
with io.BytesIO() as in_mem_file: | ||
try: | ||
bucket.download_fileobj(f"/thumbnails/{meme['id']}", in_mem_file) | ||
except ClientError as error: | ||
if error.response["Error"]["Code"] == "404": | ||
continue | ||
else: | ||
raise error | ||
|
||
# now write the image into the thumbnail as a base64 data URL | ||
# base 64 conversion code courtesy of https://stackoverflow.com/a/68989496/4062628 | ||
thumbnail["imageUrl"] = ( | ||
"data:image/jpeg;base64," | ||
+ base64.b64encode(in_mem_file.getvalue()).decode("utf-8")) | ||
|
||
# add the thumbnail to the response | ||
thumbnails.append(thumbnail) | ||
|
||
return { | ||
"statusCode": 200, | ||
"body": json.dumps(thumbnails) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import json | ||
import boto3 | ||
|
||
def lambda_handler(event, context): | ||
objects = [] | ||
|
||
for item in event["Records"]: | ||
meme_id = item["dynamodb"]["Keys"]["id"]["S"] | ||
objects.append({"Key": f"/memes/{meme_id}"}) | ||
objects.append({"Key": f"/thumbnails/{meme_id}"}) | ||
|
||
s3 = boto3.resource("s3") | ||
bucket = s3.Bucket("jriehl-quantic-im-memes") | ||
bucket.delete_objects(Delete={"Objects": objects}) | ||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import json | ||
import uuid | ||
import time | ||
import base64, binascii | ||
import io | ||
from PIL import Image, UnidentifiedImageError | ||
import boto3 | ||
|
||
def lambda_handler(event, context): | ||
# event is a JSON string as described in https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html | ||
# the client provides data as a dictionary in the "body" entry as follows: | ||
# { "image": <string, base 64 data URL>, | ||
# "userName": <string, user's name> } | ||
|
||
meme_data = json.loads(event["body"]) | ||
|
||
# the data URL format is "data:image/<format>;base64,<data>" so we split | ||
# the data from the header, then the extension from the header | ||
try: | ||
header, data = meme_data["image"].split(";base64,") | ||
extension = header.split("image/")[-1] | ||
except ValueError: | ||
return { | ||
"statusCode": 400, | ||
"body": json.dumps("badly-formed image data") | ||
} | ||
|
||
if extension not in ("bmp", "gif", "jpeg", "png", "tiff"): | ||
return { | ||
"statusCode": 400, | ||
"body": json.dumps("badly-formed image data") | ||
} | ||
|
||
# use Pillow (https://pillow.readthedocs.io/en/stable/index.html) | ||
# to load the image (base 64 conversion code courtesy of | ||
# https://stackoverflow.com/a/68989496/4062628) | ||
|
||
# get the PIL image from the base64 data | ||
try: | ||
image = Image.open(io.BytesIO(base64.decodebytes(bytes(data, "utf-8")))) | ||
except (UnidentifiedImageError, binascii.Error): | ||
return { | ||
"statusCode": 400, | ||
"body": json.dumps("badly-formed image data") | ||
} | ||
|
||
# use a random UUID as the id for the meme (both the full image | ||
# and thumbnail) | ||
id = uuid.uuid4().hex | ||
|
||
# get the S3 bucket | ||
s3 = boto3.resource("s3") | ||
bucket = s3.Bucket("<username>-quantic-im-memes") | ||
|
||
# save it in an in-memory file-like object | ||
# if you're unfamiliar with the "with" statement, read this: https://www.geeksforgeeks.org/with-statement-in-python/ | ||
with io.BytesIO() as in_mem_file: | ||
image.save(in_mem_file, format=image.format) | ||
in_mem_file.seek(0) | ||
bucket.upload_fileobj(in_mem_file, f"/memes/{id}") | ||
|
||
# make a thumbnail and repeat the process above | ||
image.thumbnail((200, 200)) | ||
|
||
# JPG doesn't support an alpha channel, so we need to remove it | ||
# if it exists. conversion courtesy of | ||
# https://stackoverflow.com/a/49255449/4062628 | ||
if image.mode in ("RGBA", "P"): | ||
image = image.convert("RGB") | ||
|
||
with io.BytesIO() as in_mem_file: | ||
image.save(in_mem_file, format="jpeg") | ||
in_mem_file.seek(0) | ||
bucket.upload_fileobj(in_mem_file, f"/thumbnails/{id}") | ||
|
||
# write the entry to the database | ||
posted = int(time.time()) # current epoch time in seconds | ||
timeToDie = posted + 24 * 60 * 60 | ||
db_entry = { | ||
"id": id, | ||
"userName": meme_data["userName"], | ||
"timePosted": posted, | ||
"timeToDie": timeToDie | ||
} | ||
|
||
dynamodb = boto3.resource("dynamodb") | ||
table = dynamodb.Table("im-memes") | ||
table.put_item(Item=db_entry) | ||
|
||
return { | ||
"statusCode": 200, | ||
"body": json.dumps({ "id": id }) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import json | ||
import boto3 | ||
import time | ||
|
||
def lambda_handler(event, context): | ||
### get the meme_id and user_name | ||
meme_id = event["pathParameters"]["id"] | ||
event_body = json.loads(event["body"]) | ||
user_name = event_body["userName"] | ||
|
||
### query the database for the meme's info. return 404 error if it's not in the database. | ||
dynamodb = boto3.resource("dynamodb") | ||
table = dynamodb.Table("im-memes") | ||
db_meme = table.get_item(Key={"id": meme_id}) | ||
|
||
### return an error code if the meme's not in the database | ||
meme = db_meme.get("Item") | ||
|
||
if not meme: | ||
return { | ||
"statusCode": 404, | ||
"body": json.dumps("no such meme") | ||
} | ||
|
||
### return 400 error if the meme was already liked by this user or if this user posted the meme | ||
likes = meme.get("likes", []) | ||
|
||
if (user_name in likes | ||
or user_name == meme["userName"]): | ||
return { | ||
"statusCode": 400, | ||
"body": json.dumps("You can't like this!") | ||
} | ||
|
||
### build an update expression to add the like and increment timeToDie by one hour | ||
likes.append(user_name) | ||
expression_attribute_values = { | ||
":hour": 60 * 60, | ||
":new_likes": likes | ||
} | ||
update_expression = ( | ||
"SET timeToDie = timeToDie + :hour, " | ||
"likes = :new_likes" | ||
) | ||
|
||
### update the item | ||
table.update_item( | ||
Key={"id": meme_id}, | ||
UpdateExpression=update_expression, | ||
ExpressionAttributeValues=expression_attribute_values) | ||
|
||
### return 201 success | ||
return { | ||
"statusCode": 201, | ||
"body": json.dumps(f"/{meme_id}") | ||
} |