From df77ed144d316ef4e5fbd1aaacb824763779bc52 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Tue, 14 Jan 2025 07:12:25 +0000 Subject: [PATCH 01/28] refactor: update resources name with stack name --- template.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/template.yaml b/template.yaml index ef92c4d..5f37dca 100644 --- a/template.yaml +++ b/template.yaml @@ -137,7 +137,7 @@ Resources: Type: AWS::Cognito::UserPoolGroup Properties: Description: User group for API Administrators - GroupName: !Ref UserPoolAdminGroupName + GroupName: !Sub ${AWS::StackName}-${UserPoolAdminGroupName} Precedence: 0 UserPoolId: !Ref UserPool @@ -145,31 +145,31 @@ Resources: TasksAssignmentNotificationTopic: Type: AWS::SNS::Topic Properties: - TopicName: tasks-assignment-notifications + TopicName: !Sub ${AWS::StackName}-tasks-assignment-notifications DisplayName: "Task Assignment Notifications" TasksDeadlineNotificationTopic: Type: AWS::SNS::Topic Properties: - TopicName: tasks-deadline-notifications + TopicName: !Sub ${AWS::StackName}-tasks-deadline-notifications DisplayName: "Task Deadline Notifications" ClosedTasksNotificationTopic: Type: AWS::SNS::Topic Properties: - TopicName: closed-tasks-notifications + TopicName: !Sub ${AWS::StackName}-closed-tasks-notifications DisplayName: "Closed Tasks Notifications" ReopenedTasksNotificationTopic: Type: AWS::SNS::Topic Properties: - TopicName: reopened-tasks-notifications + TopicName: !Sub ${AWS::StackName}-reopened-tasks-notifications DisplayName: "Reopened Tasks Notifications" TaskCompleteNotificationTopic: Type: AWS::SNS::Topic Properties: - TopicName: task-complete-notifications + TopicName: !Sub ${AWS::StackName}-task-complete-notifications DisplayName: "task completion Notifications" SNSTopicSubscriptionFunction: @@ -355,7 +355,7 @@ Resources: TasksTable: # Creates the DynamoDB table for user tasks Type: AWS::DynamoDB::Table Properties: - TableName: !Ref TasksTableName + TableName: !Sub ${AWS::StackName}-${TasksTableName} AttributeDefinitions: - AttributeName: "taskId" AttributeType: "S" @@ -406,9 +406,9 @@ Resources: CodeUri: . Policies: - SQSSendMessagePolicy: # Predefined policy to send messages - QueueName: tasks-queue + QueueName: !Sub ${AWS::StackName}-tasks-queue - DynamoDBStreamReadPolicy: # Predefined policy to read from DynamoDB streams - TableName: !Ref TasksTable + TableName: !Sub ${AWS::StackName}-${TasksTableName} StreamName: !GetAtt TasksTable.StreamArn Environment: Variables: @@ -464,7 +464,7 @@ Resources: TasksQueue: Type: AWS::SQS::Queue Properties: - QueueName: tasks-queue + QueueName: !Sub ${AWS::StackName}-tasks-queue TaskQueuePolicy: Type: AWS::SQS::QueuePolicy From 1302e5d12d45ab97db4945a0c1ae204c104dfd54 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Tue, 14 Jan 2025 07:18:06 +0000 Subject: [PATCH 02/28] update workflow pipeline.yaml --- .github/workflows/pipeline.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 41b72af..76e0091 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -4,6 +4,7 @@ on: push: branches: - 'main' + - 'dev' env: PIPELINE_USER_ACCESS_KEY_ID: ${{ secrets.PIPELINE_USER_ACCESS_KEY_ID}} From 940344bd0aa791e5411dbcb465540c97394ea3f5 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Tue, 14 Jan 2025 07:23:20 +0000 Subject: [PATCH 03/28] update workflow pipeline.yaml --- .github/workflows/pipeline.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 76e0091..ecf7cf2 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -14,7 +14,7 @@ env: TESTING_PIPELINE_EXECUTION_ROLE: ${{ secrets.TESTING_PIPELINE_EXECUTION_ROLE }} TESTING_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.TESTING_CLOUDFORMATION_EXECUTION_ROLE }} TESTING_ARTIFACTS_BUCKET: ${{ secrets.TESTING_ARTIFACTS_BUCKET }} - TESTING_PARAMETER_OVERRIDES: TasksTableName=tasks,CognitoCallbackURL=https://start.spring.io/,UserPoolAdminGroupName=apiAdmins + TESTING_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://start.spring.io/ UserPoolAdminGroupName=apiAdmins # If there are functions with "Image" PackageType in your template, # uncomment the line below and add "--image-repository ${TESTING_IMAGE_REPOSITORY}" to # testing "sam package" and "sam deploy" commands. @@ -24,7 +24,7 @@ env: PROD_PIPELINE_EXECUTION_ROLE: ${{ secrets.PROD_PIPELINE_EXECUTION_ROLE }} PROD_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.PROD_CLOUDFORMATION_EXECUTION_ROLE }} PROD_ARTIFACTS_BUCKET: ${{ secrets.PROD_ARTIFACTS_BUCKET }} - PRODUCTION_PARAMETER_OVERRIDES: TasksTableName=tasks,CognitoCallbackURL=https://django-rest-framework.org/tutorial/,UserPoolAdminGroupName=apiAdmins + PRODUCTION_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://django-rest-framework.org/tutorial/ UserPoolAdminGroupName=apiAdmins # If there are functions with "Image" PackageType in your template, # uncomment the line below and add "--image-repository ${PROD_IMAGE_REPOSITORY}" to # prod "sam package" and "sam deploy" commands. @@ -192,4 +192,4 @@ jobs: --s3-bucket ${PROD_ARTIFACTS_BUCKET} \ --no-fail-on-empty-changeset \ --role-arn ${PROD_CLOUDFORMATION_EXECUTION_ROLE} \ - --parameter-overrides TasksTableName=tasks CognitoCallbackURL=https://django-rest-framework.org/tutorial/ UserPoolAdminGroupName=apiAdmins \ No newline at end of file + --parameter-overrides ${PRODUCTION_PARAMETER_OVERRIDES} \ No newline at end of file From 444ca8c5a7094631ac1f44d31f54a6af11f84f9e Mon Sep 17 00:00:00 2001 From: umaxcode Date: Tue, 14 Jan 2025 09:17:20 +0000 Subject: [PATCH 04/28] update workflow pipeline.yaml with hosted callback urls --- .github/workflows/pipeline.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index ecf7cf2..77616a4 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -14,7 +14,7 @@ env: TESTING_PIPELINE_EXECUTION_ROLE: ${{ secrets.TESTING_PIPELINE_EXECUTION_ROLE }} TESTING_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.TESTING_CLOUDFORMATION_EXECUTION_ROLE }} TESTING_ARTIFACTS_BUCKET: ${{ secrets.TESTING_ARTIFACTS_BUCKET }} - TESTING_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://start.spring.io/ UserPoolAdminGroupName=apiAdmins + TESTING_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://dev.d2x151q2vf1tfl.amplifyapp.com/ UserPoolAdminGroupName=apiAdmins # If there are functions with "Image" PackageType in your template, # uncomment the line below and add "--image-repository ${TESTING_IMAGE_REPOSITORY}" to # testing "sam package" and "sam deploy" commands. @@ -24,7 +24,7 @@ env: PROD_PIPELINE_EXECUTION_ROLE: ${{ secrets.PROD_PIPELINE_EXECUTION_ROLE }} PROD_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.PROD_CLOUDFORMATION_EXECUTION_ROLE }} PROD_ARTIFACTS_BUCKET: ${{ secrets.PROD_ARTIFACTS_BUCKET }} - PRODUCTION_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://django-rest-framework.org/tutorial/ UserPoolAdminGroupName=apiAdmins + PRODUCTION_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://main.d1m5j798vicdd1.amplifyapp.com/ UserPoolAdminGroupName=apiAdmins # If there are functions with "Image" PackageType in your template, # uncomment the line below and add "--image-repository ${PROD_IMAGE_REPOSITORY}" to # prod "sam package" and "sam deploy" commands. From c2af932c0b47c10a66407ab1809f31db7bb08bd0 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Tue, 14 Jan 2025 09:46:16 +0000 Subject: [PATCH 05/28] update workflow pipeline.yaml with hosted callback urls --- .github/workflows/pipeline.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 77616a4..207206c 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -14,7 +14,7 @@ env: TESTING_PIPELINE_EXECUTION_ROLE: ${{ secrets.TESTING_PIPELINE_EXECUTION_ROLE }} TESTING_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.TESTING_CLOUDFORMATION_EXECUTION_ROLE }} TESTING_ARTIFACTS_BUCKET: ${{ secrets.TESTING_ARTIFACTS_BUCKET }} - TESTING_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://dev.d2x151q2vf1tfl.amplifyapp.com/ UserPoolAdminGroupName=apiAdmins + TESTING_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://dev.d2x151q2vf1tfl.amplifyapp.com/auth/callback UserPoolAdminGroupName=apiAdmins # If there are functions with "Image" PackageType in your template, # uncomment the line below and add "--image-repository ${TESTING_IMAGE_REPOSITORY}" to # testing "sam package" and "sam deploy" commands. @@ -24,7 +24,7 @@ env: PROD_PIPELINE_EXECUTION_ROLE: ${{ secrets.PROD_PIPELINE_EXECUTION_ROLE }} PROD_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.PROD_CLOUDFORMATION_EXECUTION_ROLE }} PROD_ARTIFACTS_BUCKET: ${{ secrets.PROD_ARTIFACTS_BUCKET }} - PRODUCTION_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://main.d1m5j798vicdd1.amplifyapp.com/ UserPoolAdminGroupName=apiAdmins + PRODUCTION_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://main.d1m5j798vicdd1.amplifyapp.com/auth/callback UserPoolAdminGroupName=apiAdmins # If there are functions with "Image" PackageType in your template, # uncomment the line below and add "--image-repository ${PROD_IMAGE_REPOSITORY}" to # prod "sam package" and "sam deploy" commands. From e1aaa523e767c5a71a7e1859707e3d4c5f9e3e99 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Tue, 14 Jan 2025 09:50:32 +0000 Subject: [PATCH 06/28] refactor: update cors allowed origins --- src/main/java/org/umaxcode/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/umaxcode/config/SecurityConfig.java b/src/main/java/org/umaxcode/config/SecurityConfig.java index eb13184..6179e96 100644 --- a/src/main/java/org/umaxcode/config/SecurityConfig.java +++ b/src/main/java/org/umaxcode/config/SecurityConfig.java @@ -48,7 +48,7 @@ public CorsConfigurationSource corsConfigurationSource() { cors.addAllowedHeader("*"); cors.addAllowedMethod("*"); cors.setAllowCredentials(true); - cors.setAllowedOrigins(List.of("http://localhost:3000")); + cors.setAllowedOrigins(List.of("http://localhost:3000", "https://dev.d2x151q2vf1tfl.amplifyapp.com", "https://main.d1m5j798vicdd1.amplifyapp.com")); source.registerCorsConfiguration("/**", cors); return source; From 0a00569759730501aa7472182c355020884880ee Mon Sep 17 00:00:00 2001 From: umaxcode Date: Tue, 14 Jan 2025 15:07:03 +0000 Subject: [PATCH 07/28] refactor: update resource name --- template.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/template.yaml b/template.yaml index 5f37dca..17efa0d 100644 --- a/template.yaml +++ b/template.yaml @@ -34,7 +34,7 @@ Resources: - DynamoDBCrudPolicy: TableName: !Ref TasksTable - SQSSendMessagePolicy: - QueueName: tasks-queue + QueueName: !Sub ${AWS::StackName}-tasks-queue - Statement: Effect: Allow Action: @@ -408,7 +408,7 @@ Resources: - SQSSendMessagePolicy: # Predefined policy to send messages QueueName: !Sub ${AWS::StackName}-tasks-queue - DynamoDBStreamReadPolicy: # Predefined policy to read from DynamoDB streams - TableName: !Sub ${AWS::StackName}-${TasksTableName} + TableName: !Ref TasksTable StreamName: !GetAtt TasksTable.StreamArn Environment: Variables: @@ -492,7 +492,7 @@ Resources: CodeUri: . Policies: - SQSSendMessagePolicy: # Predefined policy to send messages - QueueName: tasks-queue + QueueName: !Sub ${AWS::StackName}-tasks-queue - DynamoDBCrudPolicy: TableName: !Ref TasksTable Environment: From 80a6837f1fb499702b3e8fb7a7304ea17a314c08 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Tue, 14 Jan 2025 15:13:43 +0000 Subject: [PATCH 08/28] refactor: provide task table name for testing and production environment --- .github/workflows/pipeline.yaml | 4 ++-- template.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 207206c..83b7ff8 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -14,7 +14,7 @@ env: TESTING_PIPELINE_EXECUTION_ROLE: ${{ secrets.TESTING_PIPELINE_EXECUTION_ROLE }} TESTING_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.TESTING_CLOUDFORMATION_EXECUTION_ROLE }} TESTING_ARTIFACTS_BUCKET: ${{ secrets.TESTING_ARTIFACTS_BUCKET }} - TESTING_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://dev.d2x151q2vf1tfl.amplifyapp.com/auth/callback UserPoolAdminGroupName=apiAdmins + TESTING_PARAMETER_OVERRIDES: TasksTableName=test-tasks CognitoCallbackURL=https://dev.d2x151q2vf1tfl.amplifyapp.com/auth/callback UserPoolAdminGroupName=apiAdmins # If there are functions with "Image" PackageType in your template, # uncomment the line below and add "--image-repository ${TESTING_IMAGE_REPOSITORY}" to # testing "sam package" and "sam deploy" commands. @@ -24,7 +24,7 @@ env: PROD_PIPELINE_EXECUTION_ROLE: ${{ secrets.PROD_PIPELINE_EXECUTION_ROLE }} PROD_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.PROD_CLOUDFORMATION_EXECUTION_ROLE }} PROD_ARTIFACTS_BUCKET: ${{ secrets.PROD_ARTIFACTS_BUCKET }} - PRODUCTION_PARAMETER_OVERRIDES: TasksTableName=tasks CognitoCallbackURL=https://main.d1m5j798vicdd1.amplifyapp.com/auth/callback UserPoolAdminGroupName=apiAdmins + PRODUCTION_PARAMETER_OVERRIDES: TasksTableName=prod-tasks CognitoCallbackURL=https://main.d1m5j798vicdd1.amplifyapp.com/auth/callback UserPoolAdminGroupName=apiAdmins # If there are functions with "Image" PackageType in your template, # uncomment the line below and add "--image-repository ${PROD_IMAGE_REPOSITORY}" to # prod "sam package" and "sam deploy" commands. diff --git a/template.yaml b/template.yaml index 17efa0d..becfa92 100644 --- a/template.yaml +++ b/template.yaml @@ -355,7 +355,7 @@ Resources: TasksTable: # Creates the DynamoDB table for user tasks Type: AWS::DynamoDB::Table Properties: - TableName: !Sub ${AWS::StackName}-${TasksTableName} + TableName: !Ref TasksTableName AttributeDefinitions: - AttributeName: "taskId" AttributeType: "S" From 2471e8be97a14c16dd7b46566406de721f8d98d9 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Wed, 15 Jan 2025 10:41:34 +0000 Subject: [PATCH 09/28] feat: add sns service --- .../java/org/umaxcode/service/SNSService.java | 5 ++ .../umaxcode/service/impl/SNSServiceImpl.java | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/main/java/org/umaxcode/service/SNSService.java create mode 100644 src/main/java/org/umaxcode/service/impl/SNSServiceImpl.java diff --git a/src/main/java/org/umaxcode/service/SNSService.java b/src/main/java/org/umaxcode/service/SNSService.java new file mode 100644 index 0000000..153ca8b --- /dev/null +++ b/src/main/java/org/umaxcode/service/SNSService.java @@ -0,0 +1,5 @@ +package org.umaxcode.service; + +public interface SNSService { + +} diff --git a/src/main/java/org/umaxcode/service/impl/SNSServiceImpl.java b/src/main/java/org/umaxcode/service/impl/SNSServiceImpl.java new file mode 100644 index 0000000..69f6c5e --- /dev/null +++ b/src/main/java/org/umaxcode/service/impl/SNSServiceImpl.java @@ -0,0 +1,52 @@ +package org.umaxcode.service.impl; + +import com.amazonaws.services.lambda.runtime.Context; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.umaxcode.service.SNSService; +import software.amazon.awssdk.services.sns.SnsClient; +import software.amazon.awssdk.services.sns.model.SetSubscriptionAttributesRequest; +import software.amazon.awssdk.services.sns.model.SubscribeRequest; +import software.amazon.awssdk.services.sns.model.SubscribeResponse; + +@Service +@RequiredArgsConstructor +public class SNSServiceImpl implements SNSService { + + public static void subscribeToTopic(SnsClient snsClient, Context context, String topic, String email) { + + // Create SNS subscription request + SubscribeRequest subscribeRequest = SubscribeRequest.builder() + .protocol("email") // Protocol is email to receive notifications + .endpoint(email) // User's email address to subscribe + .returnSubscriptionArn(true) + .topicArn(topic) // SNS Topic ARN + .build(); + + try { + SubscribeResponse response = snsClient.subscribe(subscribeRequest); + context.getLogger().log("Subscription result: " + response); + + // Define a filter policy + String filterPolicy = String.format("{ \"endpointEmail\": [\"%s\"] }", email); + + // Set the filter policy for the subscription + String subscriptionArn = response.subscriptionArn(); + + System.out.println("SubscriptionArn" + subscriptionArn); + SetSubscriptionAttributesRequest filterPolicyRequest = SetSubscriptionAttributesRequest.builder() + .subscriptionArn(subscriptionArn) + .attributeName("FilterPolicy") + .attributeValue(filterPolicy) + .build(); + + snsClient.setSubscriptionAttributes(filterPolicyRequest); + + context.getLogger().log("Filter policy set for subscription: " + subscriptionArn); + context.getLogger().log("Successfully subscribed " + email + " to the SNS topic with filter policy: " + topic); + + } catch (Exception e) { + context.getLogger().log("Error subscribing user: " + e.getMessage()); + } + } +} From 1afd53676d3d98061874d057f07a6c9f03891452 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Wed, 15 Jan 2025 10:42:32 +0000 Subject: [PATCH 10/28] refactor: update lambda handler with sns service --- .../umaxcode/SNSTopicSubscriptionHandler.java | 43 +++---------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/umaxcode/SNSTopicSubscriptionHandler.java b/src/main/java/org/umaxcode/SNSTopicSubscriptionHandler.java index f3ba4e6..4b45775 100644 --- a/src/main/java/org/umaxcode/SNSTopicSubscriptionHandler.java +++ b/src/main/java/org/umaxcode/SNSTopicSubscriptionHandler.java @@ -2,14 +2,12 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.umaxcode.service.impl.SNSServiceImpl; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.SetSubscriptionAttributesRequest; -import software.amazon.awssdk.services.sns.model.SubscribeRequest; -import software.amazon.awssdk.services.sns.model.SubscribeResponse; import java.util.Map; -public class SNSTopicSubscriptionHandler implements RequestHandler, String> { +public class SNSTopicSubscriptionHandler implements RequestHandler, Void> { private final SnsClient snsClient; @@ -18,7 +16,7 @@ public SNSTopicSubscriptionHandler() { } @Override - public String handleRequest(Map event, Context context) { + public Void handleRequest(Map event, Context context) { System.out.println("Event" + event); // Extract the user's email from the event @@ -27,39 +25,8 @@ public String handleRequest(Map event, Context context) { // SNS Topic ARN (can be passed in environment variables or hardcoded) String snsTopicArn = event.get("topicArn"); - // Create SNS subscription request - SubscribeRequest subscribeRequest = SubscribeRequest.builder() - .protocol("email") // Protocol is email to receive notifications - .endpoint(userEmail) // User's email address to subscribe - .returnSubscriptionArn(true) - .topicArn(snsTopicArn) // SNS Topic ARN - .build(); + SNSServiceImpl.subscribeToTopic(snsClient, context, snsTopicArn, userEmail); - try { - SubscribeResponse response = snsClient.subscribe(subscribeRequest); - context.getLogger().log("Subscription result: " + response); - - // Define a filter policy - String filterPolicy = String.format("{ \"endpointEmail\": [\"%s\"] }", userEmail); - - // Set the filter policy for the subscription - String subscriptionArn = response.subscriptionArn(); - - System.out.println("SubscriptionArn" + subscriptionArn); - SetSubscriptionAttributesRequest filterPolicyRequest = SetSubscriptionAttributesRequest.builder() - .subscriptionArn(subscriptionArn) - .attributeName("FilterPolicy") - .attributeValue(filterPolicy) - .build(); - - snsClient.setSubscriptionAttributes(filterPolicyRequest); - - context.getLogger().log("Filter policy set for subscription: " + subscriptionArn); - - return "Successfully subscribed " + userEmail + " to the SNS topic with filter policy: " + snsTopicArn; - } catch (Exception e) { - context.getLogger().log("Error subscribing user: " + e.getMessage()); - return "Failed to subscribe user to SNS topic."; - } + return null; } } From 86ed3cbcfc2bad79ad28a5150e087e07762ad092 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Wed, 15 Jan 2025 10:55:41 +0000 Subject: [PATCH 11/28] feat: add admin creation during stack creation --- .github/workflows/pipeline.yaml | 4 +- pom.xml | 5 + ...viteMessageAdminCreationLambdaHandler.java | 188 ++++++++++++++++++ template.yaml | 44 ++++ 4 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 83b7ff8..cd30cf1 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -14,7 +14,7 @@ env: TESTING_PIPELINE_EXECUTION_ROLE: ${{ secrets.TESTING_PIPELINE_EXECUTION_ROLE }} TESTING_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.TESTING_CLOUDFORMATION_EXECUTION_ROLE }} TESTING_ARTIFACTS_BUCKET: ${{ secrets.TESTING_ARTIFACTS_BUCKET }} - TESTING_PARAMETER_OVERRIDES: TasksTableName=test-tasks CognitoCallbackURL=https://dev.d2x151q2vf1tfl.amplifyapp.com/auth/callback UserPoolAdminGroupName=apiAdmins + TESTING_PARAMETER_OVERRIDES: TasksTableName=test-tasks CognitoCallbackURL=https://dev.d2x151q2vf1tfl.amplifyapp.com/auth/callback UserPoolAdminGroupName=apiAdmins FrontendLoginUrl=https://dev.d2x151q2vf1tfl.amplifyapp.com/ AdminEmail=${{ secrets.ADMIN_EMAIL_TEST }} AdminUsername=${{ secrets.ADMIN_USERNAME_TEST }} # If there are functions with "Image" PackageType in your template, # uncomment the line below and add "--image-repository ${TESTING_IMAGE_REPOSITORY}" to # testing "sam package" and "sam deploy" commands. @@ -24,7 +24,7 @@ env: PROD_PIPELINE_EXECUTION_ROLE: ${{ secrets.PROD_PIPELINE_EXECUTION_ROLE }} PROD_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.PROD_CLOUDFORMATION_EXECUTION_ROLE }} PROD_ARTIFACTS_BUCKET: ${{ secrets.PROD_ARTIFACTS_BUCKET }} - PRODUCTION_PARAMETER_OVERRIDES: TasksTableName=prod-tasks CognitoCallbackURL=https://main.d1m5j798vicdd1.amplifyapp.com/auth/callback UserPoolAdminGroupName=apiAdmins + PRODUCTION_PARAMETER_OVERRIDES: TasksTableName=prod-tasks CognitoCallbackURL=https://main.d1m5j798vicdd1.amplifyapp.com/auth/callback UserPoolAdminGroupName=apiAdmins FrontendLoginUrl=https://main.d1m5j798vicdd1.amplifyapp.com/ AdminEmail=${{ secrets.ADMIN_EMAIL_PROD }} AdminUsername=${{ secrets.ADMIN_USERNAME_PROD }} # If there are functions with "Image" PackageType in your template, # uncomment the line below and add "--image-repository ${PROD_IMAGE_REPOSITORY}" to # prod "sam package" and "sam deploy" commands. diff --git a/pom.xml b/pom.xml index a99cd35..af2c5bb 100644 --- a/pom.xml +++ b/pom.xml @@ -102,6 +102,11 @@ sqs 2.20.31 + + org.apache.httpcomponents.client5 + httpclient5 + 5.4.1 + org.junit.jupiter junit-jupiter diff --git a/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java b/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java new file mode 100644 index 0000000..8f225f4 --- /dev/null +++ b/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java @@ -0,0 +1,188 @@ +package org.umaxcode; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.umaxcode.domain.enums.Role; +import org.umaxcode.utils.PasswordGenerator; +import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; +import software.amazon.awssdk.services.cognitoidentityprovider.model.*; +import software.amazon.awssdk.services.sns.SnsClient; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.umaxcode.service.impl.SNSServiceImpl.subscribeToTopic; + +public class UpdateInviteMessageAdminCreationLambdaHandler implements RequestHandler { + + private final CognitoIdentityProviderClient cognitoIdentityProviderClient; + private final SnsClient snsClient; + private final ExecutorService executor; + + public UpdateInviteMessageAdminCreationLambdaHandler() { + this.cognitoIdentityProviderClient = CognitoIdentityProviderClient.create(); + this.snsClient = SnsClient.create(); + this.executor = Executors.newFixedThreadPool(2); + } + + @Override + public Void handleRequest(CloudFormationCustomResourceEvent event, Context context) { + String status = "SUCCESS"; + Map properties = event.getResourceProperties(); + Map responseData = new HashMap<>(); + + // Extract properties from the event + String requestType = event.getRequestType(); + String responseUrl = event.getResponseUrl(); + String userPoolId = properties.get("UserPoolId").toString(); + String adminEmail = properties.get("AdminEmail").toString(); + String adminUsername = properties.get("AdminUsername").toString(); + String loginUrl = properties.get("FrontendLoginUrl").toString(); + String taskCompleteTopicArn = properties.get("TaskCompleteTopicArn").toString(); + String closedTaskTopicArn = properties.get("ClosedTaskTopicArn").toString(); + + try { + + if ("Delete".equalsIgnoreCase(requestType)) { + sendResponse(responseUrl, event, context, status, null); + return null; + } + + updateInviteMessageTemplate(adminEmail, loginUrl, userPoolId, responseData, context); + + createAdminUser(adminEmail, adminUsername, userPoolId, responseData, context); + + CompletableFuture taskCompletionTopicSubscription = CompletableFuture + .runAsync(() -> subscribeToTopic(snsClient, context, taskCompleteTopicArn, adminEmail), executor); + + CompletableFuture taskClosedTopicSubscription = CompletableFuture + .runAsync(() -> subscribeToTopic(snsClient, context, closedTaskTopicArn, adminEmail), executor); + + CompletableFuture topicSubscriptionTasks = CompletableFuture.allOf(taskCompletionTopicSubscription, + taskClosedTopicSubscription); + + topicSubscriptionTasks.join(); + + // Send success response to CloudFormation + sendResponse(responseUrl, event, context, status, responseData); + + } catch (Exception e) { + context.getLogger().log("Error: " + e.getMessage()); + responseData.put("Error", e.getMessage()); + } + + return null; + } + + private void updateInviteMessageTemplate(String email, String loginUrl, String userPoolId, Map responseData, Context context) { + + MessageTemplateType inviteMessageTemplate = MessageTemplateType.builder() + .emailMessage((""" + Hello {user}, Welcome to our Task Management System! + Your username is %s and temporary password is {####} + + Click here to sign in: %s + """) + .formatted(email, loginUrl)) + .emailSubject("Welcome to Task Management System") + .build(); + + cognitoIdentityProviderClient.updateUserPool(UpdateUserPoolRequest.builder() + .userPoolId(userPoolId) + .adminCreateUserConfig(AdminCreateUserConfigType.builder() + .inviteMessageTemplate(inviteMessageTemplate) + .build()) + .build()); + + context.getLogger().log("UserPool updated successfully"); + responseData.put("Message", "UserPool updated successfully"); + } + + + private void createAdminUser(String adminEmail, String adminUsername, String userPoolId, Map responseData, Context context) { + + if (adminEmail != null && !adminEmail.trim().equals("None") && !adminEmail.isEmpty()) { + try { + cognitoIdentityProviderClient.adminGetUser(AdminGetUserRequest.builder() + .userPoolId(userPoolId) + .username(adminEmail) + .build()); + + context.getLogger().log("User already exists: " + adminEmail); + responseData.put("Message", "User already exists: " + adminEmail); + } catch (UserNotFoundException e) { + List userAttributes = new ArrayList<>(); + userAttributes.add(AttributeType.builder().name("email").value(adminEmail).build()); + userAttributes.add(AttributeType.builder().name("name").value(adminUsername).build()); + userAttributes.add(AttributeType.builder().name("custom:role").value(Role.ADMIN.toString()).build()); + userAttributes.add(AttributeType.builder().name("email_verified").value("true").build()); + + AdminCreateUserRequest createUserRequest = AdminCreateUserRequest.builder() + .userPoolId(userPoolId) + .username(adminEmail) + .userAttributes(userAttributes) + .temporaryPassword(PasswordGenerator.generatePassword()) + .desiredDeliveryMediums(DeliveryMediumType.EMAIL) + .build(); + + cognitoIdentityProviderClient.adminCreateUser(createUserRequest); + + } + } + + + } + + private void configureNotificationTopicsSubscription(String snsTopic, String adminEmail, Context context) { + subscribeToTopic(snsClient, context, snsTopic, adminEmail); + } + + private void sendResponse(String url, CloudFormationCustomResourceEvent event, Context context, String status, Map data) { + LambdaLogger logger = context.getLogger(); + ObjectMapper objectMapper = new ObjectMapper(); + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + + Map responseBody = Map.of( + "Status", status, + "Reason", "See the details in CloudWatch Log Stream: " + context.getLogStreamName(), + "PhysicalResourceId", event.getPhysicalResourceId() != null ? event.getPhysicalResourceId() : context.getLogStreamName(), + "StackId", event.getStackId(), + "RequestId", event.getRequestId(), + "LogicalResourceId", event.getLogicalResourceId(), + "Data", data + ); + + try { + StringEntity entity = new StringEntity(objectMapper.writeValueAsString(responseBody)); + HttpPut request = new HttpPut(url); + request.setEntity(entity); + request.setHeader("Content-Type", "application/json"); + + httpClient.execute(request, response -> { + EntityUtils.consume(response.getEntity()); + logger.log("Response sent to CloudFormation successfully."); + return null; + }); + logger.log("Response sent to CloudFormation successfully."); + } catch (IOException e) { + logger.log("Failed to send response to CloudFormation: " + e.getMessage()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/template.yaml b/template.yaml index becfa92..fa9c4be 100644 --- a/template.yaml +++ b/template.yaml @@ -14,6 +14,15 @@ Parameters: Description: User pool group name for API administrators Type: String Default: apiAdmins + FrontendLoginUrl: + Description: Frontend url for login + Type: String + AdminEmail: + Description: Email address of admin + Type: String + AdminUsername: + Description: Username of admin + Type: String Globals: Api: @@ -541,6 +550,41 @@ Resources: Variables: TASKS_CLOSED_NOTIFICATION_TOPIC_ARN: !Ref ClosedTasksNotificationTopic + UpdateInviteMessageAdminCreationLambdaHandler: + Type: AWS::Serverless::Function + Properties: + Handler: org.umaxcode.UpdateInviteMessageAdminCreationLambdaHandler::handleRequest + CodeUri: . + Policies: + - AWSLambdaBasicExecutionRole + - Statement: + Effect: Allow + Action: + - cognito-idp:UpdateUserPool + - cognito-idp:AdminCreateUser + - cognito-idp:AdminGetUser + Resource: !GetAtt UserPool.Arn + - Statement: + Effect: Allow + Action: + - sns:Subscribe + - sns:SetSubscriptionAttributes + Resource: + - !Ref ClosedTasksNotificationTopic + - !Ref TaskCompleteNotificationTopic + + UpdateInviteMessageTemplateAndCreateAdminUserCustomResource: + Type: Custom::UpdateInviteMessageTemplate + Properties: + ServiceToken: !GetAtt UpdateInviteMessageAdminCreationLambdaHandler.Arn + ServiceTimeout: 30 + UserPoolId: !Ref UserPool + FrontendLoginUrl: !Ref FrontendLoginUrl + AdminEmail: !Ref AdminEmail + AdminUsername: !Ref AdminUsername + TaskCompleteTopicArn: !Ref TaskCompleteNotificationTopic + ClosedTaskTopicArn: !Ref ClosedTasksNotificationTopic + Outputs: TaskManagementSystemApi: Description: URL for application From fb0335322a0f78c14f7c2ebae054fe526216fcf2 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Wed, 15 Jan 2025 11:26:30 +0000 Subject: [PATCH 12/28] refactor: update invite message template --- .../UpdateInviteMessageAdminCreationLambdaHandler.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java b/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java index 8f225f4..8733bf3 100644 --- a/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java +++ b/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java @@ -62,7 +62,7 @@ public Void handleRequest(CloudFormationCustomResourceEvent event, Context conte return null; } - updateInviteMessageTemplate(adminEmail, loginUrl, userPoolId, responseData, context); + updateInviteMessageTemplate(adminUsername, loginUrl, userPoolId, responseData, context); createAdminUser(adminEmail, adminUsername, userPoolId, responseData, context); @@ -88,16 +88,16 @@ public Void handleRequest(CloudFormationCustomResourceEvent event, Context conte return null; } - private void updateInviteMessageTemplate(String email, String loginUrl, String userPoolId, Map responseData, Context context) { + private void updateInviteMessageTemplate(String username, String loginUrl, String userPoolId, Map responseData, Context context) { MessageTemplateType inviteMessageTemplate = MessageTemplateType.builder() .emailMessage((""" - Hello {user}, Welcome to our Task Management System! - Your username is %s and temporary password is {####} + Hello %s, Welcome to our Task Management System! + Your username is {username} and temporary password is {####} Click here to sign in: %s """) - .formatted(email, loginUrl)) + .formatted(username, loginUrl)) .emailSubject("Welcome to Task Management System") .build(); From f823cec772aae1d6d822a9178d4381c6eb575fd2 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Thu, 16 Jan 2025 08:04:10 +0000 Subject: [PATCH 13/28] refactor: update condition expression parameter --- .../org/umaxcode/service/impl/TaskManagementServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java index be4c2a9..67ef529 100644 --- a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java +++ b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java @@ -255,7 +255,7 @@ public TaskDto updateTaskDetails(String id, TaskDetailsUpdateDto request) { .tableName(tasksTableName) .key(key) .updateExpression("SET #name = :name, description = :description") - .conditionExpression("#status = :open") + .conditionExpression("#status = :status") .expressionAttributeValues(Map.of( ":status", AttributeValue.builder().s("open").build(), ":name", AttributeValue.builder().s(request.name()).build(), From 60c9a133a89cc173d17819713be2d9dc43c5c28f Mon Sep 17 00:00:00 2001 From: umaxcode Date: Thu, 16 Jan 2025 09:35:20 +0000 Subject: [PATCH 14/28] refactor: update endpoint return type --- .../org/umaxcode/controller/TaskManagementController.java | 3 ++- src/main/java/org/umaxcode/service/TaskManagementService.java | 2 +- .../org/umaxcode/service/impl/TaskManagementServiceImpl.java | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/umaxcode/controller/TaskManagementController.java b/src/main/java/org/umaxcode/controller/TaskManagementController.java index 4bbb6e7..c7245a5 100644 --- a/src/main/java/org/umaxcode/controller/TaskManagementController.java +++ b/src/main/java/org/umaxcode/controller/TaskManagementController.java @@ -26,9 +26,10 @@ public class TaskManagementController { public SuccessResponse createTask(@RequestBody TasksCreationDto request, @AuthenticationPrincipal Jwt jwt) { String adminEmail = jwt.getClaimAsString("email"); - taskManagementService.createItem(request, adminEmail); + TaskDto createdTask = taskManagementService.createItem(request, adminEmail); return SuccessResponse.builder() .message("Task created successfully") + .data(createdTask) .build(); } diff --git a/src/main/java/org/umaxcode/service/TaskManagementService.java b/src/main/java/org/umaxcode/service/TaskManagementService.java index e300627..ce6d421 100644 --- a/src/main/java/org/umaxcode/service/TaskManagementService.java +++ b/src/main/java/org/umaxcode/service/TaskManagementService.java @@ -8,7 +8,7 @@ public interface TaskManagementService { - void createItem(TasksCreationDto item, String email); + TaskDto createItem(TasksCreationDto item, String email); TaskDto readItem(String id); diff --git a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java index 67ef529..68ec535 100644 --- a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java +++ b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java @@ -46,7 +46,7 @@ public TaskManagementServiceImpl(DynamoDbClient dynamoDbClient, CognitoIdentityP } @Override - public void createItem(TasksCreationDto request, String email) { + public TaskDto createItem(TasksCreationDto request, String email) { Map item = new HashMap<>(); item.put("taskId", AttributeValue.builder().s(UUID.randomUUID().toString()).build()); @@ -62,10 +62,12 @@ public void createItem(TasksCreationDto request, String email) { PutItemRequest putRequest = PutItemRequest.builder() .tableName(tasksTableName) .item(item) + .returnValues("ALL_OLD") .build(); PutItemResponse putItemResponse = dynamoDbClient.putItem(putRequest); System.out.println("results " + putItemResponse.attributes()); + return TaskMapper.mapToTaskDto(putItemResponse.attributes()); } @Override From e3fc75ba39909c08464c296ff8771015043a2061 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Thu, 16 Jan 2025 10:01:38 +0000 Subject: [PATCH 15/28] refactor: update response object --- .../service/impl/TaskManagementServiceImpl.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java index 68ec535..1b7d522 100644 --- a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java +++ b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java @@ -65,9 +65,16 @@ public TaskDto createItem(TasksCreationDto request, String email) { .returnValues("ALL_OLD") .build(); - PutItemResponse putItemResponse = dynamoDbClient.putItem(putRequest); - System.out.println("results " + putItemResponse.attributes()); - return TaskMapper.mapToTaskDto(putItemResponse.attributes()); + dynamoDbClient.putItem(putRequest); + return TaskDto.builder() + .id(item.get("taskId").s()) + .name(request.name()) + .description(request.description()) + .status(TaskStatus.OPEN) + .responsibility(request.responsibility()) + .deadline(request.deadline().toString()) + .assignedBy(email) + .build(); } @Override From bf7e715f41f080ebc67122f874a891252bbe1e6f Mon Sep 17 00:00:00 2001 From: umaxcode Date: Thu, 16 Jan 2025 10:44:20 +0000 Subject: [PATCH 16/28] refactor: update user creation response type and user mapper function --- .../org/umaxcode/controller/UserAuthController.java | 5 +++-- .../org/umaxcode/domain/dto/response/UserDto.java | 4 +++- src/main/java/org/umaxcode/mapper/UserMapper.java | 11 +++++++++-- .../java/org/umaxcode/service/UserAuthService.java | 2 +- .../umaxcode/service/impl/UserAuthServiceImpl.java | 11 +++++++++-- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/umaxcode/controller/UserAuthController.java b/src/main/java/org/umaxcode/controller/UserAuthController.java index 68bca81..910e027 100644 --- a/src/main/java/org/umaxcode/controller/UserAuthController.java +++ b/src/main/java/org/umaxcode/controller/UserAuthController.java @@ -23,9 +23,10 @@ public class UserAuthController { @ResponseStatus(HttpStatus.CREATED) public SuccessResponse signup(@RequestBody UserCreationDto request) { - String message = userAuthService.register(request); + UserDto registeredUser = userAuthService.register(request); return SuccessResponse.builder() - .message(message) + .message("User created successfully") + .data(registeredUser) .build(); } diff --git a/src/main/java/org/umaxcode/domain/dto/response/UserDto.java b/src/main/java/org/umaxcode/domain/dto/response/UserDto.java index 2e42648..56e655a 100644 --- a/src/main/java/org/umaxcode/domain/dto/response/UserDto.java +++ b/src/main/java/org/umaxcode/domain/dto/response/UserDto.java @@ -5,6 +5,8 @@ @Builder public record UserDto( String userId, - String email + String username, + String email, + String role ) { } diff --git a/src/main/java/org/umaxcode/mapper/UserMapper.java b/src/main/java/org/umaxcode/mapper/UserMapper.java index 6532110..7733d77 100644 --- a/src/main/java/org/umaxcode/mapper/UserMapper.java +++ b/src/main/java/org/umaxcode/mapper/UserMapper.java @@ -19,8 +19,15 @@ public static List toUserDto(List users) { .email(u.attributes().stream() .filter(a -> "email".equalsIgnoreCase(a.name())) .map(AttributeType::value) - .findFirst().orElse(null) - ) + .findFirst().orElse(null)) + .username(u.attributes().stream() + .filter(a -> "name".equalsIgnoreCase(a.name())) + .map(AttributeType::value) + .findFirst().orElse(null)) + .role(u.attributes().stream() + .filter(a -> "custom:role".equalsIgnoreCase(a.name())) + .map(AttributeType::value) + .findFirst().orElse(null)) .build() ).toList(); } diff --git a/src/main/java/org/umaxcode/service/UserAuthService.java b/src/main/java/org/umaxcode/service/UserAuthService.java index 506761d..9612dd1 100644 --- a/src/main/java/org/umaxcode/service/UserAuthService.java +++ b/src/main/java/org/umaxcode/service/UserAuthService.java @@ -7,7 +7,7 @@ public interface UserAuthService { - String register(UserCreationDto request); + UserDto register(UserCreationDto request); List fetchAllUsers(); } diff --git a/src/main/java/org/umaxcode/service/impl/UserAuthServiceImpl.java b/src/main/java/org/umaxcode/service/impl/UserAuthServiceImpl.java index 50b2db4..47a0774 100644 --- a/src/main/java/org/umaxcode/service/impl/UserAuthServiceImpl.java +++ b/src/main/java/org/umaxcode/service/impl/UserAuthServiceImpl.java @@ -32,7 +32,7 @@ public class UserAuthServiceImpl implements UserAuthService { private String userPoolId; @Override - public String register(UserCreationDto request) { + public UserDto register(UserCreationDto request) { try { AdminCreateUserRequest adminRequest = AdminCreateUserRequest.builder() @@ -51,7 +51,14 @@ public String register(UserCreationDto request) { AdminCreateUserResponse response = cognitoClient.adminCreateUser(adminRequest); startStateMachineForSNSSub(request.email()); System.out.println("User" + response.user()); - return "User created: " + response.user().username(); + + return UserDto.builder() + .userId(request.username()) + .email(request.email()) + .username(request.username()) + .role(Role.USER.toString()) + .build(); + } catch (CognitoIdentityProviderException e) { throw new UserAuthException("Failed to create user: " + e.getMessage()); } From c9d548cf9136886cadebe2eb7b81b7bf3968c069 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Thu, 16 Jan 2025 11:26:27 +0000 Subject: [PATCH 17/28] refactor: return the right userId --- .../java/org/umaxcode/service/impl/UserAuthServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/umaxcode/service/impl/UserAuthServiceImpl.java b/src/main/java/org/umaxcode/service/impl/UserAuthServiceImpl.java index 47a0774..187ad10 100644 --- a/src/main/java/org/umaxcode/service/impl/UserAuthServiceImpl.java +++ b/src/main/java/org/umaxcode/service/impl/UserAuthServiceImpl.java @@ -53,7 +53,7 @@ public UserDto register(UserCreationDto request) { System.out.println("User" + response.user()); return UserDto.builder() - .userId(request.username()) + .userId(response.user().username()) .email(request.email()) .username(request.username()) .role(Role.USER.toString()) From b11d96aaf24a22d57e6edcdcff89e2b74d0151c3 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Thu, 16 Jan 2025 11:55:25 +0000 Subject: [PATCH 18/28] refactor: update response message --- .../org/umaxcode/controller/TaskManagementController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/umaxcode/controller/TaskManagementController.java b/src/main/java/org/umaxcode/controller/TaskManagementController.java index c7245a5..007353e 100644 --- a/src/main/java/org/umaxcode/controller/TaskManagementController.java +++ b/src/main/java/org/umaxcode/controller/TaskManagementController.java @@ -103,7 +103,7 @@ public SuccessResponse reAssignTask(@PathVariable("id") String id, @RequestBody TaskDto updatedTask = taskManagementService.reAssignTask(id, request); return SuccessResponse.builder() - .message("Task comment updated successfully") + .message("Task reassigned successfully") .data(updatedTask) .build(); } @@ -116,7 +116,7 @@ public SuccessResponse updateTaskDetails(@PathVariable("id") String id, @Request TaskDto updatedTask = taskManagementService.updateTaskDetails(id, request); return SuccessResponse.builder() - .message("Task comment updated successfully") + .message("Task details updated successfully") .data(updatedTask) .build(); } From f56d32d4dda62a30fd09a5ccbd3d7ccc70643d55 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Fri, 17 Jan 2025 08:30:09 +0000 Subject: [PATCH 19/28] refactor: update invite message --- ...viteMessageAdminCreationLambdaHandler.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java b/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java index 8733bf3..c020132 100644 --- a/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java +++ b/src/main/java/org/umaxcode/UpdateInviteMessageAdminCreationLambdaHandler.java @@ -62,7 +62,7 @@ public Void handleRequest(CloudFormationCustomResourceEvent event, Context conte return null; } - updateInviteMessageTemplate(adminUsername, loginUrl, userPoolId, responseData, context); + updateInviteMessageTemplate(loginUrl, userPoolId, responseData, context); createAdminUser(adminEmail, adminUsername, userPoolId, responseData, context); @@ -88,17 +88,20 @@ public Void handleRequest(CloudFormationCustomResourceEvent event, Context conte return null; } - private void updateInviteMessageTemplate(String username, String loginUrl, String userPoolId, Map responseData, Context context) { + private void updateInviteMessageTemplate(String loginUrl, String userPoolId, Map responseData, Context context) { MessageTemplateType inviteMessageTemplate = MessageTemplateType.builder() - .emailMessage((""" - Hello %s, Welcome to our Task Management System! - Your username is {username} and temporary password is {####} - - Click here to sign in: %s - """) - .formatted(username, loginUrl)) - .emailSubject("Welcome to Task Management System") + .emailMessage(String.format(""" + + +

Hello Sir/Madam

+

Welcome to our Task Management System!

+

Your username is: {username} and your temporary password is: {####}

+

Click here to sign in.

+ + + """, loginUrl)) + .emailSubject("Task Management System") .build(); cognitoIdentityProviderClient.updateUserPool(UpdateUserPoolRequest.builder() From f4404092040b3e3c346dda7f8ed91fefadf91e48 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Fri, 17 Jan 2025 09:59:17 +0000 Subject: [PATCH 20/28] chore: update README.md --- README.md | 166 +++++++++++++++++++++++++++--------------------------- 1 file changed, 84 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 16e1d71..41f4631 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,84 @@ -# task-management-system serverless API -The task-management-system project, created with [`aws-serverless-java-container`](https://github.com/aws/serverless-java-container). - -The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. - -The project folder also includes a `template.yml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with the [SAM CLI](https://github.com/awslabs/aws-sam-cli). - -## Pre-requisites -* [AWS CLI](https://aws.amazon.com/cli/) -* [SAM CLI](https://github.com/awslabs/aws-sam-cli) -* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) - -## Building the project -You can use the SAM CLI to quickly build the project -```bash -$ mvn archetype:generate -DartifactId=task-management-system -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-jersey-archetype -DarchetypeVersion=2.1.0 -DgroupId=org.umaxcode -Dversion=1.0-SNAPSHOT -Dinteractive=false -$ cd task-management-system -$ sam build -Building resource 'TaskManagementSystemFunction' -Running JavaGradleWorkflow:GradleBuild -Running JavaGradleWorkflow:CopyArtifacts - -Build Succeeded - -Built Artifacts : .aws-sam/build -Built Template : .aws-sam/build/template.yaml - -Commands you can use next -========================= -[*] Invoke Function: sam local invoke -[*] Deploy: sam deploy --guided -``` - -## Testing locally with the SAM CLI - -From the project root folder - where the `template.yml` file is located - start the API with the SAM CLI. - -```bash -$ sam local start-api - -... -Mounting com.amazonaws.serverless.archetypes.StreamLambdaHandler::handleRequest (java11) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] -... -``` - -Using a new shell, you can send a test ping request to your API: - -```bash -$ curl -s http://127.0.0.1:3000/ping | python -m json.tool - -{ - "pong": "Hello, World!" -} -``` - -## Deploying to AWS -To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen - -``` -$ sam deploy --guided -``` - -Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL - -``` -... -------------------------------------------------------------------------------------------------------------- -OutputKey-Description OutputValue -------------------------------------------------------------------------------------------------------------- -TaskManagementSystemApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets -------------------------------------------------------------------------------------------------------------- -``` - -Copy the `OutputValue` into a browser or use curl to test your first request: - -```bash -$ curl -s https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping | python -m json.tool - -{ - "pong": "Hello, World!" -} -``` +# Task Management System + +## Project Overview + +The **Task Management System** is designed to streamline task allocation, monitoring, and updates for a field team. The system enables an administrator to create and assign tasks, track their progress, and ensure timely completion. Team members can log in to view, update, and complete their tasks, while notifications and deadline tracking ensure efficient workflows. + +## Features + +- **Role-based Access**: Administrators manage all tasks, assignments, and deadlines. Team members only see their assigned tasks. +- **Notifications**: + - Assigned users receive email notifications for task details and deadlines. + - Administrators are notified of task completions or deadline breaches. +- **Task Management**: + - Tasks can be created, updated, and reopened. + - Tasks include attributes such as name, description, status, responsibility, deadlines, and user comments. +- **Scalable and Secure Architecture**: Built using AWS serverless services to ensure high availability and scalability. + +--- + +## Technical Requirements + +### Backend + +- **Deployment**: + - AWS SAM for backend resources. + - Support for test and production environments. +- **Services Used**: + - **Amazon Cognito**: User onboarding and authentication. + - **Amazon DynamoDB**: Data storage for tasks and user information. Also utilized DynamoDB Stream + - **AWS Lambda**: Business logic implementation. + - **Amazon SNS**: Notifications via email for various task-related events. + - **Amazon SQS**: Queueing for notifications and processing tasks. + - **AWS Step Functions**: Orchestrating workflows for task notifications and updates. + - **Amazon Amplify**: Serving the frontend securely and efficiently. + +### Frontend + +- **Deployment**: + - Hosted on **AWS Amplify** with testing and production environment +- **Continuous Integration/Deployment**: + - GitHub integration for automated deployments to Amplify. +- **Code**: + - [Frontend GitHub Repository](https://github.com/UmaxCode/tasks-management-app.git) + +--- + +## Functional Requirements + +1. **Task Creation**: + - Admin creates tasks with attributes: name, description, status (default: open), responsibility, deadline, and comments. +2. **Task Assignment**: + - Admin assigns tasks to users. + - Notifications are sent to assigned users via email using SNS Topic filtering. +3. **Task Status Updates**: + - Team members can update task status (e.g., mark as completed). + - Admin is notified of status changes. + - Team members can add comment +4. **Task Deadline Notifications**: + - 1 hour before a deadline, users are notified via SNS Topic filtering. +5. **Task Reopening**: + - Only admins can reopen closed tasks, triggering notifications to the assigned user. +6. **Task Deadline Breaches**: + - When a task deadline is missed: + - Task status is updated to "expired." + - Notifications are sent to the user and admin. +7. **Reassignment**: + - If a task's assigned user is changed: + - The task is removed from the old user's list. + - The new user is notified. + +--- + +## Architectural Diagram +![Local Image](ARCHITECTURAL-DIAGRAM.png) + +--- + +## Deployment Guidelines + +### Manual Deployment +Follow these steps to deploy the project manually. + +### Automated Deployment - GITHUB ACTIONS +Follow these steps to deploy the project manually. \ No newline at end of file From 586f29afdfdc60e6dd6593f7f3a0ed0993147bec Mon Sep 17 00:00:00 2001 From: umaxcode Date: Fri, 17 Jan 2025 11:17:28 +0000 Subject: [PATCH 21/28] fix: fix isNotifiedForApproachDeadline state when task is reassigned --- .../org/umaxcode/service/impl/TaskManagementServiceImpl.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java index 1b7d522..98ee331 100644 --- a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java +++ b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java @@ -228,12 +228,13 @@ public TaskDto reopenTask(String id, TaskReopenDto request) { UpdateItemRequest updateItemRequest = UpdateItemRequest.builder() .tableName(tasksTableName) .key(key) - .updateExpression("SET #status = :status, deadline = :deadline") + .updateExpression("SET #status = :status, deadline = :deadline, isNotifiedForApproachDeadline = :false") .conditionExpression("#status = :expired") .expressionAttributeValues(Map.of( ":status", AttributeValue.builder().s("open").build(), ":deadline", AttributeValue.builder().s(request.deadline().toString()).build(), - ":expired", AttributeValue.builder().s("expired").build() + ":expired", AttributeValue.builder().s("expired").build(), + ":false", AttributeValue.builder().s("0").build() )) .expressionAttributeNames(Map.of( "#status", "status" From 8ba24e98baea7c6e16a4e0c12c3ac4199f9f1e18 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Fri, 17 Jan 2025 11:31:31 +0000 Subject: [PATCH 22/28] fix: fix isNotifiedForApproachDeadline state type mismatch error --- .../org/umaxcode/service/impl/TaskManagementServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java index 98ee331..c8f9406 100644 --- a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java +++ b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java @@ -234,7 +234,7 @@ public TaskDto reopenTask(String id, TaskReopenDto request) { ":status", AttributeValue.builder().s("open").build(), ":deadline", AttributeValue.builder().s(request.deadline().toString()).build(), ":expired", AttributeValue.builder().s("expired").build(), - ":false", AttributeValue.builder().s("0").build() + ":false", AttributeValue.builder().n("0").build() )) .expressionAttributeNames(Map.of( "#status", "status" From 398fa6463db3e3c6d651642d77436d9d37973bdc Mon Sep 17 00:00:00 2001 From: umaxcode Date: Fri, 17 Jan 2025 14:15:28 +0000 Subject: [PATCH 23/28] feat: add validation to request body and method renaming --- pom.xml | 4 ++++ .../controller/TaskManagementController.java | 17 +++++++++-------- .../domain/dto/request/ReassignTaskDto.java | 6 ++++++ .../dto/request/TaskCommentUpdateDto.java | 4 ++++ .../dto/request/TaskDetailsUpdateDto.java | 6 ++++++ .../domain/dto/request/TaskReopenDto.java | 6 ++++++ .../domain/dto/request/TasksCreationDto.java | 14 ++++++++++++++ .../domain/dto/request/UserCreationDto.java | 8 ++++++++ .../umaxcode/service/TaskManagementService.java | 4 ++-- .../service/impl/TaskManagementServiceImpl.java | 4 ++-- 10 files changed, 61 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index af2c5bb..37d7eb6 100644 --- a/pom.xml +++ b/pom.xml @@ -112,6 +112,10 @@ junit-jupiter test
+ + org.springframework.boot + spring-boot-starter-validation + diff --git a/src/main/java/org/umaxcode/controller/TaskManagementController.java b/src/main/java/org/umaxcode/controller/TaskManagementController.java index 007353e..7f2803a 100644 --- a/src/main/java/org/umaxcode/controller/TaskManagementController.java +++ b/src/main/java/org/umaxcode/controller/TaskManagementController.java @@ -1,5 +1,6 @@ package org.umaxcode.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -23,10 +24,10 @@ public class TaskManagementController { @PostMapping @PreAuthorize(value = "hasRole('ADMIN')") @ResponseStatus(HttpStatus.CREATED) - public SuccessResponse createTask(@RequestBody TasksCreationDto request, @AuthenticationPrincipal Jwt jwt) { + public SuccessResponse createTask(@Valid @RequestBody TasksCreationDto request, @AuthenticationPrincipal Jwt jwt) { String adminEmail = jwt.getClaimAsString("email"); - TaskDto createdTask = taskManagementService.createItem(request, adminEmail); + TaskDto createdTask = taskManagementService.createAndAssignTask(request, adminEmail); return SuccessResponse.builder() .message("Task created successfully") .data(createdTask) @@ -38,7 +39,7 @@ public SuccessResponse createTask(@RequestBody TasksCreationDto request, @Authen @ResponseStatus(HttpStatus.OK) public SuccessResponse retrieveTask(@PathVariable("id") String taskId) { - TaskDto taskDto = taskManagementService.readItem(taskId); + TaskDto taskDto = taskManagementService.fetchTask(taskId); return SuccessResponse.builder() .message("Task retrieved successfully") .data(taskDto) @@ -46,7 +47,7 @@ public SuccessResponse retrieveTask(@PathVariable("id") String taskId) { } @GetMapping("/users/{email}") - @PreAuthorize(value= "hasAnyRole('ADMIN', 'USER')") + @PreAuthorize(value = "hasAnyRole('ADMIN', 'USER')") @ResponseStatus(HttpStatus.OK) public SuccessResponse retrieveUserTasks(@PathVariable String email) { @@ -72,7 +73,7 @@ public SuccessResponse makeTaskAsCompleted(@PathVariable("id") String id, @Authe @PatchMapping("/{id}/reopen") @PreAuthorize(value = "hasRole('ADMIN')") @ResponseStatus(HttpStatus.OK) - public SuccessResponse reopenTask(@PathVariable("id") String id, @RequestBody TaskReopenDto request) { + public SuccessResponse reopenTask(@PathVariable("id") String id, @Valid @RequestBody TaskReopenDto request) { TaskDto updatedTask = taskManagementService.reopenTask(id, request); return SuccessResponse.builder() @@ -85,7 +86,7 @@ public SuccessResponse reopenTask(@PathVariable("id") String id, @RequestBody Ta @PreAuthorize(value = "hasRole('USER')") @ResponseStatus(HttpStatus.OK) public SuccessResponse updateTaskComment(@PathVariable("id") String id, - @RequestBody TaskCommentUpdateDto request + @Valid @RequestBody TaskCommentUpdateDto request ) { TaskDto updatedTask = taskManagementService.updateTaskComment(id, request); @@ -98,7 +99,7 @@ public SuccessResponse updateTaskComment(@PathVariable("id") String id, @PatchMapping("/{id}/reassign") @PreAuthorize(value = "hasRole('ADMIN')") @ResponseStatus(HttpStatus.OK) - public SuccessResponse reAssignTask(@PathVariable("id") String id, @RequestBody ReassignTaskDto request + public SuccessResponse reAssignTask(@PathVariable("id") String id, @Valid @RequestBody ReassignTaskDto request ) { TaskDto updatedTask = taskManagementService.reAssignTask(id, request); @@ -111,7 +112,7 @@ public SuccessResponse reAssignTask(@PathVariable("id") String id, @RequestBody @PatchMapping("/{id}") @PreAuthorize(value = "hasRole('ADMIN')") @ResponseStatus(HttpStatus.OK) - public SuccessResponse updateTaskDetails(@PathVariable("id") String id, @RequestBody TaskDetailsUpdateDto request + public SuccessResponse updateTaskDetails(@PathVariable("id") String id, @Valid @RequestBody TaskDetailsUpdateDto request ) { TaskDto updatedTask = taskManagementService.updateTaskDetails(id, request); diff --git a/src/main/java/org/umaxcode/domain/dto/request/ReassignTaskDto.java b/src/main/java/org/umaxcode/domain/dto/request/ReassignTaskDto.java index 2cdb20e..137fb37 100644 --- a/src/main/java/org/umaxcode/domain/dto/request/ReassignTaskDto.java +++ b/src/main/java/org/umaxcode/domain/dto/request/ReassignTaskDto.java @@ -1,6 +1,12 @@ package org.umaxcode.domain.dto.request; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + public record ReassignTaskDto( + + @NotBlank(message = "User email is required") + @Email(message = "Invalid email") String userEmail ) { } diff --git a/src/main/java/org/umaxcode/domain/dto/request/TaskCommentUpdateDto.java b/src/main/java/org/umaxcode/domain/dto/request/TaskCommentUpdateDto.java index 9415edd..d39e63e 100644 --- a/src/main/java/org/umaxcode/domain/dto/request/TaskCommentUpdateDto.java +++ b/src/main/java/org/umaxcode/domain/dto/request/TaskCommentUpdateDto.java @@ -1,6 +1,10 @@ package org.umaxcode.domain.dto.request; +import jakarta.validation.constraints.NotBlank; + public record TaskCommentUpdateDto( + + @NotBlank(message = "Comment is required") String comment ) { } diff --git a/src/main/java/org/umaxcode/domain/dto/request/TaskDetailsUpdateDto.java b/src/main/java/org/umaxcode/domain/dto/request/TaskDetailsUpdateDto.java index 172efa7..b1e276a 100644 --- a/src/main/java/org/umaxcode/domain/dto/request/TaskDetailsUpdateDto.java +++ b/src/main/java/org/umaxcode/domain/dto/request/TaskDetailsUpdateDto.java @@ -1,7 +1,13 @@ package org.umaxcode.domain.dto.request; +import jakarta.validation.constraints.NotBlank; + public record TaskDetailsUpdateDto( + + @NotBlank(message = "Name is required") String name, + + @NotBlank(message = "Description is required") String description ) { } diff --git a/src/main/java/org/umaxcode/domain/dto/request/TaskReopenDto.java b/src/main/java/org/umaxcode/domain/dto/request/TaskReopenDto.java index 5d18973..c741779 100644 --- a/src/main/java/org/umaxcode/domain/dto/request/TaskReopenDto.java +++ b/src/main/java/org/umaxcode/domain/dto/request/TaskReopenDto.java @@ -1,8 +1,14 @@ package org.umaxcode.domain.dto.request; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; + import java.time.LocalDateTime; public record TaskReopenDto( + + @NotBlank(message = "Deadline is required") + @Future(message = "Deadline must be in the future") LocalDateTime deadline ) { } diff --git a/src/main/java/org/umaxcode/domain/dto/request/TasksCreationDto.java b/src/main/java/org/umaxcode/domain/dto/request/TasksCreationDto.java index bd25c74..854b3d9 100644 --- a/src/main/java/org/umaxcode/domain/dto/request/TasksCreationDto.java +++ b/src/main/java/org/umaxcode/domain/dto/request/TasksCreationDto.java @@ -1,11 +1,25 @@ package org.umaxcode.domain.dto.request; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; + import java.time.LocalDateTime; public record TasksCreationDto( + + @NotBlank(message = "Name is required") String name, + + @NotBlank(message = "Description is required") String description, + + @NotBlank(message = "Deadline is required") + @Future(message = "Deadline must be in the future") LocalDateTime deadline, + + @NotBlank(message = "Deadline is required") + @Email(message = "Invalid email") String responsibility ) { } diff --git a/src/main/java/org/umaxcode/domain/dto/request/UserCreationDto.java b/src/main/java/org/umaxcode/domain/dto/request/UserCreationDto.java index 0745f07..912fd14 100644 --- a/src/main/java/org/umaxcode/domain/dto/request/UserCreationDto.java +++ b/src/main/java/org/umaxcode/domain/dto/request/UserCreationDto.java @@ -1,7 +1,15 @@ package org.umaxcode.domain.dto.request; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + public record UserCreationDto( + + @NotBlank(message = "Username is required") String username, + + @NotBlank(message = "Email is required") + @Email(message = "Invalid email") String email ) { } diff --git a/src/main/java/org/umaxcode/service/TaskManagementService.java b/src/main/java/org/umaxcode/service/TaskManagementService.java index ce6d421..bb2cada 100644 --- a/src/main/java/org/umaxcode/service/TaskManagementService.java +++ b/src/main/java/org/umaxcode/service/TaskManagementService.java @@ -8,9 +8,9 @@ public interface TaskManagementService { - TaskDto createItem(TasksCreationDto item, String email); + TaskDto createAndAssignTask(TasksCreationDto item, String email); - TaskDto readItem(String id); + TaskDto fetchTask(String id); List getAllTasks(); diff --git a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java index c8f9406..a3f72aa 100644 --- a/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java +++ b/src/main/java/org/umaxcode/service/impl/TaskManagementServiceImpl.java @@ -46,7 +46,7 @@ public TaskManagementServiceImpl(DynamoDbClient dynamoDbClient, CognitoIdentityP } @Override - public TaskDto createItem(TasksCreationDto request, String email) { + public TaskDto createAndAssignTask(TasksCreationDto request, String email) { Map item = new HashMap<>(); item.put("taskId", AttributeValue.builder().s(UUID.randomUUID().toString()).build()); @@ -78,7 +78,7 @@ public TaskDto createItem(TasksCreationDto request, String email) { } @Override - public TaskDto readItem(String id) { + public TaskDto fetchTask(String id) { Map key = new HashMap<>(); key.put("taskId", AttributeValue.builder().s(id).build()); From 1ffd3039f80e43814576493a12168641bbc6a93b Mon Sep 17 00:00:00 2001 From: umaxcode Date: Fri, 17 Jan 2025 14:18:17 +0000 Subject: [PATCH 24/28] feat: add validation to request body --- src/main/java/org/umaxcode/controller/UserAuthController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/umaxcode/controller/UserAuthController.java b/src/main/java/org/umaxcode/controller/UserAuthController.java index 910e027..0b3543d 100644 --- a/src/main/java/org/umaxcode/controller/UserAuthController.java +++ b/src/main/java/org/umaxcode/controller/UserAuthController.java @@ -1,5 +1,6 @@ package org.umaxcode.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -21,7 +22,7 @@ public class UserAuthController { @PostMapping("/signup") @PreAuthorize(value = "hasRole('ADMIN')") @ResponseStatus(HttpStatus.CREATED) - public SuccessResponse signup(@RequestBody UserCreationDto request) { + public SuccessResponse signup(@Valid @RequestBody UserCreationDto request) { UserDto registeredUser = userAuthService.register(request); return SuccessResponse.builder() From f87291ba8b546a5391f508492c77d49ad237d385 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Fri, 17 Jan 2025 14:57:49 +0000 Subject: [PATCH 25/28] feat: update global exception handler --- .../org/umaxcode/exception/ErrorMessage.java | 2 +- .../exception/GlobalExceptionHandler.java | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/umaxcode/exception/ErrorMessage.java b/src/main/java/org/umaxcode/exception/ErrorMessage.java index 3e77f14..0596ede 100644 --- a/src/main/java/org/umaxcode/exception/ErrorMessage.java +++ b/src/main/java/org/umaxcode/exception/ErrorMessage.java @@ -10,6 +10,6 @@ public class ErrorMessage { private String path; - private String message; + private Object message; private String timestamp; } diff --git a/src/main/java/org/umaxcode/exception/GlobalExceptionHandler.java b/src/main/java/org/umaxcode/exception/GlobalExceptionHandler.java index e744276..af1afbd 100644 --- a/src/main/java/org/umaxcode/exception/GlobalExceptionHandler.java +++ b/src/main/java/org/umaxcode/exception/GlobalExceptionHandler.java @@ -2,11 +2,17 @@ import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; @RestControllerAdvice public class GlobalExceptionHandler { @@ -22,6 +28,44 @@ public ErrorMessage taskManagementExceptionHandler(TaskManagementException ex, H .build(); } + @ExceptionHandler(AuthenticationException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public ErrorMessage authenticationExceptionHandler(AuthenticationException ex, HttpServletRequest request) { + + return ErrorMessage.builder() + .path(request.getRequestURI()) + .message(ex.getMessage()) + .timestamp(LocalDateTime.now().toString()) + .build(); + } + + @ExceptionHandler(AccessDeniedException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ErrorMessage accessDeniedHandler(AccessDeniedException ex, HttpServletRequest request) { + + return ErrorMessage.builder() + .path(request.getRequestURI()) + .message("You do not have permission to access this resource.") + .timestamp(LocalDateTime.now().toString()) + .build(); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ErrorMessage handleArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) { + + Map errors = new HashMap<>(); + + for (FieldError error : ex.getBindingResult().getFieldErrors()) { + errors.put(error.getField(), error.getDefaultMessage()); + } + + return ErrorMessage.builder() + .path(request.getRequestURI()) + .message(errors) + .timestamp(LocalDateTime.now().toString()) + .build(); + } + @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorMessage exceptionHandler(Exception ex, HttpServletRequest request) { From 6b9828a8eed975297e31a5069b6e68f9e962dcb1 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Fri, 17 Jan 2025 15:08:16 +0000 Subject: [PATCH 26/28] refactor: add response status to exception handler --- src/main/java/org/umaxcode/exception/GlobalExceptionHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/umaxcode/exception/GlobalExceptionHandler.java b/src/main/java/org/umaxcode/exception/GlobalExceptionHandler.java index af1afbd..5e86161 100644 --- a/src/main/java/org/umaxcode/exception/GlobalExceptionHandler.java +++ b/src/main/java/org/umaxcode/exception/GlobalExceptionHandler.java @@ -51,6 +51,7 @@ public ErrorMessage accessDeniedHandler(AccessDeniedException ex, HttpServletReq } @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorMessage handleArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) { Map errors = new HashMap<>(); From e22d10bd6f1b374b63260dae6abb55d3923ab4e1 Mon Sep 17 00:00:00 2001 From: umaxcode Date: Fri, 17 Jan 2025 16:13:53 +0000 Subject: [PATCH 27/28] fix: fix resource not found response status --- src/main/java/org/umaxcode/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/umaxcode/config/SecurityConfig.java b/src/main/java/org/umaxcode/config/SecurityConfig.java index 6179e96..9b34ce9 100644 --- a/src/main/java/org/umaxcode/config/SecurityConfig.java +++ b/src/main/java/org/umaxcode/config/SecurityConfig.java @@ -29,7 +29,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, DelegatedEntry .cors(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth - .requestMatchers("/ping").permitAll() + .requestMatchers("/ping", "/pong").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt From 556d12683f0f33a283a14edc301a40b3b9ce9d4c Mon Sep 17 00:00:00 2001 From: umaxcode Date: Fri, 17 Jan 2025 21:09:35 +0000 Subject: [PATCH 28/28] chore: update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41f4631..14af859 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ The **Task Management System** is designed to streamline task allocation, monito --- ## Architectural Diagram -![Local Image](ARCHITECTURAL-DIAGRAM.png) +[Link to architectural diagram](https://drive.google.com/file/d/1hnCXGY69D11RBGM_5vxP1taGss-_BIyE/view?usp=sharing) ---