diff --git a/lib/constructs/business/rest-api-service.ts b/lib/constructs/business/rest-api-service.ts index fe13adb58..64bbacee8 100644 --- a/lib/constructs/business/rest-api-service.ts +++ b/lib/constructs/business/rest-api-service.ts @@ -716,6 +716,7 @@ export class ForumThreadsApiService extends RestApiService { const root = scope.apiEndpoint.root.addResource('forum'); const boardResource = root.addResource('{board_id}'); const threadResource = boardResource.addResource('{thread_id}'); + const userResource = root.addResource('{uid}'); const optionsForumHome = root.addCorsPreflight({ allowOrigins: allowOrigins, @@ -753,6 +754,18 @@ export class ForumThreadsApiService extends RestApiService { ], }); + const optionsUserThreads = userResource.addCorsPreflight({ + allowOrigins: allowOrigins, + allowHeaders: allowHeaders, + allowMethods: [ + apigw2.HttpMethod.GET, + apigw2.HttpMethod.POST, + apigw2.HttpMethod.PATCH, + apigw2.HttpMethod.DELETE, + apigw2.HttpMethod.OPTIONS, + ], + }); + const getRespModel = scope.apiEndpoint.addModel('threads-get-resp-model', { schema: forumThreadGetRespSchema, contentType: 'application/json', @@ -786,8 +799,8 @@ export class ForumThreadsApiService extends RestApiService { forumThreadsFunctions.getAllFunction, { proxy: true }, ); - const getBoardIntegration = new apigw.LambdaIntegration( - forumThreadsFunctions.getBoardFunction, + const getUserIntegration = new apigw.LambdaIntegration( + forumThreadsFunctions.getUserFunction, { proxy: true }, ); const getThreadIntegration = new apigw.LambdaIntegration( @@ -823,15 +836,15 @@ export class ForumThreadsApiService extends RestApiService { }, ); - const getBoardForumThreads = boardResource.addMethod( + const getUserForumThreads = userResource.addMethod( apigw2.HttpMethod.GET, - getBoardIntegration, + getUserIntegration, { - operationName: 'GetBoardThreads', + operationName: 'GetUserThreads', methodResponses: [ { statusCode: '200', - responseModels: { ['application/json']: getRespModel }, + responseModels: { ['application/json']: apigw.Model.EMPTY_MODEL }, responseParameters: lambdaRespParams, }, ], @@ -910,8 +923,11 @@ export class ForumThreadsApiService extends RestApiService { [apigw2.HttpMethod.GET]: getAllForumThreads, [apigw2.HttpMethod.OPTIONS]: optionsForumHome, }, + '/forum/{uid}': { + [apigw2.HttpMethod.GET]: getUserForumThreads, + [apigw2.HttpMethod.OPTIONS]: optionsUserThreads, + }, '/forum/{board_id}': { - [apigw2.HttpMethod.GET]: getBoardForumThreads, [apigw2.HttpMethod.POST]: postForumThreads, [apigw2.HttpMethod.OPTIONS]: optionsForumBoards, }, diff --git a/lib/constructs/common/lambda-functions.ts b/lib/constructs/common/lambda-functions.ts index 403eebd93..a51645a03 100644 --- a/lib/constructs/common/lambda-functions.ts +++ b/lib/constructs/common/lambda-functions.ts @@ -486,7 +486,7 @@ export class SyllabusUpdateFunction extends Construct { export class ForumThreadFunctions extends Construct { readonly getAllFunction: lambda.Function; - readonly getBoardFunction: lambda.Function; + readonly getUserFunction: lambda.Function; readonly getSingleFunction: lambda.Function; readonly postFunction: lambda.Function; readonly patchFunction: lambda.Function; @@ -559,13 +559,13 @@ export class ForumThreadFunctions extends Construct { }, ); - this.getBoardFunction = new lambda_py.PythonFunction( + this.getUserFunction = new lambda_py.PythonFunction( this, - 'get-board-threads', + 'get-user-threads', { - entry: 'src/lambda/get-board-threads', - description: 'Get forum threads from the database.', - functionName: 'get-board-threads', + entry: 'src/lambda/get-user-threads', + description: "Get user's threads from the database.", + functionName: 'get-user-threads', logRetention: logs.RetentionDays.ONE_MONTH, memorySize: 128, role: dynamoDBReadRole, diff --git a/lib/constructs/persistence/database.ts b/lib/constructs/persistence/database.ts index d1c7cf053..19a3270ce 100644 --- a/lib/constructs/persistence/database.ts +++ b/lib/constructs/persistence/database.ts @@ -92,7 +92,7 @@ export class DynamoDatabase extends Construct { }, billingMode: dynamodb.BillingMode.PROVISIONED, encryption: dynamodb.TableEncryption.DEFAULT, - removalPolicy: RemovalPolicy.DESTROY, + removalPolicy: RemovalPolicy.RETAIN, sortKey: { name: 'thread_id', type: dynamodb.AttributeType.STRING }, tableName: 'forum-threads', readCapacity: 15, @@ -100,6 +100,12 @@ export class DynamoDatabase extends Construct { pointInTimeRecovery: true, }, ); + this.tables[Collection.THREAD].addGlobalSecondaryIndex({ + indexName: 'UidbyThreadIDIndex', + partitionKey: { name: 'uid', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'thread_id', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.ALL, + }); // this.tables[Collection.THREAD].addLocalSecondaryIndex({ // indexName: 'GroupIndex', @@ -113,13 +119,6 @@ export class DynamoDatabase extends Construct { // projectionType: dynamodb.ProjectionType.ALL, // }); - // this.tables[Collection.THREAD].addGlobalSecondaryIndex({ - // indexName: "UidbyCreated_at", - // partitionKey: { name: "uid", type: dynamodb.AttributeType.STRING }, - // sortKey: { name: "created_at", type: dynamodb.AttributeType.NUMBER }, - // projectionType: dynamodb.ProjectionType.ALL, - // }); - this.tables[Collection.COMMENT] = new dynamodb.Table( this, 'dynamodb-comment-table', diff --git a/src/lambda/get-all-threads/index.py b/src/lambda/get-all-threads/index.py index c1335ef23..e8c728c3d 100644 --- a/src/lambda/get-all-threads/index.py +++ b/src/lambda/get-all-threads/index.py @@ -13,7 +13,7 @@ def get_all_threads(uid, index, num, school, tags, board_id): response = table.query(KeyConditionExpression=Key( "board_id").eq(board_id), ScanIndexForward=False) else: - response = table.scan() + response = table.scan(ConsistentRead=False) items = response['Items'] diff --git a/src/lambda/get-board-threads/index.py b/src/lambda/get-board-threads/index.py deleted file mode 100644 index a6eaddda6..000000000 --- a/src/lambda/get-board-threads/index.py +++ /dev/null @@ -1,59 +0,0 @@ -from boto3.dynamodb.conditions import Key -import boto3 -from datetime import datetime -from utils import JsonPayloadBuilder, table, resp_handler - - -@resp_handler -def get_board_threads(uid, index, num, school, board_id, tags): - - index = int(index) - num = int(num) - - results = table.query(KeyConditionExpression=Key( - "board_id").eq(board_id), ScanIndexForward=False)["Items"] - if not results: - raise LookupError - - if school: - results = [item for item in results if item.get("group_id") in school] - if tags: - results = [item for item in results if item.get("tag_id") in tags] - - start_index = index - end_index = min(len(results), start_index + num) - paginated_results = results[start_index:end_index] - - for item in paginated_results: - item['mod'] = False - if 'uid' in item and item['uid'] == uid: - item['mod'] = True - - body = JsonPayloadBuilder().add_status( - True).add_data(paginated_results).add_message(end_index).compile() - return body - - -def handler(event, context): - - uid = "" - index = "0" # default index - num = "10" # default num - school = "" # default school - board_id = event["pathParameters"]["board_id"] # from path parameters - tags = "" - - if "queryStringParameters" in event: - params = event["queryStringParameters"] - uid = params.get("uid", "") - index = params.get("index", "0") - num = params.get("num", "10") - school = params.get("school", "") - tags = params.get("tags", "") - - if school: - school = school.split(',') - if tags: - tags = tags.split(',') - - return get_board_threads(uid, index, num, school, board_id, tags) diff --git a/src/lambda/get-user-threads/index.py b/src/lambda/get-user-threads/index.py new file mode 100644 index 000000000..6502ca0d6 --- /dev/null +++ b/src/lambda/get-user-threads/index.py @@ -0,0 +1,42 @@ +from boto3.dynamodb.conditions import Key, Attr +import boto3 +from datetime import datetime +from utils import JsonPayloadBuilder, table, resp_handler + + +@resp_handler +def get_user_threads(uid): + + # Query the GSI + response = table.query( + IndexName='UidbyThreadIDIndex', # Replace with your actual GSI name + KeyConditionExpression=Key('uid').eq(uid), + FilterExpression=Attr('new_comment').eq(True), + ScanIndexForward=False # Sorting by thread_id in descending order + ) + + results = response['Items'] + + # Count the threads with new comments + new_comment_count = len(results) + + # Collect the thread_ids + thread_ids = [item['thread_id'] for item in results] + + # Determine the response data based on new_comment_count + response_data = True if new_comment_count > 1 else { + 'thread_ids': thread_ids, 'new_comment_count': new_comment_count} + + body = JsonPayloadBuilder().add_status(True)\ + .add_data(response_data)\ + .add_message('Fetched successfully').compile() + + return body + + +def handler(event, context): + uid = event['queryStringParameters'].get('uid', '') + # index = event['queryStringParameters'].get('index', '0') + # num = event['queryStringParameters'].get('num', '10') + + return get_user_threads(uid) diff --git a/src/lambda/get-board-threads/utils.py b/src/lambda/get-user-threads/utils.py similarity index 100% rename from src/lambda/get-board-threads/utils.py rename to src/lambda/get-user-threads/utils.py