Skip to content

Commit

Permalink
completed back end code
Browse files Browse the repository at this point in the history
  • Loading branch information
John-J-Riehl committed Apr 4, 2023
1 parent a0aa42c commit b460041
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 0 deletions.
64 changes: 64 additions & 0 deletions back-end-complete/get_meme.py
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)
}
60 changes: 60 additions & 0 deletions back-end-complete/get_thumbnails.py
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)
}
15 changes: 15 additions & 0 deletions back-end-complete/on_db_delete.py
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
94 changes: 94 additions & 0 deletions back-end-complete/post_meme.py
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 })
}

56 changes: 56 additions & 0 deletions back-end-complete/put_like.py
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}")
}

0 comments on commit b460041

Please sign in to comment.