Skip to content

Commit

Permalink
feat: creating test lambda for forum get single thread (#330)
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonNotJson authored Sep 24, 2023
1 parent c167d39 commit fe74aad
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 4 deletions.
23 changes: 22 additions & 1 deletion lib/constructs/business/rest-api-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,10 @@ export class ForumThreadsApiService extends RestApiService {
forumThreadsFunctions.testPostFunction,
{ proxy: true },
);
const testGetIntegration = new apigw.LambdaIntegration(
forumThreadsFunctions.testGetFunction,
{ proxy: true },
);

const getAllForumThreads = root.addMethod(
apigw2.HttpMethod.GET,
Expand Down Expand Up @@ -940,7 +944,23 @@ export class ForumThreadsApiService extends RestApiService {
apigw2.HttpMethod.POST,
testPostIntegration,
{
operationName: 'testThread',
operationName: 'testPostThread',
methodResponses: [
{
statusCode: '200',
responseParameters: lambdaRespParams,
},
],
authorizer: props.authorizer,
requestValidator: props.validator,
},
);

const testGetForumThreads = testResource.addMethod(
apigw2.HttpMethod.GET,
testGetIntegration,
{
operationName: 'testGetThread',
methodResponses: [
{
statusCode: '200',
Expand Down Expand Up @@ -973,6 +993,7 @@ export class ForumThreadsApiService extends RestApiService {
},
'/forum/test': {
[apigw2.HttpMethod.POST]: testPostForumThreads,
[apigw2.HttpMethod.GET]: testGetForumThreads,
[apigw2.HttpMethod.OPTIONS]: optionsTestThreads,
},
};
Expand Down
19 changes: 18 additions & 1 deletion lib/constructs/common/lambda-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ export class ForumThreadFunctions extends Construct {
readonly patchFunction: lambda.Function;
readonly deleteFunction: lambda.Function;
readonly testPostFunction: lambda.Function;
readonly testGetFunction: lambda.Function;

constructor(scope: Construct, id: string, props: FunctionsProps) {
super(scope, id);
Expand Down Expand Up @@ -650,7 +651,23 @@ export class ForumThreadFunctions extends Construct {
{
entry: 'src/lambda/test-post-thread',
description: 'lambda to test forum functionalities',
functionName: 'test-forum-thread',
functionName: 'test-post-forum-thread',
logRetention: logs.RetentionDays.ONE_MONTH,
memorySize: 128,
role: DBPutRole,
runtime: lambda.Runtime.PYTHON_3_9,
timeout: Duration.seconds(3),
environment: props.envVars,
},
);

this.testGetFunction = new lambda_py.PythonFunction(
this,
'test-get-thread',
{
entry: 'src/lambda/test-get-single-thread',
description: 'lambda to test forum get functionalities',
functionName: 'test-get-forum-thread',
logRetention: logs.RetentionDays.ONE_MONTH,
memorySize: 128,
role: DBPutRole,
Expand Down
82 changes: 82 additions & 0 deletions src/lambda/test-get-single-thread/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from boto3.dynamodb.conditions import Key, Attr
from datetime import datetime
from utils import JsonPayloadBuilder, table, resp_handler, s3_client, bucket, generate_url


@resp_handler
def get_single_thread(board_id, thread_id, uid=""):
results = table.query(
KeyConditionExpression=Key("board_id").eq(
board_id) & Key("thread_id").eq(thread_id)
)["Items"]

if not results:
raise LookupError

item = results[0]

if item["uid"] == uid:
table.update_item(
Key={
"board_id": board_id,
"thread_id": thread_id,
},
UpdateExpression="SET #v = #v + :incr, #nc = :newComment",
ConditionExpression="#uid = :uidValue",
ExpressionAttributeNames={
'#v': 'views',
'#nc': 'new_comment',
'#uid': 'uid'
},
ExpressionAttributeValues={
":incr": 1,
":newComment": False,
":uidValue": uid
}
)
else:
# Increment the view count but do not update new_comment
table.update_item(
Key={
"board_id": board_id,
"thread_id": thread_id,
},
UpdateExpression="SET #v = #v + :incr",
ExpressionAttributeNames={
'#v': 'views'
},
ExpressionAttributeValues={
":incr": 1
}
)

item["mod"] = False
if item["uid"] == uid:
item["mod"] = True
item['user_liked'] = uid in item.get('likes', [])
item['total_likes'] = len(item.get('likes', []))

if "object_key" in item:
bucket_name = bucket
presigned_url = generate_url(bucket_name, item["object_key"])
if presigned_url:
item["url"] = presigned_url

item.pop('uid', None)
item.pop('likes', None)
item.pop('object_key', None)

body = JsonPayloadBuilder().add_status(
True).add_data(item).add_message('').compile()
return body


def handler(event, context):
params = {
"board_id": event["queryStringParameters"]["board_id"],
"thread_id": event["queryStringParameters"]["thread_id"],
}
if "uid" in event["queryStringParameters"]:
params["uid"] = event["queryStringParameters"]["uid"]

return get_single_thread(**params)
82 changes: 82 additions & 0 deletions src/lambda/test-get-single-thread/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import boto3
import json
import logging
import os
from decimal import Decimal

db = boto3.resource("dynamodb", region_name="ap-northeast-1")
table = db.Table(os.getenv('TABLE_NAME'))

s3_client = boto3.client('s3')
bucket = os.getenv('BUCKET_NAME')


class DecimalEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Decimal):
return float(obj)
return json.JSONEncoder.default(self, obj)


class JsonPayloadBuilder:
payload = {}

def add_status(self, success):
self.payload['success'] = success
return self

def add_data(self, data):
self.payload['data'] = data
return self

def add_message(self, msg):
self.payload['message'] = msg
return self

def compile(self):
return json.dumps(self.payload, cls=DecimalEncoder, ensure_ascii=False).encode('utf8')


def api_response(code, body):
return {
"isBase64Encoded": False,
"statusCode": code,
'headers': {
"Access-Control-Allow-Origin": '*',
"Content-Type": "application/json",
"Referrer-Policy": "origin"
},
"multiValueHeaders": {"Access-Control-Allow-Methods": ["POST", "OPTIONS", "GET", "PATCH", "DELETE"]},
"body": body
}


def resp_handler(func):
def handle(*args, **kwargs):
try:
resp = func(*args, **kwargs)
return api_response(200, resp)
except LookupError:
resp = JsonPayloadBuilder().add_status(False).add_data(None) \
.add_message("Not found").compile()
return api_response(404, resp)
except Exception as e:
logging.error(str(e))
resp = JsonPayloadBuilder().add_status(False).add_data(None) \
.add_message("Internal error, please contact [email protected].").compile()
return api_response(500, resp)

return handle


def generate_url(bucket_name, object_key, expiration=3600):
try:
response = s3_client.generate_presigned_url('get_object',
Params={'Bucket': bucket_name,
'Key': object_key},
ExpiresIn=expiration)
except Exception as e:
logging.error(str(e))
return None

return response
5 changes: 3 additions & 2 deletions src/lambda/test-post-thread/index.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from boto3.dynamodb.conditions import Key
import json
from datetime import datetime
from utils import JsonPayloadBuilder, table, resp_handler, build_thread_id, s3_client, bucket
from utils import JsonPayloadBuilder, table, resp_handler, build_thread_id, s3_client, bucket, sanitize_title
import uuid
import base64

Expand All @@ -24,7 +24,8 @@ def test_post_thread(thread, uid):
raise ValueError("Invalid content type")
# Extracts 'jpeg', 'png', or 'gif' from the MIME type
extension = content_type.split("/")[-1]
object_key = f"{thread_id}/image.{extension}"
sanitized_title = sanitize_title(thread["title"])
object_key = f"{thread_id}/{sanitized_title}.{extension}"

s3_client.put_object(Bucket=bucket, Key=object_key,
Body=image_data, ContentType=content_type)
Expand Down

0 comments on commit fe74aad

Please sign in to comment.