From 91834a4d454962a7af66f53346454d210fdbb746 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 26 Jun 2025 08:59:16 +0800 Subject: [PATCH 01/34] feat: add command to list the parameters in parameters store --- unicorn_shared/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unicorn_shared/Makefile b/unicorn_shared/Makefile index ffc3833..415997c 100644 --- a/unicorn_shared/Makefile +++ b/unicorn_shared/Makefile @@ -32,3 +32,7 @@ delete-images: ## Deletes all shared images stacks --stack-name "uni-prop-$$env-images"; \ fi; \ done + +list-parameters: ## Lists all parameters in the Unicorn Properties namespace + aws ssm get-parameters-by-path --path "/uni-prop" --recursive --with-decryption --query 'Parameters[*].[Name,Value,Type]' --output table + \ No newline at end of file From facfcf1a0add12429700739d7011691a16e62cc6 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 26 Jun 2025 13:51:08 +0800 Subject: [PATCH 02/34] refactor: renamed UnicornPropertiesNamespace to UnicornApprovalsNamespace in shared namespace definition --- unicorn_shared/uni-prop-namespaces.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unicorn_shared/uni-prop-namespaces.yaml b/unicorn_shared/uni-prop-namespaces.yaml index 55602fb..3045c2b 100644 --- a/unicorn_shared/uni-prop-namespaces.yaml +++ b/unicorn_shared/uni-prop-namespaces.yaml @@ -14,12 +14,12 @@ Resources: Name: !Sub /uni-prop/UnicornContractsNamespace Value: "unicorn.contracts" - UnicornPropertiesNamespaceParam: + UnicornApprovalsNamespaceParam: Type: AWS::SSM::Parameter Properties: Type: String - Name: !Sub /uni-prop/UnicornPropertiesNamespace - Value: "unicorn.properties" + Name: !Sub /uni-prop/UnicornApprovalsNamespace + Value: "unicorn.approvals" UnicornWebNamespaceParam: Type: AWS::SSM::Parameter @@ -37,7 +37,7 @@ Outputs: UnicornPropertiesNamespace: Description: Unicorn Properties namespace parameter - Value: !Ref UnicornPropertiesNamespaceParam + Value: !Ref UnicornApprovalsNamespaceParam UnicornWebNamespace: Description: Unicorn Web namespace parameter @@ -49,7 +49,7 @@ Outputs: UnicornPropertiesNamespaceValue: Description: Unicorn Properties namespace parameter value - Value: !GetAtt UnicornPropertiesNamespaceParam.Value + Value: !GetAtt UnicornApprovalsNamespaceParam.Value UnicornWebNamespaceValue: Description: Unicorn Web namespace parameter value From 1364fadd870a5bc94e77f37df4e3d28f58967427 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 26 Jun 2025 13:52:54 +0800 Subject: [PATCH 03/34] refactor: updated resolve:ssm references for UnicornPropertiesNamespace to UnicornApprovalsNamespace across multiple templates --- unicorn_contracts/template.yaml | 2 +- .../integration/subscriber-policies.yaml | 2 +- unicorn_properties/template.yaml | 22 +++++++++---------- unicorn_web/integration/subscriptions.yaml | 2 +- unicorn_web/template.yaml | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/unicorn_contracts/template.yaml b/unicorn_contracts/template.yaml index 9ff3280..8600007 100644 --- a/unicorn_contracts/template.yaml +++ b/unicorn_contracts/template.yaml @@ -292,7 +292,7 @@ Resources: - !Ref AWS::AccountId source: - "{{resolve:ssm:/uni-prop/UnicornContractsNamespace}}" - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - "{{resolve:ssm:/uni-prop/UnicornWebNamespace}}" State: ENABLED #You may want to disable this rule in production Targets: diff --git a/unicorn_properties/integration/subscriber-policies.yaml b/unicorn_properties/integration/subscriber-policies.yaml index 6dfd3b2..5d2a4e5 100644 --- a/unicorn_properties/integration/subscriber-policies.yaml +++ b/unicorn_properties/integration/subscriber-policies.yaml @@ -46,6 +46,6 @@ Resources: "events:creatorAccount": "${aws:PrincipalAccount}" StringEquals: "events:source": - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" "Null": "events:source": "false" diff --git a/unicorn_properties/template.yaml b/unicorn_properties/template.yaml index be633bc..f2234c7 100644 --- a/unicorn_properties/template.yaml +++ b/unicorn_properties/template.yaml @@ -54,18 +54,18 @@ Globals: Variables: CONTRACT_STATUS_TABLE: !Ref ContractStatusTable EVENT_BUS: !Ref UnicornPropertiesEventBus - SERVICE_NAMESPACE: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" - POWERTOOLS_SERVICE_NAME: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + SERVICE_NAMESPACE: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" + POWERTOOLS_SERVICE_NAME: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" POWERTOOLS_TRACE_DISABLED: "false" # Explicitly disables tracing, default POWERTOOLS_LOGGER_LOG_EVENT: !If [IsProd, "false", "true"] # Logs incoming event, default POWERTOOLS_LOGGER_SAMPLE_RATE: !If [IsProd, "0.1", "0"] # Debug log sampling percentage, default - POWERTOOLS_METRICS_NAMESPACE: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + POWERTOOLS_METRICS_NAMESPACE: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" POWERTOOLS_LOG_LEVEL: INFO # Log level for Logger (INFO, DEBUG, etc.), default LOG_LEVEL: INFO # Log level for Logger Tags: stage: !Ref Stage project: !FindInMap [Constants, ProjectName, Value] - namespace: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + namespace: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" Resources: #### SSM PARAMETERS @@ -260,7 +260,7 @@ Resources: TableName: !Ref ContractStatusTable ImageUploadBucketName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/ImagesBucket}}" EventBusName: !GetAtt UnicornPropertiesEventBus.Name - ServiceName: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + ServiceName: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" # Store ApprovalStateMachineLogGroup workflow execution logs ApprovalStateMachineLogGroup: @@ -284,7 +284,7 @@ Resources: - Key: project Value: !FindInMap [Constants, ProjectName, Value] - Key: namespace - Value: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + Value: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - Key: stage Value: !Ref Stage @@ -300,7 +300,7 @@ Resources: - Key: project Value: !FindInMap [Constants, ProjectName, Value] - Key: namespace - Value: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + Value: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - Key: stage Value: !Ref Stage @@ -323,7 +323,7 @@ Resources: - Key: project Value: !FindInMap [Constants, ProjectName, Value] - Key: namespace - Value: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + Value: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - Key: stage Value: !Ref Stage @@ -354,7 +354,7 @@ Resources: Condition: StringEquals: events:source: - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" # Catchall rule used for development purposes. Logs all events matching any of the services to CloudWatch Logs UnicornPropertiesCatchAllRule: @@ -370,7 +370,7 @@ Resources: - !Ref AWS::AccountId source: - "{{resolve:ssm:/uni-prop/UnicornContractsNamespace}}" - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - "{{resolve:ssm:/uni-prop/UnicornWebNamespace}}" State: ENABLED #You may want to disable this rule in production Targets: @@ -386,7 +386,7 @@ Resources: LogGroupName: !Sub - "/aws/events/${Stage}/${NS}-catchall" - Stage: !Ref Stage - NS: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + NS: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" RetentionInDays: !FindInMap [LogsRetentionPeriodMap, !Ref Stage, Days] # Permissions to allow EventBridge to send logs to CloudWatch diff --git a/unicorn_web/integration/subscriptions.yaml b/unicorn_web/integration/subscriptions.yaml index d455cea..66c7c3d 100644 --- a/unicorn_web/integration/subscriptions.yaml +++ b/unicorn_web/integration/subscriptions.yaml @@ -23,7 +23,7 @@ Resources: Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" EventPattern: source: - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" detail-type: - PublicationEvaluationCompleted State: ENABLED diff --git a/unicorn_web/template.yaml b/unicorn_web/template.yaml index 6d36831..67f242b 100644 --- a/unicorn_web/template.yaml +++ b/unicorn_web/template.yaml @@ -156,7 +156,7 @@ Resources: EventBusName: !Ref UnicornWebEventBus Pattern: source: - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" detail-type: - PublicationEvaluationCompleted @@ -361,7 +361,7 @@ Resources: - !Ref AWS::AccountId source: - "{{resolve:ssm:/uni-prop/UnicornContractsNamespace}}" - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - "{{resolve:ssm:/uni-prop/UnicornWebNamespace}}" State: ENABLED #You may want to disable this rule in production Targets: From 98d78f9a46bd348ec0ab48943a05c1bb37d4977e Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Mon, 7 Jul 2025 08:19:06 +0800 Subject: [PATCH 04/34] refactor: change event bus references from UnicornPropertiesEventBus to UnicornApprovalsEventBus across multiple files --- unicorn_contracts/events/put_events.json | 4 +- .../put_event_contract_status_changed.json | 2 +- ..._event_publication_approval_requested.json | 2 +- .../src/test/events/send_events_cli.json | 2 +- .../integration/event-schemas.yaml | 4 +- .../integration/subscriber-policies.yaml | 4 +- .../integration/subscriptions.yaml | 8 ++-- unicorn_properties/samconfig.toml | 4 +- unicorn_properties/template.yaml | 38 +++++++++---------- unicorn_shared/uni-prop-namespaces.yaml | 2 +- unicorn_web/integration/subscriptions.yaml | 2 +- 11 files changed, 36 insertions(+), 36 deletions(-) diff --git a/unicorn_contracts/events/put_events.json b/unicorn_contracts/events/put_events.json index c3cfa63..4f81222 100644 --- a/unicorn_contracts/events/put_events.json +++ b/unicorn_contracts/events/put_events.json @@ -3,12 +3,12 @@ "Source": "Unicorn.Contracts", "Detail": "{\"contract_updated_on\":\"10/08/2022 20:36:30\",\"contract_id\": 111,\"property_id\":\"aaa\",\"contract_status\":\"DRAFT\"}", "DetailType": "ContractStatusChanged", - "EventBusName": "Dev-UnicornPropertiesEventBus" + "EventBusName": "Dev-UnicornApprovalsEventBus" }, { "Source": "Unicorn.Contracts", "Detail": "{\"contract_updated_on\":\"10/08/2022 19:56:30\",\"contract_id\":222,\"property_id\":\"bbb\",\"contract_status\":\"DRAFT\"}", "DetailType": "ContractStatusChanged", - "EventBusName": "Dev-UnicornPropertiesEventBus" + "EventBusName": "Dev-UnicornApprovalsEventBus" } ] diff --git a/unicorn_properties/PropertyFunctions/src/test/events/put_event_contract_status_changed.json b/unicorn_properties/PropertyFunctions/src/test/events/put_event_contract_status_changed.json index 3d395a1..1ef4ab4 100644 --- a/unicorn_properties/PropertyFunctions/src/test/events/put_event_contract_status_changed.json +++ b/unicorn_properties/PropertyFunctions/src/test/events/put_event_contract_status_changed.json @@ -3,6 +3,6 @@ "Source": "unicorn.contracts", "Detail": "{\"contract_updated_on\":\"10/08/2022 20:36:30\",\"contract_id\": \"199\",\"property_id\":\"bbb\",\"contract_status\":\"APPROVED\"}", "DetailType": "ContractStatusChanged", - "EventBusName": "Dev-UnicornPropertiesEventBus" + "EventBusName": "Dev-UnicornApprovalsEventBus" } ] \ No newline at end of file diff --git a/unicorn_properties/PropertyFunctions/src/test/events/put_event_publication_approval_requested.json b/unicorn_properties/PropertyFunctions/src/test/events/put_event_publication_approval_requested.json index 3791334..ee1026e 100644 --- a/unicorn_properties/PropertyFunctions/src/test/events/put_event_publication_approval_requested.json +++ b/unicorn_properties/PropertyFunctions/src/test/events/put_event_publication_approval_requested.json @@ -1,6 +1,6 @@ [ { - "EventBusName": "Dev-UnicornPropertiesEventBus", + "EventBusName": "Dev-UnicornApprovalsEventBus", "Source": "unicorn.properties.web", "DetailType": "PublicationApprovalRequested", "Detail": "{\"property_id\": \"usa/anytown/main-street/123\",\"country\": \"USA\",\"city\": \"Anytown\",\"street\": \"Main Street\",\"number\": 123,\"description\": \"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\": \"sale\",\"listprice\": 200,\"currency\": \"SPL\",\"images\": [ \"usa/anytown/main-street-123-0d61b4e3\"]}" diff --git a/unicorn_properties/PropertyFunctions/src/test/events/send_events_cli.json b/unicorn_properties/PropertyFunctions/src/test/events/send_events_cli.json index abf6541..6c6d6b5 100644 --- a/unicorn_properties/PropertyFunctions/src/test/events/send_events_cli.json +++ b/unicorn_properties/PropertyFunctions/src/test/events/send_events_cli.json @@ -1,6 +1,6 @@ [ { - "EventBusName": "Dev-UnicornPropertiesEventBus", + "EventBusName": "Dev-UnicornApprovalsEventBus", "Source": "Unicorn.Web", "DetailType": "PublicationApprovalRequested", "Detail": "{\"property_id\": \"usa/anytown/main-street/123\",\"country\": \"USA\",\"city\": \"Anytown\",\"street\": \"Main Street\",\"number\": 123,\"description\": \"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\": \"sale\",\"listprice\": 200,\"currency\": \"SPL\",\"images\": [ \"usa/anytown/main-street-123-0d61b4e3\"]}" diff --git a/unicorn_properties/integration/event-schemas.yaml b/unicorn_properties/integration/event-schemas.yaml index 9a9f318..1817b39 100644 --- a/unicorn_properties/integration/event-schemas.yaml +++ b/unicorn_properties/integration/event-schemas.yaml @@ -18,7 +18,7 @@ Resources: Properties: Description: 'Event schemas for Unicorn Properties' RegistryName: - Fn::Sub: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}-${Stage}" + Fn::Sub: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}-${Stage}" EventRegistryPolicy: Type: AWS::EventSchemas::RegistryPolicy @@ -52,7 +52,7 @@ Resources: RegistryName: Fn::GetAtt: EventRegistry.RegistryName SchemaName: - Fn::Sub: '{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}@PublicationEvaluationCompleted' + Fn::Sub: '{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}@PublicationEvaluationCompleted' Description: 'The schema for when a property evaluation is completed' Content: Fn::Sub: | diff --git a/unicorn_properties/integration/subscriber-policies.yaml b/unicorn_properties/integration/subscriber-policies.yaml index 5d2a4e5..87b9770 100644 --- a/unicorn_properties/integration/subscriber-policies.yaml +++ b/unicorn_properties/integration/subscriber-policies.yaml @@ -20,7 +20,7 @@ Resources: Type: AWS::Events::EventBusPolicy Properties: EventBusName: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBus}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBus}}" StatementId: Fn::Sub: "OnlyRulesForPropertiesServiceEvents-${Stage}" Statement: @@ -40,7 +40,7 @@ Resources: - Fn::Sub: - arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/${eventBusName}/* - eventBusName: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBus}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBus}}" Condition: StringEqualsIfExists: "events:creatorAccount": "${aws:PrincipalAccount}" diff --git a/unicorn_properties/integration/subscriptions.yaml b/unicorn_properties/integration/subscriptions.yaml index 100ecf8..245a05f 100644 --- a/unicorn_properties/integration/subscriptions.yaml +++ b/unicorn_properties/integration/subscriptions.yaml @@ -30,7 +30,7 @@ Resources: Targets: - Id: SendEventTo Arn: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBusArn}}" RoleArn: Fn::GetAtt: [ UnicornPropertiesSubscriptionRole, Arn ] @@ -51,13 +51,13 @@ Resources: Targets: - Id: SendEventTo Arn: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBusArn}}" RoleArn: Fn::GetAtt: [ UnicornPropertiesSubscriptionRole, Arn ] # This IAM role allows EventBridge to assume the permissions necessary to send events - # from the publishing event bus, to the subscribing event bus (UnicornPropertiesEventBusArn) + # from the publishing event bus, to the subscribing event bus (UnicornApprovalsEventBusArn) UnicornPropertiesSubscriptionRole: Type: AWS::IAM::Role Properties: @@ -75,7 +75,7 @@ Resources: - Effect: Allow Action: events:PutEvents Resource: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBusArn}}" Outputs: ContractStatusChangedSubscription: diff --git a/unicorn_properties/samconfig.toml b/unicorn_properties/samconfig.toml index 80abe0b..e8b9e83 100644 --- a/unicorn_properties/samconfig.toml +++ b/unicorn_properties/samconfig.toml @@ -1,8 +1,8 @@ version = 0.1 [default.global.parameters] -stack_name = "uni-prop-local-properties" -s3_prefix = "uni-prop-local-properties" +stack_name = "uni-prop-local-approvals" +s3_prefix = "uni-prop-local-approvals" resolve_s3 = true resolve_image_repositories = true diff --git a/unicorn_properties/template.yaml b/unicorn_properties/template.yaml index f2234c7..df70c08 100644 --- a/unicorn_properties/template.yaml +++ b/unicorn_properties/template.yaml @@ -53,7 +53,7 @@ Globals: Environment: Variables: CONTRACT_STATUS_TABLE: !Ref ContractStatusTable - EVENT_BUS: !Ref UnicornPropertiesEventBus + EVENT_BUS: !Ref UnicornApprovalsEventBus SERVICE_NAMESPACE: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" POWERTOOLS_SERVICE_NAME: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" POWERTOOLS_TRACE_DISABLED: "false" # Explicitly disables tracing, default @@ -70,23 +70,23 @@ Globals: Resources: #### SSM PARAMETERS # Services share their event bus name and arn - UnicornPropertiesEventBusNameParam: + UnicornApprovalsEventBusNameParam: Type: AWS::SSM::Parameter DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: Type: String - Name: !Sub /uni-prop/${Stage}/UnicornPropertiesEventBus - Value: !GetAtt UnicornPropertiesEventBus.Name + Name: !Sub /uni-prop/${Stage}/UnicornApprovalsEventBus + Value: !GetAtt UnicornApprovalsEventBus.Name - UnicornPropertiesEventBusArnParam: + UnicornApprovalsEventBusArnParam: Type: AWS::SSM::Parameter DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: Type: String - Name: !Sub /uni-prop/${Stage}/UnicornPropertiesEventBusArn - Value: !GetAtt UnicornPropertiesEventBus.Arn + Name: !Sub /uni-prop/${Stage}/UnicornApprovalsEventBusArn + Value: !GetAtt UnicornApprovalsEventBus.Arn #### LAMBDA FUNCTIONS # Listens to ContractStatusChanged events from EventBridge @@ -105,7 +105,7 @@ Resources: Type: EventBridgeRule Properties: RuleName: unicorn.properties-ContractStatusChanged - EventBusName: !GetAtt UnicornPropertiesEventBus.Name + EventBusName: !GetAtt UnicornApprovalsEventBus.Name Pattern: source: - "{{resolve:ssm:/uni-prop/UnicornContractsNamespace}}" @@ -218,7 +218,7 @@ Resources: - S3ReadPolicy: BucketName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/ImagesBucket}}" - EventBridgePutEventsPolicy: - EventBusName: !GetAtt UnicornPropertiesEventBus.Name + EventBusName: !GetAtt UnicornApprovalsEventBus.Name - Statement: - Effect: Allow Action: @@ -243,7 +243,7 @@ Resources: Type: EventBridgeRule Properties: RuleName: unicorn.properties-PublicationApprovalRequested - EventBusName: !GetAtt UnicornPropertiesEventBus.Name + EventBusName: !GetAtt UnicornApprovalsEventBus.Name Pattern: source: - "{{resolve:ssm:/uni-prop/UnicornWebNamespace}}" @@ -259,7 +259,7 @@ Resources: WaitForContractApprovalArn: !GetAtt WaitForContractApprovalFunction.Arn TableName: !Ref ContractStatusTable ImageUploadBucketName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/ImagesBucket}}" - EventBusName: !GetAtt UnicornPropertiesEventBus.Name + EventBusName: !GetAtt UnicornApprovalsEventBus.Name ServiceName: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" # Store ApprovalStateMachineLogGroup workflow execution logs @@ -329,20 +329,20 @@ Resources: #### EVENT BUS # Event bus for Unicorn Properties Service, used to publish and consume events - UnicornPropertiesEventBus: + UnicornApprovalsEventBus: Type: AWS::Events::EventBus DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: Name: !Sub UnicornPropertiesBus-${Stage} - # Event bus policy to restrict who can publish events (should only be services from UnicornPropertiesNamespace) + # Event bus policy to restrict who can publish events (should only be services from UnicornApprovalsNamespace) UnicornPropertiesEventsBusPublishPolicy: Type: AWS::Events::EventBusPolicy DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: - EventBusName: !Ref UnicornPropertiesEventBus + EventBusName: !Ref UnicornApprovalsEventBus StatementId: !Sub OnlyPropertiesServiceCanPublishToEventBus-${Stage} Statement: Effect: Allow @@ -350,7 +350,7 @@ Resources: AWS: - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" Action: events:PutEvents - Resource: !GetAtt UnicornPropertiesEventBus.Arn + Resource: !GetAtt UnicornApprovalsEventBus.Arn Condition: StringEquals: events:source: @@ -364,7 +364,7 @@ Resources: Properties: Name: properties.catchall Description: Catchall rule used for development purposes. - EventBusName: !Ref UnicornPropertiesEventBus + EventBusName: !Ref UnicornApprovalsEventBus EventPattern: account: - !Ref AWS::AccountId @@ -435,7 +435,7 @@ Resources: UpdateReplacePolicy: Delete DeletionPolicy: Delete DependsOn: - - UnicornPropertiesEventBusNameParam + - UnicornApprovalsEventBusNameParam Properties: Location: "integration/subscriber-policies.yaml" Parameters: @@ -447,7 +447,7 @@ Resources: UpdateReplacePolicy: Delete DeletionPolicy: Delete DependsOn: - - UnicornPropertiesEventBusArnParam + - UnicornApprovalsEventBusArnParam Properties: Location: "integration/subscriptions.yaml" Parameters: @@ -483,7 +483,7 @@ Outputs: #### EVENT BRIDGE OUTPUTS UnicornPropertiesEventBusName: - Value: !GetAtt UnicornPropertiesEventBus.Name + Value: !GetAtt UnicornApprovalsEventBus.Name #### CLOUDWATCH LOGS OUTPUTS UnicornPropertiesCatchAllLogGroupArn: diff --git a/unicorn_shared/uni-prop-namespaces.yaml b/unicorn_shared/uni-prop-namespaces.yaml index 3045c2b..7df02f4 100644 --- a/unicorn_shared/uni-prop-namespaces.yaml +++ b/unicorn_shared/uni-prop-namespaces.yaml @@ -35,7 +35,7 @@ Outputs: Description: Unicorn Contracts namespace parameter Value: !Ref UnicornContractsNamespaceParam - UnicornPropertiesNamespace: + UnicornApprovalsNamespace: Description: Unicorn Properties namespace parameter Value: !Ref UnicornApprovalsNamespaceParam diff --git a/unicorn_web/integration/subscriptions.yaml b/unicorn_web/integration/subscriptions.yaml index 66c7c3d..fc27711 100644 --- a/unicorn_web/integration/subscriptions.yaml +++ b/unicorn_web/integration/subscriptions.yaml @@ -20,7 +20,7 @@ Resources: Name: unicorn.web-PublicationEvaluationCompleted Description: PublicationEvaluationCompleted subscription EventBusName: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBusArn}}" EventPattern: source: - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" From 4ab4699b3c1a0d464a7310840cd720715d9abc5e Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Mon, 7 Jul 2025 08:39:53 +0800 Subject: [PATCH 05/34] chore: upgrade actions/upload-artifact from v3 to v4 in GitHub workflow --- .github/workflows/record_pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/record_pr.yml b/.github/workflows/record_pr.yml index 44f445a..70cbe15 100644 --- a/.github/workflows/record_pr.yml +++ b/.github/workflows/record_pr.yml @@ -16,7 +16,7 @@ jobs: script: | const script = require('.github/scripts/save_pr_details.js') await script({github, context, core}) - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: pr path: pr.txt From b498f6d68c19e3c57645a66247bf38030407c4cf Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 17 Jul 2025 13:49:49 +0800 Subject: [PATCH 06/34] feat: refactored unicorn properties to approvals again. Renamed modules to align with oother runtimes. --- .github/workflows/build.yml | 4 +-- .gitignore | 4 +-- pom.xml | 4 +-- .../.gitignore | 0 .../ApprovalsService}/pom.xml | 0 .../ContractStatusChangedHandlerFunction.java | 22 +++++++-------- .../ContractStatusNotFoundException.java | 2 +- .../PropertiesApprovalSyncFunction.java | 13 ++++----- .../WaitForContractApprovalFunction.java | 20 ++++++-------- .../java/approvals}/dao/ContractStatus.java | 2 +- .../ContractStatusChanged.java | 5 ++-- .../contractstatuschanged/Event.java | 8 ++---- .../marshaller/Marshaller.java | 1 - .../src/main/resources/log4j2.xml | 0 .../contract_status_changed_draft.json | 0 .../dbb_stream_events/sfn_check_exists.json | 0 .../dbb_stream_events/sfn_wait_approval.json | 0 .../status_approved_waiting_for_approval.json | 0 .../status_approved_with_no_workflow.json | 0 ...tus_changed_event_contract_1_approved.json | 0 ...status_changed_event_contract_1_draft.json | 0 ...tus_changed_event_contract_2_approved.json | 0 ...status_changed_event_contract_2_draft.json | 0 .../publication_approval_requested_event.json | 0 ...ion_approval_requested_event_all_good.json | 0 ...ested_event_inappropriate_description.json | 0 ..._requested_event_inappropriate_images.json | 0 ...requested_event_non_existing_contract.json | 0 ...proval_requested_event_pause_workflow.json | 0 ...ublication_evaluation_completed_event.json | 0 ...put_event_property_approval_requested.json | 0 .../lambda/contract_status_changed.json | 0 .../lambda/contract_status_checker.json | 0 .../put_event_contract_status_changed.json | 0 ..._event_publication_approval_requested.json | 0 .../src/test/events/send_events_cli.json | 0 .../java/approvals}/ContractStatusTests.java | 26 +++++++----------- .../target/classes}/log4j2.xml | 0 ...ContractStatusChangedHandlerFunction.class | Bin 0 -> 5333 bytes .../ContractStatusNotFoundException.class | Bin 0 -> 412 bytes .../PropertiesApprovalSyncFunction.class | Bin 0 -> 8040 bytes .../WaitForContractApprovalFunction.class | Bin 0 -> 7641 bytes .../properties/dao/ContractStatus.class | Bin 0 -> 1919 bytes .../ContractStatusChanged.class | Bin 0 -> 3630 bytes .../contractstatuschanged/Event.class | Bin 0 -> 6259 bytes .../marshaller/Marshaller.class | Bin 0 -> 1774 bytes .../properties/ContractStatusTests.class | Bin 0 -> 2597 bytes .../README.md | 2 +- .../PublicationEvaluationCompleted.json | 0 .../integration/event-schemas.yaml | 0 .../integration/subscriber-policies.yaml | 0 .../integration/subscriptions.yaml | 4 +-- .../samconfig.toml | 0 .../state_machine/property_approval.asl.yaml | 0 .../template.yaml | 12 ++++---- .../pom.xml | 2 +- .../java/contracts/ContractEventHandler.java | 10 +------ .../main/java/contracts/utils/Address.java | 0 .../main/java/contracts/utils/Contract.java | 0 .../utils/ContractStatusChangedEvent.java | 0 .../contracts/utils/ContractStatusEnum.java | 0 .../java/contracts/utils/ResponseParser.java | 5 ++-- .../src/main/resources/log4j2.xml | 16 +++++++++++ .../events/create_empty_dict_body_event.json | 0 .../events/create_missing_body_event.json | 0 .../src/test/events/create_valid_event.json | 0 .../src/test/events/create_wrong_event.json | 0 .../events/update_empty_dict_body_event.json | 0 .../events/update_missing_body_event.json | 0 .../src/test/events/update_valid_event.json | 0 .../src/test/events/update_wrong_event.json | 0 .../java/contracts/CreateContractTests.java | 9 ++---- .../contracts/ContractEventHandler.class | Bin 0 -> 10893 bytes .../classes/contracts/utils/Address.class | Bin 0 -> 1112 bytes .../classes/contracts/utils/Contract.class | Bin 0 -> 2386 bytes .../utils/ContractStatusChangedEvent.class | Bin 0 -> 1663 bytes .../contracts/utils/ContractStatusEnum.class | Bin 0 -> 1284 bytes .../contracts/utils/ResponseParser.class | Bin 0 -> 2298 bytes .../target/classes/log4j2.xml | 16 +++++++++++ .../contracts/CreateContractTests.class | Bin 0 -> 1770 bytes 80 files changed, 95 insertions(+), 92 deletions(-) rename {unicorn_properties => unicorn_approvals}/.gitignore (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/pom.xml (100%) rename {unicorn_properties/PropertyFunctions/src/main/java/properties => unicorn_approvals/ApprovalsService/src/main/java/approvals}/ContractStatusChangedHandlerFunction.java (99%) rename {unicorn_properties/PropertyFunctions/src/main/java/properties => unicorn_approvals/ApprovalsService/src/main/java/approvals}/ContractStatusNotFoundException.java (89%) rename {unicorn_properties/PropertyFunctions/src/main/java/properties => unicorn_approvals/ApprovalsService/src/main/java/approvals}/PropertiesApprovalSyncFunction.java (98%) rename {unicorn_properties/PropertyFunctions/src/main/java/properties => unicorn_approvals/ApprovalsService/src/main/java/approvals}/WaitForContractApprovalFunction.java (99%) rename {unicorn_properties/PropertyFunctions/src/main/java/properties => unicorn_approvals/ApprovalsService/src/main/java/approvals}/dao/ContractStatus.java (98%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/main/java/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.java (96%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/main/java/schema/unicorn_contracts/contractstatuschanged/Event.java (96%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/main/java/schema/unicorn_contracts/contractstatuschanged/marshaller/Marshaller.java (93%) rename {unicorn_contracts/ContractsFunction => unicorn_approvals/ApprovalsService}/src/main/resources/log4j2.xml (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/dbb_stream_events/contract_status_changed_draft.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/dbb_stream_events/sfn_check_exists.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/dbb_stream_events/sfn_wait_approval.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/dbb_stream_events/status_approved_waiting_for_approval.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/dbb_stream_events/status_approved_with_no_workflow.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/contract_status_changed_event_contract_1_approved.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/contract_status_changed_event_contract_1_draft.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/contract_status_changed_event_contract_2_approved.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/contract_status_changed_event_contract_2_draft.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/publication_approval_requested_event.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/publication_approval_requested_event_all_good.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_description.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_images.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/publication_approval_requested_event_non_existing_contract.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/publication_approval_requested_event_pause_workflow.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/publication_evaluation_completed_event.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/eventbridge/put_event_property_approval_requested.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/lambda/contract_status_changed.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/lambda/contract_status_checker.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/put_event_contract_status_changed.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/put_event_publication_approval_requested.json (100%) rename {unicorn_properties/PropertyFunctions => unicorn_approvals/ApprovalsService}/src/test/events/send_events_cli.json (100%) rename {unicorn_properties/PropertyFunctions/src/test/java/properties => unicorn_approvals/ApprovalsService/src/test/java/approvals}/ContractStatusTests.java (90%) rename {unicorn_properties/PropertyFunctions/src/main/resources => unicorn_approvals/ApprovalsService/target/classes}/log4j2.xml (100%) create mode 100644 unicorn_approvals/ApprovalsService/target/classes/properties/ContractStatusChangedHandlerFunction.class create mode 100644 unicorn_approvals/ApprovalsService/target/classes/properties/ContractStatusNotFoundException.class create mode 100644 unicorn_approvals/ApprovalsService/target/classes/properties/PropertiesApprovalSyncFunction.class create mode 100644 unicorn_approvals/ApprovalsService/target/classes/properties/WaitForContractApprovalFunction.class create mode 100644 unicorn_approvals/ApprovalsService/target/classes/properties/dao/ContractStatus.class create mode 100644 unicorn_approvals/ApprovalsService/target/classes/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.class create mode 100644 unicorn_approvals/ApprovalsService/target/classes/schema/unicorn_contracts/contractstatuschanged/Event.class create mode 100644 unicorn_approvals/ApprovalsService/target/classes/schema/unicorn_contracts/contractstatuschanged/marshaller/Marshaller.class create mode 100644 unicorn_approvals/ApprovalsService/target/test-classes/properties/ContractStatusTests.class rename {unicorn_properties => unicorn_approvals}/README.md (84%) rename {unicorn_properties => unicorn_approvals}/integration/PublicationEvaluationCompleted.json (100%) rename {unicorn_properties => unicorn_approvals}/integration/event-schemas.yaml (100%) rename {unicorn_properties => unicorn_approvals}/integration/subscriber-policies.yaml (100%) rename {unicorn_properties => unicorn_approvals}/integration/subscriptions.yaml (96%) rename {unicorn_properties => unicorn_approvals}/samconfig.toml (100%) rename {unicorn_properties => unicorn_approvals}/state_machine/property_approval.asl.yaml (100%) rename {unicorn_properties => unicorn_approvals}/template.yaml (97%) rename unicorn_contracts/{ContractsFunction => ContractsService}/pom.xml (99%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/main/java/contracts/ContractEventHandler.java (95%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/main/java/contracts/utils/Address.java (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/main/java/contracts/utils/Contract.java (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/main/java/contracts/utils/ContractStatusChangedEvent.java (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/main/java/contracts/utils/ContractStatusEnum.java (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/main/java/contracts/utils/ResponseParser.java (99%) create mode 100644 unicorn_contracts/ContractsService/src/main/resources/log4j2.xml rename unicorn_contracts/{ContractsFunction => ContractsService}/src/test/events/create_empty_dict_body_event.json (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/test/events/create_missing_body_event.json (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/test/events/create_valid_event.json (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/test/events/create_wrong_event.json (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/test/events/update_empty_dict_body_event.json (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/test/events/update_missing_body_event.json (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/test/events/update_valid_event.json (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/test/events/update_wrong_event.json (100%) rename unicorn_contracts/{ContractsFunction => ContractsService}/src/test/java/contracts/CreateContractTests.java (99%) create mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/ContractEventHandler.class create mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/utils/Address.class create mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/utils/Contract.class create mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/utils/ContractStatusChangedEvent.class create mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/utils/ContractStatusEnum.class create mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/utils/ResponseParser.class create mode 100644 unicorn_contracts/ContractsService/target/classes/log4j2.xml create mode 100644 unicorn_contracts/ContractsService/target/test-classes/contracts/CreateContractTests.class diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dae9ea7..9f0ee04 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,13 +5,13 @@ on: branches: [develop, main] paths: - 'unicorn_contracts/**' - - 'unicorn_properties/**' + - 'unicorn_approvals/**' - 'unicorn_web/**' pull_request: branches: [develop, main] paths: - 'unicorn_contracts/**' - - 'unicorn_properties/**' + - 'unicorn_approvals/**' - 'unicorn_web/**' defaults: diff --git a/.gitignore b/.gitignore index 50ad28e..34e4044 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -unicorn_properties/PropertyFunctions/target/* +unicorn_approvals/PropertyFunctions/target/* unicorn_contracts/ContractsFunction/target/** unicorn_web/PropertyFunctions/target/** **/.aws-sam/ @@ -8,7 +8,7 @@ buildall.sh deleteall.sh /.idea/** /cloudapp.iml -/unicorn_properties/PropertyFunctions/PropertyService.iml +/unicorn_approvals/PropertyFunctions/PropertyService.iml /unicorn_web/PropertyFunctions/PropertyWeb.iml /unicorn_contracts/ContractsFunction/ContractsModule.iml **/cdk.out/ \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2b009f0..c143525 100644 --- a/pom.xml +++ b/pom.xml @@ -8,8 +8,8 @@ pom - unicorn_contracts/ContractsFunction - unicorn_properties/PropertyFunctions + unicorn_contracts/ContractsService + unicorn_approvals/ApprovalsService unicorn_web/PropertyFunctions \ No newline at end of file diff --git a/unicorn_properties/.gitignore b/unicorn_approvals/.gitignore similarity index 100% rename from unicorn_properties/.gitignore rename to unicorn_approvals/.gitignore diff --git a/unicorn_properties/PropertyFunctions/pom.xml b/unicorn_approvals/ApprovalsService/pom.xml similarity index 100% rename from unicorn_properties/PropertyFunctions/pom.xml rename to unicorn_approvals/ApprovalsService/pom.xml diff --git a/unicorn_properties/PropertyFunctions/src/main/java/properties/ContractStatusChangedHandlerFunction.java b/unicorn_approvals/ApprovalsService/src/main/java/approvals/ContractStatusChangedHandlerFunction.java similarity index 99% rename from unicorn_properties/PropertyFunctions/src/main/java/properties/ContractStatusChangedHandlerFunction.java rename to unicorn_approvals/ApprovalsService/src/main/java/approvals/ContractStatusChangedHandlerFunction.java index 31954fc..7358dc0 100644 --- a/unicorn_properties/PropertyFunctions/src/main/java/properties/ContractStatusChangedHandlerFunction.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/approvals/ContractStatusChangedHandlerFunction.java @@ -1,21 +1,11 @@ -package properties; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; +package approvals; import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.databind.ObjectMapper; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; - -import schema.unicorn_contracts.contractstatuschanged.Event; import schema.unicorn_contracts.contractstatuschanged.ContractStatusChanged; +import schema.unicorn_contracts.contractstatuschanged.Event; import schema.unicorn_contracts.contractstatuschanged.marshaller.Marshaller; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; @@ -24,6 +14,14 @@ import software.amazon.lambda.powertools.metrics.Metrics; import software.amazon.lambda.powertools.tracing.Tracing; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + /** * Lambda handler to update the contract status change */ diff --git a/unicorn_properties/PropertyFunctions/src/main/java/properties/ContractStatusNotFoundException.java b/unicorn_approvals/ApprovalsService/src/main/java/approvals/ContractStatusNotFoundException.java similarity index 89% rename from unicorn_properties/PropertyFunctions/src/main/java/properties/ContractStatusNotFoundException.java rename to unicorn_approvals/ApprovalsService/src/main/java/approvals/ContractStatusNotFoundException.java index 34e2608..7ece33c 100644 --- a/unicorn_properties/PropertyFunctions/src/main/java/properties/ContractStatusNotFoundException.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/approvals/ContractStatusNotFoundException.java @@ -1,4 +1,4 @@ -package properties; +package approvals; public class ContractStatusNotFoundException extends Exception { public ContractStatusNotFoundException(String errorMessage) { diff --git a/unicorn_properties/PropertyFunctions/src/main/java/properties/PropertiesApprovalSyncFunction.java b/unicorn_approvals/ApprovalsService/src/main/java/approvals/PropertiesApprovalSyncFunction.java similarity index 98% rename from unicorn_properties/PropertyFunctions/src/main/java/properties/PropertiesApprovalSyncFunction.java rename to unicorn_approvals/ApprovalsService/src/main/java/approvals/PropertiesApprovalSyncFunction.java index 5ad7349..5d70818 100644 --- a/unicorn_properties/PropertyFunctions/src/main/java/properties/PropertiesApprovalSyncFunction.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/approvals/PropertiesApprovalSyncFunction.java @@ -1,4 +1,4 @@ -package properties; +package approvals; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; @@ -8,14 +8,15 @@ import com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; - -import properties.dao.ContractStatus; +import approvals.dao.ContractStatus; import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; import software.amazon.awssdk.services.sfn.SfnAsyncClient; import software.amazon.awssdk.services.sfn.model.SendTaskSuccessRequest; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.tracing.Tracing; import java.io.Serializable; import java.util.ArrayList; @@ -23,10 +24,6 @@ import java.util.List; import java.util.Map; -import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.metrics.Metrics; -import software.amazon.lambda.powertools.tracing.Tracing; - public class PropertiesApprovalSyncFunction implements RequestHandler { Logger logger = LogManager.getLogger(); diff --git a/unicorn_properties/PropertyFunctions/src/main/java/properties/WaitForContractApprovalFunction.java b/unicorn_approvals/ApprovalsService/src/main/java/approvals/WaitForContractApprovalFunction.java similarity index 99% rename from unicorn_properties/PropertyFunctions/src/main/java/properties/WaitForContractApprovalFunction.java rename to unicorn_approvals/ApprovalsService/src/main/java/approvals/WaitForContractApprovalFunction.java index 9dbe5b1..7d0202e 100644 --- a/unicorn_properties/PropertyFunctions/src/main/java/properties/WaitForContractApprovalFunction.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/approvals/WaitForContractApprovalFunction.java @@ -1,20 +1,10 @@ -package properties; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; +package approvals; import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; - import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; @@ -24,6 +14,14 @@ import software.amazon.lambda.powertools.metrics.Metrics; import software.amazon.lambda.powertools.tracing.Tracing; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + /** * Lambda handler to update the contract status change */ diff --git a/unicorn_properties/PropertyFunctions/src/main/java/properties/dao/ContractStatus.java b/unicorn_approvals/ApprovalsService/src/main/java/approvals/dao/ContractStatus.java similarity index 98% rename from unicorn_properties/PropertyFunctions/src/main/java/properties/dao/ContractStatus.java rename to unicorn_approvals/ApprovalsService/src/main/java/approvals/dao/ContractStatus.java index db40a77..df99fcd 100644 --- a/unicorn_properties/PropertyFunctions/src/main/java/properties/dao/ContractStatus.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/approvals/dao/ContractStatus.java @@ -1,4 +1,4 @@ -package properties.dao; +package approvals.dao; public class ContractStatus { String contract_id; diff --git a/unicorn_properties/PropertyFunctions/src/main/java/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.java b/unicorn_approvals/ApprovalsService/src/main/java/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.java similarity index 96% rename from unicorn_properties/PropertyFunctions/src/main/java/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.java rename to unicorn_approvals/ApprovalsService/src/main/java/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.java index 9bc2295..ed24f9a 100644 --- a/unicorn_properties/PropertyFunctions/src/main/java/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.java @@ -1,10 +1,9 @@ package schema.unicorn_contracts.contractstatuschanged; -import java.util.Objects; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; + import java.io.Serializable; +import java.util.Objects; public class ContractStatusChanged implements Serializable { private static final long serialVersionUID = 1L; diff --git a/unicorn_properties/PropertyFunctions/src/main/java/schema/unicorn_contracts/contractstatuschanged/Event.java b/unicorn_approvals/ApprovalsService/src/main/java/schema/unicorn_contracts/contractstatuschanged/Event.java similarity index 96% rename from unicorn_properties/PropertyFunctions/src/main/java/schema/unicorn_contracts/contractstatuschanged/Event.java rename to unicorn_approvals/ApprovalsService/src/main/java/schema/unicorn_contracts/contractstatuschanged/Event.java index 763098d..8a8b6fe 100644 --- a/unicorn_properties/PropertyFunctions/src/main/java/schema/unicorn_contracts/contractstatuschanged/Event.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/schema/unicorn_contracts/contractstatuschanged/Event.java @@ -1,14 +1,12 @@ package schema.unicorn_contracts.contractstatuschanged; -import java.util.Objects; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; + +import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; -import schema.unicorn_contracts.contractstatuschanged.ContractStatusChanged; -import java.io.Serializable; +import java.util.Objects; public class Event implements Serializable { private static final long serialVersionUID = 1L; diff --git a/unicorn_properties/PropertyFunctions/src/main/java/schema/unicorn_contracts/contractstatuschanged/marshaller/Marshaller.java b/unicorn_approvals/ApprovalsService/src/main/java/schema/unicorn_contracts/contractstatuschanged/marshaller/Marshaller.java similarity index 93% rename from unicorn_properties/PropertyFunctions/src/main/java/schema/unicorn_contracts/contractstatuschanged/marshaller/Marshaller.java rename to unicorn_approvals/ApprovalsService/src/main/java/schema/unicorn_contracts/contractstatuschanged/marshaller/Marshaller.java index e4ecf39..1ce5c6f 100644 --- a/unicorn_properties/PropertyFunctions/src/main/java/schema/unicorn_contracts/contractstatuschanged/marshaller/Marshaller.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/schema/unicorn_contracts/contractstatuschanged/marshaller/Marshaller.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.type.TypeFactory; import java.io.IOException; import java.io.InputStream; diff --git a/unicorn_contracts/ContractsFunction/src/main/resources/log4j2.xml b/unicorn_approvals/ApprovalsService/src/main/resources/log4j2.xml similarity index 100% rename from unicorn_contracts/ContractsFunction/src/main/resources/log4j2.xml rename to unicorn_approvals/ApprovalsService/src/main/resources/log4j2.xml diff --git a/unicorn_properties/PropertyFunctions/src/test/events/dbb_stream_events/contract_status_changed_draft.json b/unicorn_approvals/ApprovalsService/src/test/events/dbb_stream_events/contract_status_changed_draft.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/dbb_stream_events/contract_status_changed_draft.json rename to unicorn_approvals/ApprovalsService/src/test/events/dbb_stream_events/contract_status_changed_draft.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/dbb_stream_events/sfn_check_exists.json b/unicorn_approvals/ApprovalsService/src/test/events/dbb_stream_events/sfn_check_exists.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/dbb_stream_events/sfn_check_exists.json rename to unicorn_approvals/ApprovalsService/src/test/events/dbb_stream_events/sfn_check_exists.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/dbb_stream_events/sfn_wait_approval.json b/unicorn_approvals/ApprovalsService/src/test/events/dbb_stream_events/sfn_wait_approval.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/dbb_stream_events/sfn_wait_approval.json rename to unicorn_approvals/ApprovalsService/src/test/events/dbb_stream_events/sfn_wait_approval.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/dbb_stream_events/status_approved_waiting_for_approval.json b/unicorn_approvals/ApprovalsService/src/test/events/dbb_stream_events/status_approved_waiting_for_approval.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/dbb_stream_events/status_approved_waiting_for_approval.json rename to unicorn_approvals/ApprovalsService/src/test/events/dbb_stream_events/status_approved_waiting_for_approval.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/dbb_stream_events/status_approved_with_no_workflow.json b/unicorn_approvals/ApprovalsService/src/test/events/dbb_stream_events/status_approved_with_no_workflow.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/dbb_stream_events/status_approved_with_no_workflow.json rename to unicorn_approvals/ApprovalsService/src/test/events/dbb_stream_events/status_approved_with_no_workflow.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/contract_status_changed_event_contract_1_approved.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_approved.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/contract_status_changed_event_contract_1_approved.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_approved.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/contract_status_changed_event_contract_1_draft.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_draft.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/contract_status_changed_event_contract_1_draft.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_draft.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/contract_status_changed_event_contract_2_approved.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_approved.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/contract_status_changed_event_contract_2_approved.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_approved.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/contract_status_changed_event_contract_2_draft.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_draft.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/contract_status_changed_event_contract_2_draft.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_draft.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event_all_good.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_all_good.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event_all_good.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_all_good.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_description.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_description.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_description.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_description.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_images.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_images.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_images.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_images.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event_non_existing_contract.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_non_existing_contract.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event_non_existing_contract.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_non_existing_contract.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event_pause_workflow.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_pause_workflow.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_approval_requested_event_pause_workflow.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_pause_workflow.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_evaluation_completed_event.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_evaluation_completed_event.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/publication_evaluation_completed_event.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_evaluation_completed_event.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/eventbridge/put_event_property_approval_requested.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/put_event_property_approval_requested.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/eventbridge/put_event_property_approval_requested.json rename to unicorn_approvals/ApprovalsService/src/test/events/eventbridge/put_event_property_approval_requested.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/lambda/contract_status_changed.json b/unicorn_approvals/ApprovalsService/src/test/events/lambda/contract_status_changed.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/lambda/contract_status_changed.json rename to unicorn_approvals/ApprovalsService/src/test/events/lambda/contract_status_changed.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/lambda/contract_status_checker.json b/unicorn_approvals/ApprovalsService/src/test/events/lambda/contract_status_checker.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/lambda/contract_status_checker.json rename to unicorn_approvals/ApprovalsService/src/test/events/lambda/contract_status_checker.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/put_event_contract_status_changed.json b/unicorn_approvals/ApprovalsService/src/test/events/put_event_contract_status_changed.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/put_event_contract_status_changed.json rename to unicorn_approvals/ApprovalsService/src/test/events/put_event_contract_status_changed.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/put_event_publication_approval_requested.json b/unicorn_approvals/ApprovalsService/src/test/events/put_event_publication_approval_requested.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/put_event_publication_approval_requested.json rename to unicorn_approvals/ApprovalsService/src/test/events/put_event_publication_approval_requested.json diff --git a/unicorn_properties/PropertyFunctions/src/test/events/send_events_cli.json b/unicorn_approvals/ApprovalsService/src/test/events/send_events_cli.json similarity index 100% rename from unicorn_properties/PropertyFunctions/src/test/events/send_events_cli.json rename to unicorn_approvals/ApprovalsService/src/test/events/send_events_cli.json diff --git a/unicorn_properties/PropertyFunctions/src/test/java/properties/ContractStatusTests.java b/unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java similarity index 90% rename from unicorn_properties/PropertyFunctions/src/test/java/properties/ContractStatusTests.java rename to unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java index 135aea3..fe71bac 100644 --- a/unicorn_properties/PropertyFunctions/src/test/java/properties/ContractStatusTests.java +++ b/unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java @@ -1,25 +1,19 @@ -package properties; - -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; +package approvals; import com.amazonaws.services.lambda.runtime.Context; - -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - import org.junit.Before; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; @RunWith(MockitoJUnitRunner.class) public class ContractStatusTests { diff --git a/unicorn_properties/PropertyFunctions/src/main/resources/log4j2.xml b/unicorn_approvals/ApprovalsService/target/classes/log4j2.xml similarity index 100% rename from unicorn_properties/PropertyFunctions/src/main/resources/log4j2.xml rename to unicorn_approvals/ApprovalsService/target/classes/log4j2.xml diff --git a/unicorn_approvals/ApprovalsService/target/classes/properties/ContractStatusChangedHandlerFunction.class b/unicorn_approvals/ApprovalsService/target/classes/properties/ContractStatusChangedHandlerFunction.class new file mode 100644 index 0000000000000000000000000000000000000000..c6a5431a2c442ec67b45f1fe1b364e0aeda0a770 GIT binary patch literal 5333 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 B%G&?{ literal 0 HcmV?d00001 diff --git a/unicorn_approvals/ApprovalsService/target/classes/properties/WaitForContractApprovalFunction.class b/unicorn_approvals/ApprovalsService/target/classes/properties/WaitForContractApprovalFunction.class new file mode 100644 index 0000000000000000000000000000000000000000..1fa49522d0902ded5dd136dc17517de74a734578 GIT binary patch literal 7641 zcmcIpX<$^<75?sIk~f(=AcRFm5yGAXa8O+61S3fZ5DWpy5J1%MGV_wWWb)oP?+ql_ z*4A3PS=-vhT3cHyEnRF~Dj^Z8c5k&SUF^Qu&C-2ewcmYjW|A=(NTmMAyzSm|&-u=G z&OP_!@n;@-1i*T6UJyPMDe$W(Mu|XquXaESCp5D=yuGtmk2wM*>kZRz>I90as@sDI zpj1InMHy6qC043CtR=Nrj~-4~-Q5P~@p@rzxW($;rkPr|o)ReS)*U{(_e-j(TTV5J zgKA4L9WxY^tC)!jftqB>O6n=c(CzSU&2TnZsYc6mQd-QZPx9LVEwL$W#vH>ksjXB) zF1qNn%LFi6U{2%q*67aq#%M=dv_874ts`3B(9#sdT%4s~o{A98&O@bb(025Gu0xx2 z^MJtes+LjA+ME<2sI4CVO4>AE#kp7@5Omk*XszGYBv3JaY#A!CNWo$iOR!X6y=`?l z2ep(Q*7~(WmKoL#+IGAzZ0o55MvS1u2TiTtig$)Lx;Gm;>+L}^)|fDKlm1gW(?%jL zA=ymq&qP*(C#W`vHZ8 z&d9i|W&+duwE?m=mQJN~Gd3u2)mYwS7cc0aDFPvlxJ1P|X~BH1yhAtRBqkLw8Pg1u z`q;H;BcLTP+DTC@;|1a_we`H4dv`-sA?mrNX_B$wws_xj*pUU0 zAyq)Nn8m<|xm*u-dIZhM$SRU!Y2AH8(dko z))i7@QWz2IEdncJR)4rlV>om-GCiQO9l1zI+Y?cZ7^8761 z=9wV2p;f_l6+3W+z%17g##^(QOgl_Py2eDM@)EC4BpL=C-4^gyRqtyEVk_EY#*Y$- zs(lSIuWv@Xirv^F5RfyXDP3o1k0og?xt&J)t+b`pitDujTuJ$^HbGp4s})?MqC+mR zVv-`b=`ha*pb3;^c7eeTBQB-IRK!6QOEf#G4=~T=yE3C}0NvbwqMF?owfb~307GD! z!^cYZxiV5&hCU<|^s6vo2`oI#=0pSyvx@^)OaRSzDpj`!a4lK1!`s3JE1Tn7Gsw-r zR^cGcV4;E8?X1nwoHU)ZWCGb4o~9im5hgRp=7SheFeni{M5A2iXTLsYTgvcy0;(%` zap43$p3&T8^hCJcA^kejj@~YFX%N@pr7EtM&JeaV?d2+Nz$=(}(n(TUcag3)<2$sp ztv3zmF={Ed>eC>YrCYm#R|=e+CHMBUlVkVpl)(@x#jEgY1+P)@TD*=MzdKUyJFy-u zW$R9uu$pl#6>s#O32^bQ=%$W!lUPPi zH>!9OZsl@uy)#WB&Y8%sjJ+H278P%GM;??W$v3yDc)P5~iem}O))l-XyPvr}-lEw( zWI+(Oqewd7T`KOxyE(cmW%aiSv`w_qB)|#l)ngHdIyDNwj__~5`NL0Q#^*w5{ z?&Rf#=YeXzh%u->iEk_Tj!X*QojR%$OBL92#<^f($xPc%;`=fY{NVrOx>Ky<#^R4v z`~*Mc!L!@*fq*gPK+Iob@=>VZX99Io%+tBW?4)Jd+D+$irbBYrh%BAly!}Q-{iL}@{7Axi~WnV8PAOm;%WR&#qVYLG>eF}STQYO9Ma?4 zmcs+LUU zJc<5EEepvW7E1{|x1nC0;bs20B2)T|D~M!W?{z8Iq!{f`B((m{xE4;OO~>fhnLT+N zA8=}`+gaBeV^1q{yjtNoi}4g=jVb$hcFzsVtH;PWCR-_5Alx!FUE~^W$ICl*39NPn zO!AaQ-~x8aanCxe!t?`f>!td1B63G1?p)dPO>^AcurntXs+B!~dYjq|&-5G-dAQ(~ z=@Qq=#XJ(ZMI;+O^CyJKtD33t+ zvcV)hzdiSbd;d<4Gwp_LklOX8Y4MQ6I3u&yj?*H;v!r#9{X)x1*kRcwlwHZFcjda1 znboiJgcGyF+q^4hmB+NC+~*suM4ab|lvBk1shFHJ*^-nx$R4O{50Wc<)#Py`z)H~V zNeNsw&Ypsuz6lF-o)AXMpIiVpOjYW*;*JWJ>e>9pQ_L7Tl^};9>Y_rm-n7+x7Tfx%Pw8+ns3NfoRvlWeMM> zwZMf1cHmP=z#{ezd<-iYTI~Guw_G;Nd4+7o@z)O~SCsJgPkg%7eFiqx9D}GChH@0s zYAR+8W6n{W!{>#=SXLe%M$HJ;_%L%txqExDk4KId`FLK4jNmfCYvoZi-cNZx{F%Qs z2=aFUWmoc_9E&jQ*QTU=Ob04UA z5YsApLV*$V7QuZ;h7|81qzoYyDXj?wLZub^M{qzIb=?qNGK809WJ+ zG29#}lOjUF5xl{NP+2HAgg1xEhVV9Nf01i{9cJ+Ue3aq>{ug8|7P1k(1{YyHE)NK6iSc4sgl0{AEX<>GW0|Hgk@FCWH#@eJ22 z#T|H-5c;{=EzGD3=;4KI`S?T;J-r5w@beve)aVh#ybEykO`=37oOwN#i2&aPsl_}| zDuO5zWn5qS#2cleR7~TW68OY)oD}7}pNAqblm4k-0-trIm@Ve;W-f227aLccE4??L zx+xspdmOjZdv~<18pb;#{?!%xL;exGrwF?GJBeIJ*sI}$13fW}bC2p;ia2%p?N>Vu0Rb>2){w=gD}`Cq8Z$;1|- z(MoS@XS03>k-ma&+jtknmDmM?o{`vZ@wCoi-|M0#Ioj(Y>7&$6+)Mn5DElgLmY7HE z7jxdjd{<0arN9v6vzJqg_g2FZMiB4Xwh=5_gjG&Nr)!(l9XT~nr3K6SXy?Q-2ua!_!ydK zO!(jf_)x}kc1yb~OF#HwW;$ojIrBR+|C#>zd-@x|Dhf$-p*xP4fgbbe<%-5y0 zmw&r^pekCRch$5^y)Mw5o8Rt30)25L4fMkhm})q7Lpj=1ZoVq*{Dy65M^s2Gcb+`fw4nbQ-oKM`n{?5=@%_xX=41CyNLCFmidce z0FY%*0-f&^1Nw`>3x%!ri&dnwjhwuw!eh}!@0nb;n#{zQ~YKlUQsHrmQI+sf8Pp={ycR3Nyg6?_9Xn<(qF6xZLou=o@4b|L%}1H)ADh?im1 z_iAR~LV34%53-RU17VQyNRYW`kbyABB!Ns_0>a0|Ah#kxQelvpNRZpnAgM4&mO$n% z0b#u`NG=j269&meg3L#QWWpdz1hRYy2n&|}BC+5pX%R~_Cdk4{S;j+}v6vs>37%o% E9}HZXGynhq literal 0 HcmV?d00001 diff --git a/unicorn_approvals/ApprovalsService/target/classes/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.class b/unicorn_approvals/ApprovalsService/target/classes/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.class new file mode 100644 index 0000000000000000000000000000000000000000..fd6e30fdc5d7f703f88bd026a4643c3b28c99912 GIT binary patch literal 3630 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 literal 0 HcmV?d00001 diff --git a/unicorn_approvals/ApprovalsService/target/test-classes/properties/ContractStatusTests.class b/unicorn_approvals/ApprovalsService/target/test-classes/properties/ContractStatusTests.class new file mode 100644 index 0000000000000000000000000000000000000000..97faffea7b2a2c58cece2396df593aa969f771b1 GIT binary patch literal 2597 zcmbVOT~icC6g>@#i|eSkMr9QgO)xA9qtS@6NQCtZT_6F3ph?W^Yy$(cGwXbynEa_! z1+YR@D$n^rc}(T>%rZMXSQUKOp5C6@_ug~w>C^M~zkmD*U<|+J(1!LbG8#H?M4)Tk z*fez8a8~u%>bf)ofg@v^?E}TdEII{FMMhy@+4@t%Uz;`>Ib_j(6vv=taa_X* zoD{g&@Z5&<0!#XO*>wWXFoXHP2tt2B`hhRd=}EugI=&P*S!ubXyHXaX1cpqvt{ZjZ zz3UinechMdre)HZZPcqZL-#@_uoW^HaoY8O==LAl>-l|@A%?&GX_38L2 zkWniF{R45l2xr+ge7{sot;EPn3Qs}9d0Y_4nnb`T0(#|8X%R}Vz=-dz1aA#bHu2~L zqOR<4L2QLs8LmNI=`Sdu-XV6S}}H-!t`qoYLhcYnBjXKh4*E#JbmJ3~zYdSD>!r zrsDip0%y`}U^vTJEfp6P?Asc?#vKygmqFZ^TC*_%V*{Vu26c??3Y<$LnQ~HCM=+{# zD=~62H`YbrZRT}f!#KVXI8H=t)tr12628EXE$fuOQcP{SNLRFUNFJ-CC)oV`q?0zK zBB?HwH9W*4fn$N2nDF2Y6kko|@BmK~u%`kY1Ft5P%V!5PC$7G-t7w>3ZZvkkHeuV! zm!HEFo^;|{9`E_kq!#!qq1`vvl#x!%F>KOxtES8sG`zqf#YU|gmP6gSknT6x>&>hB zIVP+7GKcT6oW)BGukZs?Q!26^QvdV`p8~`h=cpE(EO;Sx>ulOplR7Vp40m+iT6L&( z9*wGT{Ug1kxQ(Thrn%++Gl?1@ZG9pjORHfZ7Y#d_M?}nZ!)iDt`k!*;L4KJ@11-NivImKM2(rGzt2epP*S7MfgJ7}Aa4I=o>9%d2l zSA{2SYBp7so=S^G>tVwRq^BDVOII~!;M{yF@$T{gagEwAtZFTAKIMj&pF=M!_mUN? zsXnogijh|%qy397rf&24d-j4n<_>Nw;WBTQHhuzR_~Q-75$c+Wo>ckh$!i=G70%o6 z6WTfFHv-4kTp5r416_l=5QBTjF7M*#Hgdm3)<1JpUlu@t5&B?org10jQH9B;K#jiI zBVW&HsooU^^Iad1{|jB)=pM}XMDw0)Tx>Z#!7V^Pon1vY(`-2|G#yL6&(Pfp);Lq? zT6_wuC*l6#`y*WQnD!N>{lzx=_fX{QT4fK{m-EBBxG|lMKlnTP`MbD-TYDH;zP^il zGiq(<8prM(jP2pU^0ggI?&9%iW)D-#h0G3~?_y@OBR^Z{$bYwk`43oX;TUH5fgz5= z{BF_FiyPF=o4AczxX(-H8SY>SBapa@HyDj@j>lOyah_)BHeeH3FBPs1hecF7T{Kvu pCARbpJR+4i&hIgwt=q3mkW^-qIke$zG`@>gkI~;2fA3BF@*kkd%w+%o literal 0 HcmV?d00001 diff --git a/unicorn_properties/README.md b/unicorn_approvals/README.md similarity index 84% rename from unicorn_properties/README.md rename to unicorn_approvals/README.md index 158150e..67ce873 100644 --- a/unicorn_properties/README.md +++ b/unicorn_approvals/README.md @@ -15,7 +15,7 @@ A core component of Unicorn Properties is the approvals workflow. The approvals The workflow is initiated by a request made by an Unicorn Properties **agent** to have the property approved for publication. Once they have created a property listing (added property details and photos), they initiate the request in Unicorn Web, which generates a `PublicationApprovalRequested` event. This event contains the property information which the workflow processes. -In order process the approvals workflow successfully, the properties service needs to know the current status of a contract. To remain fully decoupled from the **Contracts Service**, it maintains a local copy of contract status by consuming the `ContractStatusChanged` event. This is eliminates the need for the Contracts service to expose an API that gives other services access to its database, and allows the Properties service to function autonomously. +In order process the approvals workflow successfully, the approvals service needs to know the current status of a contract. To remain fully decoupled from the **Contracts Service**, it maintains a local copy of contract status by consuming the `ContractStatusChanged` event. This is eliminates the need for the Contracts service to expose an API that gives other services access to its database, and allows the Properties service to function autonomously. When the workflow is paused to check to see whether or not the contract is in an approved state, the `WaitForContractApproval` state will update a contract status for a specified property with its task token. This initiates a stream event on the DynamoDB table. The Property approvals sync function handles DynamoDB stream events. It determines whether or not to pass AWS Step Function task token back to the state machine based on the contract state. diff --git a/unicorn_properties/integration/PublicationEvaluationCompleted.json b/unicorn_approvals/integration/PublicationEvaluationCompleted.json similarity index 100% rename from unicorn_properties/integration/PublicationEvaluationCompleted.json rename to unicorn_approvals/integration/PublicationEvaluationCompleted.json diff --git a/unicorn_properties/integration/event-schemas.yaml b/unicorn_approvals/integration/event-schemas.yaml similarity index 100% rename from unicorn_properties/integration/event-schemas.yaml rename to unicorn_approvals/integration/event-schemas.yaml diff --git a/unicorn_properties/integration/subscriber-policies.yaml b/unicorn_approvals/integration/subscriber-policies.yaml similarity index 100% rename from unicorn_properties/integration/subscriber-policies.yaml rename to unicorn_approvals/integration/subscriber-policies.yaml diff --git a/unicorn_properties/integration/subscriptions.yaml b/unicorn_approvals/integration/subscriptions.yaml similarity index 96% rename from unicorn_properties/integration/subscriptions.yaml rename to unicorn_approvals/integration/subscriptions.yaml index 245a05f..0c3be95 100644 --- a/unicorn_properties/integration/subscriptions.yaml +++ b/unicorn_approvals/integration/subscriptions.yaml @@ -17,7 +17,7 @@ Resources: ContractStatusChangedSubscriptionRule: Type: AWS::Events::Rule Properties: - Name: unicorn.properties-ContractStatusChanged + Name: unicorn.approvals-ContractStatusChanged Description: Contract Status Changed subscription EventBusName: Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsEventBusArn}}" @@ -38,7 +38,7 @@ Resources: PublicationApprovalRequestedSubscriptionRule: Type: AWS::Events::Rule Properties: - Name: unicorn.properties-PublicationApprovalRequested + Name: unicorn.approvals-PublicationApprovalRequested Description: Publication evaluation completed subscription EventBusName: Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBusArn}}" diff --git a/unicorn_properties/samconfig.toml b/unicorn_approvals/samconfig.toml similarity index 100% rename from unicorn_properties/samconfig.toml rename to unicorn_approvals/samconfig.toml diff --git a/unicorn_properties/state_machine/property_approval.asl.yaml b/unicorn_approvals/state_machine/property_approval.asl.yaml similarity index 100% rename from unicorn_properties/state_machine/property_approval.asl.yaml rename to unicorn_approvals/state_machine/property_approval.asl.yaml diff --git a/unicorn_properties/template.yaml b/unicorn_approvals/template.yaml similarity index 97% rename from unicorn_properties/template.yaml rename to unicorn_approvals/template.yaml index df70c08..cc7f43d 100644 --- a/unicorn_properties/template.yaml +++ b/unicorn_approvals/template.yaml @@ -94,7 +94,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: ./PropertyFunctions - Handler: properties.ContractStatusChangedHandlerFunction::handleRequest + Handler: approvals.ContractStatusChangedHandlerFunction::handleRequest Policies: - DynamoDBWritePolicy: TableName: !Ref ContractStatusTable @@ -104,7 +104,7 @@ Resources: StatusChangedEvent: Type: EventBridgeRule Properties: - RuleName: unicorn.properties-ContractStatusChanged + RuleName: unicorn.approvals-ContractStatusChanged EventBusName: !GetAtt UnicornApprovalsEventBus.Name Pattern: source: @@ -136,7 +136,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: ./PropertyFunctions - Handler: properties.PropertiesApprovalSyncFunction::handleRequest + Handler: approvals.PropertiesApprovalSyncFunction::handleRequest Policies: - DynamoDBReadPolicy: TableName: !Ref ContractStatusTable @@ -183,7 +183,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: PropertyFunctions - Handler: properties.WaitForContractApprovalFunction::handleRequest + Handler: approvals.WaitForContractApprovalFunction::handleRequest Policies: - DynamoDBCrudPolicy: TableName: !Ref ContractStatusTable @@ -242,7 +242,7 @@ Resources: PublicationApprovalRequestedEvent: Type: EventBridgeRule Properties: - RuleName: unicorn.properties-PublicationApprovalRequested + RuleName: unicorn.approvals-PublicationApprovalRequested EventBusName: !GetAtt UnicornApprovalsEventBus.Name Pattern: source: @@ -362,7 +362,7 @@ Resources: DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: - Name: properties.catchall + Name: approvals.catchall Description: Catchall rule used for development purposes. EventBusName: !Ref UnicornApprovalsEventBus EventPattern: diff --git a/unicorn_contracts/ContractsFunction/pom.xml b/unicorn_contracts/ContractsService/pom.xml similarity index 99% rename from unicorn_contracts/ContractsFunction/pom.xml rename to unicorn_contracts/ContractsService/pom.xml index 2d697b9..75f8e7f 100644 --- a/unicorn_contracts/ContractsFunction/pom.xml +++ b/unicorn_contracts/ContractsService/pom.xml @@ -2,7 +2,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 contracts - ContractsModule + ContractsService 1.0 jar Module for Contract service diff --git a/unicorn_contracts/ContractsFunction/src/main/java/contracts/ContractEventHandler.java b/unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandler.java similarity index 95% rename from unicorn_contracts/ContractsFunction/src/main/java/contracts/ContractEventHandler.java rename to unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandler.java index cb2fc81..e6c3722 100644 --- a/unicorn_contracts/ContractsFunction/src/main/java/contracts/ContractEventHandler.java +++ b/unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandler.java @@ -2,24 +2,16 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; - import com.amazonaws.services.lambda.runtime.events.SQSEvent; import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import contracts.utils.Contract; - import contracts.utils.ContractStatusEnum; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; - import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; -import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; -import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException; -import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; +import software.amazon.awssdk.services.dynamodb.model.*; import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; import software.amazon.lambda.powertools.metrics.MetricsUtils; import software.amazon.lambda.powertools.tracing.Tracing; diff --git a/unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/Address.java b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Address.java similarity index 100% rename from unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/Address.java rename to unicorn_contracts/ContractsService/src/main/java/contracts/utils/Address.java diff --git a/unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/Contract.java b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Contract.java similarity index 100% rename from unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/Contract.java rename to unicorn_contracts/ContractsService/src/main/java/contracts/utils/Contract.java diff --git a/unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/ContractStatusChangedEvent.java b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ContractStatusChangedEvent.java similarity index 100% rename from unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/ContractStatusChangedEvent.java rename to unicorn_contracts/ContractsService/src/main/java/contracts/utils/ContractStatusChangedEvent.java diff --git a/unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/ContractStatusEnum.java b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ContractStatusEnum.java similarity index 100% rename from unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/ContractStatusEnum.java rename to unicorn_contracts/ContractsService/src/main/java/contracts/utils/ContractStatusEnum.java diff --git a/unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/ResponseParser.java b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ResponseParser.java similarity index 99% rename from unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/ResponseParser.java rename to unicorn_contracts/ContractsService/src/main/java/contracts/utils/ResponseParser.java index 54eda4c..107963b 100644 --- a/unicorn_contracts/ContractsFunction/src/main/java/contracts/utils/ResponseParser.java +++ b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ResponseParser.java @@ -1,13 +1,12 @@ package contracts.utils; -import java.util.Map; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; - import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import java.util.Map; + public class ResponseParser { Contract parseResponse(Map queryResponse) diff --git a/unicorn_contracts/ContractsService/src/main/resources/log4j2.xml b/unicorn_contracts/ContractsService/src/main/resources/log4j2.xml new file mode 100644 index 0000000..0cc0953 --- /dev/null +++ b/unicorn_contracts/ContractsService/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/unicorn_contracts/ContractsFunction/src/test/events/create_empty_dict_body_event.json b/unicorn_contracts/ContractsService/src/test/events/create_empty_dict_body_event.json similarity index 100% rename from unicorn_contracts/ContractsFunction/src/test/events/create_empty_dict_body_event.json rename to unicorn_contracts/ContractsService/src/test/events/create_empty_dict_body_event.json diff --git a/unicorn_contracts/ContractsFunction/src/test/events/create_missing_body_event.json b/unicorn_contracts/ContractsService/src/test/events/create_missing_body_event.json similarity index 100% rename from unicorn_contracts/ContractsFunction/src/test/events/create_missing_body_event.json rename to unicorn_contracts/ContractsService/src/test/events/create_missing_body_event.json diff --git a/unicorn_contracts/ContractsFunction/src/test/events/create_valid_event.json b/unicorn_contracts/ContractsService/src/test/events/create_valid_event.json similarity index 100% rename from unicorn_contracts/ContractsFunction/src/test/events/create_valid_event.json rename to unicorn_contracts/ContractsService/src/test/events/create_valid_event.json diff --git a/unicorn_contracts/ContractsFunction/src/test/events/create_wrong_event.json b/unicorn_contracts/ContractsService/src/test/events/create_wrong_event.json similarity index 100% rename from unicorn_contracts/ContractsFunction/src/test/events/create_wrong_event.json rename to unicorn_contracts/ContractsService/src/test/events/create_wrong_event.json diff --git a/unicorn_contracts/ContractsFunction/src/test/events/update_empty_dict_body_event.json b/unicorn_contracts/ContractsService/src/test/events/update_empty_dict_body_event.json similarity index 100% rename from unicorn_contracts/ContractsFunction/src/test/events/update_empty_dict_body_event.json rename to unicorn_contracts/ContractsService/src/test/events/update_empty_dict_body_event.json diff --git a/unicorn_contracts/ContractsFunction/src/test/events/update_missing_body_event.json b/unicorn_contracts/ContractsService/src/test/events/update_missing_body_event.json similarity index 100% rename from unicorn_contracts/ContractsFunction/src/test/events/update_missing_body_event.json rename to unicorn_contracts/ContractsService/src/test/events/update_missing_body_event.json diff --git a/unicorn_contracts/ContractsFunction/src/test/events/update_valid_event.json b/unicorn_contracts/ContractsService/src/test/events/update_valid_event.json similarity index 100% rename from unicorn_contracts/ContractsFunction/src/test/events/update_valid_event.json rename to unicorn_contracts/ContractsService/src/test/events/update_valid_event.json diff --git a/unicorn_contracts/ContractsFunction/src/test/events/update_wrong_event.json b/unicorn_contracts/ContractsService/src/test/events/update_wrong_event.json similarity index 100% rename from unicorn_contracts/ContractsFunction/src/test/events/update_wrong_event.json rename to unicorn_contracts/ContractsService/src/test/events/update_wrong_event.json diff --git a/unicorn_contracts/ContractsFunction/src/test/java/contracts/CreateContractTests.java b/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java similarity index 99% rename from unicorn_contracts/ContractsFunction/src/test/java/contracts/CreateContractTests.java rename to unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java index 9d579f7..60525a9 100644 --- a/unicorn_contracts/ContractsFunction/src/test/java/contracts/CreateContractTests.java +++ b/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java @@ -1,18 +1,15 @@ package contracts; -import static org.mockito.Mockito.mock; - import com.amazonaws.services.lambda.runtime.Context; - import com.amazonaws.services.lambda.runtime.events.SQSEvent; import com.amazonaws.services.lambda.runtime.tests.annotations.Event; - -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; - import org.junit.Before; import org.junit.jupiter.params.ParameterizedTest; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +import static org.mockito.Mockito.mock; @RunWith(MockitoJUnitRunner.class) public class CreateContractTests { diff --git a/unicorn_contracts/ContractsService/target/classes/contracts/ContractEventHandler.class b/unicorn_contracts/ContractsService/target/classes/contracts/ContractEventHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..fac6aa5f88386505b69f576fa3fc6eb77a957527 GIT binary patch literal 10893 zcmcIq33y!9b^gz2d85(8*z$tKI>rV|UKkd$Nyb>4BNe0&UZzN%tmQ(~>rAX`6Iss)^RM36Ri7iOC^LgI9tOxI?ly;g0qz^xKJjNr(ork&@t&R&oW1r)ID%&gw#%b<`ZBZMG@758^WFr`5K{h{a(mR*5aRLc^6hu0lT{IVDK;rsPOh zaBA-SuCJ~y;0iPtf(=I|(do>Ga5bVjVoI)a$b`G9(Pyl`@-*v^k3~FIU1$>af&&8s}@YVmm{0ZP(~XDP_%xrcA>& z{TLDi#WKf#3U?dZ%$SC4f-?(;588qm*~rM%nX(uN(gaD%j;J|i$E{=t zjwgyL2F+`Ad;-@Aju|yZ)Z9_Sz9eoB^Db>0w7F@j@u4vjEed*{)NzAaN|~8TSt<4A zCLK597QPu|IHdsdaOUfoI2*@pI&N1n`{*&^o62Aq?qO7=E48Xn3U+RO<;cn~G( z=TRMx;c-`SJ@iP9#cvE@7e1@ub2^^H=l#J>y3uYa8`ZWcG;JoB=GSb@g|lj=d-{T4 zaWtDfDEBm#L#R~aOFF)crx3mRV3u@4i1lV=ki;bV`dTax3W6kbz9M^j6CdkcZEjJcPetmTV)Y6Q`+7n3?N z%34a9O_`NAfR{CVQ^zZKRnYRG#Nn?+AT+Km1+-a2c2-E<{sasL2e4Rq-`8|}8?O_x zW8?HG=jBf6nP3ipe50KCDv0kA(shjVX)~rY@qHa{C^eKfwQufjX%6D|7@_LS9b@s7 z+u;v%{6KZkS}yHu>ry>_D5xAuSxlwvo&9kFZ8!c%$B&h$kD_Cu?{b>j*s=$&1@R~B zpH`Sm^(#%N{(q+9&;9=0PE!Y4g7{0iXJ5Pht}i8(UARce;b%JjTIo)bLHw$lka_nb1@9FGO@b2_E$3wlK&ORfH&biAb&SJvFsu%Rc2f95))788~f z-TH3)QpdmI-&}v&5R0Ygk_E@n5`0qWh>Jk4k+6Fk|DoYOb^MoF-Z8WEb162t69w_# z#Aq~bbIP~zj*kCPLsfKX;=3SzL*=CHlxfc2P}Ru7~s9*K;H;3Q8rpGF&N8 z9deW|bA)>*85LaW;nB&?R08o);Xfqvq)HRDb&>_ed$G=Oo0;Bd7vsB`rrjKY7t1G+ zHB4@3WRWh%$YM&mxw)~wr=hXEg*j%9&3T@^QjV45G&x?E6Xe9=T~A>KI4k$I2~*k4 zN92MEPgJki59_i-P9{v5a(^W1o@K6}EF+iN>0^fxzfUXj*rDvV%4%^Oc^%sRC}#1E7VG>ImmmR z9}3g#rCjnnS*^=y!kwKq=Auk+W6k03WL>wPFNfmICObYo#%#~5*W?UAN0Gs;$rpu@ zXc{)7BO8o36NsG5b6Cz&USrL)M6y7b@gPs%D(C2OuJWzHhR)8e&3!G+RS-F!sjAco zHs%H}Z_rH3nfWHyJ$b{bSTDMkd8%BX%Q}@0Tjgqyr45$Eu zbSjT>v7q(CvgZ>PTvb3XMf>tYGv2LBkMuGojhH*9JlBy&j@f4m3irGcQlym8am_(>E(y=6RG~zMl;I9f%RFZ_+yZ8* z$=2eL+GVD#@l@38uT;%YW^$V* zI|OSEuQuW>HCv)CZQ{m_X>NNwX}Wn|k4g|!N@7KgM4yq0t7q?JsXZKLG8@iGWz~{Y z4LrB{f zj2I~}<9Bo^h#V#jU-=Xc_ngrYX1tDAHDR4&_2H?{qrn0nr*|`v!r@oYS!`B8+J~aZ zHCLwXl;S|?o4O$j&TU&vJvcQiRXQP5b3(9F@L)XmI}47byxf8QM#!hhDdtXPP)-ej zXgk`ami6_VOeK_l^ZcA`Sa#6lT>>Ts0VjzbDDs^qn z>V02VWUS3k9_!a}_Q9MN{@TxIyTN2^#KK;S-m-lX$lAzsf<8N`Mb5oVkgLrUB0g8G5)*fXtC@$+S(!l zYyKHreQlqhH#bT0t1m@aeO}n`D9~cuVtc2N;--#g7}QK~Z8YH>c2;!9hmtH@q-d7+ z=K!-f2J)lE+QW2s(>!o}&h$>uQ?L$oJE3|5_YOpx?+9{($!V%`$z?WLmQDX-c!vw= zpgb!$p*J~_w6-V1S@u=s``-kVerENok0*GFZOtni?f3j2kp@N!vx zTFjruo&tTgPkf) zJh{ypF(a;@qWF0Vuz}v)O3|hs%|)!&(FL2EKBSkmTh_?%9oa3{s6Qy_X%E+30OK{38yyY)S zlW+5c|Il5Tfw>aE5*|?mxKrKE&ksVXzQY3+sILT^D-W=pD-VO6D^F5+)xbOi zPa`PdGq8HaOOO=@pk4M7D)u3?ViI#RSR^=zdHZlYAH(}_DlaSc;q>P@ARr&-@3ABV zgqh$jV5oWNNboX<+Y>I^Ov z9(%mRn-(>5YhCGz7jbHJ=>#@r&=G*T>#4dlgUbVKzIp-!b!Am%b=d@l9z){_)jT?Z zF*T?v?Tpxi!MbvXNcjY|?+2OA;Q9dER~g($qHMW!57t%P;SpNn6S{K`Y7TGo`y1pr zE@&f<91Wf=J-{;Xlf0HVLh8dpE;K^HoPkx;!Frsh|=is#4=u;(tckWvG7qY*FiWcXDy<8`#*d)Vj*xRpYTVyRp&pQI{wXYbbY5bOrx zp^URWB{#}VR1t;kDMH=dEVnqyc|&fM+o;al9UjYni;#x(@AC|{TtlUXHJaSP7aCeM z9!-jd@#Vezc^Aum!vzP_Q6iT~D3xS5MTo#?nZ!L4cwo~3JV-D-Jb_0h@k9p&9$Hzo zdjelPh~0Ii2e5~YU#TmrF01~ktIMnYkR269ev~)Q1b%V}8~@mA{L?~>f5FDp6ZoqPemcdVH}G!`(e`(2 zTUChQKd@mh{&70biloo6R;^Y2g54Cy|1yDppGiim`qfnXuM4%mJJtTbh1%cCwhJbu zbV4c$tZ=@BR5vXndj8@kW$s>Cn2~B_L>W0LfcdgCBd74OEF(4SEGwx>nO7I2`j4*; zW@J?Wm+Zl^x(detl`bfSNjY<`oXy|;a-L_O&qD$W0^z_?IUS{r6+KS(0-o?{=%hVe zOo!8r)#$+{^kS57y$6@#VO)k6a5;X+L#UtN3Z9p2!7p(ok0P#;Qd}uPeh{z(SIdPM zkXA%x7%{m6raaB__h&F9Z}R#B#O24>DnIA-7Z{OW^Bn#?j0VbJ1wt4LEI=v{#`eHc zqywkG4m4wjV~K+Z@%bdVQ|>~AG6Ub%cy~7~k>Mb)PVV7d01E@B%f0ex`rc)MC32tK zPt#c)SS+8B2WYVmGk{A_9&$g=18~yW7+g|rT|uFq)P#{FC&+#h?0>l>ha2q^eezJ=Q}8E zb!84%77lLeC?0~VHEmgexoQl;_>%)?FE-dHBaSj9n zs1cDT3AYmR@>A9iA0Q~+LW6u>cB9Mz-Xem+?e%!J*JE6l3LZ;Q@;;ez!v?WNpb=$T zG--2Cc7-PK`LiHRH?1bRk#s`UZ)W`a&(B`~PSJ0mf~pC_Mh$g^=C$|5bEjT3aX*f( z179oDPs1qGX9`uPH?*(<%S6M*Dr|+0A4fXze4V;89ZpmC-FTb?X{uoQ@r+kD3a!B+ z=cP`w_=~ zK4khQZ#1RS8pOUg9ePP9=Ug=ORY;tj!4lFFg~lY%=l8T5PVX@Kiu|Y}3j$P{j_dJ$Jw}ba_&Eju*85 z2Y-io%gcwstEIs&v6}~<(OZb?{i8dWi>Kue6!sdLS%$5-SN^=9Nqs`SR|>LL1ZkIo z?3aVA6+t=#*&IIu5I1a-5rMZbf(mUZuBIORL~2BVWMK0uB}yEN3FZM;i&mw%#hHkRhv5w!(<_@ z+VxF25v^ar00tS7m$r4uZTIV8l_6EQ<|w+hZk~JEl!9ST8wNbAiV&j;#=L2V8wR(J zwHg=G6AC6V#n2zBi>~I@323niD8%*^`)0l7tv93K7G@a+!U%brYc40i!3bc%3h-_# zxFh6ap}e3u?pv#>pXt1MVp1mF*G*Ti@sjT7rv~3OO^c9p%Ot2(f~u)!J>YSlBOj}tetln^Cf8^(*Y*IoZn za5N*HPz0Tf1Z}GDNSL@SS+weQg&*jmX8J?I7pU2R$74g)y1$S1sREhtVuy~a*&ZY$U1>+v;gr-nb9BhGs)46$3Zrw zAUk+2JDTx0$QFU*T7dZXhT#QvB_PvrkR2(=9`Z7f={U$9f#h3&_?ML7r4(c?4ssv` Y*_VUN#X*h;;#{zCM%tj#sDu%UYw3E(#D81nx!<~1YRAR{6!WCVMzmU?VV+mWj3B)<)TDB8q@X?dKYM!7o{d3T0kAv!iyV){}i=(N+( zaXMNmT#AdL>FRh{$=f`aixJE!VO=r^jCk-K3SMtU;W3-FNjo-~nCXF`Cj0 zJv41#SoBv^V_M&JagRirk(-JoQqVx$R4kK%M&hR8KBj1segX7eAtQsnjJ^}~?_ghO zF@{xoYc*{W8hjwVeuzgT(NJNX-q)SKBKw(?apz*H^9D9U=L53Vr=F~DeM9ytwf<27 z6-qeoM_34~!t@#~s1ZXIwG@z65Kh4r~YMl_6Nl(PV@i( literal 0 HcmV?d00001 diff --git a/unicorn_contracts/ContractsService/target/classes/contracts/utils/ContractStatusEnum.class b/unicorn_contracts/ContractsService/target/classes/contracts/utils/ContractStatusEnum.class new file mode 100644 index 0000000000000000000000000000000000000000..9be6693b267b10eab9f96859d2b01099a514dfe1 GIT binary patch literal 1284 zcma)5?@!ZE6g_Wk*RB?r1NkK=4v-C$i2@><$TCJGscfORxcI4z3MHjWx{lBONjefC z8V#TQqm1{x%{YU`HED0p?LFtcd*A8zAD_Pg$l-;G2t%?~_rt)hg^f%zbiGEV(7{${ zhs{ROZ=S}$ARAB+Wl*;E^4t3iGv@z!Sx(dp#5hsQm&<#lN^y%PZx}FmGFHg%7K*0X z&h{G^;90f!ro6M)&fPSS;JIkQELrW;kbzqmrm~qz-h5rO7-rx0vKt+v7*jE9U>vup zlbToe9fqM)+B~t(?2KpoN14*WiBk)+Iwo;P#a#naxW~}{yMPab);_bnrc-*yu#)Ox z7d*Ss$aW>HFmU~&Z1)OWKPU?n`to2rP4i3g4X;+kqh-h8p43-`p$0i^uP)B?E{Z8Yk%2Hx|cCCB+Pov z_9}MZa;c7?SN`RC!kj3@Ytmgv)m(}d>9Z_N~J60qg>7F z%xkqvi%&Jz%l0<147ycs1~q5f<&BP9H~b<$?#(5d@g)5z(akaLeSjiaSwwQLh~!rh z$)iAgoEwuE3e`PAhWJZ_k`(!+w=nn#BUc#xK#1WnJ)`2AMD@orYFdgU%{$U3zAipTTrd4(xT~Bqb>AVRkeka~0pgcYptAH{H1mK@m$g&77v>{@5fR{i6J$UD=Ww_Oc<^cc-#nly%AWpuNR_k6^eA$ z&oFDaHGPZwf$$#HY<-6tyT0q_CJ%Vka?Hs28n4%dr$R+4g8>GGo2DmxzaK+5p&+Ya z7$XeFLr^l5P8I}YTM(}EGsSMO`P^ZVf-#00zPlAX!7@0}NDUqtPomX&@VZ57}p zZWLP47>1KxLKBJXGDCkGnMI%!T+#3b-lXy;ysVlGqe%x5Y0||thRL=IVlJpqF|932 zm=)KN4zFv-OIVW$hegs`i_|up3~n$Cww?F^4;sFTxABgGWl7LYhO_Z;_w6r*+AB_@ z#&GQ@jE727i2CNhGNkX8h85gqIFZ2FQen7oxWGqvk#xLAjMhDu8Z6kav>3gk;r*D= z&FE;=%wUaFePP?et56%zeOIudVKY`Og-7djqTnvWscr)93cv0;zS!g*H55a-KuttE z7A;4tH)>Vk-Q!i8B-x^CaJ$SsOMXYfbnw8UraFCyolduTx(Ia{AQsb*iAato19eI_ zw3r)d{Z&JF`v=y8tww7bGi1Cd5r(N3dD7f)G`6M}!#wH!>tRPgCJ*_J+LzA8IvLup z$1GJl(Jm&YL_9bC(~Qe6QFL|Xks<1VMa5J=^;-Cc#O}~cmn_iyYg;OynQ{4OL&K#B zERE>$(bV;mb~G4E*0w_p=TYQuyGr|i + + + + + + + + + + + + + + + diff --git a/unicorn_contracts/ContractsService/target/test-classes/contracts/CreateContractTests.class b/unicorn_contracts/ContractsService/target/test-classes/contracts/CreateContractTests.class new file mode 100644 index 0000000000000000000000000000000000000000..9c02a70a503d7ade20982c2d4fb2f5a42be99e20 GIT binary patch literal 1770 zcmbVNYi|=r6g^`n*~Tm{Qy>8nLfa&^37NhrIE8|ts#?UceKqKYN~WXOL<22tbiHFN$T=VLI3wU$gxnL5?Kg75z~=-OCkPhQZf`TQU#U3k z>$HNiDBE!7eJtSu!)@gU0!s|Hmz&p^Hm&WuKHkH!!0}9*ymVnvI)RPafpb>xSm&!g zYN!hw&N1E9KaE%wft}huGodZhOm6Ki8*|L}8P6t(AmY;AlIF^|zxz zSEu(>x6hqwI|)?3qteK(i_Iys74Z_CpUmW|w~TJ5nZEl&fy(WjKo3on#F>W=dEv)5 zdhbMF{%(F- z?=zMEuYkZ>+ct3yus0e;MyGP9QZ>lr_f}|bM9+0-8<5bi?=z6G{VLTs6*4b5A(Q-w z6GKw33ffC^kG9=hB-bbIT)EQwiU^K3|i%4RZ31N~)W>pSa zi_>F=M;xLBI$OjXmL?p}7mnYjqba)k03UH?0etMjeS%LJ#$w%}56f#W+MUMlIQ!#E zEbL;j@e3B$Uf>-oFLF5j0xJ*3@lTO6u#8!(Fbk_VXP*TAEWxGRYk`CQaT%X6{w(um mb5?SQth_?)tCY=*|6rd7)GFZ`_e!|#bPmz#3x3T2H~s<4K;~Zn literal 0 HcmV?d00001 From fa53715008e6a77bb94f542abc943aa4922074c0 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 17 Jul 2025 14:28:44 +0800 Subject: [PATCH 07/34] refactor: updated POM file to rename groupId and artifactId from PropertyService to ApprovalsService for consistency with recent changes --- unicorn_approvals/ApprovalsService/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unicorn_approvals/ApprovalsService/pom.xml b/unicorn_approvals/ApprovalsService/pom.xml index 4d5d7fd..6ce3751 100644 --- a/unicorn_approvals/ApprovalsService/pom.xml +++ b/unicorn_approvals/ApprovalsService/pom.xml @@ -1,8 +1,8 @@ 4.0.0 - property - PropertyService + approval + ApprovalsService 1.0 jar Property service module for java reference architecture. From 5def8e5ada7df500da7972b2f888b5cfbe652cd2 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 17 Jul 2025 16:17:31 +0800 Subject: [PATCH 08/34] chore: update .gitignore to include new target directories for ApprovalService, ContractsService, SearchService, and Common --- .gitignore | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 34e4044..c987984 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ -unicorn_approvals/PropertyFunctions/target/* -unicorn_contracts/ContractsFunction/target/** -unicorn_web/PropertyFunctions/target/** +unicorn_web/ApprovalService/target/** +unicorn_contracts/ContractsService/target/** +unicorn_web/ApprovalService/target/** +unicorn_web/SearchService/target/** +unicorn_web/Common/target/** **/.aws-sam/ .DS_Store** .vscode/settings.json @@ -11,4 +13,4 @@ deleteall.sh /unicorn_approvals/PropertyFunctions/PropertyService.iml /unicorn_web/PropertyFunctions/PropertyWeb.iml /unicorn_contracts/ContractsFunction/ContractsModule.iml -**/cdk.out/ \ No newline at end of file +**/cdk.out/ From 3d0c1da179d4de2e843c03da4476bdb3edf69c7d Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 17 Jul 2025 16:17:56 +0800 Subject: [PATCH 09/34] refactor: simplify setUp method in CreateContractTests by removing unnecessary exception declaration --- .../src/test/java/contracts/CreateContractTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java b/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java index 60525a9..b3b10af 100644 --- a/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java +++ b/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java @@ -21,7 +21,7 @@ public class CreateContractTests { DynamoDbClient client; @Before - public void setUp() throws Exception { + public void setUp() { client = mock(DynamoDbClient.class); context = mock(Context.class); From 5191df704c5b6d7e4df44d8d21582a352f6b7d6a Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 17 Jul 2025 16:19:36 +0800 Subject: [PATCH 10/34] refactor: simplify setUp method in ContractStatusTests by removing unnecessary exception declaration --- .../src/test/java/approvals/ContractStatusTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java b/unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java index fe71bac..63caac2 100644 --- a/unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java +++ b/unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java @@ -26,7 +26,7 @@ public class ContractStatusTests { Map response = new HashMap(); @Before - public void setUp() throws Exception { + public void setUp() { context = mock(Context.class); client = mock(DynamoDbClient.class); From 36b6fb156a503dc104d8192519bb63f91be43972 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 17 Jul 2025 16:24:59 +0800 Subject: [PATCH 11/34] refactor: update POM files to rename artifactId and module names for consistency, and upgrade compiler version to 17 --- pom.xml | 6 ++++-- unicorn_approvals/ApprovalsService/pom.xml | 21 ++++----------------- unicorn_contracts/ContractsService/pom.xml | 2 +- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index c143525..c920fc5 100644 --- a/pom.xml +++ b/pom.xml @@ -3,13 +3,15 @@ 4.0.0 unicornproperties - cloudapp + UnicornProperties 1.0 pom unicorn_contracts/ContractsService unicorn_approvals/ApprovalsService - unicorn_web/PropertyFunctions + unicorn_web/ApprovalService + unicorn_web/SearchService + unicorn_web/Common \ No newline at end of file diff --git a/unicorn_approvals/ApprovalsService/pom.xml b/unicorn_approvals/ApprovalsService/pom.xml index 6ce3751..0fd849c 100644 --- a/unicorn_approvals/ApprovalsService/pom.xml +++ b/unicorn_approvals/ApprovalsService/pom.xml @@ -5,7 +5,7 @@ ApprovalsService 1.0 jar - Property service module for java reference architecture. + Unicorn Approvals Service module 17 17 @@ -16,7 +16,6 @@ 4.13.2 1.1.1 1.2.3 - 3.13.0 2.27.21 @@ -72,25 +71,12 @@ eventbridge ${aws.java.sdk.version} - - com.amazonaws - aws-lambda-java-events - ${aws-lambda-java-events.version} - - software.amazon.awssdk netty-nio-client ${netty-nio-client.version} - - - com.amazonaws - aws-lambda-java-core - 1.2.2 - - com.fasterxml.jackson.core @@ -202,9 +188,10 @@ org.apache.maven.plugins maven-compiler-plugin + 3.11.0 - 16 - 16 + 17 + 17 diff --git a/unicorn_contracts/ContractsService/pom.xml b/unicorn_contracts/ContractsService/pom.xml index 75f8e7f..f9b730e 100644 --- a/unicorn_contracts/ContractsService/pom.xml +++ b/unicorn_contracts/ContractsService/pom.xml @@ -5,7 +5,7 @@ ContractsService 1.0 jar - Module for Contract service + Unicorn Contracts Service module 17 17 From 6845a75fb14ae425220d26e96f4a6f00104b699e Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 17 Jul 2025 16:25:15 +0800 Subject: [PATCH 12/34] chore: update .gitignore to reflect the new directory structure for ApprovalsService --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c987984..62dfa60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -unicorn_web/ApprovalService/target/** +unicorn_approvals/ApprovalsService/target/** unicorn_contracts/ContractsService/target/** unicorn_web/ApprovalService/target/** unicorn_web/SearchService/target/** From 917a42a89bbf16dcc14c099978d583bc9823bf77 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 17 Jul 2025 16:28:14 +0800 Subject: [PATCH 13/34] feat: refactored unicorn web into separate modules - ApprovalService and SearchService - to align with implementations in other runtimes. --- unicorn_web/ApprovalService/pom.xml | 237 ++++++++++++++++++ .../PublicationApprovedFunction.java | 17 +- .../RequestApprovalFunction.java | 23 +- .../AWSEvent.java | 0 .../PublicationEvaluationCompleted.java | 0 .../marshaller/Marshaller.java | 0 .../src/main/resources/log4j2.xml | 2 +- ...vent_publication_evaluation_completed.json | 8 + unicorn_web/Common/pom.xml | 36 +++ .../Common/src/main/java/dao/Property.java | 150 +++++++++++ .../src/main/java/property/dao/Property.java | 151 +---------- .../populate/PopulateDataFunction.java | 106 -------- .../pom.xml | 16 +- .../search/PropertySearchFunction.java | 6 +- .../src/main/resources/log4j2.xml | 14 ++ ...vent_publication_evaluation_completed.json | 8 + unicorn_web/integration/event-schemas.yaml | 2 +- unicorn_web/template.yaml | 10 +- 18 files changed, 491 insertions(+), 295 deletions(-) create mode 100644 unicorn_web/ApprovalService/pom.xml rename unicorn_web/{PropertyFunctions/src/main/java/property => ApprovalService/src/main/java/approval}/requestapproval/PublicationApprovedFunction.java (88%) rename unicorn_web/{PropertyFunctions/src/main/java/property => ApprovalService/src/main/java/approval}/requestapproval/RequestApprovalFunction.java (90%) rename unicorn_web/{PropertyFunctions => ApprovalService}/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java (100%) rename unicorn_web/{PropertyFunctions => ApprovalService}/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java (100%) rename unicorn_web/{PropertyFunctions => ApprovalService}/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java (100%) rename unicorn_web/{PropertyFunctions => ApprovalService}/src/main/resources/log4j2.xml (88%) create mode 100644 unicorn_web/ApprovalService/tests/events/eventbridge/put_event_publication_evaluation_completed.json create mode 100644 unicorn_web/Common/pom.xml create mode 100644 unicorn_web/Common/src/main/java/dao/Property.java delete mode 100644 unicorn_web/PropertyFunctions/src/main/java/property/populate/PopulateDataFunction.java rename unicorn_web/{PropertyFunctions => SearchService}/pom.xml (96%) rename unicorn_web/{PropertyFunctions/src/main/java/property => SearchService/src/main/java/search}/search/PropertySearchFunction.java (98%) create mode 100644 unicorn_web/SearchService/src/main/resources/log4j2.xml create mode 100644 unicorn_web/SearchService/tests/events/eventbridge/put_event_publication_evaluation_completed.json diff --git a/unicorn_web/ApprovalService/pom.xml b/unicorn_web/ApprovalService/pom.xml new file mode 100644 index 0000000..d0709f2 --- /dev/null +++ b/unicorn_web/ApprovalService/pom.xml @@ -0,0 +1,237 @@ + + 4.0.0 + approval + ApprovalService + 1.0 + jar + Unicorn Web Approval Service module + + 17 + 17 + 2.27.21 + 1.18.0 + 3.13.0 + 5.13.0 + 4.13.2 + 1.1.1 + 1.2.3 + 3.13.0 + 2.27.21 + + + + + software.amazon.awssdk + bom + ${aws.java.sdk.version} + pom + import + + + + + + + + software.amazon.lambda + powertools-tracing + ${aws.java.powertool.version} + + + software.amazon.lambda + powertools-logging + ${aws.java.powertool.version} + + + software.amazon.lambda + powertools-metrics + ${aws.java.powertool.version} + + + software.amazon.lambda + powertools-cloudformation + ${aws.java.powertool.version} + + + software.amazon.cloudwatchlogs + aws-embedded-metrics + 4.2.0 + + + + + software.amazon.awssdk + lambda + ${aws.java.sdk.version} + + + software.amazon.awssdk + dynamodb + ${aws.java.sdk.version} + + + software.amazon.awssdk + dynamodb-enhanced + ${aws.java.sdk.version} + + + + software.amazon.awssdk + sfn + ${aws.java.sdk.version} + + + + software.amazon.awssdk + eventbridge + ${aws.java.sdk.version} + + + com.amazonaws + aws-lambda-java-events + ${aws-lambda-java-events.version} + + + software.amazon.awssdk + netty-nio-client + ${netty-nio-client.version} + + + + org.mockito + mockito-core + ${mockito-core.version} + test + + + junit + junit + ${junit.version} + test + + + com.amazonaws + aws-lambda-java-tests + ${aws-lambda-java-tests.version} + test + + + + com.amazonaws + aws-lambda-java-core + 1.2.2 + + + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + com.fasterxml.jackson.core + jackson-core + 2.15.2 + + + com.fasterxml.jackson.core + jackson-annotations + 2.15.2 + + + + + org.apache.logging.log4j + log4j-api + 2.20.0 + + + org.apache.logging.log4j + log4j-core + 2.20.0 + + + common + Common + 1.0 + compile + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + handler + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + + + package + + shade + + + + + + dev.aspectj + aspectj-maven-plugin + 1.13.1 + + 17 + 17 + 17 + + + software.amazon.lambda + powertools-tracing + + + software.amazon.lambda + powertools-logging + + + software.amazon.lambda + powertools-metrics + + + + + + + compile + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + + diff --git a/unicorn_web/PropertyFunctions/src/main/java/property/requestapproval/PublicationApprovedFunction.java b/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java similarity index 88% rename from unicorn_web/PropertyFunctions/src/main/java/property/requestapproval/PublicationApprovedFunction.java rename to unicorn_web/ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java index 1295ae3..f9ff09c 100644 --- a/unicorn_web/PropertyFunctions/src/main/java/property/requestapproval/PublicationApprovedFunction.java +++ b/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java @@ -1,4 +1,4 @@ -package property.requestapproval; +package approval.requestapproval; import java.io.IOException; import java.io.InputStream; @@ -7,18 +7,16 @@ import java.nio.charset.StandardCharsets; import com.amazonaws.services.lambda.runtime.Context; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import property.dao.Property; +import dao.Property; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbAsyncTable; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; import software.amazon.awssdk.enhanced.dynamodb.Key; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; -import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest; import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.lambda.powertools.logging.Logging; @@ -30,8 +28,7 @@ /** * Function checks for the existence of a contract status entry for a specified - * property. - * + * search. * If an entry exists, pause the workflow, and update the record with task * token. */ @@ -67,7 +64,7 @@ public void handleRequest(InputStream inputStream, OutputStream outputStream, ObjectMapper objectMapper = new ObjectMapper(); OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); - writer.write(objectMapper.writeValueAsString("'result': 'Successfully updated property status'")); + writer.write(objectMapper.writeValueAsString("'result': 'Successfully updated search status'")); writer.close(); } @@ -80,7 +77,7 @@ private void publicationApproved(String evaluationResult, String propertyId) { String city = splitString[1]; String street = splitString[2]; String number = splitString[3]; - String strPartionKey = ("property#" + country + "#" + city).replace(' ', '-').toLowerCase(); + String strPartionKey = ("search#" + country + "#" + city).replace(' ', '-').toLowerCase(); String strSortKey = (street + "#" + number).replace(' ', '-').toLowerCase(); Key key = Key.builder().partitionValue(strPartionKey).sortValue(strSortKey).build(); @@ -91,11 +88,11 @@ private void publicationApproved(String evaluationResult, String propertyId) { throw new RuntimeException("Property not found with ID: " + propertyId); } - // Always set the property number explicitly to ensure it's correct + // Always set the search number explicitly to ensure it's correct existingProperty.setPropertyNumber(number); existingProperty.setStatus(evaluationResult); - logger.info("Updating property with status: {} and propertyNumber: {}", + logger.info("Updating search with status: {} and propertyNumber: {}", evaluationResult, existingProperty.getPropertyNumber()); propertyTable.putItem(existingProperty).join(); } diff --git a/unicorn_web/PropertyFunctions/src/main/java/property/requestapproval/RequestApprovalFunction.java b/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/RequestApprovalFunction.java similarity index 90% rename from unicorn_web/PropertyFunctions/src/main/java/property/requestapproval/RequestApprovalFunction.java rename to unicorn_web/ApprovalService/src/main/java/approval/requestapproval/RequestApprovalFunction.java index 46d1bf2..648187f 100644 --- a/unicorn_web/PropertyFunctions/src/main/java/property/requestapproval/RequestApprovalFunction.java +++ b/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/RequestApprovalFunction.java @@ -1,4 +1,4 @@ -package property.requestapproval; +package search.requestapproval; import java.util.ArrayList; import java.util.Arrays; @@ -24,18 +24,16 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import property.dao.Property; +import dao.Property; import software.amazon.awssdk.core.async.SdkPublisher; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbAsyncTable; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; -import software.amazon.awssdk.enhanced.dynamodb.Expression; import software.amazon.awssdk.enhanced.dynamodb.Key; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional; import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; import software.amazon.awssdk.services.eventbridge.EventBridgeAsyncClient; import software.amazon.awssdk.services.eventbridge.model.PutEventsRequest; @@ -46,7 +44,7 @@ import software.amazon.lambda.powertools.tracing.Tracing; /** - * Validates the integrity of the property content + * Validates the integrity of the search content */ public class RequestApprovalFunction { @@ -81,7 +79,7 @@ public class RequestApprovalFunction { @Metrics(captureColdStart = true) @Logging(logEvent = true, correlationIdPath = CorrelationIdPathConstants.API_GATEWAY_REST) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, - final Context context) throws JsonMappingException, JsonProcessingException { + final Context context) throws JsonProcessingException { { Map headers = new HashMap<>(); @@ -95,25 +93,23 @@ public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEv Matcher matcher = pattern.matcher(propertyId); boolean valid = matcher.matches(); if (!valid) { - APIGatewayProxyResponseEvent errorResponse = response + return response .withBody("Input invalid; must conform to regular expression: " + EXPRESSION) .withStatusCode(500); - return errorResponse; } String[] splitString = propertyId.split("/"); String country = splitString[0]; String city = splitString[1]; String street = splitString[2]; String number = splitString[3]; - String strPartionKey = ("property#" + country + "#" + city).replace(' ', '-').toLowerCase(); + String strPartionKey = ("search#" + country + "#" + city).replace(' ', '-').toLowerCase(); String strSortKey = (street + "#" + number).replace(' ', '-').toLowerCase(); try { List properties = queryTable(strPartionKey, strSortKey); if (properties.size() <= 0) { - APIGatewayProxyResponseEvent errorResponse = response - .withBody("No property found in database with the requested property id") + return response + .withBody("No search found in database with the requested search id") .withStatusCode(500); - return errorResponse; } Property property = properties.get(0); if (noActionSet.contains(property.getStatus())) { @@ -124,10 +120,9 @@ public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEv sendEvent(property); } catch (Exception e) { - APIGatewayProxyResponseEvent errorResponse = response + return response .withBody("Error in searching") .withStatusCode(500); - return errorResponse; } return response .withStatusCode(200) diff --git a/unicorn_web/PropertyFunctions/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java b/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java similarity index 100% rename from unicorn_web/PropertyFunctions/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java rename to unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java diff --git a/unicorn_web/PropertyFunctions/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java b/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java similarity index 100% rename from unicorn_web/PropertyFunctions/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java rename to unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java diff --git a/unicorn_web/PropertyFunctions/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java b/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java similarity index 100% rename from unicorn_web/PropertyFunctions/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java rename to unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java diff --git a/unicorn_web/PropertyFunctions/src/main/resources/log4j2.xml b/unicorn_web/ApprovalService/src/main/resources/log4j2.xml similarity index 88% rename from unicorn_web/PropertyFunctions/src/main/resources/log4j2.xml rename to unicorn_web/ApprovalService/src/main/resources/log4j2.xml index fcffc72..e4b9c1a 100644 --- a/unicorn_web/PropertyFunctions/src/main/resources/log4j2.xml +++ b/unicorn_web/ApprovalService/src/main/resources/log4j2.xml @@ -9,6 +9,6 @@ - + \ No newline at end of file diff --git a/unicorn_web/ApprovalService/tests/events/eventbridge/put_event_publication_evaluation_completed.json b/unicorn_web/ApprovalService/tests/events/eventbridge/put_event_publication_evaluation_completed.json new file mode 100644 index 0000000..c4f3c7f --- /dev/null +++ b/unicorn_web/ApprovalService/tests/events/eventbridge/put_event_publication_evaluation_completed.json @@ -0,0 +1,8 @@ +[ + { + "Source": "unicorn.properties", + "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"evaluation_result\": \"APPROVED\"}", + "DetailType": "PublicationEvaluationCompleted", + "EventBusName": "UnicornWebBus-local" + } +] diff --git a/unicorn_web/Common/pom.xml b/unicorn_web/Common/pom.xml new file mode 100644 index 0000000..7207ec0 --- /dev/null +++ b/unicorn_web/Common/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + unicornproperties + UnicornProperties + 1.0 + ../../pom.xml + + + common + Common + + + 21 + 21 + UTF-8 + + + + software.amazon.awssdk + dynamodb-enhanced + 2.27.21 + compile + + + com.fasterxml.jackson.core + jackson-annotations + 2.15.3 + compile + + + + \ No newline at end of file diff --git a/unicorn_web/Common/src/main/java/dao/Property.java b/unicorn_web/Common/src/main/java/dao/Property.java new file mode 100644 index 0000000..d4b53af --- /dev/null +++ b/unicorn_web/Common/src/main/java/dao/Property.java @@ -0,0 +1,150 @@ +package dao; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; + +@DynamoDbBean +public class Property { + + String country; + String city; + String street; + String propertyNumber; + String description; + String contract; + Float listprice; + String currency; + List images; + String status; + @JsonIgnore + String pk; + @JsonIgnore + String sk; + String id; + + @DynamoDbPartitionKey + @DynamoDbAttribute("PK") + public String getPk() { + return ("PROPERTY#" + getCountry() + "#" + getCity()).replace(' ', '-').toLowerCase(); + } + + public void setPk(String pk) { + this.pk = pk; + } + + @DynamoDbSortKey + @DynamoDbAttribute("SK") + public String getSk() { + return (getStreet() + "#" + getPropertyNumber()).replace(' ', '-').toLowerCase(); + } + + public void setSk(String sk) { + this.sk = sk; + } + + @JsonIgnore + @software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnore + public String getId() { + return (getPk() + '/' + getSk()).replace('#', '/'); + } + + public void setId(String id) { + this.id = id; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + @DynamoDbAttribute(value = "number") + public String getPropertyNumber() { + return propertyNumber; + } + + public void setPropertyNumber(String propertyNumber) { + this.propertyNumber = propertyNumber; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getContract() { + return contract; + } + + public void setContract(String contract) { + this.contract = contract; + } + + public Float getListprice() { + return listprice; + } + + public void setListprice(Float listprice) { + this.listprice = listprice; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } + + public List getImages() { + return images; + } + + public void setImages(List images) { + this.images = images; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + @Override + public String toString() { + return "Property [city=" + city + ", contract=" + contract + ", country=" + country + ", currency=" + currency + + ", description=" + description + ", id=" + getId() + ", images=" + images + ", listprice=" + listprice + + ", pk=" + getPk() + ", propertyNumber=" + propertyNumber + ", sk=" + getSk() + ", status=" + status + + ", street=" + street + "]"; + } + +} diff --git a/unicorn_web/PropertyFunctions/src/main/java/property/dao/Property.java b/unicorn_web/PropertyFunctions/src/main/java/property/dao/Property.java index c0c6405..0519ecb 100644 --- a/unicorn_web/PropertyFunctions/src/main/java/property/dao/Property.java +++ b/unicorn_web/PropertyFunctions/src/main/java/property/dao/Property.java @@ -1,150 +1 @@ -package property.dao; - -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; - -@DynamoDbBean -public class Property { - - String country; - String city; - String street; - String propertyNumber; - String description; - String contract; - Float listprice; - String currency; - List images; - String status; - @JsonIgnore - String pk; - @JsonIgnore - String sk; - String id; - - @DynamoDbPartitionKey - @DynamoDbAttribute("PK") - public String getPk() { - return ("PROPERTY#" + getCountry() + "#" + getCity()).replace(' ', '-').toLowerCase(); - } - - public void setPk(String pk) { - this.pk = pk; - } - - @DynamoDbSortKey - @DynamoDbAttribute("SK") - public String getSk() { - return (getStreet() + "#" + getPropertyNumber()).replace(' ', '-').toLowerCase(); - } - - public void setSk(String sk) { - this.sk = sk; - } - - @JsonIgnore - @software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnore - public String getId() { - return (getPk() + '/' + getSk()).replace('#', '/'); - } - - public void setId(String id) { - this.id = id; - } - - public String getCountry() { - return country; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getStreet() { - return street; - } - - public void setStreet(String street) { - this.street = street; - } - - @DynamoDbAttribute(value = "number") - public String getPropertyNumber() { - return propertyNumber; - } - - public void setPropertyNumber(String propertyNumber) { - this.propertyNumber = propertyNumber; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getContract() { - return contract; - } - - public void setContract(String contract) { - this.contract = contract; - } - - public Float getListprice() { - return listprice; - } - - public void setListprice(Float listprice) { - this.listprice = listprice; - } - - public String getCurrency() { - return currency; - } - - public void setCurrency(String currency) { - this.currency = currency; - } - - public List getImages() { - return images; - } - - public void setImages(List images) { - this.images = images; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - @Override - public String toString() { - return "Property [city=" + city + ", contract=" + contract + ", country=" + country + ", currency=" + currency - + ", description=" + description + ", id=" + getId() + ", images=" + images + ", listprice=" + listprice - + ", pk=" + getPk() + ", propertyNumber=" + propertyNumber + ", sk=" + getSk() + ", status=" + status - + ", street=" + street + "]"; - } - -} + \ No newline at end of file diff --git a/unicorn_web/PropertyFunctions/src/main/java/property/populate/PopulateDataFunction.java b/unicorn_web/PropertyFunctions/src/main/java/property/populate/PopulateDataFunction.java deleted file mode 100644 index 3b15534..0000000 --- a/unicorn_web/PropertyFunctions/src/main/java/property/populate/PopulateDataFunction.java +++ /dev/null @@ -1,106 +0,0 @@ -package property.populate; - -import java.util.List; -import java.util.Map; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; - -import property.dao.Property; -import software.amazon.awssdk.enhanced.dynamodb.DynamoDbAsyncTable; -import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; -import software.amazon.awssdk.enhanced.dynamodb.TableSchema; - -import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; -import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; -import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler; -import software.amazon.lambda.powertools.cloudformation.Response; - -public class PopulateDataFunction extends AbstractCustomResourceHandler { - - String DYNAMODB_TABLE = System.getenv("DYNAMODB_TABLE"); - - String[] validKeys = { "country", "city", "street", "number", "description", "contract", "listprice", "currency", - "images" }; - - DynamoDbAsyncClient dynamodbClient = DynamoDbAsyncClient.builder() - .httpClientBuilder(NettyNioAsyncHttpClient.builder() - .maxConcurrency(100) - .maxPendingConnectionAcquires(10_000)) - .build(); - - DynamoDbEnhancedAsyncClient enhancedClient = DynamoDbEnhancedAsyncClient.builder() - .dynamoDbClient(dynamodbClient) - .build(); - - @Override - protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { - try { - return handleEvent(createEvent); - } catch (Exception e) { - return Response.builder() - .value(Map.of("Resource", DYNAMODB_TABLE + "-" + createEvent.getLogicalResourceId())) - .status(Response.Status.FAILED).build(); - } - - } - - @Override - protected Response update(CloudFormationCustomResourceEvent updateEvent, Context context) { - try { - return handleEvent(updateEvent); - } catch (Exception e) { - return Response.builder() - .value(Map.of("Resource", DYNAMODB_TABLE + "-" + updateEvent.getLogicalResourceId())) - .status(Response.Status.FAILED).build(); - } - } - - @Override - protected Response delete(CloudFormationCustomResourceEvent deleteEvent, Context context) { - return null; - } - - private void saveInDatabase(Property property, String table_name) { - - DynamoDbAsyncTable propertyTable = enhancedClient.table(table_name, - TableSchema.fromBean(Property.class)); - propertyTable.putItem(property).join(); - - } - - Property createPropertyFromEvent(CloudFormationCustomResourceEvent event) throws Exception { - Map propertyMap = event.getResourceProperties(); - // Iterate over map and check the keys - for (String strKey : validKeys) { - - if (!propertyMap.containsKey(strKey)) { - throw new Exception("Invalid input: missing mandatory field " + strKey); - } - - } - - Property property = new Property(); - property.setCountry((String) propertyMap.get("country")); - property.setCity((String) propertyMap.get("city")); - property.setStreet((String) propertyMap.get("street")); - property.setPropertyNumber((String) propertyMap.get("number")); - property.setDescription((String) propertyMap.get("description")); - property.setContract((String) propertyMap.get("contract")); - property.setListprice(Float.parseFloat((String) propertyMap.get("listprice"))); - property.setCurrency((String) propertyMap.get("currency")); - List images = (List) propertyMap.get("images"); - property.setImages(images); - property.setStatus("NEW"); - return property; - - } - - private Response handleEvent(CloudFormationCustomResourceEvent createEvent) throws Exception { - - Property property = createPropertyFromEvent(createEvent); - saveInDatabase(property, DYNAMODB_TABLE); - return Response.builder().value(Map.of("Resource", DYNAMODB_TABLE + "-" + createEvent.getLogicalResourceId())) - .status(Response.Status.SUCCESS).build(); - } -} diff --git a/unicorn_web/PropertyFunctions/pom.xml b/unicorn_web/SearchService/pom.xml similarity index 96% rename from unicorn_web/PropertyFunctions/pom.xml rename to unicorn_web/SearchService/pom.xml index 2142889..2e7b01e 100644 --- a/unicorn_web/PropertyFunctions/pom.xml +++ b/unicorn_web/SearchService/pom.xml @@ -1,11 +1,11 @@ 4.0.0 - property - PropertyWeb + search + SearchService 1.0 jar - Property web module for java reference architecture. + Unicorn Web Search Service module 17 17 @@ -126,7 +126,7 @@ aws-lambda-java-core 1.2.2 - + com.fasterxml.jackson.core @@ -143,7 +143,7 @@ jackson-annotations 2.15.2 - + org.apache.logging.log4j @@ -155,6 +155,12 @@ log4j-core 2.20.0 + + common + Common + 1.0 + compile + diff --git a/unicorn_web/PropertyFunctions/src/main/java/property/search/PropertySearchFunction.java b/unicorn_web/SearchService/src/main/java/search/search/PropertySearchFunction.java similarity index 98% rename from unicorn_web/PropertyFunctions/src/main/java/property/search/PropertySearchFunction.java rename to unicorn_web/SearchService/src/main/java/search/search/PropertySearchFunction.java index e3a3e7d..2c44495 100644 --- a/unicorn_web/PropertyFunctions/src/main/java/property/search/PropertySearchFunction.java +++ b/unicorn_web/SearchService/src/main/java/search/search/PropertySearchFunction.java @@ -1,4 +1,4 @@ -package property.search; +package search.search; import java.util.ArrayList; import java.util.HashMap; @@ -16,7 +16,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import property.dao.Property; +import dao.Property; import software.amazon.awssdk.core.async.SdkPublisher; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbAsyncTable; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; @@ -82,7 +82,7 @@ public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEv } String requestPath = input.getResource(); String responseString = null; - String strPartitionKey = ("property#" + input.getPathParameters().get("country") + "#" + String strPartitionKey = ("search#" + input.getPathParameters().get("country") + "#" + input.getPathParameters().get("city")).replace(' ', '-').toLowerCase(); String strSortKey = null; diff --git a/unicorn_web/SearchService/src/main/resources/log4j2.xml b/unicorn_web/SearchService/src/main/resources/log4j2.xml new file mode 100644 index 0000000..e4b9c1a --- /dev/null +++ b/unicorn_web/SearchService/src/main/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/unicorn_web/SearchService/tests/events/eventbridge/put_event_publication_evaluation_completed.json b/unicorn_web/SearchService/tests/events/eventbridge/put_event_publication_evaluation_completed.json new file mode 100644 index 0000000..c4f3c7f --- /dev/null +++ b/unicorn_web/SearchService/tests/events/eventbridge/put_event_publication_evaluation_completed.json @@ -0,0 +1,8 @@ +[ + { + "Source": "unicorn.properties", + "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"evaluation_result\": \"APPROVED\"}", + "DetailType": "PublicationEvaluationCompleted", + "EventBusName": "UnicornWebBus-local" + } +] diff --git a/unicorn_web/integration/event-schemas.yaml b/unicorn_web/integration/event-schemas.yaml index 27b6e46..10815f0 100644 --- a/unicorn_web/integration/event-schemas.yaml +++ b/unicorn_web/integration/event-schemas.yaml @@ -53,7 +53,7 @@ Resources: Fn::GetAtt: EventRegistry.RegistryName SchemaName: Fn::Sub: '{{resolve:ssm:/uni-prop/UnicornWebNamespace}}@PublicationApprovalRequested' - Description: 'The schema for a request to publish a property' + Description: 'The schema for a request to publish a search' Content: Fn::Sub: | { diff --git a/unicorn_web/template.yaml b/unicorn_web/template.yaml index 67f242b..a9872a9 100644 --- a/unicorn_web/template.yaml +++ b/unicorn_web/template.yaml @@ -91,7 +91,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: PropertyFunctions - Handler: property.search.PropertySearchFunction::handleRequest + Handler: search.search.PropertySearchFunction::handleRequest Policies: - DynamoDBReadPolicy: TableName: !Ref WebTable @@ -110,7 +110,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: PropertyFunctions - Handler: property.requestapproval.RequestApprovalFunction::handleRequest + Handler: search.requestapproval.RequestApprovalFunction::handleRequest Policies: - EventBridgePutEventsPolicy: EventBusName: !Ref UnicornWebEventBus @@ -140,7 +140,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: PropertyFunctions - Handler: property.requestapproval.PublicationApprovedFunction::handleRequest + Handler: search.requestapproval.PublicationApprovedFunction::handleRequest Policies: - DynamoDBCrudPolicy: TableName: !Ref WebTable @@ -461,10 +461,10 @@ Outputs: Description: "GET request to list all properties in a given street" Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/search/{country}/{city}/{street}" ApiPropertyApproval: - Description: "POST request to add a property to the database" + Description: "POST request to add a search to the database" Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/request_approval" ApiPropertyDetails: - Description: "GET request to get the full details of a single property" + Description: "GET request to get the full details of a single search" Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/properties/{country}/{city}/{street}/{number}" #### SQS OUTPUTS From 1d262c71229b8557fab24f282d1ad58320dc27d2 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Fri, 18 Jul 2025 10:48:05 +0800 Subject: [PATCH 14/34] chore: update Approvals readme --- unicorn_approvals/README.md | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/unicorn_approvals/README.md b/unicorn_approvals/README.md index 67ce873..f70eb7f 100644 --- a/unicorn_approvals/README.md +++ b/unicorn_approvals/README.md @@ -1,26 +1,15 @@ -# Developing Unicorn Properties +# Developing Unicorn Approvals -![Properties Approval Architecture](https://static.us-east-1.prod.workshops.aws/public/f273b5fc-17cd-406b-9e63-1d331b00589d/static/images/architecture-properties.png) +![Properties Approval Architecture](https://static.us-east-1.prod.workshops.aws/public/f273b5fc-17cd-406b-9e63-1d331b00589d/static/images/architecture-approvals.png) ## Architecture overview -Unicorn Properties is primarily responsible for approving property listings for Unicorn Web. +**Unicorn Approvals** uses an AWS Step Functions state machine to approve property listings for Unicorn Web. The workflow checks for contract information, description sentiment and safe images, and verifies the contract is approved before approving the listing. It publishes the result via the `PublicationEvaluationCompleted` event. -A core component of Unicorn Properties is the approvals workflow. The approvals workflow is implemented using an AWS Step Functions state machine. At a high level, the workflow will: +A Unicorn Properties agent initiates the workflow by requesting to approve a listing, generating a `PublicationApprovalRequested` event with property information. To decouple from the Contracts Service, the Approval service maintains a local copy of contract status by consuming the ContractStatusChanged event. -* Check whether or not it has any contract information for the property it needs to approve. If there is no contract information, the approval process cannot be completed. -* Ensure the sentiment of the property description is positive and that there no unsafe images. All checks must pass for the listing to be made public. -* Ensure that the contract is in an APPROVED state before it can approve the listing. This accounts for a situation where the property listings are created before the contract has been signed and the services for Unicorn Properties are paid for. -* Publish the result of the workflow via the `PublicationEvaluationCompleted` event. +The workflow checks the contract state. If the contract is in the WaitForContractApproval state, it updates the contract status for the property with its task token, triggering a DynamoDB stream event. The Property approvals sync function handles these events and passes the task token back to the state machine based on the contract state. -The workflow is initiated by a request made by an Unicorn Properties **agent** to have the property approved for publication. Once they have created a property listing (added property details and photos), they initiate the request in Unicorn Web, which generates a `PublicationApprovalRequested` event. This event contains the property information which the workflow processes. +If the workflow completes successfully, it emits a PublicationEvaluationCompleted event with an **approved** or **declined** evaluation result, which Unicorn Web listens to update its publication flag. -In order process the approvals workflow successfully, the approvals service needs to know the current status of a contract. To remain fully decoupled from the **Contracts Service**, it maintains a local copy of contract status by consuming the `ContractStatusChanged` event. This is eliminates the need for the Contracts service to expose an API that gives other services access to its database, and allows the Properties service to function autonomously. - -When the workflow is paused to check to see whether or not the contract is in an approved state, the `WaitForContractApproval` state will update a contract status for a specified property with its task token. This initiates a stream event on the DynamoDB table. The Property approvals sync function handles DynamoDB stream events. It determines whether or not to pass AWS Step Function task token back to the state machine based on the contract state. - -If workflow is completed successfully, it will emit a `PublicationEvaluationCompleted` event, with an evaluation result of `APPROVED` or `DECLINED`. This is what the Property Web will listen to in order to make the list available for publication. - -## Note: - -Upon deleting the CloudFormation stack for this service, check if the `ApprovalStateMachine` StepFunction doesn't have any executions in `RUNNING` state. If there are, cancel those execution prior to deleting the CloudFormation stack. +**Note:** Upon deleting the CloudFormation stack for this service, check if the `ApprovalStateMachine` StepFunction doesn't have any executions in `RUNNING` state. If there are, cancel those execution prior to deleting the CloudFormation stack. From 4a44175d2b326ec0ef5577eeb7fb85d156016b78 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Fri, 18 Jul 2025 10:53:49 +0800 Subject: [PATCH 15/34] chore: updated contracts readme --- unicorn_contracts/README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/unicorn_contracts/README.md b/unicorn_contracts/README.md index 1cfad3b..61fdd99 100644 --- a/unicorn_contracts/README.md +++ b/unicorn_contracts/README.md @@ -4,13 +4,22 @@ ## Architecture overview -Unicorn Contract manages the contractual relationship between the customers and the Unicorn Properties agency. It's primary function is to allow Unicorn Properties agents to create a new contract for a property listing, and to have the contract approved once it's ready. +The **Unicorn Contracts** service manages contractual relationships between customers and Unicorn Properties agency. The service handles standard terms and conditions, property service rates, fees, and additional services. -The architecture is fairly straight forward. An API exposes the create contract and update contract methods. This information is recorded in a Amazon DynamoDB table which will contain all latest information about the contract and it's status. +Each property can have only one active contract. Properties use their address as a unique identifier instead of a GUID, which correlates across services. -Each time a new contract is created or updated, Unicorn Contracts publishes a `ContractStatusChanged` event to Amazon EventBridge signalling changes to the contract status. These events are consumed by **Unicorn Properties**, so it can track changes to contracts, without needing to take a direct dependency on Unicorn Contracts and it's database. +For example: `usa/anytown/main-street/111`. -Here is an example of an event that is published to EventBridge: +The contract workflow operates as follows: + +1. Agents submit contract creation/update commands through the Contracts API +1. The API sends requests to Amazon SQS +1. A Contracts function processes the queue messages and updates Amazon DynamoDB +1. DynamoDB Streams captures contract changes +1. Amazon EventBridge Pipes transforms the DynamoDB records into ContractStatusChanged events +1. Unicorn Approvals consumes these events to track contract changes without direct database dependencies + +An example of `ContractStatusChanged` event: ```json { From 1bf88d106857101613018b9126877809e13bcce7 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Fri, 18 Jul 2025 10:58:40 +0800 Subject: [PATCH 16/34] chore: update web readme --- unicorn_web/README.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/unicorn_web/README.md b/unicorn_web/README.md index 9e70253..0bd021c 100644 --- a/unicorn_web/README.md +++ b/unicorn_web/README.md @@ -4,18 +4,15 @@ ## Architecture Overview -Unicorn Web is primarily responsible for allowing customers to search and view property listings. It also supports ability for agents to request approval for specific property. Those approval requests are sent to Property service for validation, before Properties table is updated with approval evaluation results. +Unicorn Web lets customers search for and view property listings. The Web API also allows Unicorn Properties agents to request approval for specific properties that they want to publish so they may be returned in customer searches results. These requests are sent to the Unicorn Approvals service for validation. -A core component of Unicorn Web are the Lambda functions which are responsible with completing API Gateway requests to: +Lambda functions handle API Gateway requests to: -- search approved property listings -This function interacts with DynamoDB table to retrieve property listings marked as `APPROVED`. The API Gateway implementation and lambda code support multiple types of search patterns, and allow searching by city, street, or house number. +- Search approved property listings: The **Search function** retrieves property listings marked as APPROVED from the DynamoDB table using multiple search patterns. -- request approval of property listing -This function sends an event to EventBridge requesting an approval for a property listing specified in the payload sent from client +- Request property listing approval: The **Approval function** sends an EventBridge event requesting approval for a property listing specified in the payload. -- publication approved function -There is also a lambda function responsible for receiving any "Approval Evaluation Completed" events from EventBridge. This function writes the evaluation result to DynamoDB table. +- Process approved listings: The **Publication Evaluation Event Handler function** processes `PublicationEvaluationCompleted` events from the Unicorn Approvals service and writes the evaluation result to the DynamoDB table. ### Testing the APIs @@ -24,10 +21,10 @@ export API=`aws cloudformation describe-stacks --stack-name uni-prop-local-web - curl --location --request POST "${API}request_approval" \ --header 'Content-Type: application/json' \ ---data-raw '{"property_id": "usa/anytown/main-street/111"}' +--data-raw '{"PropertyId": "usa/anytown/main-street/111"}' curl -X POST ${API_URL}request_approval \ -H 'Content-Type: application/json' \ - -d '{"property_id":"usa/anytown/main-street/111"}' | jq + -d '{"PropertyId":"usa/anytown/main-street/111"}' | jq ``` From 0aaf50187f01d98a6a41464bd8000bbb6b6afb0a Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Fri, 18 Jul 2025 11:45:09 +0800 Subject: [PATCH 17/34] chore: updated main readme --- README.md | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 580b489..1dd303f 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,32 @@ -AWS Serverless Developer Experience Workshop Reference Architecture +[![Build & Test Workflow](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build_test.yml/badge.svg)](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build_test.yml) + +< img src="./docs/workshop_logo.png" alt="AWS Serverless Developer Experience Workshop Reference Architecture" width="80%" /> # AWS Serverless Developer Experience workshop reference architecture (Java) -This repository contains the reference architecture for the AWS Serverless Developer Experience workshop. +This repository contains the Java reference architecture for the AWS Serverless Developer Experience workshop. -The AWS Serverless Developer Experience workshop provides you with an immersive experience as a serverless developer. The goal of this workshop is to provide you with hands-on experience building a serverless solution using the [**AWS Serverless Application Model (AWS SAM)**](https://aws.amazon.com/serverless/sam/) and **AWS SAM CLI**. +The AWS Serverless Developer Experience Workshop is a comprehensive, hands-on training program designed to equip developers with practical serverless development skills using the [**AWS Serverless Application Model (AWS SAM)**](https://aws.amazon.com/serverless/sam/) and **AWS SAM CLI**. -Along the way, you will learn about principals of distributed event-driven architectures, messaging patterns, orchestration, and observability and how to apply them in code. You will explore exciting open-source tools, the core features of Powertools for AWS Lambda, and simplified CI/CD deployments supported by AWS SAM Pipelines. +The workshop employs a practical, code-centric approach, emphasizing direct implementation and real-world scenario exploration to ensure you develop serverless development skills across several critical areas including distributed event-driven architectures, messaging patterns, orchestration, and observability. You will explore open-source tools, [Powertools for AWS](https://powertools.aws.dev/), and simplified CI/CD deployments with AWS SAM Pipelines. By the end, you will be familiar with serverless developer workflows, microservice composition using AWS SAM, serverless development best practices, and applied event-driven architectures. -At the end of this workshop, you will be familiar with Serverless developer workflows and microservice composition using AWS SAM, Serverless development best practices, and applied event-driven architectures. +The 6-8 hour workshop assumes your practical development skills in Python, TypeScript, Java, or .NET, and familiarity with [Amazon API Gateway](https://aws.amazon.com/apigateway/), [AWS Lambda](https://aws.amazon.com/lambda/), [Amazon EventBridge](https://aws.amazon.com/eventbridge/), [AWS Step Functions](https://aws.amazon.com/step-functions/), and [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). ## Introducing the Unicorn Properties architecture ![AWS Serverless Developer Experience Workshop Reference Architecture](./docs/architecture.png) -Our use case is based on a real estate company called **Unicorn Properties**. - -As a real estate agency, **Unicorn Properties** needs to manage the publishing of new property listings and sale contracts linked to individual properties, and provide a way for their customers to view approved property listings. - -To support their needs, Unicorn Properties have adopted a serverless, event-driven approach to designing their architecture. This architecture is centred around two primary domains: **Contracts** (managed by the Contracts Service) and **Properties** (managed by the Web and Properties Services). - -The **Unicorn Contracts** service (namespace: `Unicorn.Contracts`) is a simplified service that manages the contractual relationship between a seller of a property and Unicorn Properties. Contracts are drawn up that define the property for sale, the terms and conditions that Unicorn Properties sets, and how much it will cost the seller to engage the services of the agency. +Real estate company **Unicorn Properties** needs to manage publishing of new property listings and sale contracts linked to individual properties, and provide a way for customers to view approved listings. They adopted a serverless, event-driven architecture with two primary domains: **Contracts** (managed by Contracts Service) and **Properties** (managed by Web and Approvals Services). -The **Unicorn Web** (namespace: `Unicorn.Web`) manages the details of a property listing to be published on the Unicorn Properties website. Every property listing has an address, a sale price, a description of the property, and some photos that members of the public can look at to get them interested in purchasing the property. Only properties that have been approved for publication can be made visible to the public. +**Unicorn Contracts** (using the `Unicorn.Contracts` namespace) service manages contractual relationships between property sellers and Unicorn Approvals, defining properties for sale, terms, and engagement costs. -The **Unicorn Properties** service (namespace: `Unicorn.Properties`) approves a property listings. This service implements a workflow that checks for the existence of a contract, makes sure that the content and the images are safe to publish, and finally checks that the contract has been approved. We don’t want to publish a property until we have an approved contract! +**Unicorn Approvals** (using the `Unicorn.Approvals` namespace) service approves property listings by implementing a workflow that checks for contract existence, content and image safety, and contract approval before publishing. -Have a go at building this architecture yourself! Head over to the [Serverless Developer Experience Workshop](https://catalog.workshops.aws/serverless-developer-experience) for more details. +**Unicorn Web** (using the `Unicorn.Web` namespace) manages property listing details (address, sale price, description, photos) to be published on the website, with only approved listings visible to the public. ## Credits -Throughout this workshop we wanted to introduce you to some Open Source tools that can help you build serverless applications. This is not an exhaustive list, just a small selection of what we will be using in the workshop. +This workshop introduces you to some open-source tools that can help you build serverless applications. This is not an exhaustive list, but a small selection of what you will be using in the workshop. Many thanks to all the AWS teams and community builders who have contributed to this list: From d93ae357ebf24c763418cc654f1bfab2a0a5abff Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Fri, 18 Jul 2025 11:54:28 +0800 Subject: [PATCH 18/34] chore: update GitHub Actions workflows to use latest versions of actions and increase build timeout --- .github/workflows/auto_assign.yml | 2 +- .github/workflows/build.yml | 12 ++++++------ .github/workflows/codeql-analysis.yml | 12 ++++++------ .github/workflows/label_pr_on_title.yml | 4 ++-- .github/workflows/on_label_added.yml | 4 ++-- .github/workflows/on_merged_pr.yml | 4 ++-- .github/workflows/on_opened_pr.yml | 8 ++++---- .github/workflows/record_pr.yml | 4 ++-- .github/workflows/reusable_export_pr_details.yml | 4 ++-- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/auto_assign.yml b/.github/workflows/auto_assign.yml index 5728867..093e194 100644 --- a/.github/workflows/auto_assign.yml +++ b/.github/workflows/auto_assign.yml @@ -7,4 +7,4 @@ jobs: add-reviews: runs-on: ubuntu-latest steps: - - uses: kentaro-m/auto-assign-action@v1.2.5 \ No newline at end of file + - uses: kentaro-m/auto-assign-action@v2.0.0 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9f0ee04..36acbf8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,22 +21,22 @@ defaults: jobs: build: runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 strategy: max-parallel: 4 matrix: - # test against latest update of each major Java version, as well as specific updates of LTS versions: - java: [17] + # test against latest LTS Java versions + java: [17, 21] name: Java ${{ matrix.java }} env: JAVA: ${{ matrix.java }} AWS_REGION: us-west-2 steps: - - uses: actions/checkout@v3 - - name: Setup java + - uses: actions/checkout@v4 + - name: Setup Java uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'temurin' java-version: ${{ matrix.java }} cache: maven cache-dependency-path: '**/pom.xml' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a0a806a..61866de 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,24 +26,24 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 + java-version: 21 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@2ca79b6fa8d3ec278944088b4aa5f46912db5d63 #v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@2ca79b6fa8d3ec278944088b4aa5f46912db5d63 #v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -56,4 +56,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2ca79b6fa8d3ec278944088b4aa5f46912db5d63 #v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/label_pr_on_title.yml b/.github/workflows/label_pr_on_title.yml index 3815a49..e6ce47d 100644 --- a/.github/workflows/label_pr_on_title.yml +++ b/.github/workflows/label_pr_on_title.yml @@ -22,9 +22,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Label PR based on title" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} PR_TITLE: ${{ needs.get_pr_details.outputs.prTitle }} diff --git a/.github/workflows/on_label_added.yml b/.github/workflows/on_label_added.yml index e9180d8..ab04444 100644 --- a/.github/workflows/on_label_added.yml +++ b/.github/workflows/on_label_added.yml @@ -23,10 +23,10 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Maintenance: Persist state per PR as an artifact to avoid spam on label add - name: "Suggest split large Pull Request" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} PR_ACTION: ${{ needs.get_pr_details.outputs.prAction }} diff --git a/.github/workflows/on_merged_pr.yml b/.github/workflows/on_merged_pr.yml index cd97e1c..2bce046 100644 --- a/.github/workflows/on_merged_pr.yml +++ b/.github/workflows/on_merged_pr.yml @@ -20,9 +20,9 @@ jobs: runs-on: ubuntu-latest if: needs.get_pr_details.outputs.prIsMerged == 'true' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Label PR related issue for release" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} PR_BODY: ${{ needs.get_pr_details.outputs.prBody }} diff --git a/.github/workflows/on_opened_pr.yml b/.github/workflows/on_opened_pr.yml index 043ff96..9712a3f 100644 --- a/.github/workflows/on_opened_pr.yml +++ b/.github/workflows/on_opened_pr.yml @@ -19,9 +19,9 @@ jobs: needs: get_pr_details runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Ensure related issue is present" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_BODY: ${{ needs.get_pr_details.outputs.prBody }} PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} @@ -36,9 +36,9 @@ jobs: needs: get_pr_details runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Ensure acknowledgement section is present" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_BODY: ${{ needs.get_pr_details.outputs.prBody }} PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} diff --git a/.github/workflows/record_pr.yml b/.github/workflows/record_pr.yml index 70cbe15..7ef50e4 100644 --- a/.github/workflows/record_pr.yml +++ b/.github/workflows/record_pr.yml @@ -9,9 +9,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Extract PR details" - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const script = require('.github/scripts/save_pr_details.js') diff --git a/.github/workflows/reusable_export_pr_details.yml b/.github/workflows/reusable_export_pr_details.yml index c3b2aaf..e834e60 100644 --- a/.github/workflows/reusable_export_pr_details.yml +++ b/.github/workflows/reusable_export_pr_details.yml @@ -53,9 +53,9 @@ jobs: prIsMerged: ${{ steps.prIsMerged.outputs.prIsMerged }} steps: - name: Checkout repository # in case caller workflow doesn't checkout thus failing with file not found - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Download previously saved PR" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: WORKFLOW_ID: ${{ inputs.record_pr_workflow_id }} # For security, we only download artifacts tied to the successful PR recording workflow From 8edaf98ec5fed2a4902d498eaf0a570060470a55 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Fri, 18 Jul 2025 12:00:31 +0800 Subject: [PATCH 19/34] chore: downgrade Maven compiler version to 17 in Common module --- unicorn_web/Common/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unicorn_web/Common/pom.xml b/unicorn_web/Common/pom.xml index 7207ec0..c25865c 100644 --- a/unicorn_web/Common/pom.xml +++ b/unicorn_web/Common/pom.xml @@ -14,8 +14,8 @@ Common - 21 - 21 + 17 + 17 UTF-8 From 373616ca02487db5fc1ccccf9857aa600e1a9ecd Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Fri, 18 Jul 2025 12:05:03 +0800 Subject: [PATCH 20/34] chore: reorder sections in README and update image placement --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1dd303f..7892652 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -[![Build & Test Workflow](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build_test.yml/badge.svg)](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build_test.yml) +# AWS Serverless Developer Experience workshop reference architecture (Java) -< img src="./docs/workshop_logo.png" alt="AWS Serverless Developer Experience Workshop Reference Architecture" width="80%" /> +[![Build & Test Workflow](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build_test.yml/badge.svg)](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build_test.yml) -# AWS Serverless Developer Experience workshop reference architecture (Java) +AWS Serverless Developer Experience Workshop Reference Architecture This repository contains the Java reference architecture for the AWS Serverless Developer Experience workshop. From de4e8fb063eb368ede750ae3f150c2d5e579d3fb Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Fri, 18 Jul 2025 12:08:36 +0800 Subject: [PATCH 21/34] chore: update README badge link to reflect new GitHub Actions workflow --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7892652..b726591 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AWS Serverless Developer Experience workshop reference architecture (Java) -[![Build & Test Workflow](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build_test.yml/badge.svg)](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build_test.yml) +[![Build & Test Workflow](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build.yml/badge.svg)](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build.yml) AWS Serverless Developer Experience Workshop Reference Architecture From 18e1bec39b16a627036d8d4e31dfb1cac5687a9a Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Fri, 18 Jul 2025 12:22:34 +0800 Subject: [PATCH 22/34] chore: fix formatting in README by adjusting header and image placement --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b726591..1c2b059 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# AWS Serverless Developer Experience workshop reference architecture (Java) - [![Build & Test Workflow](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build.yml/badge.svg)](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-java/actions/workflows/build.yml) +# AWS Serverless Developer Experience workshop reference architecture (Java) + AWS Serverless Developer Experience Workshop Reference Architecture This repository contains the Java reference architecture for the AWS Serverless Developer Experience workshop. From f5f24f944cc7578064f8149ece5484db444f9ed1 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Mon, 21 Jul 2025 10:07:25 +0800 Subject: [PATCH 23/34] refactor: change package name from search.requestapproval to approval.requestapproval in RequestApprovalFunction.java --- .../java/approval/requestapproval/RequestApprovalFunction.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/RequestApprovalFunction.java b/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/RequestApprovalFunction.java index 648187f..18ed789 100644 --- a/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/RequestApprovalFunction.java +++ b/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/RequestApprovalFunction.java @@ -1,4 +1,4 @@ -package search.requestapproval; +package approval.requestapproval; import java.util.ArrayList; import java.util.Arrays; @@ -17,7 +17,6 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; From be2fc06dc507c320132f63c0a39a40e5d3b95836 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Tue, 22 Jul 2025 13:15:13 +0800 Subject: [PATCH 24/34] chore: update architecture diagram --- docs/architecture.png | Bin 188874 -> 110379 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/architecture.png b/docs/architecture.png index 1e741f218fc6bb6e938e76cbfc7eaf8f55712c5c..903363bbfa84e00a3ac13403654015d38ca6f2b8 100644 GIT binary patch literal 110379 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%0DP>+tnK!?mm-}}chU%?rY)A$|SFMO#k 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 +K=@VUVY_)Ysm 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 z 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�t&Qm-fq#0CnOW5FSGV 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
    |z3iUJKvw0qDb=Y+ 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%0Aw=*+abdg3#F7_Di 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 From a0f89680c750d3aded7b84c1cc57e70af67714f0 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Tue, 22 Jul 2025 14:02:10 +0800 Subject: [PATCH 25/34] refactor: update event source and rule names to use 'unicorn.approvals' --- .../eventbridge/publication_evaluation_completed_event.json | 2 +- .../test/events/put_event_publication_approval_requested.json | 2 +- .../eventbridge/put_event_publication_evaluation_completed.json | 2 +- .../eventbridge/put_event_publication_evaluation_completed.json | 2 +- .../eventbridge/put_event_publication_evaluation_completed.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_evaluation_completed_event.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_evaluation_completed_event.json index 6ffe791..57b0842 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_evaluation_completed_event.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_evaluation_completed_event.json @@ -2,7 +2,7 @@ "version": "0", "id": "f849f683-76e1-1c84-669d-544a9828dfef", "detail-type": "PublicationEvaluationCompleted", - "source": "unicorn.properties", + "source": "unicorn.approvals", "account": "123456789", "time": "2022-08-16T06:33:05Z", "region": "ap-southeast-2", diff --git a/unicorn_approvals/ApprovalsService/src/test/events/put_event_publication_approval_requested.json b/unicorn_approvals/ApprovalsService/src/test/events/put_event_publication_approval_requested.json index ee1026e..4910194 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/put_event_publication_approval_requested.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/put_event_publication_approval_requested.json @@ -1,7 +1,7 @@ [ { "EventBusName": "Dev-UnicornApprovalsEventBus", - "Source": "unicorn.properties.web", + "Source": "unicorn.approvals.web", "DetailType": "PublicationApprovalRequested", "Detail": "{\"property_id\": \"usa/anytown/main-street/123\",\"country\": \"USA\",\"city\": \"Anytown\",\"street\": \"Main Street\",\"number\": 123,\"description\": \"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\": \"sale\",\"listprice\": 200,\"currency\": \"SPL\",\"images\": [ \"usa/anytown/main-street-123-0d61b4e3\"]}" } diff --git a/unicorn_web/ApprovalService/tests/events/eventbridge/put_event_publication_evaluation_completed.json b/unicorn_web/ApprovalService/tests/events/eventbridge/put_event_publication_evaluation_completed.json index c4f3c7f..c022083 100644 --- a/unicorn_web/ApprovalService/tests/events/eventbridge/put_event_publication_evaluation_completed.json +++ b/unicorn_web/ApprovalService/tests/events/eventbridge/put_event_publication_evaluation_completed.json @@ -1,6 +1,6 @@ [ { - "Source": "unicorn.properties", + "Source": "unicorn.approvals", "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"evaluation_result\": \"APPROVED\"}", "DetailType": "PublicationEvaluationCompleted", "EventBusName": "UnicornWebBus-local" diff --git a/unicorn_web/SearchService/tests/events/eventbridge/put_event_publication_evaluation_completed.json b/unicorn_web/SearchService/tests/events/eventbridge/put_event_publication_evaluation_completed.json index c4f3c7f..c022083 100644 --- a/unicorn_web/SearchService/tests/events/eventbridge/put_event_publication_evaluation_completed.json +++ b/unicorn_web/SearchService/tests/events/eventbridge/put_event_publication_evaluation_completed.json @@ -1,6 +1,6 @@ [ { - "Source": "unicorn.properties", + "Source": "unicorn.approvals", "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"evaluation_result\": \"APPROVED\"}", "DetailType": "PublicationEvaluationCompleted", "EventBusName": "UnicornWebBus-local" diff --git a/unicorn_web/tests/events/eventbridge/put_event_publication_evaluation_completed.json b/unicorn_web/tests/events/eventbridge/put_event_publication_evaluation_completed.json index c4f3c7f..c022083 100644 --- a/unicorn_web/tests/events/eventbridge/put_event_publication_evaluation_completed.json +++ b/unicorn_web/tests/events/eventbridge/put_event_publication_evaluation_completed.json @@ -1,6 +1,6 @@ [ { - "Source": "unicorn.properties", + "Source": "unicorn.approvals", "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"evaluation_result\": \"APPROVED\"}", "DetailType": "PublicationEvaluationCompleted", "EventBusName": "UnicornWebBus-local" From d05b4ff158f16d7d56801c20de1ab1585827decf Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Tue, 22 Jul 2025 14:20:20 +0800 Subject: [PATCH 26/34] refactor: change package names from 'unicorn_properties' to 'unicorn_approvals' in related classes --- .../requestapproval/PublicationApprovedFunction.java | 6 +++--- .../publicationevaluationcompleted/AWSEvent.java | 4 ++-- .../PublicationEvaluationCompleted.java | 2 +- .../marshaller/Marshaller.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java b/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java index f9ff09c..fa2826f 100644 --- a/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java +++ b/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java @@ -22,9 +22,9 @@ import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.metrics.Metrics; import software.amazon.lambda.powertools.tracing.Tracing; -import schema.unicorn_properties.publicationevaluationcompleted.marshaller.Marshaller; -import schema.unicorn_properties.publicationevaluationcompleted.AWSEvent; -import schema.unicorn_properties.publicationevaluationcompleted.PublicationEvaluationCompleted; +import schema.unicorn_approvals.publicationevaluationcompleted.marshaller.Marshaller; +import schema.unicorn_approvals.publicationevaluationcompleted.AWSEvent; +import schema.unicorn_approvals.publicationevaluationcompleted.PublicationEvaluationCompleted; /** * Function checks for the existence of a contract status entry for a specified diff --git a/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java b/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java index af891fb..a7a070a 100644 --- a/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java +++ b/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java @@ -1,4 +1,4 @@ -package schema.unicorn_properties.publicationevaluationcompleted; +package schema.unicorn_approvals.publicationevaluationcompleted; import java.util.Objects; import com.fasterxml.jackson.annotation.JsonProperty; @@ -7,7 +7,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import schema.unicorn_properties.publicationevaluationcompleted.PublicationEvaluationCompleted; +import schema.unicorn_approvals.publicationevaluationcompleted.PublicationEvaluationCompleted; import java.io.Serializable; public class AWSEvent { diff --git a/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java b/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java index 01e5948..0b45c97 100644 --- a/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java +++ b/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java @@ -1,4 +1,4 @@ -package schema.unicorn_properties.publicationevaluationcompleted; +package schema.unicorn_approvals.publicationevaluationcompleted; import java.util.Objects; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java b/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java index 6a8395a..732ad60 100644 --- a/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java +++ b/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java @@ -1,9 +1,9 @@ -package schema.unicorn_properties.publicationevaluationcompleted.marshaller; +package schema.unicorn_approvals.publicationevaluationcompleted.marshaller; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; -import schema.unicorn_properties.publicationevaluationcompleted.AWSEvent; +import schema.unicorn_approvals.publicationevaluationcompleted.AWSEvent; import java.io.IOException; import java.io.InputStream; From c764c36a3524c2b8b43d3c91badbddb945563115 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Mon, 28 Jul 2025 09:50:02 +0800 Subject: [PATCH 27/34] refactor: rename ApprovalService to PublicationManagerService and removed classes in the ApprovalsService module as this does not need to be tracked. --- pom.xml | 2 +- .../ContractStatusChangedHandlerFunction.class | Bin 5333 -> 0 bytes .../ContractStatusNotFoundException.class | Bin 412 -> 0 bytes .../PropertiesApprovalSyncFunction.class | Bin 8040 -> 0 bytes .../WaitForContractApprovalFunction.class | Bin 7641 -> 0 bytes .../classes/properties/dao/ContractStatus.class | Bin 1919 -> 0 bytes .../ContractStatusChanged.class | Bin 3630 -> 0 bytes .../contractstatuschanged/Event.class | Bin 6259 -> 0 bytes .../marshaller/Marshaller.class | Bin 1774 -> 0 bytes .../properties/ContractStatusTests.class | Bin 2597 -> 0 bytes .../contracts/ContractEventHandler.class | Bin 10893 -> 0 bytes .../classes/contracts/utils/Address.class | Bin 1112 -> 0 bytes .../classes/contracts/utils/Contract.class | Bin 2386 -> 0 bytes .../utils/ContractStatusChangedEvent.class | Bin 1663 -> 0 bytes .../contracts/utils/ContractStatusEnum.class | Bin 1284 -> 0 bytes .../contracts/utils/ResponseParser.class | Bin 2298 -> 0 bytes .../contracts/CreateContractTests.class | Bin 1770 -> 0 bytes .../src/main/java/property/dao/Property.java | 1 - .../pom.xml | 8 +------- .../PublicationApprovedEventHandler.java} | 4 ++-- .../RequestApprovalFunction.java | 2 +- .../AWSEvent.java | 0 .../PublicationEvaluationCompleted.java | 0 .../marshaller/Marshaller.java | 0 .../src/main/resources/log4j2.xml | 0 .../target/classes/log4j2.xml | 14 ++++++++++++++ .../classes/publicationmanager/Address.class | Bin 0 -> 1114 bytes .../PublicationApprovedEventHandler.class | Bin 0 -> 6246 bytes .../publicationmanager/RequestApproval.class | Bin 0 -> 990 bytes .../RequestApprovalFunction.class | Bin 0 -> 14455 bytes .../AWSEvent.class | Bin 0 -> 5332 bytes .../PublicationEvaluationCompleted.class | Bin 0 -> 2039 bytes .../marshaller/Marshaller.class | Bin 0 -> 1882 bytes ..._event_publication_evaluation_completed.json | 0 .../{search => }/PropertySearchFunction.java | 2 +- unicorn_web/template.yaml | 10 +++++----- 36 files changed, 25 insertions(+), 18 deletions(-) delete mode 100644 unicorn_approvals/ApprovalsService/target/classes/properties/ContractStatusChangedHandlerFunction.class delete mode 100644 unicorn_approvals/ApprovalsService/target/classes/properties/ContractStatusNotFoundException.class delete mode 100644 unicorn_approvals/ApprovalsService/target/classes/properties/PropertiesApprovalSyncFunction.class delete mode 100644 unicorn_approvals/ApprovalsService/target/classes/properties/WaitForContractApprovalFunction.class delete mode 100644 unicorn_approvals/ApprovalsService/target/classes/properties/dao/ContractStatus.class delete mode 100644 unicorn_approvals/ApprovalsService/target/classes/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.class delete mode 100644 unicorn_approvals/ApprovalsService/target/classes/schema/unicorn_contracts/contractstatuschanged/Event.class delete mode 100644 unicorn_approvals/ApprovalsService/target/classes/schema/unicorn_contracts/contractstatuschanged/marshaller/Marshaller.class delete mode 100644 unicorn_approvals/ApprovalsService/target/test-classes/properties/ContractStatusTests.class delete mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/ContractEventHandler.class delete mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/utils/Address.class delete mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/utils/Contract.class delete mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/utils/ContractStatusChangedEvent.class delete mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/utils/ContractStatusEnum.class delete mode 100644 unicorn_contracts/ContractsService/target/classes/contracts/utils/ResponseParser.class delete mode 100644 unicorn_contracts/ContractsService/target/test-classes/contracts/CreateContractTests.class delete mode 100644 unicorn_web/PropertyFunctions/src/main/java/property/dao/Property.java rename unicorn_web/{ApprovalService => PublicationManagerService}/pom.xml (97%) rename unicorn_web/{ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java => PublicationManagerService/src/main/java/publicationmanager/PublicationApprovedEventHandler.java} (98%) rename unicorn_web/{ApprovalService/src/main/java/approval/requestapproval => PublicationManagerService/src/main/java/publicationmanager}/RequestApprovalFunction.java (99%) rename unicorn_web/{ApprovalService => PublicationManagerService}/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java (100%) rename unicorn_web/{ApprovalService => PublicationManagerService}/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java (100%) rename unicorn_web/{ApprovalService => PublicationManagerService}/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java (100%) rename unicorn_web/{ApprovalService => PublicationManagerService}/src/main/resources/log4j2.xml (100%) create mode 100644 unicorn_web/PublicationManagerService/target/classes/log4j2.xml create mode 100644 unicorn_web/PublicationManagerService/target/classes/publicationmanager/Address.class create mode 100644 unicorn_web/PublicationManagerService/target/classes/publicationmanager/PublicationApprovedEventHandler.class create mode 100644 unicorn_web/PublicationManagerService/target/classes/publicationmanager/RequestApproval.class create mode 100644 unicorn_web/PublicationManagerService/target/classes/publicationmanager/RequestApprovalFunction.class create mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.class create mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.class create mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.class rename unicorn_web/{ApprovalService => PublicationManagerService}/tests/events/eventbridge/put_event_publication_evaluation_completed.json (100%) rename unicorn_web/SearchService/src/main/java/search/{search => }/PropertySearchFunction.java (99%) diff --git a/pom.xml b/pom.xml index c920fc5..8063031 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ unicorn_contracts/ContractsService unicorn_approvals/ApprovalsService - unicorn_web/ApprovalService + unicorn_web/PublicationManagerService unicorn_web/SearchService unicorn_web/Common diff --git a/unicorn_approvals/ApprovalsService/target/classes/properties/ContractStatusChangedHandlerFunction.class b/unicorn_approvals/ApprovalsService/target/classes/properties/ContractStatusChangedHandlerFunction.class deleted file mode 100644 index c6a5431a2c442ec67b45f1fe1b364e0aeda0a770..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5333 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 B%G&?{ diff --git a/unicorn_approvals/ApprovalsService/target/classes/properties/WaitForContractApprovalFunction.class b/unicorn_approvals/ApprovalsService/target/classes/properties/WaitForContractApprovalFunction.class deleted file mode 100644 index 1fa49522d0902ded5dd136dc17517de74a734578..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7641 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 E9}HZXGynhq diff --git a/unicorn_approvals/ApprovalsService/target/classes/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.class b/unicorn_approvals/ApprovalsService/target/classes/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.class deleted file mode 100644 index fd6e30fdc5d7f703f88bd026a4643c3b28c99912..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3630 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 diff --git a/unicorn_approvals/ApprovalsService/target/test-classes/properties/ContractStatusTests.class b/unicorn_approvals/ApprovalsService/target/test-classes/properties/ContractStatusTests.class deleted file mode 100644 index 97faffea7b2a2c58cece2396df593aa969f771b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2597 zcmbVOT~icC6g>@#i|eSkMr9QgO)xA9qtS@6NQCtZT_6F3ph?W^Yy$(cGwXbynEa_! z1+YR@D$n^rc}(T>%rZMXSQUKOp5C6@_ug~w>C^M~zkmD*U<|+J(1!LbG8#H?M4)Tk z*fez8a8~u%>bf)ofg@v^?E}TdEII{FMMhy@+4@t%Uz;`>Ib_j(6vv=taa_X* zoD{g&@Z5&<0!#XO*>wWXFoXHP2tt2B`hhRd=}EugI=&P*S!ubXyHXaX1cpqvt{ZjZ zz3UinechMdre)HZZPcqZL-#@_uoW^HaoY8O==LAl>-l|@A%?&GX_38L2 zkWniF{R45l2xr+ge7{sot;EPn3Qs}9d0Y_4nnb`T0(#|8X%R}Vz=-dz1aA#bHu2~L zqOR<4L2QLs8LmNI=`Sdu-XV6S}}H-!t`qoYLhcYnBjXKh4*E#JbmJ3~zYdSD>!r zrsDip0%y`}U^vTJEfp6P?Asc?#vKygmqFZ^TC*_%V*{Vu26c??3Y<$LnQ~HCM=+{# zD=~62H`YbrZRT}f!#KVXI8H=t)tr12628EXE$fuOQcP{SNLRFUNFJ-CC)oV`q?0zK zBB?HwH9W*4fn$N2nDF2Y6kko|@BmK~u%`kY1Ft5P%V!5PC$7G-t7w>3ZZvkkHeuV! zm!HEFo^;|{9`E_kq!#!qq1`vvl#x!%F>KOxtES8sG`zqf#YU|gmP6gSknT6x>&>hB zIVP+7GKcT6oW)BGukZs?Q!26^QvdV`p8~`h=cpE(EO;Sx>ulOplR7Vp40m+iT6L&( z9*wGT{Ug1kxQ(Thrn%++Gl?1@ZG9pjORHfZ7Y#d_M?}nZ!)iDt`k!*;L4KJ@11-NivImKM2(rGzt2epP*S7MfgJ7}Aa4I=o>9%d2l zSA{2SYBp7so=S^G>tVwRq^BDVOII~!;M{yF@$T{gagEwAtZFTAKIMj&pF=M!_mUN? zsXnogijh|%qy397rf&24d-j4n<_>Nw;WBTQHhuzR_~Q-75$c+Wo>ckh$!i=G70%o6 z6WTfFHv-4kTp5r416_l=5QBTjF7M*#Hgdm3)<1JpUlu@t5&B?org10jQH9B;K#jiI zBVW&HsooU^^Iad1{|jB)=pM}XMDw0)Tx>Z#!7V^Pon1vY(`-2|G#yL6&(Pfp);Lq? zT6_wuC*l6#`y*WQnD!N>{lzx=_fX{QT4fK{m-EBBxG|lMKlnTP`MbD-TYDH;zP^il zGiq(<8prM(jP2pU^0ggI?&9%iW)D-#h0G3~?_y@OBR^Z{$bYwk`43oX;TUH5fgz5= z{BF_FiyPF=o4AczxX(-H8SY>SBapa@HyDj@j>lOyah_)BHeeH3FBPs1hecF7T{Kvu pCARbpJR+4i&hIgwt=q3mkW^-qIke$zG`@>gkI~;2fA3BF@*kkd%w+%o diff --git a/unicorn_contracts/ContractsService/target/classes/contracts/ContractEventHandler.class b/unicorn_contracts/ContractsService/target/classes/contracts/ContractEventHandler.class deleted file mode 100644 index fac6aa5f88386505b69f576fa3fc6eb77a957527..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10893 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 diff --git a/unicorn_contracts/ContractsService/target/classes/contracts/utils/ContractStatusEnum.class b/unicorn_contracts/ContractsService/target/classes/contracts/utils/ContractStatusEnum.class deleted file mode 100644 index 9be6693b267b10eab9f96859d2b01099a514dfe1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1284 zcma)5?@!ZE6g_Wk*RB?r1NkK=4v-C$i2@><$TCJGscfORxcI4z3MHjWx{lBONjefC z8V#TQqm1{x%{YU`HED0p?LFtcd*A8zAD_Pg$l-;G2t%?~_rt)hg^f%zbiGEV(7{${ zhs{ROZ=S}$ARAB+Wl*;E^4t3iGv@z!Sx(dp#5hsQm&<#lN^y%PZx}FmGFHg%7K*0X z&h{G^;90f!ro6M)&fPSS;JIkQELrW;kbzqmrm~qz-h5rO7-rx0vKt+v7*jE9U>vup zlbToe9fqM)+B~t(?2KpoN14*WiBk)+Iwo;P#a#naxW~}{yMPab);_bnrc-*yu#)Ox z7d*Ss$aW>HFmU~&Z1)OWKPU?n`to2rP4i3g4X;+kqh-h8p43-`p$0i^uP)B?E{Z8Yk%2Hx|cCCB+Pov z_9}MZa;c7?SN`RC!kj3@Ytmgv)m(}d>9Z_N~J60qg>7F z%xkqvi%&Jz%l0<147ycs1~q5f<&BP9H~b<$?#(5d@g)5z(akaLeSjiaSwwQLh~!rh z$)iAgoEwuE3e`PAhWJZ_k`(!+w=nn#BUc#xK#1WnJ)`2AMD@orYFdgU%{$U3zAipTTrd4(xT~Bqb>AVRkeka~0pgcYptAH{H1mK@m$g&77v>{@5fR{i6J$UD=Ww_Oc<^cc-#nly%AWpuNR_k6^eA$ z&oFDaHGPZwf$$#HY<-6tyT0q_CJ%Vka?Hs28n4%dr$R+4g8>GGo2DmxzaK+5p&+Ya z7$XeFLr^l5P8I}YTM(}EGsSMO`P^ZVf-#00zPlAX!7@0}NDUqtPomX&@VZ57}p zZWLP47>1KxLKBJXGDCkGnMI%!T+#3b-lXy;ysVlGqe%x5Y0||thRL=IVlJpqF|932 zm=)KN4zFv-OIVW$hegs`i_|up3~n$Cww?F^4;sFTxABgGWl7LYhO_Z;_w6r*+AB_@ z#&GQ@jE727i2CNhGNkX8h85gqIFZ2FQen7oxWGqvk#xLAjMhDu8Z6kav>3gk;r*D= z&FE;=%wUaFePP?et56%zeOIudVKY`Og-7djqTnvWscr)93cv0;zS!g*H55a-KuttE z7A;4tH)>Vk-Q!i8B-x^CaJ$SsOMXYfbnw8UraFCyolduTx(Ia{AQsb*iAato19eI_ zw3r)d{Z&JF`v=y8tww7bGi1Cd5r(N3dD7f)G`6M}!#wH!>tRPgCJ*_J+LzA8IvLup z$1GJl(Jm&YL_9bC(~Qe6QFL|Xks<1VMa5J=^;-Cc#O}~cmn_iyYg;OynQ{4OL&K#B zERE>$(bV;mb~G4E*0w_p=TYQuyGr|i8nLfa&^37NhrIE8|ts#?UceKqKYN~WXOL<22tbiHFN$T=VLI3wU$gxnL5?Kg75z~=-OCkPhQZf`TQU#U3k z>$HNiDBE!7eJtSu!)@gU0!s|Hmz&p^Hm&WuKHkH!!0}9*ymVnvI)RPafpb>xSm&!g zYN!hw&N1E9KaE%wft}huGodZhOm6Ki8*|L}8P6t(AmY;AlIF^|zxz zSEu(>x6hqwI|)?3qteK(i_Iys74Z_CpUmW|w~TJ5nZEl&fy(WjKo3on#F>W=dEv)5 zdhbMF{%(F- z?=zMEuYkZ>+ct3yus0e;MyGP9QZ>lr_f}|bM9+0-8<5bi?=z6G{VLTs6*4b5A(Q-w z6GKw33ffC^kG9=hB-bbIT)EQwiU^K3|i%4RZ31N~)W>pSa zi_>F=M;xLBI$OjXmL?p}7mnYjqba)k03UH?0etMjeS%LJ#$w%}56f#W+MUMlIQ!#E zEbL;j@e3B$Uf>-oFLF5j0xJ*3@lTO6u#8!(Fbk_VXP*TAEWxGRYk`CQaT%X6{w(um mb5?SQth_?)tCY=*|6rd7)GFZ`_e!|#bPmz#3x3T2H~s<4K;~Zn diff --git a/unicorn_web/PropertyFunctions/src/main/java/property/dao/Property.java b/unicorn_web/PropertyFunctions/src/main/java/property/dao/Property.java deleted file mode 100644 index 0519ecb..0000000 --- a/unicorn_web/PropertyFunctions/src/main/java/property/dao/Property.java +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/unicorn_web/ApprovalService/pom.xml b/unicorn_web/PublicationManagerService/pom.xml similarity index 97% rename from unicorn_web/ApprovalService/pom.xml rename to unicorn_web/PublicationManagerService/pom.xml index d0709f2..9ec828c 100644 --- a/unicorn_web/ApprovalService/pom.xml +++ b/unicorn_web/PublicationManagerService/pom.xml @@ -2,7 +2,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 approval - ApprovalService + PublicationManagerService 1.0 jar Unicorn Web Approval Service module @@ -58,12 +58,6 @@ aws-embedded-metrics 4.2.0 - - software.amazon.awssdk lambda diff --git a/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationApprovedEventHandler.java similarity index 98% rename from unicorn_web/ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java rename to unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationApprovedEventHandler.java index fa2826f..ef882de 100644 --- a/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/PublicationApprovedFunction.java +++ b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationApprovedEventHandler.java @@ -1,4 +1,4 @@ -package approval.requestapproval; +package publicationmanager; import java.io.IOException; import java.io.InputStream; @@ -32,7 +32,7 @@ * If an entry exists, pause the workflow, and update the record with task * token. */ -public class PublicationApprovedFunction { +public class PublicationApprovedEventHandler { Logger logger = LogManager.getLogger(); diff --git a/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/RequestApprovalFunction.java b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/RequestApprovalFunction.java similarity index 99% rename from unicorn_web/ApprovalService/src/main/java/approval/requestapproval/RequestApprovalFunction.java rename to unicorn_web/PublicationManagerService/src/main/java/publicationmanager/RequestApprovalFunction.java index 18ed789..ed954fe 100644 --- a/unicorn_web/ApprovalService/src/main/java/approval/requestapproval/RequestApprovalFunction.java +++ b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/RequestApprovalFunction.java @@ -1,4 +1,4 @@ -package approval.requestapproval; +package publicationmanager; import java.util.ArrayList; import java.util.Arrays; diff --git a/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java b/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java similarity index 100% rename from unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java rename to unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java diff --git a/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java b/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java similarity index 100% rename from unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java rename to unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java diff --git a/unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java b/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java similarity index 100% rename from unicorn_web/ApprovalService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java rename to unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java diff --git a/unicorn_web/ApprovalService/src/main/resources/log4j2.xml b/unicorn_web/PublicationManagerService/src/main/resources/log4j2.xml similarity index 100% rename from unicorn_web/ApprovalService/src/main/resources/log4j2.xml rename to unicorn_web/PublicationManagerService/src/main/resources/log4j2.xml diff --git a/unicorn_web/PublicationManagerService/target/classes/log4j2.xml b/unicorn_web/PublicationManagerService/target/classes/log4j2.xml new file mode 100644 index 0000000..e4b9c1a --- /dev/null +++ b/unicorn_web/PublicationManagerService/target/classes/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/unicorn_web/PublicationManagerService/target/classes/publicationmanager/Address.class b/unicorn_web/PublicationManagerService/target/classes/publicationmanager/Address.class new file mode 100644 index 0000000000000000000000000000000000000000..a3589f574f05b94ea890af1e28e7206a346a0d61 GIT binary patch literal 1114 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 literal 0 HcmV?d00001 diff --git a/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.class b/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.class new file mode 100644 index 0000000000000000000000000000000000000000..ed56b0144b7e0bd58e9aeab4db63e65eefceeec8 GIT binary patch literal 2039 zcmcJPTTc@~6vxk0Xv@+Hlp+^F7f@_byWXAzO%yRHX_RoO!51>!j_u&?Ox>L&;1@H| zMBjb#6B*BLyR?z9*aw>IbZ6%5@BGhY=I8IP-vQtmJWfG^z&qb=GM8F`;I{O{Vat;( z<|)p6s}B_t6{?C7*f2^De+2EjqIQ@c zIEp~7(sj72JT4k*1ZH;vp}5Pc+~?@%g%DCjq4)%>3dUo7puS?>iR)NL)c)v8VbL=q zYYXSw?WCL{Z8!`S)lPK1A9@}4MkjfvbmR#q8tLFr+6pB>>LmRTrQZPec ztgCp_^Q4!CIRd}-1u~^`%<5*CaPCBcO+;b@?o-nk*=sVh&TNNzXwyR3w86}!b}>tF zF=-3GK*4^ge=V2Hx@5kIi7{QO?53%Z&Q4lbu|D)pfY>S*;SB?(VLk(SSRgP}IA5hO zCu0>Z*qgwuF>kNk5xI)AsZ*ucB^Y*RaY!{eR_>s3NsWs&c6ACb&0iXB5m>yi;shoe zOm$CM-1GTD@q9kf*w-0n5wYEA=CDb#Xw^GUvQGiF%U?xmpWU_Mdjjbu^_yYyMx9*7 z1tr@j4G*M}Wl?7WdnR65>CI$}(aAM-W#HNDH4krs<^ESoDLgW;2q*!cVI1cm1!;UA z!J8og20j_c;`}PkNAZ6QpK%{1a3iPNn2fCQFoky4@S7(Q8Nm1k(+9cPFL3=wM81Jz z5^eDQftxsHqIUyKR1>acUVkifMzVOc}SX$iMsrH2rD`Zr-lOSl6IJ%s)m=Cy>} z08yA)LScX?RNn0J2Wj@c;k- literal 0 HcmV?d00001 diff --git a/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.class b/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.class new file mode 100644 index 0000000000000000000000000000000000000000..efaeea33a61bf3c58021225832692d98e3f59e2b GIT binary patch literal 1882 zcmb_cTTc@~6h2c6+pd)&7sU%Y;H?GeCdMa}F=R!Cz*g ziN5=zjAwUyA)!=E=)>%po;lz7zB6ae??2yv0>C@iupmR=P`h33GbfV54V3&8svzXb z2(FzlYI(wCMg)@gm=`6M8~CBe4R1R>Q@YDMk1OY3Xj+gZFnh*&%<-7)I7h8B?ivEq z2Rp~dwG#pxbwqGJGi|s!_dVx~xnFc3oi;P9C1krFalk^PQN_S5A%)o{aH~{4C6L_> z+T4QM1d5|1HKhWTgFA%y@1^28@Or#WW2VAONK;hR^0==zshw|jIc;;-V+s$2n4Wbw zT}{hS8I%Fjs{lpo%H%3kMpam)?SN}414Dgg+%7ekh@Xe3e?p>Bz5gRqDnj^SpbWi= zIdz$g$+S2f)Wpv3d}!2qT$(XXY68lr`5N-;9<#xQVgW3eC9qhkk1nKPl#rcjd|}19 zko++6TU<36-T{HRdf+nelqnJK`+sIlS7-vq^_y)+Y#^(DLj>0Q6h+`1MJ9|4(&Wsq zULc;fYeL+vox41Yn_pY-fWUkr4`cU_h8_a>hUiFUB89@`A?fXE8CL=`Lq_!kNm+y)AtrSO- zwG#KA1?vQI1L6b<2SRInYE68#8ddvZBl6=)aIQ!oz;xXZ&L+{4KGXlK#0gy?b9qXcseJsI4mn2*O`&L)`g znLIp!r(=-lHIWuBAg#da7^G)oke-j>v@}Gr;U&C6RdIb{E%EPaEz85W43zL>2Fman Jzj0mG{sN@E6ZQZA literal 0 HcmV?d00001 diff --git a/unicorn_web/ApprovalService/tests/events/eventbridge/put_event_publication_evaluation_completed.json b/unicorn_web/PublicationManagerService/tests/events/eventbridge/put_event_publication_evaluation_completed.json similarity index 100% rename from unicorn_web/ApprovalService/tests/events/eventbridge/put_event_publication_evaluation_completed.json rename to unicorn_web/PublicationManagerService/tests/events/eventbridge/put_event_publication_evaluation_completed.json diff --git a/unicorn_web/SearchService/src/main/java/search/search/PropertySearchFunction.java b/unicorn_web/SearchService/src/main/java/search/PropertySearchFunction.java similarity index 99% rename from unicorn_web/SearchService/src/main/java/search/search/PropertySearchFunction.java rename to unicorn_web/SearchService/src/main/java/search/PropertySearchFunction.java index 2c44495..4191570 100644 --- a/unicorn_web/SearchService/src/main/java/search/search/PropertySearchFunction.java +++ b/unicorn_web/SearchService/src/main/java/search/PropertySearchFunction.java @@ -1,4 +1,4 @@ -package search.search; +package search; import java.util.ArrayList; import java.util.HashMap; diff --git a/unicorn_web/template.yaml b/unicorn_web/template.yaml index a9872a9..7fc334e 100644 --- a/unicorn_web/template.yaml +++ b/unicorn_web/template.yaml @@ -90,7 +90,7 @@ Resources: SearchFunction: Type: AWS::Serverless::Function Properties: - CodeUri: PropertyFunctions + CodeUri: SearchService Handler: search.search.PropertySearchFunction::handleRequest Policies: - DynamoDBReadPolicy: @@ -109,8 +109,8 @@ Resources: RequestApprovalFunction: Type: AWS::Serverless::Function Properties: - CodeUri: PropertyFunctions - Handler: search.requestapproval.RequestApprovalFunction::handleRequest + CodeUri: PublicationManagerService + Handler: publicationmanager.RequestApprovalFunction::handleRequest Policies: - EventBridgePutEventsPolicy: EventBusName: !Ref UnicornWebEventBus @@ -139,8 +139,8 @@ Resources: PublicationApprovedEventHandlerFunction: Type: AWS::Serverless::Function Properties: - CodeUri: PropertyFunctions - Handler: search.requestapproval.PublicationApprovedFunction::handleRequest + CodeUri: PublicationManagerService + Handler: publicationmanager.PublicationApprovedEventHandler::handleRequest Policies: - DynamoDBCrudPolicy: TableName: !Ref WebTable From 6a635706bd9c26effdb3814129027963cc880e50 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 31 Jul 2025 11:27:50 +0800 Subject: [PATCH 28/34] chore: minor update to readme file --- unicorn_web/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unicorn_web/README.md b/unicorn_web/README.md index 0bd021c..38e17ea 100644 --- a/unicorn_web/README.md +++ b/unicorn_web/README.md @@ -10,7 +10,7 @@ Lambda functions handle API Gateway requests to: - Search approved property listings: The **Search function** retrieves property listings marked as APPROVED from the DynamoDB table using multiple search patterns. -- Request property listing approval: The **Approval function** sends an EventBridge event requesting approval for a property listing specified in the payload. +- Request property listing approval: The **Request Approval function** sends an EventBridge event requesting approval for a property listing specified in the payload. - Process approved listings: The **Publication Evaluation Event Handler function** processes `PublicationEvaluationCompleted` events from the Unicorn Approvals service and writes the evaluation result to the DynamoDB table. From 9abc323b7edad3cd43815bd73dc551426f49711c Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 31 Jul 2025 11:31:03 +0800 Subject: [PATCH 29/34] fix: update namespace for event schema --- .../AWSEvent.java | 1 - .../PublicationEvaluationCompleted.java | 0 .../marshaller/Marshaller.java | 0 .../PublicationApprovedEventHandler.class | Bin 6246 -> 7567 bytes .../AWSEvent.class | Bin 0 -> 6635 bytes .../PublicationEvaluationCompleted.class | Bin 0 -> 2724 bytes .../marshaller/Marshaller.class | Bin 0 -> 2726 bytes .../AWSEvent.class | Bin 5332 -> 0 bytes .../PublicationEvaluationCompleted.class | Bin 2039 -> 0 bytes .../marshaller/Marshaller.class | Bin 1882 -> 0 bytes 10 files changed, 1 deletion(-) rename unicorn_web/PublicationManagerService/src/main/java/schema/{unicorn_properties => unicorn_approvals}/publicationevaluationcompleted/AWSEvent.java (98%) rename unicorn_web/PublicationManagerService/src/main/java/schema/{unicorn_properties => unicorn_approvals}/publicationevaluationcompleted/PublicationEvaluationCompleted.java (100%) rename unicorn_web/PublicationManagerService/src/main/java/schema/{unicorn_properties => unicorn_approvals}/publicationevaluationcompleted/marshaller/Marshaller.java (100%) create mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_approvals/publicationevaluationcompleted/AWSEvent.class create mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_approvals/publicationevaluationcompleted/PublicationEvaluationCompleted.class create mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_approvals/publicationevaluationcompleted/marshaller/Marshaller.class delete mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.class delete mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.class delete mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.class diff --git a/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java b/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_approvals/publicationevaluationcompleted/AWSEvent.java similarity index 98% rename from unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java rename to unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_approvals/publicationevaluationcompleted/AWSEvent.java index a7a070a..0f2634c 100644 --- a/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.java +++ b/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_approvals/publicationevaluationcompleted/AWSEvent.java @@ -7,7 +7,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import schema.unicorn_approvals.publicationevaluationcompleted.PublicationEvaluationCompleted; import java.io.Serializable; public class AWSEvent { diff --git a/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java b/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_approvals/publicationevaluationcompleted/PublicationEvaluationCompleted.java similarity index 100% rename from unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.java rename to unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_approvals/publicationevaluationcompleted/PublicationEvaluationCompleted.java diff --git a/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java b/unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_approvals/publicationevaluationcompleted/marshaller/Marshaller.java similarity index 100% rename from unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.java rename to unicorn_web/PublicationManagerService/src/main/java/schema/unicorn_approvals/publicationevaluationcompleted/marshaller/Marshaller.java diff --git a/unicorn_web/PublicationManagerService/target/classes/publicationmanager/PublicationApprovedEventHandler.class b/unicorn_web/PublicationManagerService/target/classes/publicationmanager/PublicationApprovedEventHandler.class index 38349e47e67b0254fe07eb4cf51c845a83ebcde2..bab0b47bd2967fdb600df4f3b51369691805d72c 100644 GIT binary patch delta 2621 zcmbtV33OCv75?sI=DnHs{%jKxcr*>6EZLbzs|RMFhSF5gq^Sji9INm$d60>-@!lJj zs!Ut0v~E;?m#V16r7m@Wrk#+W1s6o4QtMLpwOXrE1@~Ipdf%G>$N#`%cmMw` z|GnS;-8&C=?@MS?Pfp$kU^R6d()MX92c6`Il`&(5Y$}*TgrlN*CY!%xH{m(W|#qNbt_H$Q0Q&rIn12? zIl?bvxv}-GnH@Im;kDkrXt|ESTd|K2dg+F%Hm~5Y<=2_%;+tA7<9Z9;CQysF6D~df ziZky|JiT|@U>NTpENiiSAX?(hE$e#=$)x2tTMOy*?&dcc?=qaY-G>+^ipA)HnEsq^*;4yh! zBv$0OR+;cu{Qrz5Xx!4%JR{VcZ{{+qyfkN!gwR#&y8u!p2#ODvO=$d|1(tcmb`5>G zq?sNx?UXr`wl?g}TN^xquzz-y#S0iiXM1hc><||a;hOVvZo1*5u8a1><%uv35h$i% z4EL(Ihq3cJVg?%oN*I^(#q*R2fk_;ZgJNTN44)Uc4@c#_V#(4md_mxgUPtS0t}nOK zve%l973EHz&~O|lRD6ZdDa}xu?5R|huM$?Yw#f&ox@2RO9+|>7RD7LqnQvfJ=^=q{ z;-oxLxnl);qYN0%Y->CSt zysdh1#l!fm9Iw8NekV^=FMPotyy}-|cm$7nJ-%PV0RAL!+H0m-o*ZWf-PMK6kT-`0 z@MnR)c-jbSd8}}^hVeH!P_ugRKLq~i;Z-|hK$iV93C7NScCP4Y~1F8itSRNb5qEhVgyXDS-y(O|QpQmgka<3B@~V!Q^NJ}(D}`txYBMYMyP}vkKpD_q=%(oKIFFj8&+|YFvWi^U;nq zxD;2Q2W!!XEAbLsg;%oqb>+0M8C!VA3E~bM;1HhlA$%ACU-}3>$|1ve5$?ptxD+q! z$9W&(;NAEHOA1F{fKTEssC-8hpTej4^fT-&K*ed-&$4U4`>W!0E2|W~Xd8d)0GeW* zV0pYVda#7MukNZ?9uIZ}m&Ze0q2+Ol#9^SOxu&ax&y{eD5m5|v5?`8i z7&(kDd+2=4h?H=DX$aqrYwh>o<%Twa?*+I&e|!uBaox~Q;9@>+jQ*l?0>7%j5j?V~ za}vMjG;r#up&M!me>C)t63+N$f9){z68=7c$Aa+V=@WRuk0(QOw--GiJm|)KZ~_d) zK_Ut;bOLvg2VjsJ;Z6#&6hZ?IP>3Z37cuD-mQ)6FFR3hr(F>crZv-2$g(9Ru!zlVm zCjp%*`IauwQ@9mE1-p##Z@WrWWoh<$T;<+kmQXdX0ji-|{?6h3ToBFk*ZKb1NKLG1 iM~D{CLRtiav*@A4N`xoKaas(6mQahgr#8gA?|%T4_3h#S delta 1319 zcmaizdu$X{6vlsd+ufO+orG=awrpDJ3KlICS_~$(1p<;L8bpjJgb=U;OiIair=6V^ z*H;lAh@cg(;EO8wKm`>L+|nWxR6y$s---%~iZ6Vi|EqV0M1mnPnVkE5_q+GZx!*mv ztNwV6G4NO49$*50ZZo=!#zBj%4y!t8rJAZ6T-R}RS_Q|hNx62~Np{!^6APS{*5;(; zH9M(9tLw~9+AZn2gb|)RYeqwdo$?Y3tW?VJ67%iEzk56gGvzXKQnk(!fv7gfaD&G6g6Qe< z&hXskRFlb#+$0F5Ta(S699vW@_eF<n4qj9&ON?O56bE=&&siI48@ddK6ur|CJsd1lRTuzoN z3nM|3`&lDv3mb>6<^hujStAIi9oL&_CEIO-)jVuc&m)4GT#7MfeS~fv(|A-+udq9}~YD?XW$$B=(iv~A^pWrEtCj}$q+SrIeCL0+q$Y1Oy4W{#wNj7J1LE84Nbe&e)^)gqqx6HSF zb*D*hP7Nz{hG%*0X`Nm2K+%NrcAMJ%NK=pTXxMH{vcjbA9nGmbp+)pIg-(K5Tx>7lJ(u>f;b45@~ITIQ)}`q@ zb;igY%!wPltP7~id3-O^>mu>UL58aNvcjjUdU-aFU2K_M)yFpIVS9f(64$cqh(|_c z>G7BKjfzLIF<$PaKOa9~q?cFygmO$-TjxP^zxWqU`_3$?G zmO?>_*}&Thg(zhm?-BZ$3Xsb#D9z30+<> diff --git a/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_approvals/publicationevaluationcompleted/AWSEvent.class b/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_approvals/publicationevaluationcompleted/AWSEvent.class new file mode 100644 index 0000000000000000000000000000000000000000..05fd90dda9362d9a6d0074c5a0e84e77ba82e463 GIT binary patch literal 6635 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 literal 0 HcmV?d00001 diff --git a/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.class b/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.class deleted file mode 100644 index e5a29702c26311ec1cf263123b272e009b280c6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5332 zcmcJR&vV;E6vv<3#Eu<@BuKQVkIS+blMv2xNw9PPf+dqA=9`-=^EpzZb(!poir{>lr8IZQ{`=&Npzu7!OZ2Iw#oG- ztLlz^V6ZjQw79pWBau?VzRFjs!j@7m`hFvFo$J<;tl#=dnEKcb)MCPxMXvp=qcio1{ z8P=t3R)=m9WsaEb_)n^)SqKrv72VYNDgrMqR6$v{YAi!9NHpFRYSXqYdxTz+r0lNg z`eYojn(V5t8=fjMG6k8M&XSzVRvJvMG0jkIENVdBiY7D(wIRA(D-PM~d%jWWb`6z2%Zsh|e2hrzpbcN(RU7|UO zri+oP>g3WDbG)RR?0&m>!0d_&ecYs5nrc*4TX)Bu$syj*k>T~ynadx^p6T5Ib=2AA z?%DAxI2)_jV=o%7f5es-&W;CpyNE5$0p)u-LT^Yk7hkfdSTuk-;~Sk0ak01%$z~@y zoZ;ev*BP=-cWF=F109g}v=;>)dV~rJ<6MOLJeq>@BL^8C3kC6;Rj(*vyBZ6{@R#j& zyy|wTbI2viV*goigIM#3b*I~C3qk(7K}eKGppi9Gr~N^;hXL9`OCH8z_rF_JWv_qZ z8FRsNk4j#Teg-O?7dKETgJ3G&4bv-7=Z?uBKHuDx$XqeLY&0v(G z5zG*c!kWY1r~_k)@-&9Q3Bz+RjMKzv!#E6+{oa^_q0rAzfMJR*o<>cT6gcC(xiS$eg0J z@GN{RcorTDve#)NQn&HFu_egd#Qo6iG4W@C3o@q%sDooc_9qGI;2S~a-2`>;tsrw| z01dtqWN#(V;Cn&ly#yNkAjq5@K!YCz*`FrR;3q+5DS-w*3o^IqzVP|_MUeS`9!8k) lec)F?=A#69J{4l7Xot!XKjVAL--65vzBW^|+xZHu{tGLx$$tO< diff --git a/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.class b/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.class deleted file mode 100644 index ed56b0144b7e0bd58e9aeab4db63e65eefceeec8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2039 zcmcJPTTc@~6vxk0Xv@+Hlp+^F7f@_byWXAzO%yRHX_RoO!51>!j_u&?Ox>L&;1@H| zMBjb#6B*BLyR?z9*aw>IbZ6%5@BGhY=I8IP-vQtmJWfG^z&qb=GM8F`;I{O{Vat;( z<|)p6s}B_t6{?C7*f2^De+2EjqIQ@c zIEp~7(sj72JT4k*1ZH;vp}5Pc+~?@%g%DCjq4)%>3dUo7puS?>iR)NL)c)v8VbL=q zYYXSw?WCL{Z8!`S)lPK1A9@}4MkjfvbmR#q8tLFr+6pB>>LmRTrQZPec ztgCp_^Q4!CIRd}-1u~^`%<5*CaPCBcO+;b@?o-nk*=sVh&TNNzXwyR3w86}!b}>tF zF=-3GK*4^ge=V2Hx@5kIi7{QO?53%Z&Q4lbu|D)pfY>S*;SB?(VLk(SSRgP}IA5hO zCu0>Z*qgwuF>kNk5xI)AsZ*ucB^Y*RaY!{eR_>s3NsWs&c6ACb&0iXB5m>yi;shoe zOm$CM-1GTD@q9kf*w-0n5wYEA=CDb#Xw^GUvQGiF%U?xmpWU_Mdjjbu^_yYyMx9*7 z1tr@j4G*M}Wl?7WdnR65>CI$}(aAM-W#HNDH4krs<^ESoDLgW;2q*!cVI1cm1!;UA z!J8og20j_c;`}PkNAZ6QpK%{1a3iPNn2fCQFoky4@S7(Q8Nm1k(+9cPFL3=wM81Jz z5^eDQftxsHqIUyKR1>acUVkifMzVOc}SX$iMsrH2rD`Zr-lOSl6IJ%s)m=Cy>} z08yA)LScX?RNn0J2Wj@c;k- diff --git a/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.class b/unicorn_web/PublicationManagerService/target/classes/schema/unicorn_properties/publicationevaluationcompleted/marshaller/Marshaller.class deleted file mode 100644 index efaeea33a61bf3c58021225832692d98e3f59e2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1882 zcmb_cTTc@~6h2c6+pd)&7sU%Y;H?GeCdMa}F=R!Cz*g ziN5=zjAwUyA)!=E=)>%po;lz7zB6ae??2yv0>C@iupmR=P`h33GbfV54V3&8svzXb z2(FzlYI(wCMg)@gm=`6M8~CBe4R1R>Q@YDMk1OY3Xj+gZFnh*&%<-7)I7h8B?ivEq z2Rp~dwG#pxbwqGJGi|s!_dVx~xnFc3oi;P9C1krFalk^PQN_S5A%)o{aH~{4C6L_> z+T4QM1d5|1HKhWTgFA%y@1^28@Or#WW2VAONK;hR^0==zshw|jIc;;-V+s$2n4Wbw zT}{hS8I%Fjs{lpo%H%3kMpam)?SN}414Dgg+%7ekh@Xe3e?p>Bz5gRqDnj^SpbWi= zIdz$g$+S2f)Wpv3d}!2qT$(XXY68lr`5N-;9<#xQVgW3eC9qhkk1nKPl#rcjd|}19 zko++6TU<36-T{HRdf+nelqnJK`+sIlS7-vq^_y)+Y#^(DLj>0Q6h+`1MJ9|4(&Wsq zULc;fYeL+vox41Yn_pY-fWUkr4`cU_h8_a>hUiFUB89@`A?fXE8CL=`Lq_!kNm+y)AtrSO- zwG#KA1?vQI1L6b<2SRInYE68#8ddvZBl6=)aIQ!oz;xXZ&L+{4KGXlK#0gy?b9qXcseJsI4mn2*O`&L)`g znLIp!r(=-lHIWuBAg#da7^G)oke-j>v@}Gr;U&C6RdIb{E%EPaEz85W43zL>2Fman Jzj0mG{sN@E6ZQZA From 33af6daf0c6304e4040a25082ccf24529d9bf9a2 Mon Sep 17 00:00:00 2001 From: SDE Workshop User Date: Thu, 31 Jul 2025 06:25:50 +0000 Subject: [PATCH 30/34] fix: updated references to properties service to approvals service. Fixed SAM templates to refer to new Java services. Updated .gitignore. Updated test events. --- .gitignore | 1 + ...tus_changed_event_contract_1_approved.json | 2 +- ...status_changed_event_contract_1_draft.json | 2 +- ...tus_changed_event_contract_2_approved.json | 2 +- ...status_changed_event_contract_2_draft.json | 2 +- ...ion_approval_requested_event_all_good.json | 2 +- ...ested_event_inappropriate_description.json | 2 +- ..._requested_event_inappropriate_images.json | 2 +- ...requested_event_non_existing_contract.json | 2 +- ...proval_requested_event_pause_workflow.json | 2 +- ...put_event_property_approval_requested.json | 2 +- ..._event_publication_approval_requested.json | 2 +- .../src/test/events/send_events_cli.json | 2 +- unicorn_approvals/template.yaml | 56 +++++++++---------- unicorn_contracts/template.yaml | 2 +- unicorn_web/.gitignore | 9 +++ unicorn_web/Common/pom.xml | 6 +- unicorn_web/integration/subscriptions.yaml | 6 +- unicorn_web/pom.xml | 26 +++++++++ unicorn_web/template.yaml | 2 +- 20 files changed, 84 insertions(+), 48 deletions(-) create mode 100644 unicorn_web/pom.xml diff --git a/.gitignore b/.gitignore index 62dfa60..d9c73d8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ deleteall.sh /unicorn_web/PropertyFunctions/PropertyWeb.iml /unicorn_contracts/ContractsFunction/ContractsModule.iml **/cdk.out/ +**/dependency-reduced-pom.xml diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_approved.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_approved.json index e8b8f23..c7881ba 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_approved.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_approved.json @@ -2,7 +2,7 @@ { "DetailType": "ContractStatusChanged", "Source": "unicorn.contracts", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{ \"contract_last_modified_on\": 1669385541019, \"contract_id\": \"f2bedc80-3dc8-4544-9140-9b606d71a6ee\", \"property_id\": \"usa/anytown/main-street/111\", \"contract_status\": \"APPROVED\" }" } ] diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_draft.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_draft.json index 65a8a9d..af33ac3 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_draft.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_1_draft.json @@ -2,7 +2,7 @@ { "DetailType": "ContractStatusChanged", "Source": "unicorn.contracts", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{ \"contract_last_modified_on\": 1669385541019, \"contract_id\": \"f2bedc80-3dc8-4544-9140-9b606d71a6ee\", \"property_id\": \"usa/anytown/main-street/111\", \"contract_status\": \"DRAFT\" }" } ] diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_approved.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_approved.json index bf21376..2e8a644 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_approved.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_approved.json @@ -2,7 +2,7 @@ { "DetailType": "ContractStatusChanged", "Source": "unicorn.contracts", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{ \"contract_last_modified_on\": 1669385541019, \"contract_id\": \"9183453b-d284-4466-a2d9-f00b1d569ad7\", \"property_id\": \"usa/anytown/main-street/222\", \"contract_status\": \"APPROVED\" }" } ] diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_draft.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_draft.json index 52f5659..9d6d0e8 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_draft.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/contract_status_changed_event_contract_2_draft.json @@ -2,7 +2,7 @@ { "DetailType": "ContractStatusChanged", "Source": "unicorn.contracts", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{ \"contract_last_modified_on\": 1669385541019, \"contract_id\": \"9183453b-d284-4466-a2d9-f00b1d569ad7\", \"property_id\": \"usa/anytown/main-street/222\", \"contract_status\": \"DRAFT\" }" } ] diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_all_good.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_all_good.json index 677eaec..4e8ff6b 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_all_good.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_all_good.json @@ -2,7 +2,7 @@ { "DetailType": "PublicationApprovalRequested", "Source": "unicorn.web", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{\"property_id\":\"usa/anytown/main-street/222\",\"address\":{\"country\":\"USA\",\"city\":\"Anytown\",\"street\":\"Main Street\",\"number\":222},\"description\":\"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\":\"sale\",\"listprice\":200,\"currency\":\"SPL\",\"images\":[\"property_images/prop1_exterior1.jpg\",\"property_images/prop1_interior1.jpg\",\"property_images/prop1_interior2.jpg\",\"property_images/prop1_interior3.jpg\"]}" } ] \ No newline at end of file diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_description.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_description.json index b69426a..1126588 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_description.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_description.json @@ -2,7 +2,7 @@ { "DetailType": "PublicationApprovalRequested", "Source": "unicorn.web", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"address\":{\"country\":\"USA\",\"city\":\"Anytown\",\"street\":\"Main Street\",\"number\":111},\"description\":\"This is a property for goblins. The property has the worst quality and is atrocious when it comes to design. The property is not clean whatsoever, and will make any property owner have buyers' remorse as soon the property is bought. Keep away from this property as much as possible!\",\"contract\":\"sale\",\"listprice\":200,\"currency\":\"SPL\",\"images\":[\"property_images/prop1_exterior1.jpg\",\"property_images/prop1_interior1.jpg\",\"property_images/prop1_interior2.jpg\",\"property_images/prop1_interior3.jpg\"]}" } ] \ No newline at end of file diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_images.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_images.json index 964e12b..1b7f88a 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_images.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_inappropriate_images.json @@ -2,7 +2,7 @@ { "DetailType": "PublicationApprovalRequested", "Source": "unicorn.web", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"address\":{\"country\":\"USA\",\"city\":\"Anytown\",\"street\":\"Main Street\",\"number\":111},\"description\":\"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\":\"sale\",\"listprice\":200,\"currency\":\"SPL\",\"images\":[\"property_images/prop1_exterior1.jpg\",\"property_images/prop1_interior1.jpg\",\"property_images/prop1_interior2.jpg\",\"property_images/prop1_interior3.jpg\",\"property_images/prop1_interior4-bad.jpg\"]}" } ] \ No newline at end of file diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_non_existing_contract.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_non_existing_contract.json index a6ebb52..8bc45c3 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_non_existing_contract.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_non_existing_contract.json @@ -2,7 +2,7 @@ { "DetailType": "PublicationApprovalRequested", "Source": "unicorn.web", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{\"property_id\":\"usa/anytown/main-street/333\",\"address\":{\"country\":\"USA\",\"city\":\"Anytown\",\"street\":\"Main Street\",\"number\":333},\"description\":\"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\":\"sale\",\"listprice\":200,\"currency\":\"SPL\",\"images\":[\"property_images/prop1_exterior1.jpg\",\"property_images/prop1_interior1.jpg\",\"property_images/prop1_interior2.jpg\",\"property_images/prop1_interior3.jpg\"]}" } ] \ No newline at end of file diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_pause_workflow.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_pause_workflow.json index 1f6ecd8..5657437 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_pause_workflow.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/publication_approval_requested_event_pause_workflow.json @@ -2,7 +2,7 @@ { "DetailType": "PublicationApprovalRequested", "Source": "unicorn.web", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"address\":{\"country\":\"USA\",\"city\":\"Anytown\",\"street\":\"Main Street\",\"number\":111},\"description\":\"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\":\"sale\",\"listprice\":200,\"currency\":\"SPL\",\"images\":[\"property_images/prop1_exterior1.jpg\",\"property_images/prop1_interior1.jpg\",\"property_images/prop1_interior2.jpg\",\"property_images/prop1_interior3.jpg\"]}" } ] \ No newline at end of file diff --git a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/put_event_property_approval_requested.json b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/put_event_property_approval_requested.json index fed3b1f..b4e536f 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/put_event_property_approval_requested.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/eventbridge/put_event_property_approval_requested.json @@ -3,6 +3,6 @@ "Source": "unicorn.web", "Detail": "{ \"property_id\": \"usa/anytown/main-street/111\", \"country\": \"USA\", \"city\": \"Anytown\", \"street\": \"Main Street\", \"number\": 111, \"description\": \"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\", \"contract\": \"sale\", \"listprice\": 200, \"currency\": \"SPL\", \"images\": [ \"prop1_exterior1.jpg\", \"prop1_interior1.jpg\", \"prop1_interior2.jpg\", \"prop1_interior3.jpg\", \"prop1_interior4-bad.jpg\" ] }", "DetailType": "PublicationApprovalRequested", - "EventBusName": "UnicornPropertiesBus-local" + "EventBusName": "UnicornApprovalsBus-local" } ] \ No newline at end of file diff --git a/unicorn_approvals/ApprovalsService/src/test/events/put_event_publication_approval_requested.json b/unicorn_approvals/ApprovalsService/src/test/events/put_event_publication_approval_requested.json index 4910194..596d093 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/put_event_publication_approval_requested.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/put_event_publication_approval_requested.json @@ -1,7 +1,7 @@ [ { "EventBusName": "Dev-UnicornApprovalsEventBus", - "Source": "unicorn.approvals.web", + "Source": "unicorn.web", "DetailType": "PublicationApprovalRequested", "Detail": "{\"property_id\": \"usa/anytown/main-street/123\",\"country\": \"USA\",\"city\": \"Anytown\",\"street\": \"Main Street\",\"number\": 123,\"description\": \"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\": \"sale\",\"listprice\": 200,\"currency\": \"SPL\",\"images\": [ \"usa/anytown/main-street-123-0d61b4e3\"]}" } diff --git a/unicorn_approvals/ApprovalsService/src/test/events/send_events_cli.json b/unicorn_approvals/ApprovalsService/src/test/events/send_events_cli.json index 6c6d6b5..6770ed2 100644 --- a/unicorn_approvals/ApprovalsService/src/test/events/send_events_cli.json +++ b/unicorn_approvals/ApprovalsService/src/test/events/send_events_cli.json @@ -1,7 +1,7 @@ [ { "EventBusName": "Dev-UnicornApprovalsEventBus", - "Source": "Unicorn.Web", + "Source": "unicorn.web", "DetailType": "PublicationApprovalRequested", "Detail": "{\"property_id\": \"usa/anytown/main-street/123\",\"country\": \"USA\",\"city\": \"Anytown\",\"street\": \"Main Street\",\"number\": 123,\"description\": \"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\": \"sale\",\"listprice\": 200,\"currency\": \"SPL\",\"images\": [ \"usa/anytown/main-street-123-0d61b4e3\"]}" } diff --git a/unicorn_approvals/template.yaml b/unicorn_approvals/template.yaml index cc7f43d..91fd42a 100644 --- a/unicorn_approvals/template.yaml +++ b/unicorn_approvals/template.yaml @@ -4,7 +4,7 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: - AWS::Serverless-2016-10-31 Description: > - Unicorn Properties Service. Validate the content, images and contract of property listings. + Unicorn Approvals Service. Validate the content, images and contract of property listings. Metadata: cfn-lint: @@ -93,7 +93,7 @@ Resources: ContractStatusChangedHandlerFunction: Type: AWS::Serverless::Function Properties: - CodeUri: ./PropertyFunctions + CodeUri: ApprovalsService Handler: approvals.ContractStatusChangedHandlerFunction::handleRequest Policies: - DynamoDBWritePolicy: @@ -115,12 +115,12 @@ Resources: MaximumRetryAttempts: 5 MaximumEventAgeInSeconds: 900 DeadLetterConfig: - Arn: !GetAtt PropertiesEventBusRuleDLQ.Arn + Arn: !GetAtt ApprovalsEventBusRuleDLQ.Arn EventInvokeConfig: DestinationConfig: OnFailure: Type: SQS - Destination: !GetAtt PropertiesServiceDLQ.Arn + Destination: !GetAtt ApprovalsServiceDLQ.Arn # Log group for the ContractStatusChangedHandlerFunction ContractStatusChangedHandlerFunctionLogGroup: @@ -135,7 +135,7 @@ Resources: PropertiesApprovalSyncFunction: Type: AWS::Serverless::Function Properties: - CodeUri: ./PropertyFunctions + CodeUri: ApprovalsService Handler: approvals.PropertiesApprovalSyncFunction::handleRequest Policies: - DynamoDBReadPolicy: @@ -145,7 +145,7 @@ Resources: StreamName: !Select [3, !Split ["/", !GetAtt ContractStatusTable.StreamArn]] - SQSSendMessagePolicy: - QueueName: !GetAtt PropertiesServiceDLQ.QueueName + QueueName: !GetAtt ApprovalsServiceDLQ.QueueName - Statement: - Effect: Allow Action: @@ -162,12 +162,12 @@ Resources: MaximumRetryAttempts: 3 DestinationConfig: OnFailure: - Destination: !GetAtt PropertiesServiceDLQ.Arn + Destination: !GetAtt ApprovalsServiceDLQ.Arn EventInvokeConfig: DestinationConfig: OnFailure: Type: SQS - Destination: !GetAtt PropertiesServiceDLQ.Arn + Destination: !GetAtt ApprovalsServiceDLQ.Arn # Log group for the PropertiesApprovalSyncFunction PropertiesApprovalSyncFunctionLogGroup: @@ -182,7 +182,7 @@ Resources: WaitForContractApprovalFunction: Type: AWS::Serverless::Function Properties: - CodeUri: PropertyFunctions + CodeUri: ApprovalsService Handler: approvals.WaitForContractApprovalFunction::handleRequest Policies: - DynamoDBCrudPolicy: @@ -254,7 +254,7 @@ Resources: MaximumEventAgeInSeconds: 900 DeadLetterConfig: Type: SQS - Destination: !GetAtt PropertiesServiceDLQ.Arn + Destination: !GetAtt ApprovalsServiceDLQ.Arn DefinitionSubstitutions: WaitForContractApprovalArn: !GetAtt WaitForContractApprovalFunction.Arn TableName: !Ref ContractStatusTable @@ -273,7 +273,7 @@ Resources: #### DEAD LETTER QUEUES # Store EventBridge events that failed to be DELIVERED to ContractStatusChangedHandlerFunction - PropertiesEventBusRuleDLQ: + ApprovalsEventBusRuleDLQ: Type: AWS::SQS::Queue UpdateReplacePolicy: Delete DeletionPolicy: Delete @@ -288,8 +288,8 @@ Resources: - Key: stage Value: !Ref Stage - # Store failed INVOCATIONS to each Lambda function in Unicorn Properties Service - PropertiesServiceDLQ: + # Store failed INVOCATIONS to each Lambda function in Unicorn Approvals Service + ApprovalsServiceDLQ: Type: AWS::SQS::Queue UpdateReplacePolicy: Delete DeletionPolicy: Delete @@ -328,22 +328,22 @@ Resources: Value: !Ref Stage #### EVENT BUS - # Event bus for Unicorn Properties Service, used to publish and consume events + # Event bus for Unicorn Approvals Service, used to publish and consume events UnicornApprovalsEventBus: Type: AWS::Events::EventBus DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: - Name: !Sub UnicornPropertiesBus-${Stage} + Name: !Sub UnicornApprovalsBus-${Stage} # Event bus policy to restrict who can publish events (should only be services from UnicornApprovalsNamespace) - UnicornPropertiesEventsBusPublishPolicy: + UnicornApprovalsEventsBusPublishPolicy: Type: AWS::Events::EventBusPolicy DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: EventBusName: !Ref UnicornApprovalsEventBus - StatementId: !Sub OnlyPropertiesServiceCanPublishToEventBus-${Stage} + StatementId: !Sub OnlyApprovalsServiceCanPublishToEventBus-${Stage} Statement: Effect: Allow Principal: @@ -357,7 +357,7 @@ Resources: - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" # Catchall rule used for development purposes. Logs all events matching any of the services to CloudWatch Logs - UnicornPropertiesCatchAllRule: + UnicornApprovalsCatchAllRule: Type: AWS::Events::Rule DeletionPolicy: Delete UpdateReplacePolicy: Delete @@ -374,11 +374,11 @@ Resources: - "{{resolve:ssm:/uni-prop/UnicornWebNamespace}}" State: ENABLED #You may want to disable this rule in production Targets: - - Arn: !GetAtt UnicornPropertiesCatchAllLogGroup.Arn - Id: !Sub UnicornPropertiesCatchAllLogGroupTarget-${Stage} + - Arn: !GetAtt UnicornApprovalsCatchAllLogGroup.Arn + Id: !Sub UnicornApprovalsCatchAllLogGroupTarget-${Stage} # CloudWatch log group used to catch all events - UnicornPropertiesCatchAllLogGroup: + UnicornApprovalsCatchAllLogGroup: Type: AWS::Logs::LogGroup UpdateReplacePolicy: Delete DeletionPolicy: Delete @@ -412,14 +412,14 @@ Resources: "logs:PutLogEvents" ], "Resource": [ - "${UnicornPropertiesCatchAllLogGroup.Arn}" + "${UnicornApprovalsCatchAllLogGroup.Arn}" ] } ] } #### CLOUDFORMATION NESTED STACKS - # CloudFormation Stack with the Properties Service Event Registry and Schemas + # CloudFormation Stack with the Approvals Service Event Registry and Schemas EventSchemasStack: Type: AWS::Serverless::Application UpdateReplacePolicy: Delete @@ -429,7 +429,7 @@ Resources: Parameters: Stage: !Ref Stage - # CloudFormation Stack with the Cross-service EventBus policy for Properties Service + # CloudFormation Stack with the Cross-service EventBus policy for Approvals Service SubscriberPoliciesStack: Type: AWS::Serverless::Application UpdateReplacePolicy: Delete @@ -441,7 +441,7 @@ Resources: Parameters: Stage: !Ref Stage - # CloudFormation Stack with the Cross-service EventBus Rules for Properties Service + # CloudFormation Stack with the Cross-service EventBus Rules for Approvals Service SubscriptionsStack: Type: AWS::Serverless::Application UpdateReplacePolicy: Delete @@ -482,13 +482,13 @@ Outputs: Value: !Ref ApprovalStateMachine #### EVENT BRIDGE OUTPUTS - UnicornPropertiesEventBusName: + UnicornApprovalsEventBusName: Value: !GetAtt UnicornApprovalsEventBus.Name #### CLOUDWATCH LOGS OUTPUTS - UnicornPropertiesCatchAllLogGroupArn: + UnicornApprovalsCatchAllLogGroupArn: Description: Log all events on the service's EventBridge Bus - Value: !GetAtt UnicornPropertiesCatchAllLogGroup.Arn + Value: !GetAtt UnicornApprovalsCatchAllLogGroup.Arn ApprovalStateMachineLogGroupName: Value: !Ref ApprovalStateMachineLogGroup diff --git a/unicorn_contracts/template.yaml b/unicorn_contracts/template.yaml index 8600007..d2aa148 100644 --- a/unicorn_contracts/template.yaml +++ b/unicorn_contracts/template.yaml @@ -90,7 +90,7 @@ Resources: ContractEventHandlerFunction: Type: AWS::Serverless::Function Properties: - CodeUri: ContractsFunction + CodeUri: ContractsService Handler: contracts.ContractEventHandler::handleRequest Policies: - DynamoDBWritePolicy: diff --git a/unicorn_web/.gitignore b/unicorn_web/.gitignore index dace86b..12b83f2 100644 --- a/unicorn_web/.gitignore +++ b/unicorn_web/.gitignore @@ -82,3 +82,12 @@ # We ignore Python's requirements.txt as we use Poetry instead **/requirements.txt **/.aws-sam + +### Maven ### +**/target/ +**/dependency-reduced-pom.xml +**/*.jar +**/*.war +**/*.ear +**/*.class +**/builddef.lst diff --git a/unicorn_web/Common/pom.xml b/unicorn_web/Common/pom.xml index c25865c..4178d15 100644 --- a/unicorn_web/Common/pom.xml +++ b/unicorn_web/Common/pom.xml @@ -4,10 +4,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - unicornproperties - UnicornProperties + unicorn.web + unicorn-web-parent 1.0 - ../../pom.xml + ../pom.xml common diff --git a/unicorn_web/integration/subscriptions.yaml b/unicorn_web/integration/subscriptions.yaml index fc27711..a5033d1 100644 --- a/unicorn_web/integration/subscriptions.yaml +++ b/unicorn_web/integration/subscriptions.yaml @@ -13,7 +13,7 @@ Parameters: - prod Resources: - #### UNICORN PROPERTIES EVENT SUBSCRIPTIONS + #### UNICORN APPROVALS EVENT SUBSCRIPTIONS PublicationEvaluationCompletedSubscriptionRule: Type: AWS::Events::Rule Properties: @@ -32,11 +32,11 @@ Resources: Arn: Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBusArn}}" RoleArn: - Fn::GetAtt: [ UnicornPropertiesEventBusToUnicornWebEventBusRole, Arn ] + Fn::GetAtt: [ UnicornApprovalsEventBusToUnicornWebEventBusRole, Arn ] # This IAM role allows EventBridge to assume the permissions necessary to send events # from the publishing event bus, to the subscribing event bus (UnicornWebEventBusArn) - UnicornPropertiesEventBusToUnicornWebEventBusRole: + UnicornApprovalsEventBusToUnicornWebEventBusRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: diff --git a/unicorn_web/pom.xml b/unicorn_web/pom.xml new file mode 100644 index 0000000..d6e816a --- /dev/null +++ b/unicorn_web/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + unicorn.web + unicorn-web-parent + 1.0 + pom + + Unicorn Web Services + + + 17 + 17 + UTF-8 + + + + Common + PublicationManagerService + SearchService + + + \ No newline at end of file diff --git a/unicorn_web/template.yaml b/unicorn_web/template.yaml index 7fc334e..6b819db 100644 --- a/unicorn_web/template.yaml +++ b/unicorn_web/template.yaml @@ -91,7 +91,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: SearchService - Handler: search.search.PropertySearchFunction::handleRequest + Handler: search.PropertySearchFunction::handleRequest Policies: - DynamoDBReadPolicy: TableName: !Ref WebTable From 2cc2196c8f403cb70720911d522f4aa6a0e904e5 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Tue, 5 Aug 2025 17:40:38 +0800 Subject: [PATCH 31/34] chore: updated approvals readme --- unicorn_approvals/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unicorn_approvals/README.md b/unicorn_approvals/README.md index f70eb7f..f442855 100644 --- a/unicorn_approvals/README.md +++ b/unicorn_approvals/README.md @@ -12,4 +12,4 @@ The workflow checks the contract state. If the contract is in the WaitForContrac If the workflow completes successfully, it emits a PublicationEvaluationCompleted event with an **approved** or **declined** evaluation result, which Unicorn Web listens to update its publication flag. -**Note:** Upon deleting the CloudFormation stack for this service, check if the `ApprovalStateMachine` StepFunction doesn't have any executions in `RUNNING` state. If there are, cancel those execution prior to deleting the CloudFormation stack. +**Note:** Upon deleting the CloudFormation stack for this service, check if the `ApprovalStateMachine` StepFunction doesn't have any executions in `RUNNING` state. If there are, cancel those execution prior to deleting the CloudFormation stack. \ No newline at end of file From c95189a18e08e915b2472d342ed2c5149cc068ca Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Tue, 5 Aug 2025 17:50:14 +0800 Subject: [PATCH 32/34] chore: updated readme and spelling mistakes --- README.md | 2 +- unicorn_approvals/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1c2b059..f55f4b3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The 6-8 hour workshop assumes your practical development skills in Python, TypeS ![AWS Serverless Developer Experience Workshop Reference Architecture](./docs/architecture.png) -Real estate company **Unicorn Properties** needs to manage publishing of new property listings and sale contracts linked to individual properties, and provide a way for customers to view approved listings. They adopted a serverless, event-driven architecture with two primary domains: **Contracts** (managed by Contracts Service) and **Properties** (managed by Web and Approvals Services). +Real estate company **Unicorn Properties** needs to manage publishing of new property listings and sale contracts linked to individual properties, and provide a way for customers to view approved listings. They adopted a serverless, event-driven architecture with two primary domains: **Contracts** (managed by the Contracts Service) and **Properties** (managed by the Web and Approvals Services). **Unicorn Contracts** (using the `Unicorn.Contracts` namespace) service manages contractual relationships between property sellers and Unicorn Approvals, defining properties for sale, terms, and engagement costs. diff --git a/unicorn_approvals/README.md b/unicorn_approvals/README.md index f442855..78cbc75 100644 --- a/unicorn_approvals/README.md +++ b/unicorn_approvals/README.md @@ -6,9 +6,9 @@ **Unicorn Approvals** uses an AWS Step Functions state machine to approve property listings for Unicorn Web. The workflow checks for contract information, description sentiment and safe images, and verifies the contract is approved before approving the listing. It publishes the result via the `PublicationEvaluationCompleted` event. -A Unicorn Properties agent initiates the workflow by requesting to approve a listing, generating a `PublicationApprovalRequested` event with property information. To decouple from the Contracts Service, the Approval service maintains a local copy of contract status by consuming the ContractStatusChanged event. +A Unicorn Properties agent initiates the workflow by requesting to approve a listing, generating a `PublicationApprovalRequested` event with property information. To decouple from the Contracts Service, the Approvals service maintains a local copy of contract status by consuming the ContractStatusChanged event. -The workflow checks the contract state. If the contract is in the WaitForContractApproval state, it updates the contract status for the property with its task token, triggering a DynamoDB stream event. The Property approvals sync function handles these events and passes the task token back to the state machine based on the contract state. +The workflow checks the contract state. If the contract is in the WaitForContractApproval state, it updates the contract status for the property with its task token, triggering a DynamoDB stream event. The Property Approval Sync function handles these events and passes the task token back to the state machine based on the contract state. If the workflow completes successfully, it emits a PublicationEvaluationCompleted event with an **approved** or **declined** evaluation result, which Unicorn Web listens to update its publication flag. From 96c27cde52593d51804d0e1b9966a37eb1a44288 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Thu, 7 Aug 2025 15:47:55 +0800 Subject: [PATCH 33/34] fix: syncing templates --- unicorn_approvals/template.yaml | 1 - unicorn_contracts/template.yaml | 2 -- unicorn_web/integration/event-schemas.yaml | 2 +- unicorn_web/template.yaml | 5 ++--- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/unicorn_approvals/template.yaml b/unicorn_approvals/template.yaml index 91fd42a..a89caa8 100644 --- a/unicorn_approvals/template.yaml +++ b/unicorn_approvals/template.yaml @@ -16,7 +16,6 @@ Metadata: - WS2001 # Rule disabled because check does not support !ToJsonString transform - ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure - W3002 # Rule disabled as nested templates are being packaged - - E3030 # Rule disabled due to using cfn-lint-serverless rules v0.3 Parameters: Stage: diff --git a/unicorn_contracts/template.yaml b/unicorn_contracts/template.yaml index d2aa148..673c658 100644 --- a/unicorn_contracts/template.yaml +++ b/unicorn_contracts/template.yaml @@ -15,8 +15,6 @@ Metadata: - WS2001 # Rule disabled because check does not support !ToJsonString transform - ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure - W3002 # Rule disabled as nested templates are being packaged - - E3030 # Rule disabled due to using cfn-lint-serverless rules v0.3 - - E3002 # Rule disabled due to using cfn-lint-serverless rules v0.3 Parameters: Stage: diff --git a/unicorn_web/integration/event-schemas.yaml b/unicorn_web/integration/event-schemas.yaml index 10815f0..27b6e46 100644 --- a/unicorn_web/integration/event-schemas.yaml +++ b/unicorn_web/integration/event-schemas.yaml @@ -53,7 +53,7 @@ Resources: Fn::GetAtt: EventRegistry.RegistryName SchemaName: Fn::Sub: '{{resolve:ssm:/uni-prop/UnicornWebNamespace}}@PublicationApprovalRequested' - Description: 'The schema for a request to publish a search' + Description: 'The schema for a request to publish a property' Content: Fn::Sub: | { diff --git a/unicorn_web/template.yaml b/unicorn_web/template.yaml index 6b819db..e510753 100644 --- a/unicorn_web/template.yaml +++ b/unicorn_web/template.yaml @@ -15,7 +15,6 @@ Metadata: - WS2001 # Rule disabled because check does not support !ToJsonString transform - ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure - W3002 # Rule disabled as nested templates are being packaged - - E3030 # Rule disabled due to using cfn-lint-serverless rules v0.3 Parameters: Stage: @@ -461,10 +460,10 @@ Outputs: Description: "GET request to list all properties in a given street" Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/search/{country}/{city}/{street}" ApiPropertyApproval: - Description: "POST request to add a search to the database" + Description: "POST request approval to allow property to be searchable" Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/request_approval" ApiPropertyDetails: - Description: "GET request to get the full details of a single search" + Description: "GET request to get the full details of a single property" Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/properties/{country}/{city}/{street}/{number}" #### SQS OUTPUTS From cf2603f5d5ca83426d343f33cd2301075177e108 Mon Sep 17 00:00:00 2001 From: Steven Cook Date: Wed, 27 Aug 2025 13:23:32 +1000 Subject: [PATCH 34/34] Updated Java dependencies and Code (#47) * Updated unicorn approvals dependencies and updated code. * Updated Unicorn Contracts. * Updated unicorn web. --- .gitignore | 1 + unicorn_approvals/ApprovalsService/pom.xml | 42 ++-- .../ContractStatusChangedHandlerFunction.java | 120 ++++----- .../PropertiesApprovalSyncFunction.java | 128 +++++----- .../WaitForContractApprovalFunction.java | 142 +++++------ .../java/approvals/dao/ContractStatus.java | 94 +++++-- .../java/approvals/ContractStatusTests.java | 73 +++--- unicorn_contracts/ContractsService/pom.xml | 52 ++-- .../java/contracts/ContractEventHandler.java | 237 +++++++++++------- .../main/java/contracts/utils/Address.java | 61 ++++- .../main/java/contracts/utils/Contract.java | 69 +++-- .../utils/ContractStatusChangedEvent.java | 47 +++- .../java/contracts/utils/ResponseParser.java | 66 +++-- .../java/contracts/CreateContractTests.java | 107 ++++++-- unicorn_web/Common/pom.xml | 4 +- .../Common/src/main/java/dao/Property.java | 74 ++++-- unicorn_web/PublicationManagerService/pom.xml | 46 ++-- .../PublicationApprovedEventHandler.java | 149 ++++++----- .../RequestApprovalFunction.java | 230 +++++++++-------- .../classes/publicationmanager/Address.class | Bin 1114 -> 0 bytes .../PublicationApprovedEventHandler.class | Bin 7567 -> 0 bytes .../publicationmanager/RequestApproval.class | Bin 990 -> 0 bytes .../RequestApprovalFunction.class | Bin 14455 -> 0 bytes .../AWSEvent.class | Bin 6635 -> 0 bytes .../PublicationEvaluationCompleted.class | Bin 2724 -> 0 bytes .../marshaller/Marshaller.class | Bin 2726 -> 0 bytes unicorn_web/SearchService/pom.xml | 46 ++-- .../java/search/PropertySearchFunction.java | 218 +++++++--------- 28 files changed, 1183 insertions(+), 823 deletions(-) delete mode 100644 unicorn_web/PublicationManagerService/target/classes/publicationmanager/Address.class delete mode 100644 unicorn_web/PublicationManagerService/target/classes/publicationmanager/PublicationApprovedEventHandler.class delete mode 100644 unicorn_web/PublicationManagerService/target/classes/publicationmanager/RequestApproval.class delete mode 100644 unicorn_web/PublicationManagerService/target/classes/publicationmanager/RequestApprovalFunction.class delete mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_approvals/publicationevaluationcompleted/AWSEvent.class delete mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_approvals/publicationevaluationcompleted/PublicationEvaluationCompleted.class delete mode 100644 unicorn_web/PublicationManagerService/target/classes/schema/unicorn_approvals/publicationevaluationcompleted/marshaller/Marshaller.class diff --git a/.gitignore b/.gitignore index d9c73d8..856e984 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ unicorn_approvals/ApprovalsService/target/** unicorn_contracts/ContractsService/target/** unicorn_web/ApprovalService/target/** unicorn_web/SearchService/target/** +unicorn_web/PublicationManagerService/target/** unicorn_web/Common/target/** **/.aws-sam/ .DS_Store** diff --git a/unicorn_approvals/ApprovalsService/pom.xml b/unicorn_approvals/ApprovalsService/pom.xml index 0fd849c..24f69f3 100644 --- a/unicorn_approvals/ApprovalsService/pom.xml +++ b/unicorn_approvals/ApprovalsService/pom.xml @@ -9,14 +9,14 @@ 17 17 - 2.27.21 - 1.18.0 - 3.13.0 - 5.13.0 + 2.32.29 + 1.20.2 + 3.16.1 + 5.18.0 4.13.2 - 1.1.1 - 1.2.3 - 2.27.21 + 1.1.2 + 1.3.0 + 2.32.29 @@ -81,29 +81,29 @@ com.fasterxml.jackson.core jackson-databind - 2.15.2 + 2.18.4 com.fasterxml.jackson.core jackson-core - 2.15.2 + 2.18.4 com.fasterxml.jackson.core jackson-annotations - 2.15.2 + 2.18.4 org.apache.logging.log4j log4j-api - 2.20.0 + 2.25.1 org.apache.logging.log4j log4j-core - 2.20.0 + 2.25.1 org.mockito @@ -132,7 +132,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.5.3 handler @@ -142,7 +142,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 @@ -157,11 +157,10 @@ dev.aspectj aspectj-maven-plugin - 1.13.1 + 1.14.1 - 17 - 17 17 + 17 software.amazon.lambda @@ -184,11 +183,18 @@ + + + org.aspectj + aspectjtools + 1.9.24 + + org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.14.0 17 17 diff --git a/unicorn_approvals/ApprovalsService/src/main/java/approvals/ContractStatusChangedHandlerFunction.java b/unicorn_approvals/ApprovalsService/src/main/java/approvals/ContractStatusChangedHandlerFunction.java index 7358dc0..0a82802 100644 --- a/unicorn_approvals/ApprovalsService/src/main/java/approvals/ContractStatusChangedHandlerFunction.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/approvals/ContractStatusChangedHandlerFunction.java @@ -27,70 +27,60 @@ */ public class ContractStatusChangedHandlerFunction { - Logger logger = LogManager.getLogger(); - - final String TABLE_NAME = System.getenv("CONTRACT_STATUS_TABLE"); - - ObjectMapper objectMapper = new ObjectMapper(); - - DynamoDbClient dynamodbClient = DynamoDbClient.builder() - .build(); - - /** - * - * @param inputStream - * @param outputStream - * @param context - * @return - * @throws IOException - * - */ - @Tracing - @Metrics(captureColdStart = true) - @Logging(logEvent = true) - public void handleRequest(InputStream inputStream, OutputStream outputStream, - Context context) throws IOException { - - // deseralised and save contract status change in dynamodb table - - Event event = Marshaller.unmarshal(inputStream, - Event.class); - // save to database - ContractStatusChanged contractStatusChanged = event.getDetail(); - saveContractStatus(contractStatusChanged.getPropertyId(), contractStatusChanged.getContractStatus(), - contractStatusChanged.getContractId(), - contractStatusChanged.getContractLastModifiedOn()); - - OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); - writer.write(objectMapper.writeValueAsString(event.getDetail())); - writer.close(); - } - - @Tracing - void saveContractStatus(String propertyId, - String contractStatus, String contractId, Long contractLastModifiedOn) { - Map key = new HashMap(); - AttributeValue keyvalue = AttributeValue.fromS(propertyId); - key.put("property_id", keyvalue); - - Map expressionAttributeValues = new HashMap(); - expressionAttributeValues.put(":t", AttributeValue.fromS(contractStatus)); - expressionAttributeValues.put(":c", AttributeValue.fromS(contractId)); - expressionAttributeValues.put(":m", AttributeValue - .fromN(String.valueOf(contractLastModifiedOn))); - - UpdateItemRequest updateItemRequest = UpdateItemRequest.builder() - .key(key) - .tableName(TABLE_NAME) - .updateExpression( - "set contract_status=:t, contract_last_modified_on=:m, contract_id=:c") - .expressionAttributeValues(expressionAttributeValues) - .build(); - - dynamodbClient.updateItem(updateItemRequest); - } - - public void setDynamodbClient(DynamoDbClient dynamodbClient) { - this.dynamodbClient = dynamodbClient; + private static final Logger logger = LogManager.getLogger(); + private static final String TABLE_NAME = System.getenv("CONTRACT_STATUS_TABLE"); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private DynamoDbClient dynamodbClient = DynamoDbClient.builder().build(); + + /** + * Handles contract status change events from EventBridge + * + * @param inputStream the input stream containing the event + * @param outputStream the output stream for the response + * @param context the Lambda context + * @throws IOException if there's an error processing the event + */ + @Tracing + @Metrics(captureColdStart = true) + @Logging(logEvent = true) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + Event event = Marshaller.unmarshal(inputStream, Event.class); + ContractStatusChanged contractStatusChanged = event.getDetail(); + + saveContractStatus( + contractStatusChanged.getPropertyId(), + contractStatusChanged.getContractStatus(), + contractStatusChanged.getContractId(), + contractStatusChanged.getContractLastModifiedOn() + ); + + try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { + writer.write(objectMapper.writeValueAsString(event.getDetail())); } + } + + @Tracing + void saveContractStatus(String propertyId, String contractStatus, String contractId, Long contractLastModifiedOn) { + Map key = Map.of("property_id", AttributeValue.fromS(propertyId)); + + Map expressionAttributeValues = Map.of( + ":t", AttributeValue.fromS(contractStatus), + ":c", AttributeValue.fromS(contractId), + ":m", AttributeValue.fromN(String.valueOf(contractLastModifiedOn)) + ); + + UpdateItemRequest updateItemRequest = UpdateItemRequest.builder() + .key(key) + .tableName(TABLE_NAME) + .updateExpression("set contract_status=:t, contract_last_modified_on=:m, contract_id=:c") + .expressionAttributeValues(expressionAttributeValues) + .build(); + + dynamodbClient.updateItem(updateItemRequest); + } + + public void setDynamodbClient(DynamoDbClient dynamodbClient) { + this.dynamodbClient = dynamodbClient; + } } diff --git a/unicorn_approvals/ApprovalsService/src/main/java/approvals/PropertiesApprovalSyncFunction.java b/unicorn_approvals/ApprovalsService/src/main/java/approvals/PropertiesApprovalSyncFunction.java index 5d70818..afcfb45 100644 --- a/unicorn_approvals/ApprovalsService/src/main/java/approvals/PropertiesApprovalSyncFunction.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/approvals/PropertiesApprovalSyncFunction.java @@ -20,69 +20,41 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Lambda function that processes DynamoDB stream events to sync property approval status + * with Step Functions workflows + */ public class PropertiesApprovalSyncFunction implements RequestHandler { - Logger logger = LogManager.getLogger(); - SfnAsyncClient snfClient = SfnAsyncClient.builder() - .httpClientBuilder(NettyNioAsyncHttpClient.builder() - .maxConcurrency(100) - .maxPendingConnectionAcquires(10_000)) - .build(); + private static final Logger logger = LogManager.getLogger(); + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final String APPROVED_STATUS = "APPROVED"; + + private final SfnAsyncClient sfnClient = SfnAsyncClient.builder() + .httpClientBuilder(NettyNioAsyncHttpClient.builder() + .maxConcurrency(100) + .maxPendingConnectionAcquires(10_000)) + .build(); @Tracing @Metrics(captureColdStart = true) @Logging(logEvent = true) public StreamsEventResponse handleRequest(DynamodbEvent input, Context context) { - List batchItemFailures = new ArrayList<>(); - String curRecordSequenceNumber = ""; for (DynamodbEvent.DynamodbStreamRecord dynamodbStreamRecord : input.getRecords()) { + String sequenceNumber = dynamodbStreamRecord.getDynamodb().getSequenceNumber(); + try { - // Process your record - - StreamRecord dynamodbRecord = dynamodbStreamRecord.getDynamodb(); - curRecordSequenceNumber = dynamodbRecord.getSequenceNumber(); - Map newImage = dynamodbRecord.getNewImage(); - Map oldImage = dynamodbRecord.getOldImage(); - if (oldImage == null) { - oldImage = new HashMap(); - } - if (newImage == null) { - logger.debug("New image is null. Hence return empty stream response"); - return new StreamsEventResponse(); - } - // if there is no token do nothing - if (newImage.get("sfn_wait_approved_task_token") == null - && oldImage.get("sfn_wait_approved_task_token") == null) { - logger.debug("No task token in both the images. Hence return empty stream response"); - return new StreamsEventResponse(); - } - - // if contract status is approved, send the task token - - if (!newImage.get("contract_status").getS().equalsIgnoreCase("APPROVED")) { - logger.debug("Contract status for property is not APPROVED : " + - newImage.get("property_id").getS()); - return new StreamsEventResponse(); + if (!processRecord(dynamodbStreamRecord)) { + continue; // Skip this record but don't fail } - logger.debug("Contract status for property is APPROVED : " + - newImage.get("property_id").getS()); - - // send task successful token - taskSuccessful(newImage.get("sfn_wait_approved_task_token").getS(), newImage); - } catch (Exception e) { - /* - * Since we are working with streams, we can return the failed item immediately. - * Lambda will immediately begin to retry processing from this failed item - * onwards. - */ - batchItemFailures.add(new StreamsEventResponse.BatchItemFailure(curRecordSequenceNumber)); + logger.error("Failed to process record with sequence number: {}", sequenceNumber, e); + batchItemFailures.add(new StreamsEventResponse.BatchItemFailure(sequenceNumber)); return new StreamsEventResponse(batchItemFailures); } } @@ -90,21 +62,55 @@ public StreamsEventResponse handleRequest(DynamodbEvent input, Context context) return new StreamsEventResponse(); } - private void taskSuccessful(String s, Map item) throws JsonProcessingException { - // create the json structure and send the token - ObjectMapper mapper = new ObjectMapper(); - ContractStatus contractStatus = new ContractStatus(); - contractStatus.setContract_id(item.get("contract_id").getS()); - contractStatus.setContract_status(item.get("contract_status").getS()); - contractStatus.setProperty_id(item.get("property_id").getS()); - contractStatus.setSfn_wait_approved_task_token(item.get("sfn_wait_approved_task_token").getS()); - String taskResult = mapper.writeValueAsString(contractStatus); + private boolean processRecord(DynamodbEvent.DynamodbStreamRecord streamRecord) throws JsonProcessingException { + StreamRecord dynamodbRecord = streamRecord.getDynamodb(); + Map newImage = dynamodbRecord.getNewImage(); + Map oldImage = dynamodbRecord.getOldImage(); - SendTaskSuccessRequest request = SendTaskSuccessRequest.builder() - .taskToken(contractStatus.getSfn_wait_approved_task_token()) - .output(taskResult) - .build(); - snfClient.sendTaskSuccess(request).join(); + if (newImage == null) { + logger.debug("New image is null, skipping record"); + return false; + } + + if (!hasTaskToken(newImage, oldImage)) { + logger.debug("No task token found in either image, skipping record"); + return false; + } + + String contractStatus = newImage.get("contract_status").getS(); + String propertyId = newImage.get("property_id").getS(); + + if (!APPROVED_STATUS.equalsIgnoreCase(contractStatus)) { + logger.debug("Contract status for property {} is not APPROVED: {}", propertyId, contractStatus); + return false; + } + + logger.info("Contract approved for property: {}", propertyId); + sendTaskSuccess(newImage.get("sfn_wait_approved_task_token").getS(), newImage); + return true; + } + + private boolean hasTaskToken(Map newImage, Map oldImage) { + return (newImage.get("sfn_wait_approved_task_token") != null) || + (oldImage != null && oldImage.get("sfn_wait_approved_task_token") != null); + } + + private void sendTaskSuccess(String taskToken, Map item) throws JsonProcessingException { + ContractStatus contractStatus = ContractStatus.builder() + .contractId(item.get("contract_id").getS()) + .contractStatus(item.get("contract_status").getS()) + .propertyId(item.get("property_id").getS()) + .sfnWaitApprovedTaskToken(item.get("sfn_wait_approved_task_token").getS()) + .build(); + String taskResult = objectMapper.writeValueAsString(contractStatus); + + SendTaskSuccessRequest request = SendTaskSuccessRequest.builder() + .taskToken(taskToken) + .output(taskResult) + .build(); + + sfnClient.sendTaskSuccess(request).join(); + logger.info("Task success sent for property: {}", contractStatus.getPropertyId()); } } \ No newline at end of file diff --git a/unicorn_approvals/ApprovalsService/src/main/java/approvals/WaitForContractApprovalFunction.java b/unicorn_approvals/ApprovalsService/src/main/java/approvals/WaitForContractApprovalFunction.java index 7d0202e..c1f5f3e 100644 --- a/unicorn_approvals/ApprovalsService/src/main/java/approvals/WaitForContractApprovalFunction.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/approvals/WaitForContractApprovalFunction.java @@ -19,89 +19,77 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletionException; /** - * Lambda handler to update the contract status change + * Lambda handler to wait for contract approval in Step Functions workflow */ public class WaitForContractApprovalFunction { - Logger logger = LogManager.getLogger(); - - final String TABLE_NAME = System.getenv("CONTRACT_STATUS_TABLE"); - - DynamoDbAsyncClient dynamodbClient = DynamoDbAsyncClient.builder() - .httpClientBuilder(NettyNioAsyncHttpClient.builder() - .maxConcurrency(100) - .maxPendingConnectionAcquires(10_000)) - .build(); - - @Tracing - @Metrics(captureColdStart = true) - @Logging(logEvent = true) - public void handleRequest(InputStream inputStream, OutputStream outputStream, - Context context) throws IOException, ContractStatusNotFoundException { - - // deseralised to contract status - ObjectMapper objectMapper = new ObjectMapper(); - String srtInput = new String(inputStream.readAllBytes()); - JsonNode event = objectMapper.readTree(srtInput); - String propertyId = event.get("Input").get("property_id").asText(); - String taskToken = event.get("TaskToken").asText(); - - logger.info("task Token : ", taskToken); - logger.info("Property Id : ", propertyId); - - // get contract status - Map dynamodbItem = getContractStatus(propertyId); - updateTokenAndPauseExecution(taskToken, dynamodbItem.get("property_id").s()); - - String responseString = event.get("Input").asText(); - OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); - logger.debug(responseString); - writer.write(responseString); - writer.close(); - + private static final Logger logger = LogManager.getLogger(); + private static final String TABLE_NAME = System.getenv("CONTRACT_STATUS_TABLE"); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private final DynamoDbAsyncClient dynamodbClient = DynamoDbAsyncClient.builder() + .httpClientBuilder(NettyNioAsyncHttpClient.builder() + .maxConcurrency(100) + .maxPendingConnectionAcquires(10_000)) + .build(); + + @Tracing + @Metrics(captureColdStart = true) + @Logging(logEvent = true) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) + throws IOException, ContractStatusNotFoundException { + + String input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + JsonNode event = objectMapper.readTree(input); + + String propertyId = event.get("Input").get("property_id").asText(); + String taskToken = event.get("TaskToken").asText(); + + logger.info("Processing property: {} with task token: {}", propertyId, taskToken); + + Map contractItem = getContractStatus(propertyId); + updateTokenAndPauseExecution(taskToken, contractItem.get("property_id").s()); + + String responseString = event.get("Input").toString(); + try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { + writer.write(responseString); } - - private void updateTokenAndPauseExecution(String taskToken, String propertyId) { - Map key = new HashMap(); - AttributeValue keyvalue = AttributeValue.fromS(propertyId); - key.put("property_id", keyvalue); - - Map expressionAttributeValues = new HashMap(); - expressionAttributeValues.put(":g", AttributeValue.fromS(taskToken)); - - UpdateItemRequest updateItemRequest = UpdateItemRequest.builder() - .key(key) - .tableName(TABLE_NAME) - .updateExpression( - "set sfn_wait_approved_task_token = :g") - .expressionAttributeValues(expressionAttributeValues) - .build(); - dynamodbClient.updateItem(updateItemRequest).join(); - } - - private Map getContractStatus(String propertyId) - throws ContractStatusNotFoundException { - HashMap keyToGet = new HashMap(); - - keyToGet.put("property_id", AttributeValue.builder() - .s(propertyId).build()); - - GetItemRequest request = GetItemRequest.builder() - .key(keyToGet) - .tableName(TABLE_NAME) - .build(); - Map returnvalue = null; - try { - returnvalue = dynamodbClient.getItem(request).join().item(); - } catch (Exception exception) { - throw new ContractStatusNotFoundException(exception.getLocalizedMessage()); - } - - return returnvalue; + } + + private void updateTokenAndPauseExecution(String taskToken, String propertyId) { + Map key = Map.of("property_id", AttributeValue.fromS(propertyId)); + Map expressionAttributeValues = Map.of(":g", AttributeValue.fromS(taskToken)); + + UpdateItemRequest updateItemRequest = UpdateItemRequest.builder() + .key(key) + .tableName(TABLE_NAME) + .updateExpression("set sfn_wait_approved_task_token = :g") + .expressionAttributeValues(expressionAttributeValues) + .build(); + + dynamodbClient.updateItem(updateItemRequest).join(); + } + + private Map getContractStatus(String propertyId) throws ContractStatusNotFoundException { + Map key = Map.of("property_id", AttributeValue.fromS(propertyId)); + + GetItemRequest request = GetItemRequest.builder() + .key(key) + .tableName(TABLE_NAME) + .build(); + + try { + Map item = dynamodbClient.getItem(request).join().item(); + if (item == null || item.isEmpty()) { + throw new ContractStatusNotFoundException("Contract status not found for property: " + propertyId); + } + return item; + } catch (CompletionException e) { + throw new ContractStatusNotFoundException("Failed to retrieve contract status: " + e.getCause().getMessage()); } - + } } diff --git a/unicorn_approvals/ApprovalsService/src/main/java/approvals/dao/ContractStatus.java b/unicorn_approvals/ApprovalsService/src/main/java/approvals/dao/ContractStatus.java index df99fcd..7244edc 100644 --- a/unicorn_approvals/ApprovalsService/src/main/java/approvals/dao/ContractStatus.java +++ b/unicorn_approvals/ApprovalsService/src/main/java/approvals/dao/ContractStatus.java @@ -1,47 +1,89 @@ package approvals.dao; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Data class representing contract status information + */ public class ContractStatus { - String contract_id; - String contract_status; - String property_id; - String sfn_wait_approved_task_token; + + @JsonProperty("contract_id") + private final String contractId; + + @JsonProperty("contract_status") + private final String contractStatus; + + @JsonProperty("property_id") + private final String propertyId; + + @JsonProperty("sfn_wait_approved_task_token") + private final String sfnWaitApprovedTaskToken; - @Override - public String toString() { - return "Property [contract_id=" + contract_id + ", contract_status=" + contract_status + ", property_id=" - + property_id + ", sfn_wait_approved_task_token=" + sfn_wait_approved_task_token + "]"; + private ContractStatus(Builder builder) { + this.contractId = builder.contractId; + this.contractStatus = builder.contractStatus; + this.propertyId = builder.propertyId; + this.sfnWaitApprovedTaskToken = builder.sfnWaitApprovedTaskToken; } - public String getContract_id() { - return contract_id; + public String getContractId() { + return contractId; } - public void setContract_id(String contract_id) { - this.contract_id = contract_id; + public String getContractStatus() { + return contractStatus; } - public String getContract_status() { - return contract_status; + public String getPropertyId() { + return propertyId; } - public void setContract_status(String contract_status) { - this.contract_status = contract_status; + public String getSfnWaitApprovedTaskToken() { + return sfnWaitApprovedTaskToken; } - public String getProperty_id() { - return property_id; + public static Builder builder() { + return new Builder(); } - public void setProperty_id(String property_id) { - this.property_id = property_id; - } + public static class Builder { + private String contractId; + private String contractStatus; + private String propertyId; + private String sfnWaitApprovedTaskToken; - public String getSfn_wait_approved_task_token() { - return sfn_wait_approved_task_token; - } + public Builder contractId(String contractId) { + this.contractId = contractId; + return this; + } + + public Builder contractStatus(String contractStatus) { + this.contractStatus = contractStatus; + return this; + } - public void setSfn_wait_approved_task_token(String sfn_wait_approved_task_token) { - this.sfn_wait_approved_task_token = sfn_wait_approved_task_token; + public Builder propertyId(String propertyId) { + this.propertyId = propertyId; + return this; + } + + public Builder sfnWaitApprovedTaskToken(String sfnWaitApprovedTaskToken) { + this.sfnWaitApprovedTaskToken = sfnWaitApprovedTaskToken; + return this; + } + + public ContractStatus build() { + return new ContractStatus(this); + } } + @Override + public String toString() { + return "ContractStatus{" + + "contractId='" + contractId + '\'' + + ", contractStatus='" + contractStatus + '\'' + + ", propertyId='" + propertyId + '\'' + + ", sfnWaitApprovedTaskToken='" + sfnWaitApprovedTaskToken + '\'' + + '}'; + } } diff --git a/unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java b/unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java index 63caac2..c8e01da 100644 --- a/unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java +++ b/unicorn_approvals/ApprovalsService/src/test/java/approvals/ContractStatusTests.java @@ -2,53 +2,50 @@ import com.amazonaws.services.lambda.runtime.Context; import org.junit.Before; -import org.junit.jupiter.api.Test; +import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import java.io.*; -import java.util.HashMap; -import java.util.Map; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; @RunWith(MockitoJUnitRunner.class) public class ContractStatusTests { - Context context; - DynamoDbClient client; - - ContractStatusChangedHandlerFunction contractStatusChangedHandler; - - Map response = new HashMap(); - - @Before - public void setUp() { - - context = mock(Context.class); - client = mock(DynamoDbClient.class); - - } - - @Test - public void validStatusCheckEvent() throws IOException { - - contractStatusChangedHandler = new ContractStatusChangedHandlerFunction(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - File resourceFile = new File("src/test/events/lambda/contract_status_changed.json"); - client = mock(DynamoDbClient.class); - contractStatusChangedHandler.setDynamodbClient(client); - - FileInputStream fis = new FileInputStream(resourceFile); - - contractStatusChangedHandler.handleRequest(fis, outputStream, context); - ByteArrayInputStream inStream = new ByteArrayInputStream(outputStream.toByteArray()); - String response = new String(inStream.readAllBytes()); - assertTrue("Successful Response", response.contains("contract_id")); - - } - + @Mock + private Context context; + + @Mock + private DynamoDbClient dynamoDbClient; + + private ContractStatusChangedHandlerFunction contractStatusChangedHandler; + + @Before + public void setUp() { + contractStatusChangedHandler = new ContractStatusChangedHandlerFunction(); + contractStatusChangedHandler.setDynamodbClient(dynamoDbClient); + } + + @Test + public void shouldProcessValidContractStatusChangeEvent() throws IOException { + // Given + Path testEventPath = Paths.get("src/test/events/lambda/contract_status_changed.json"); + + // When + try (InputStream inputStream = Files.newInputStream(testEventPath); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + + contractStatusChangedHandler.handleRequest(inputStream, outputStream, context); + + // Then + String response = outputStream.toString(); + assertTrue("Response should contain contract_id", response.contains("contract_id")); + } + } } diff --git a/unicorn_contracts/ContractsService/pom.xml b/unicorn_contracts/ContractsService/pom.xml index f9b730e..ebd87fd 100644 --- a/unicorn_contracts/ContractsService/pom.xml +++ b/unicorn_contracts/ContractsService/pom.xml @@ -9,12 +9,12 @@ 17 17 - 2.27.21 - 1.18.0 - 3.13.0 - 5.13.0 + 2.32.29 + 1.20.2 + 3.16.1 + 5.18.0 4.13.2 - 1.1.1 + 1.1.2 @@ -37,36 +37,36 @@ com.amazonaws aws-lambda-java-core - 1.2.2 + 1.3.0 com.fasterxml.jackson.core jackson-databind - 2.15.2 + 2.18.4 com.fasterxml.jackson.core jackson-core - 2.15.2 + 2.18.4 com.fasterxml.jackson.core jackson-annotations - 2.15.2 + 2.18.4 org.apache.logging.log4j log4j-api - 2.20.0 + 2.25.1 org.apache.logging.log4j log4j-core - 2.20.0 + 2.25.1 @@ -123,17 +123,25 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.5.3 + false handler + + + org.apache.maven.surefire + surefire-junit4 + 3.5.3 + + org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 @@ -146,13 +154,13 @@ - dev.aspectj + org.codehaus.mojo aspectj-maven-plugin - 1.13.1 + 1.15.0 + 17 17 17 - 17 software.amazon.lambda @@ -168,6 +176,13 @@ + + + org.aspectj + aspectjtools + 1.9.22.1 + + @@ -179,9 +194,10 @@ org.apache.maven.plugins maven-compiler-plugin + 3.14.0 - 16 - 16 + 17 + 17 diff --git a/unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandler.java b/unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandler.java index e6c3722..c1f2918 100644 --- a/unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandler.java +++ b/unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandler.java @@ -12,139 +12,186 @@ import org.apache.logging.log4j.Logger; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.*; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.lambda.powertools.metrics.MetricsUtils; import software.amazon.lambda.powertools.tracing.Tracing; -import java.util.Date; -import java.util.HashMap; +import java.time.Instant; import java.util.Map; +import java.util.Optional; import java.util.UUID; public class ContractEventHandler implements RequestHandler { - // Initialise environment variables - private static String DDB_TABLE = System.getenv("DYNAMODB_TABLE"); - ObjectMapper objectMapper = new ObjectMapper(); + private static final String DDB_TABLE = System.getenv("DYNAMODB_TABLE"); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final Logger LOGGER = LogManager.getLogger(ContractEventHandler.class); + private static final String HTTP_METHOD_ATTR = "HttpMethod"; - DynamoDbClient dynamodbClient = DynamoDbClient.builder() - .build(); + private final DynamoDbClient dynamodbClient; - Logger logger = LogManager.getLogger(); - MetricsLogger metricsLogger = MetricsUtils.metricsLogger(); + public ContractEventHandler() { + this(DynamoDbClient.builder().build()); + } + + public ContractEventHandler(DynamoDbClient dynamodbClient) { + this.dynamodbClient = dynamodbClient; + } @Override + @Tracing public Void handleRequest(SQSEvent event, Context context) { + if (event == null || event.getRecords() == null) { + LOGGER.warn("Received null or empty SQS event"); + return null; + } for (SQSMessage msg : event.getRecords()) { - // cehck in message attributes about the http method (HttpMethod) - logger.debug(msg.toString()); - String httpMethod = msg.getMessageAttributes().get("HttpMethod").getStringValue(); - if ("POST".equalsIgnoreCase(httpMethod)) { - try { - createContract(msg.getBody()); - logger.debug("Contract Saved"); - } catch (JsonProcessingException jsonException) { - logger.error("Unknown Exception occoured: " + jsonException.getMessage()); - logger.fatal(jsonException); - jsonException.printStackTrace(); - } - - } else if ("PUT".equalsIgnoreCase(httpMethod)) { - try { - // update the event - updateContract(msg.getBody()); - } catch (JsonProcessingException jsonException) { - logger.error("Unknown Exception occoured: " + jsonException.getMessage()); - logger.fatal(jsonException); - jsonException.printStackTrace(); - } + processMessage(msg); + } + return null; + } + private void processMessage(SQSMessage msg) { + LOGGER.debug("Processing message: {}", msg.getMessageId()); + + try { + String httpMethod = extractHttpMethod(msg); + String body = msg.getBody(); + + if (body == null || body.trim().isEmpty()) { + LOGGER.warn("Empty message body for message: {}", msg.getMessageId()); + return; } + switch (httpMethod.toUpperCase()) { + case "POST": + createContract(body); + LOGGER.info("Contract created successfully for message: {}", msg.getMessageId()); + break; + case "PUT": + updateContract(body); + LOGGER.info("Contract updated successfully for message: {}", msg.getMessageId()); + break; + default: + LOGGER.warn("Unsupported HTTP method: {} for message: {}", httpMethod, msg.getMessageId()); + } + } catch (Exception e) { + LOGGER.error("Error processing message {}: {}", msg.getMessageId(), e.getMessage(), e); + throw new RuntimeException("Failed to process contract message", e); } - return null; + } + + private String extractHttpMethod(SQSMessage msg) { + return Optional.ofNullable(msg.getMessageAttributes()) + .map(attrs -> attrs.get(HTTP_METHOD_ATTR)) + .map(attr -> attr.getStringValue()) + .orElseThrow(() -> new IllegalArgumentException("Missing HttpMethod attribute")); } @Tracing - private void createContract(String strContract) throws JsonProcessingException { + private void createContract(String contractJson) throws JsonProcessingException { + Contract contract = OBJECT_MAPPER.readValue(contractJson, Contract.class); + validateContract(contract); + String contractId = UUID.randomUUID().toString(); - Long createDate = new Date().getTime(); - Contract contract = objectMapper.readValue(strContract, Contract.class); - - Map expressionValues = new HashMap<>(); - expressionValues.put(":cancelled", AttributeValue.builder().s(ContractStatusEnum.CANCELLED.name()).build()); - expressionValues.put(":closed", AttributeValue.builder().s(ContractStatusEnum.CLOSED.name()).build()); - expressionValues.put(":expired", AttributeValue.builder().s(ContractStatusEnum.EXPIRED.name()).build()); - - HashMap itemValues = new HashMap<>(); - itemValues.put("property_id", AttributeValue.builder().s(contract.getPropertyId()).build()); - itemValues.put("seller_name", AttributeValue.builder().s(contract.getSellerName()).build()); - itemValues.put("contract_created", - AttributeValue.builder().n(createDate.toString()).build()); - itemValues.put("contract_last_modified_on", - AttributeValue.builder().n(createDate.toString()).build()); - itemValues.put("contract_id", AttributeValue.builder().s(contractId).build()); - itemValues.put("contract_status", AttributeValue.builder().s(ContractStatusEnum.DRAFT.name()).build()); - - HashMap address = new HashMap<>(); - address.put("country", AttributeValue.builder().s(contract.getAddress().getCountry()).build()); - address.put("city", AttributeValue.builder().s(contract.getAddress().getCity()).build()); - address.put("street", AttributeValue.builder().s(contract.getAddress().getStreet()).build()); - address.put("number", - AttributeValue.builder().n(Integer.valueOf(contract.getAddress().getNumber()).toString()).build()); - - itemValues.put("address", - AttributeValue.builder().m(address).build()); - PutItemRequest putItemRequest = PutItemRequest.builder().tableName(DDB_TABLE) - .item(itemValues) - .conditionExpression( - "attribute_not_exists(property_id) OR contract_status IN (:cancelled , :closed, :expired)") + long timestamp = Instant.now().toEpochMilli(); + + Map item = Map.of( + "property_id", AttributeValue.builder().s(contract.getPropertyId()).build(), + "seller_name", AttributeValue.builder().s(contract.getSellerName()).build(), + "contract_created", AttributeValue.builder().n(String.valueOf(timestamp)).build(), + "contract_last_modified_on", AttributeValue.builder().n(String.valueOf(timestamp)).build(), + "contract_id", AttributeValue.builder().s(contractId).build(), + "contract_status", AttributeValue.builder().s(ContractStatusEnum.DRAFT.name()).build(), + "address", AttributeValue.builder().m(buildAddressMap(contract.getAddress())).build() + ); + + Map expressionValues = Map.of( + ":cancelled", AttributeValue.builder().s(ContractStatusEnum.CANCELLED.name()).build(), + ":closed", AttributeValue.builder().s(ContractStatusEnum.CLOSED.name()).build(), + ":expired", AttributeValue.builder().s(ContractStatusEnum.EXPIRED.name()).build() + ); + + PutItemRequest request = PutItemRequest.builder() + .tableName(DDB_TABLE) + .item(item) + .conditionExpression("attribute_not_exists(property_id) OR contract_status IN (:cancelled, :closed, :expired)") .expressionAttributeValues(expressionValues) .build(); + try { - dynamodbClient.putItem(putItemRequest); - } catch (ConditionalCheckFailedException conditionalCheckFailedException) { - logger.error("Unable to create contract for Property '" + contract.getPropertyId() - + "'.There already is a contract for this property in status " + ContractStatusEnum.DRAFT + " or " - + ContractStatusEnum.APPROVED); + dynamodbClient.putItem(request); + } catch (ConditionalCheckFailedException e) { + LOGGER.error("Active contract already exists for property: {}", contract.getPropertyId()); + throw new IllegalStateException("Contract already exists for property: " + contract.getPropertyId(), e); } } @Tracing - private void updateContract(String strContract) throws JsonProcessingException { - Contract contract = objectMapper.readValue(strContract, Contract.class); - logger.info("Property ID is : " + contract.getPropertyId()); - HashMap itemKey = new HashMap<>(); - - itemKey.put("property_id", AttributeValue.builder().s(contract.getPropertyId()).build()); - - Map expressionAttributeValues = new HashMap<>(); - expressionAttributeValues.put(":draft", AttributeValue.builder().s(ContractStatusEnum.DRAFT.name()).build()); - expressionAttributeValues.put(":t", AttributeValue.builder().s(ContractStatusEnum.APPROVED.name()).build()); - expressionAttributeValues.put(":m", AttributeValue.builder().s(String.valueOf(new Date().getTime())).build()); + private void updateContract(String contractJson) throws JsonProcessingException { + Contract contract = OBJECT_MAPPER.readValue(contractJson, Contract.class); + validateContractForUpdate(contract); + + LOGGER.info("Updating contract for Property ID: {}", contract.getPropertyId()); + + Map key = Map.of( + "property_id", AttributeValue.builder().s(contract.getPropertyId()).build() + ); + + Map expressionValues = Map.of( + ":draft", AttributeValue.builder().s(ContractStatusEnum.DRAFT.name()).build(), + ":approved", AttributeValue.builder().s(ContractStatusEnum.APPROVED.name()).build(), + ":modifiedDate", AttributeValue.builder().n(String.valueOf(Instant.now().toEpochMilli())).build() + ); UpdateItemRequest request = UpdateItemRequest.builder() .tableName(DDB_TABLE) - .key(itemKey) - .updateExpression("set contract_status=:t, modified_date=:m") - .expressionAttributeValues(expressionAttributeValues) - .conditionExpression( - "attribute_exists(property_id) AND contract_status IN (:draft)") + .key(key) + .updateExpression("SET contract_status = :approved, contract_last_modified_on = :modifiedDate") + .expressionAttributeValues(expressionValues) + .conditionExpression("attribute_exists(property_id) AND contract_status = :draft") .build(); + try { dynamodbClient.updateItem(request); - } catch (ConditionalCheckFailedException conditionalCheckFailedException) { - logger.error("Unable to update contract for Property '" + contract.getPropertyId() - + "'.Status is not in " + ContractStatusEnum.DRAFT); - } catch (ResourceNotFoundException conditionalCheckFailedException) { - logger.error("Unable to update contract for Property '" + contract.getPropertyId() - + "'. Not Found"); + } catch (ConditionalCheckFailedException e) { + LOGGER.error("Contract not in DRAFT status for property: {}", contract.getPropertyId()); + throw new IllegalStateException("Contract not in valid state for update: " + contract.getPropertyId(), e); + } catch (ResourceNotFoundException e) { + LOGGER.error("Contract not found for property: {}", contract.getPropertyId()); + throw new IllegalArgumentException("Contract not found: " + contract.getPropertyId(), e); } } - public void setDynamodbClient(DynamoDbClient dynamodbClient) { - this.dynamodbClient = dynamodbClient; + private void validateContract(Contract contract) { + if (contract == null) { + throw new IllegalArgumentException("Contract cannot be null"); + } + if (contract.getPropertyId() == null || contract.getPropertyId().trim().isEmpty()) { + throw new IllegalArgumentException("Property ID is required"); + } + if (contract.getSellerName() == null || contract.getSellerName().trim().isEmpty()) { + throw new IllegalArgumentException("Seller name is required"); + } + if (contract.getAddress() == null) { + throw new IllegalArgumentException("Address is required"); + } } + private void validateContractForUpdate(Contract contract) { + if (contract == null) { + throw new IllegalArgumentException("Contract cannot be null"); + } + if (contract.getPropertyId() == null || contract.getPropertyId().trim().isEmpty()) { + throw new IllegalArgumentException("Property ID is required for update"); + } + } + + private Map buildAddressMap(contracts.utils.Address address) { + return Map.of( + "country", AttributeValue.builder().s(address.getCountry()).build(), + "city", AttributeValue.builder().s(address.getCity()).build(), + "street", AttributeValue.builder().s(address.getStreet()).build(), + "number", AttributeValue.builder().n(String.valueOf(address.getNumber())).build() + ); + } } diff --git a/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Address.java b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Address.java index ec1be8d..d44a398 100644 --- a/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Address.java +++ b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Address.java @@ -1,14 +1,34 @@ package contracts.utils; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + public class Address { - String country; - String city; - String street; - int number; + @JsonProperty("country") + private String country; + + @JsonProperty("city") + private String city; + + @JsonProperty("street") + private String street; + + @JsonProperty("number") + private int number; + + public Address() {} + + public Address(String country, String city, String street, int number) { + this.country = country; + this.city = city; + this.street = street; + this.number = number; + } public String getCountry() { - return this.country; + return country; } public void setCountry(String country) { @@ -16,7 +36,7 @@ public void setCountry(String country) { } public String getCity() { - return this.city; + return city; } public void setCity(String city) { @@ -24,7 +44,7 @@ public void setCity(String city) { } public String getStreet() { - return this.street; + return street; } public void setStreet(String street) { @@ -32,11 +52,36 @@ public void setStreet(String street) { } public int getNumber() { - return this.number; + return number; } public void setNumber(int number) { this.number = number; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Address address = (Address) o; + return number == address.number && + Objects.equals(country, address.country) && + Objects.equals(city, address.city) && + Objects.equals(street, address.street); + } + + @Override + public int hashCode() { + return Objects.hash(country, city, street, number); + } + + @Override + public String toString() { + return "Address{" + + "country='" + country + '\'' + + ", city='" + city + '\'' + + ", street='" + street + '\'' + + ", number=" + number + + '}'; + } } diff --git a/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Contract.java b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Contract.java index 067842f..14069c3 100644 --- a/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Contract.java +++ b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Contract.java @@ -1,25 +1,43 @@ package contracts.utils; import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; public class Contract { - Address address; + @JsonProperty("address") + private Address address; + + @JsonProperty("property_id") @JsonAlias("property_id") - String propertyId; + private String propertyId; + + @JsonProperty("contract_id") @JsonAlias("contract_id") - String contractId; + private String contractId; + + @JsonProperty("seller_name") @JsonAlias("seller_name") - String sellerName; + private String sellerName; + + @JsonProperty("contract_status") @JsonAlias("contract_status") - ContractStatusEnum contractStatus; + private ContractStatusEnum contractStatus; + + @JsonProperty("contract_created") @JsonAlias("contract_created") - Long contractCreated; + private Long contractCreated; + + @JsonProperty("contract_last_modified_on") @JsonAlias("contract_last_modified_on") - Long contractLastModifiedOn; + private Long contractLastModifiedOn; + + public Contract() {} public Address getAddress() { - return this.address; + return address; } public void setAddress(Address address) { @@ -27,7 +45,7 @@ public void setAddress(Address address) { } public String getPropertyId() { - return this.propertyId; + return propertyId; } public void setPropertyId(String propertyId) { @@ -35,7 +53,7 @@ public void setPropertyId(String propertyId) { } public String getContractId() { - return this.contractId; + return contractId; } public void setContractId(String contractId) { @@ -43,7 +61,7 @@ public void setContractId(String contractId) { } public String getSellerName() { - return this.sellerName; + return sellerName; } public void setSellerName(String sellerName) { @@ -51,7 +69,7 @@ public void setSellerName(String sellerName) { } public ContractStatusEnum getContractStatus() { - return this.contractStatus; + return contractStatus; } public void setContractStatus(ContractStatusEnum contractStatus) { @@ -59,7 +77,7 @@ public void setContractStatus(ContractStatusEnum contractStatus) { } public Long getContractCreated() { - return this.contractCreated; + return contractCreated; } public void setContractCreated(Long contractCreated) { @@ -67,11 +85,34 @@ public void setContractCreated(Long contractCreated) { } public Long getContractLastModifiedOn() { - return this.contractLastModifiedOn; + return contractLastModifiedOn; } public void setContractLastModifiedOn(Long contractLastModifiedOn) { this.contractLastModifiedOn = contractLastModifiedOn; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Contract contract = (Contract) o; + return Objects.equals(propertyId, contract.propertyId) && + Objects.equals(contractId, contract.contractId); + } + + @Override + public int hashCode() { + return Objects.hash(propertyId, contractId); + } + + @Override + public String toString() { + return "Contract{" + + "propertyId='" + propertyId + '\'' + + ", contractId='" + contractId + '\'' + + ", sellerName='" + sellerName + '\'' + + ", contractStatus=" + contractStatus + + '}'; + } } diff --git a/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ContractStatusChangedEvent.java b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ContractStatusChangedEvent.java index 362d0bd..0d0995a 100644 --- a/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ContractStatusChangedEvent.java +++ b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ContractStatusChangedEvent.java @@ -2,15 +2,31 @@ import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Objects; + public class ContractStatusChangedEvent { + @JsonProperty("contract_last_modified_on") - Long contractLastModifiedOn; + private Long contractLastModifiedOn; + @JsonProperty("contract_id") - String contractId; + private String contractId; + @JsonProperty("property_id") - String propertyId; + private String propertyId; + @JsonProperty("contract_status") - ContractStatusEnum contractStatus; + private ContractStatusEnum contractStatus; + + public ContractStatusChangedEvent() {} + + public ContractStatusChangedEvent(String contractId, String propertyId, + ContractStatusEnum contractStatus, Long contractLastModifiedOn) { + this.contractId = contractId; + this.propertyId = propertyId; + this.contractStatus = contractStatus; + this.contractLastModifiedOn = contractLastModifiedOn; + } public Long getContractLastModifiedOn() { return contractLastModifiedOn; @@ -44,4 +60,27 @@ public void setContractStatus(ContractStatusEnum contractStatus) { this.contractStatus = contractStatus; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ContractStatusChangedEvent that = (ContractStatusChangedEvent) o; + return Objects.equals(contractId, that.contractId) && + Objects.equals(propertyId, that.propertyId); + } + + @Override + public int hashCode() { + return Objects.hash(contractId, propertyId); + } + + @Override + public String toString() { + return "ContractStatusChangedEvent{" + + "contractId='" + contractId + '\'' + + ", propertyId='" + propertyId + '\'' + + ", contractStatus=" + contractStatus + + ", contractLastModifiedOn=" + contractLastModifiedOn + + '}'; + } } diff --git a/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ResponseParser.java b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ResponseParser.java index 107963b..a17201a 100644 --- a/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ResponseParser.java +++ b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ResponseParser.java @@ -1,29 +1,63 @@ package contracts.utils; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import java.util.Map; +import java.util.Optional; public class ResponseParser { - Contract parseResponse(Map queryResponse) - throws JsonMappingException, JsonProcessingException { - Contract response = new Contract(); - ObjectMapper objectMapper = new ObjectMapper(); - Address address = objectMapper.readValue(queryResponse.get("address").s(), Address.class); - response.setAddress(address); - response.setContractCreated( - Long.valueOf(queryResponse.get("contract_created").s())); - response.setContractId(queryResponse.get("contract_id").s()); - response.setContractLastModifiedOn( - Long.valueOf(queryResponse.get("contract_last_modified_on").s())); - response.setContractStatus(ContractStatusEnum.valueOf(queryResponse.get("contract_status").s())); - response.setPropertyId(queryResponse.get("property_id").s()); - response.setSellerName(queryResponse.get("seller_name").s()); - return response; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public Contract parseResponse(Map queryResponse) throws JsonProcessingException { + if (queryResponse == null || queryResponse.isEmpty()) { + throw new IllegalArgumentException("Query response cannot be null or empty"); } + + Contract contract = new Contract(); + + // Parse address + Optional.ofNullable(queryResponse.get("address")) + .map(AttributeValue::s) + .ifPresent(addressJson -> { + try { + Address address = OBJECT_MAPPER.readValue(addressJson, Address.class); + contract.setAddress(address); + } catch (JsonProcessingException e) { + throw new RuntimeException("Failed to parse address", e); + } + }); + + // Parse other fields + Optional.ofNullable(queryResponse.get("contract_created")) + .map(AttributeValue::s) + .map(Long::valueOf) + .ifPresent(contract::setContractCreated); + + Optional.ofNullable(queryResponse.get("contract_id")) + .map(AttributeValue::s) + .ifPresent(contract::setContractId); + + Optional.ofNullable(queryResponse.get("contract_last_modified_on")) + .map(AttributeValue::s) + .map(Long::valueOf) + .ifPresent(contract::setContractLastModifiedOn); + + Optional.ofNullable(queryResponse.get("contract_status")) + .map(AttributeValue::s) + .map(ContractStatusEnum::valueOf) + .ifPresent(contract::setContractStatus); + + Optional.ofNullable(queryResponse.get("property_id")) + .map(AttributeValue::s) + .ifPresent(contract::setPropertyId); + + Optional.ofNullable(queryResponse.get("seller_name")) + .map(AttributeValue::s) + .ifPresent(contract::setSellerName); + + return contract; + } } diff --git a/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java b/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java index b3b10af..aec7b53 100644 --- a/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java +++ b/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java @@ -2,39 +2,108 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import com.amazonaws.services.lambda.runtime.tests.annotations.Event; +import com.amazonaws.services.lambda.runtime.events.SQSEvent.MessageAttribute; +import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; import org.junit.Before; -import org.junit.jupiter.params.ParameterizedTest; +import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.dynamodb.model.PutItemResponse; -import static org.mockito.Mockito.mock; +import java.util.Collections; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class CreateContractTests { - Context context; + @Mock + private Context context; + + @Mock + private DynamoDbClient dynamoDbClient; + + private ContractEventHandler handler; + + @Before + public void setUp() { + handler = new ContractEventHandler(dynamoDbClient); + } + + @Test + public void shouldProcessValidCreateEvent() { + // Given + SQSEvent event = createTestEvent("POST", + "{ \"address\": { \"country\": \"USA\", \"city\": \"Anytown\", \"street\": \"Main Street\", \"number\": 123 }, \"seller_name\": \"John Smith\", \"property_id\": \"usa/anytown/main-street/123\"}"); + + when(dynamoDbClient.putItem(any(PutItemRequest.class))) + .thenReturn(PutItemResponse.builder().build()); - ContractEventHandler handler; + // When + handler.handleRequest(event, context); - DynamoDbClient client; + // Then + verify(dynamoDbClient, times(1)).putItem(any(PutItemRequest.class)); + } - @Before - public void setUp() { + @Test + public void shouldHandleNullEvent() { + // When + handler.handleRequest(null, context); + + // Then + verifyNoInteractions(dynamoDbClient); + } - client = mock(DynamoDbClient.class); - context = mock(Context.class); + @Test + public void shouldHandleEmptyEvent() { + // Given + SQSEvent emptyEvent = new SQSEvent(); + + // When + handler.handleRequest(emptyEvent, context); + + // Then + verifyNoInteractions(dynamoDbClient); + } - } + @Test(expected = RuntimeException.class) + public void shouldThrowExceptionForMissingHttpMethod() { + // Given + SQSEvent event = createTestEventWithoutHttpMethod( + "{ \"address\": { \"country\": \"USA\", \"city\": \"Anytown\", \"street\": \"Main Street\", \"number\": 123 }, \"seller_name\": \"John Smith\", \"property_id\": \"usa/anytown/main-street/123\"}"); + + // When + handler.handleRequest(event, context); + } - @ParameterizedTest - @Event(value = "src/test/events/create_valid_event.json", type = SQSEvent.class) - public void validEvent(SQSEvent event) { - DynamoDbClient client = mock(DynamoDbClient.class); - handler = new ContractEventHandler(); - handler.setDynamodbClient(client); - handler.handleRequest(event, context); - } + private SQSEvent createTestEvent(String httpMethod, String body) { + SQSEvent event = new SQSEvent(); + SQSMessage message = new SQSMessage(); + message.setMessageId("test-message-id"); + message.setBody(body); + + MessageAttribute httpMethodAttr = new MessageAttribute(); + httpMethodAttr.setStringValue(httpMethod); + message.setMessageAttributes(Map.of("HttpMethod", httpMethodAttr)); + + event.setRecords(Collections.singletonList(message)); + return event; + } + private SQSEvent createTestEventWithoutHttpMethod(String body) { + SQSEvent event = new SQSEvent(); + SQSMessage message = new SQSMessage(); + message.setMessageId("test-message-id"); + message.setBody(body); + message.setMessageAttributes(Collections.emptyMap()); + + event.setRecords(Collections.singletonList(message)); + return event; + } } diff --git a/unicorn_web/Common/pom.xml b/unicorn_web/Common/pom.xml index 4178d15..2d86d38 100644 --- a/unicorn_web/Common/pom.xml +++ b/unicorn_web/Common/pom.xml @@ -22,13 +22,13 @@ software.amazon.awssdk dynamodb-enhanced - 2.27.21 + 2.32.29 compile com.fasterxml.jackson.core jackson-annotations - 2.15.3 + 2.18.4 compile diff --git a/unicorn_web/Common/src/main/java/dao/Property.java b/unicorn_web/Common/src/main/java/dao/Property.java index d4b53af..de0be86 100644 --- a/unicorn_web/Common/src/main/java/dao/Property.java +++ b/unicorn_web/Common/src/main/java/dao/Property.java @@ -1,6 +1,7 @@ package dao; import java.util.List; +import java.util.Objects; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -12,26 +13,29 @@ @DynamoDbBean public class Property { - String country; - String city; - String street; - String propertyNumber; - String description; - String contract; - Float listprice; - String currency; - List images; - String status; + private String country; + private String city; + private String street; + private String propertyNumber; + private String description; + private String contract; + private Float listprice; + private String currency; + private List images; + private String status; @JsonIgnore - String pk; + private String pk; @JsonIgnore - String sk; - String id; + private String sk; + private String id; @DynamoDbPartitionKey @DynamoDbAttribute("PK") public String getPk() { - return ("PROPERTY#" + getCountry() + "#" + getCity()).replace(' ', '-').toLowerCase(); + if (country == null || city == null) { + return pk; // Return stored value if components are null + } + return ("PROPERTY#" + country + "#" + city).replace(' ', '-').toLowerCase(); } public void setPk(String pk) { @@ -41,7 +45,10 @@ public void setPk(String pk) { @DynamoDbSortKey @DynamoDbAttribute("SK") public String getSk() { - return (getStreet() + "#" + getPropertyNumber()).replace(' ', '-').toLowerCase(); + if (street == null || propertyNumber == null) { + return sk; // Return stored value if components are null + } + return (street + "#" + propertyNumber).replace(' ', '-').toLowerCase(); } public void setSk(String sk) { @@ -51,7 +58,15 @@ public void setSk(String sk) { @JsonIgnore @software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnore public String getId() { - return (getPk() + '/' + getSk()).replace('#', '/'); + if (id != null) { + return id; + } + String partitionKey = getPk(); + String sortKey = getSk(); + if (partitionKey != null && sortKey != null) { + return (partitionKey + '/' + sortKey).replace('#', '/'); + } + return null; } public void setId(String id) { @@ -140,11 +155,28 @@ public void setStatus(String status) { } @Override - public String toString() { - return "Property [city=" + city + ", contract=" + contract + ", country=" + country + ", currency=" + currency - + ", description=" + description + ", id=" + getId() + ", images=" + images + ", listprice=" + listprice - + ", pk=" + getPk() + ", propertyNumber=" + propertyNumber + ", sk=" + getSk() + ", status=" + status - + ", street=" + street + "]"; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Property property = (Property) o; + return Objects.equals(getId(), property.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(getId()); } + @Override + public String toString() { + return "Property{" + + "country='" + country + '\'' + + ", city='" + city + '\'' + + ", street='" + street + '\'' + + ", propertyNumber='" + propertyNumber + '\'' + + ", status='" + status + '\'' + + ", listprice=" + listprice + + ", currency='" + currency + '\'' + + '}'; + } } diff --git a/unicorn_web/PublicationManagerService/pom.xml b/unicorn_web/PublicationManagerService/pom.xml index 9ec828c..ace1280 100644 --- a/unicorn_web/PublicationManagerService/pom.xml +++ b/unicorn_web/PublicationManagerService/pom.xml @@ -9,15 +9,14 @@ 17 17 - 2.27.21 - 1.18.0 - 3.13.0 - 5.13.0 + 2.32.29 + 1.20.2 + 3.16.1 + 5.18.0 4.13.2 - 1.1.1 - 1.2.3 - 3.13.0 - 2.27.21 + 1.1.2 + 1.3.0 + 2.32.29 @@ -118,36 +117,36 @@ com.amazonaws aws-lambda-java-core - 1.2.2 + ${aws-lambda-java-core.version} com.fasterxml.jackson.core jackson-databind - 2.15.2 + 2.18.4 com.fasterxml.jackson.core jackson-core - 2.15.2 + 2.18.4 com.fasterxml.jackson.core jackson-annotations - 2.15.2 + 2.18.4 org.apache.logging.log4j log4j-api - 2.20.0 + 2.25.1 org.apache.logging.log4j log4j-core - 2.20.0 + 2.25.1 common @@ -164,7 +163,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.5.3 handler @@ -174,7 +173,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 @@ -187,12 +186,10 @@ - dev.aspectj + org.codehaus.mojo aspectj-maven-plugin - 1.13.1 + 1.15.0 - 17 - 17 17 @@ -216,11 +213,18 @@ + + + org.aspectj + aspectjtools + 1.9.22.1 + + org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.14.0 17 17 diff --git a/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationApprovedEventHandler.java b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationApprovedEventHandler.java index ef882de..1faef8b 100644 --- a/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationApprovedEventHandler.java +++ b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationApprovedEventHandler.java @@ -5,6 +5,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; +import java.util.Map; import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.databind.ObjectMapper; @@ -27,74 +28,100 @@ import schema.unicorn_approvals.publicationevaluationcompleted.PublicationEvaluationCompleted; /** - * Function checks for the existence of a contract status entry for a specified - * search. - * If an entry exists, pause the workflow, and update the record with task - * token. + * Processes publication evaluation completed events and updates property status. */ public class PublicationApprovedEventHandler { - Logger logger = LogManager.getLogger(); + private static final Logger logger = LogManager.getLogger(PublicationApprovedEventHandler.class); + + private final String tableName = System.getenv("DYNAMODB_TABLE"); + private final DynamoDbAsyncTable propertyTable; + private final ObjectMapper objectMapper = new ObjectMapper(); - final String TABLE_NAME = System.getenv("DYNAMODB_TABLE"); + public PublicationApprovedEventHandler() { DynamoDbAsyncClient dynamodbClient = DynamoDbAsyncClient.builder() - .httpClientBuilder(NettyNioAsyncHttpClient.builder()) - .build(); + .httpClientBuilder(NettyNioAsyncHttpClient.builder()) + .build(); DynamoDbEnhancedAsyncClient enhancedClient = DynamoDbEnhancedAsyncClient.builder() - .dynamoDbClient(dynamodbClient) - .build(); - - DynamoDbAsyncTable propertyTable = enhancedClient.table(TABLE_NAME, - TableSchema.fromBean(Property.class)); - - @Tracing - @Metrics(captureColdStart = true) - @Logging(logEvent = true) - public void handleRequest(InputStream inputStream, OutputStream outputStream, - Context context) throws IOException { - - AWSEvent event = Marshaller.unmarshalEvent(inputStream, - PublicationEvaluationCompleted.class); - - String propertyId = event.getDetail().getPropertyId(); - String evaluationResult = event.getDetail().getEvaluationResult(); - - publicationApproved(evaluationResult, propertyId); - - ObjectMapper objectMapper = new ObjectMapper(); - OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); - writer.write(objectMapper.writeValueAsString("'result': 'Successfully updated search status'")); - writer.close(); - + .dynamoDbClient(dynamodbClient) + .build(); + + this.propertyTable = enhancedClient.table(tableName, TableSchema.fromBean(Property.class)); + } + + @Tracing + @Metrics(captureColdStart = true) + @Logging(logEvent = true) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + try { + AWSEvent event = Marshaller.unmarshalEvent(inputStream, + PublicationEvaluationCompleted.class); + + if (event.getDetail() == null) { + throw new IllegalArgumentException("Event detail is null"); + } + + String propertyId = event.getDetail().getPropertyId(); + String evaluationResult = event.getDetail().getEvaluationResult(); + + if (propertyId == null || propertyId.trim().isEmpty()) { + throw new IllegalArgumentException("Property ID is null or empty"); + } + + if (evaluationResult == null || evaluationResult.trim().isEmpty()) { + throw new IllegalArgumentException("Evaluation result is null or empty"); + } + + updatePropertyStatus(evaluationResult, propertyId); + + String response = objectMapper.writeValueAsString( + Map.of("result", "Successfully updated property status")); + + try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { + writer.write(response); + } + + } catch (Exception e) { + logger.error("Error processing publication evaluation event", e); + String errorResponse = objectMapper.writeValueAsString( + Map.of("error", "Failed to process event: " + e.getMessage())); + + try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { + writer.write(errorResponse); + } + throw new RuntimeException("Event processing failed", e); } - - @Tracing - private void publicationApproved(String evaluationResult, String propertyId) { - - String[] splitString = propertyId.split("/"); - String country = splitString[0]; - String city = splitString[1]; - String street = splitString[2]; - String number = splitString[3]; - String strPartionKey = ("search#" + country + "#" + city).replace(' ', '-').toLowerCase(); - String strSortKey = (street + "#" + number).replace(' ', '-').toLowerCase(); - - Key key = Key.builder().partitionValue(strPartionKey).sortValue(strSortKey).build(); - Property existingProperty = propertyTable.getItem(key).join(); - - if (existingProperty == null) { - logger.error("Property not found for ID: {}", propertyId); - throw new RuntimeException("Property not found with ID: " + propertyId); - } - - // Always set the search number explicitly to ensure it's correct - existingProperty.setPropertyNumber(number); - existingProperty.setStatus(evaluationResult); - - logger.info("Updating search with status: {} and propertyNumber: {}", - evaluationResult, existingProperty.getPropertyNumber()); - propertyTable.putItem(existingProperty).join(); + } + + @Tracing + private void updatePropertyStatus(String evaluationResult, String propertyId) { + try { + String[] parts = propertyId.split("/"); + if (parts.length != 4) { + throw new IllegalArgumentException("Invalid property ID format: " + propertyId); + } + + String partitionKey = ("search#" + parts[0] + "#" + parts[1]).replace(' ', '-').toLowerCase(); + String sortKey = (parts[2] + "#" + parts[3]).replace(' ', '-').toLowerCase(); + + Key key = Key.builder().partitionValue(partitionKey).sortValue(sortKey).build(); + Property existingProperty = propertyTable.getItem(key).join(); + + if (existingProperty == null) { + logger.error("Property not found for ID: {}", propertyId); + throw new RuntimeException("Property not found with ID: " + propertyId); + } + + existingProperty.setPropertyNumber(parts[3]); + existingProperty.setStatus(evaluationResult); + + logger.info("Updating property {} with status: {}", propertyId, evaluationResult); + propertyTable.putItem(existingProperty).join(); + + } catch (Exception e) { + logger.error("Failed to update property status for ID: {}", propertyId, e); + throw new RuntimeException("Property update failed", e); } - + } } diff --git a/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/RequestApprovalFunction.java b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/RequestApprovalFunction.java index ed954fe..47e944f 100644 --- a/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/RequestApprovalFunction.java +++ b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/RequestApprovalFunction.java @@ -9,7 +9,6 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.regex.Matcher; import java.util.regex.Pattern; import com.amazonaws.services.lambda.runtime.Context; @@ -43,152 +42,168 @@ import software.amazon.lambda.powertools.tracing.Tracing; /** - * Validates the integrity of the search content + * Validates property requests and sends approval events. */ public class RequestApprovalFunction { - Logger logger = LogManager.getLogger(); - Set noActionSet = new HashSet(Arrays.asList("APPROVED")); - String SERVICE = "Unicorn.Web"; - String EXPRESSION = "[a-z-]+\\/[a-z-]+\\/[a-z][a-z0-9-]*\\/[0-9-]+"; - String TARGET_STATE = "PENDING"; - Pattern pattern = Pattern.compile(EXPRESSION); - - String TABLE_NAME = System.getenv("DYNAMODB_TABLE"); - String EVENT_BUS = System.getenv("EVENT_BUS"); - - DynamoDbAsyncClient dynamodbClient = DynamoDbAsyncClient.builder() - .httpClientBuilder(NettyNioAsyncHttpClient.builder()) - .build(); - - DynamoDbEnhancedAsyncClient enhancedClient = DynamoDbEnhancedAsyncClient.builder() - .dynamoDbClient(dynamodbClient) - .build(); - - DynamoDbAsyncTable propertyTable = enhancedClient.table(TABLE_NAME, - TableSchema.fromBean(Property.class)); - - EventBridgeAsyncClient eventBridgeClient = EventBridgeAsyncClient.builder() - .httpClientBuilder(NettyNioAsyncHttpClient.builder()) - .build(); - - ObjectMapper objectMapper = new ObjectMapper(); + private static final Logger logger = LogManager.getLogger(RequestApprovalFunction.class); + private static final Set NO_ACTION_STATUSES = new HashSet<>(Arrays.asList("APPROVED")); + private static final String PROPERTY_ID_PATTERN = "[a-z-]+\\/[a-z-]+\\/[a-z][a-z0-9-]*\\/[0-9-]+"; + private static final String CONTENT_TYPE = "application/json"; + + private final Pattern propertyIdPattern = Pattern.compile(PROPERTY_ID_PATTERN); + private final String tableName = System.getenv("DYNAMODB_TABLE"); + private final String eventBus = System.getenv("EVENT_BUS"); + + private final DynamoDbAsyncTable propertyTable; + private final EventBridgeAsyncClient eventBridgeClient; + private final ObjectMapper objectMapper = new ObjectMapper(); + + public RequestApprovalFunction() { + DynamoDbAsyncClient dynamodbClient = DynamoDbAsyncClient.builder() + .httpClientBuilder(NettyNioAsyncHttpClient.builder()) + .build(); + + DynamoDbEnhancedAsyncClient enhancedClient = DynamoDbEnhancedAsyncClient.builder() + .dynamoDbClient(dynamodbClient) + .build(); + + this.propertyTable = enhancedClient.table(tableName, TableSchema.fromBean(Property.class)); + this.eventBridgeClient = EventBridgeAsyncClient.builder() + .httpClientBuilder(NettyNioAsyncHttpClient.builder()) + .build(); + } @Tracing @Metrics(captureColdStart = true) @Logging(logEvent = true, correlationIdPath = CorrelationIdPathConstants.API_GATEWAY_REST) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, - final Context context) throws JsonProcessingException { - { - - Map headers = new HashMap<>(); - headers.put("Content-Type", "application/json"); - headers.put("X-Custom-Header", "application/json"); - APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() - .withHeaders(headers); + final Context context) { + try { + if (input.getBody() == null || input.getBody().trim().isEmpty()) { + return createErrorResponse(400, "Request body is required"); + } JsonNode rootNode = objectMapper.readTree(input.getBody()); - String propertyId = rootNode.get("property_id").asText(); - Matcher matcher = pattern.matcher(propertyId); - boolean valid = matcher.matches(); - if (!valid) { - return response - .withBody("Input invalid; must conform to regular expression: " + EXPRESSION) - .withStatusCode(500); + JsonNode propertyIdNode = rootNode.get("property_id"); + + if (propertyIdNode == null) { + return createErrorResponse(400, "property_id field is required"); } - String[] splitString = propertyId.split("/"); - String country = splitString[0]; - String city = splitString[1]; - String street = splitString[2]; - String number = splitString[3]; - String strPartionKey = ("search#" + country + "#" + city).replace(' ', '-').toLowerCase(); - String strSortKey = (street + "#" + number).replace(' ', '-').toLowerCase(); - try { - List properties = queryTable(strPartionKey, strSortKey); - if (properties.size() <= 0) { - return response - .withBody("No search found in database with the requested search id") - .withStatusCode(500); - } - Property property = properties.get(0); - if (noActionSet.contains(property.getStatus())) { - return response - .withStatusCode(200) - .withBody("'result': 'Property is already " + property.getStatus() + "; no action taken'"); - } - sendEvent(property); - - } catch (Exception e) { - return response - .withBody("Error in searching") - .withStatusCode(500); + + String propertyId = propertyIdNode.asText(); + if (!propertyIdPattern.matcher(propertyId).matches()) { + return createErrorResponse(400, "Invalid property_id format. Must match: " + PROPERTY_ID_PATTERN); } - return response - .withStatusCode(200) - .withBody("'result': 'Approval Requested'"); + PropertyComponents components = parsePropertyId(propertyId); + List properties = queryTable(components.partitionKey, components.sortKey); + + if (properties.isEmpty()) { + return createErrorResponse(404, "Property not found"); + } + + Property property = properties.get(0); + if (NO_ACTION_STATUSES.contains(property.getStatus())) { + return createSuccessResponse("Property is already " + property.getStatus() + "; no action taken"); + } + + sendEvent(property); + return createSuccessResponse("Approval requested successfully"); + + } catch (JsonProcessingException e) { + logger.error("Invalid JSON in request body", e); + return createErrorResponse(400, "Invalid JSON format"); + } catch (Exception e) { + logger.error("Error processing approval request", e); + return createErrorResponse(500, "Internal server error"); } + } + private PropertyComponents parsePropertyId(String propertyId) { + String[] parts = propertyId.split("/"); + String partitionKey = ("search#" + parts[0] + "#" + parts[1]).replace(' ', '-').toLowerCase(); + String sortKey = (parts[2] + "#" + parts[3]).replace(' ', '-').toLowerCase(); + return new PropertyComponents(partitionKey, sortKey); } - public List queryTable(String partitionkey, String sortKey) throws Exception { + private APIGatewayProxyResponseEvent createSuccessResponse(String message) { + String body = String.format("{\"result\":\"%s\"}", message); + return new APIGatewayProxyResponseEvent() + .withStatusCode(200) + .withHeaders(Map.of("Content-Type", CONTENT_TYPE)) + .withBody(body); + } - try { - if (partitionkey == null || sortKey == null) { - throw new Exception("Invalid Input"); - } - List result = new ArrayList(); - SdkPublisher properties = null; + private APIGatewayProxyResponseEvent createErrorResponse(int statusCode, String message) { + String body = String.format("{\"error\":\"%s\"}", message); + return new APIGatewayProxyResponseEvent() + .withStatusCode(statusCode) + .withHeaders(Map.of("Content-Type", CONTENT_TYPE)) + .withBody(body); + } - Key key = Key.builder().partitionValue(partitionkey).sortValue(sortKey).build(); + private static class PropertyComponents { + final String partitionKey; + final String sortKey; - QueryConditional queryConditional = QueryConditional.sortBeginsWith(key); - QueryEnhancedRequest request = QueryEnhancedRequest.builder().queryConditional(queryConditional) - .build(); - properties = propertyTable.query(request).items(); + PropertyComponents(String partitionKey, String sortKey) { + this.partitionKey = partitionKey; + this.sortKey = sortKey; + } + } - CompletableFuture future = properties.subscribe(res -> { - // Add response to the list - result.add(res); - }); - future.get(); + private List queryTable(String partitionKey, String sortKey) throws Exception { + if (partitionKey == null || sortKey == null) { + throw new IllegalArgumentException("Partition key and sort key cannot be null"); + } - return result; + List result = new ArrayList<>(); + Key key = Key.builder().partitionValue(partitionKey).sortValue(sortKey).build(); + QueryConditional queryConditional = QueryConditional.sortBeginsWith(key); + QueryEnhancedRequest request = QueryEnhancedRequest.builder() + .queryConditional(queryConditional) + .build(); + try { + SdkPublisher properties = propertyTable.query(request).items(); + CompletableFuture future = properties.subscribe(result::add); + future.get(); + return result; } catch (DynamoDbException | InterruptedException | ExecutionException e) { - throw new Exception(e.getMessage()); + logger.error("Error querying DynamoDB", e); + throw new Exception("Database query failed: " + e.getMessage()); } } @Tracing @Metrics - public String sendEvent(Property property) - throws JsonProcessingException { - + private void sendEvent(Property property) throws JsonProcessingException { RequestApproval event = new RequestApproval(); event.setPropertyId(property.getId()); + Address address = new Address(); address.setCity(property.getCity()); address.setCountry(property.getCountry()); address.setNumber(property.getPropertyNumber()); event.setAddress(address); - String event_string = objectMapper.writeValueAsString(event); - - List requestEntries = new ArrayList(); + String eventString = objectMapper.writeValueAsString(event); - requestEntries.add(PutEventsRequestEntry.builder() - .eventBusName(EVENT_BUS) + PutEventsRequestEntry requestEntry = PutEventsRequestEntry.builder() + .eventBusName(eventBus) .source("Unicorn.Web") .resources(property.getId()) .detailType("PublicationApprovalRequested") - .detail(event_string).build()); + .detail(eventString) + .build(); - PutEventsRequest eventsRequest = PutEventsRequest.builder().entries(requestEntries).build(); + PutEventsRequest eventsRequest = PutEventsRequest.builder() + .entries(requestEntry) + .build(); eventBridgeClient.putEvents(eventsRequest).join(); - - return event_string; + logger.info("Event sent successfully for property: {}", property.getId()); } } @@ -212,11 +227,13 @@ public Address getAddress() { public void setAddress(Address address) { this.address = address; } - } class Address { String country; + String city; + String state; + String number; public String getCountry() { return country; @@ -249,9 +266,4 @@ public String getNumber() { public void setNumber(String number) { this.number = number; } - - String city; - String state; - String number; - } diff --git a/unicorn_web/PublicationManagerService/target/classes/publicationmanager/Address.class b/unicorn_web/PublicationManagerService/target/classes/publicationmanager/Address.class deleted file mode 100644 index a3589f574f05b94ea890af1e28e7206a346a0d61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1114 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 C@;O=n diff --git a/unicorn_web/PublicationManagerService/target/classes/publicationmanager/RequestApproval.class b/unicorn_web/PublicationManagerService/target/classes/publicationmanager/RequestApproval.class deleted file mode 100644 index 5087c6df352a2072339219cf01cec9d0be954643..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 990 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 diff --git a/unicorn_web/SearchService/pom.xml b/unicorn_web/SearchService/pom.xml index 2e7b01e..b627290 100644 --- a/unicorn_web/SearchService/pom.xml +++ b/unicorn_web/SearchService/pom.xml @@ -9,15 +9,14 @@ 17 17 - 2.27.21 - 1.18.0 - 3.13.0 - 5.13.0 + 2.32.29 + 1.20.2 + 3.16.1 + 5.18.0 4.13.2 - 1.1.1 - 1.2.3 - 3.13.0 - 2.27.21 + 1.1.2 + 1.3.0 + 2.32.29 @@ -124,36 +123,36 @@ com.amazonaws aws-lambda-java-core - 1.2.2 + ${aws-lambda-java-core.version} com.fasterxml.jackson.core jackson-databind - 2.15.2 + 2.18.4 com.fasterxml.jackson.core jackson-core - 2.15.2 + 2.18.4 com.fasterxml.jackson.core jackson-annotations - 2.15.2 + 2.18.4 org.apache.logging.log4j log4j-api - 2.20.0 + 2.25.1 org.apache.logging.log4j log4j-core - 2.20.0 + 2.25.1 common @@ -170,7 +169,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.5.3 handler @@ -180,7 +179,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 @@ -193,12 +192,10 @@ - dev.aspectj + org.codehaus.mojo aspectj-maven-plugin - 1.13.1 + 1.15.0 - 17 - 17 17 @@ -222,11 +219,18 @@ + + + org.aspectj + aspectjtools + 1.9.22.1 + + org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.14.0 17 17 diff --git a/unicorn_web/SearchService/src/main/java/search/PropertySearchFunction.java b/unicorn_web/SearchService/src/main/java/search/PropertySearchFunction.java index 4191570..883f512 100644 --- a/unicorn_web/SearchService/src/main/java/search/PropertySearchFunction.java +++ b/unicorn_web/SearchService/src/main/java/search/PropertySearchFunction.java @@ -29,172 +29,132 @@ import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; import software.amazon.lambda.powertools.metrics.Metrics; -import software.amazon.lambda.powertools.metrics.MetricsUtils; import software.amazon.lambda.powertools.tracing.Tracing; import software.amazon.lambda.powertools.logging.CorrelationIdPathConstants; import software.amazon.lambda.powertools.logging.Logging; /** - * Handler for requests to Lambda function. + * Handler for property search requests. */ public class PropertySearchFunction implements RequestHandler { private static final Logger logger = LogManager.getLogger(PropertySearchFunction.class); + private static final String APPROVED_STATUS = "APPROVED"; + private static final String CONTENT_TYPE = "application/json"; - String TABLE_NAME = System.getenv("DYNAMODB_TABLE"); + private final String tableName = System.getenv("DYNAMODB_TABLE"); + private final DynamoDbAsyncTable propertyTable; + private final ObjectMapper objectMapper = new ObjectMapper(); - DynamoDbAsyncClient dynamodbClient = DynamoDbAsyncClient.builder() - .httpClientBuilder(NettyNioAsyncHttpClient.builder()) - .build(); + public PropertySearchFunction() { + DynamoDbAsyncClient dynamodbClient = DynamoDbAsyncClient.builder() + .httpClientBuilder(NettyNioAsyncHttpClient.builder()) + .build(); - DynamoDbEnhancedAsyncClient enhancedClient = DynamoDbEnhancedAsyncClient.builder() - .dynamoDbClient(dynamodbClient) - .build(); + DynamoDbEnhancedAsyncClient enhancedClient = DynamoDbEnhancedAsyncClient.builder() + .dynamoDbClient(dynamodbClient) + .build(); - DynamoDbAsyncTable propertyTable = enhancedClient.table(TABLE_NAME, - TableSchema.fromBean(Property.class)); - - final String SERVICE_NAME = System.getenv("POWERTOOLS_SERVICE_NAME"); - final String METRICS_NAMESPACE = System.getenv("POWERTOOLS_METRICS_NAMESPACE"); - final String EVENT_BUS = System.getenv("EVENT_BUS"); - - MetricsLogger metricsLogger = MetricsUtils.metricsLogger(); - ObjectMapper objectMapper = new ObjectMapper(); + this.propertyTable = enhancedClient.table(tableName, TableSchema.fromBean(Property.class)); + } @Tracing @Metrics(captureColdStart = true) @Logging(logEvent = true, correlationIdPath = CorrelationIdPathConstants.API_GATEWAY_REST) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + try { + if (!"GET".equalsIgnoreCase(input.getHttpMethod())) { + return createErrorResponse(400, "Method not allowed"); + } + + Map pathParams = input.getPathParameters(); + if (pathParams == null || pathParams.get("country") == null || pathParams.get("city") == null) { + return createErrorResponse(400, "Missing required path parameters"); + } - Map headers = new HashMap<>(); - headers.put("Content-Type", "application/json"); - headers.put("X-Custom-Header", "application/json"); - APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() - .withHeaders(headers); - String method = input.getHttpMethod(); - if (!method.equalsIgnoreCase("get")) { - return response - .withStatusCode(400) - .withBody("{ \"message\": \"ErrorInRequest\", \"requestdetails\": \"Input Invalid\" }"); + String partitionKey = buildPartitionKey(pathParams.get("country"), pathParams.get("city")); + String sortKey = buildSortKey(input.getResource(), pathParams); + + List properties = queryTable(partitionKey, sortKey); + String responseBody = objectMapper.writeValueAsString(properties); + + return createSuccessResponse(responseBody); + + } catch (Exception e) { + logger.error("Error processing request", e); + return createErrorResponse(500, "Internal server error"); } - String requestPath = input.getResource(); - String responseString = null; - String strPartitionKey = ("search#" + input.getPathParameters().get("country") + "#" - + input.getPathParameters().get("city")).replace(' ', '-').toLowerCase(); + } - String strSortKey = null; - switch (requestPath) { + private String buildPartitionKey(String country, String city) { + return ("search#" + country + "#" + city).replace(' ', '-').toLowerCase(); + } + + private String buildSortKey(String resource, Map pathParams) { + switch (resource) { case "/search/{country}/{city}": - // code to call - logger.info("path is " + requestPath); - - try { - List result = queryTable(strPartitionKey, null); - responseString = objectMapper.writeValueAsString(result); - } catch (Exception e) { - return response - .withStatusCode(500) - .withBody( - "{ \"message\": \"ErrorInRequest\", \"requestdetails\": \"Cannot Process Request\" }"); - } - break; + return null; case "/search/{country}/{city}/{street}": - // code to call - logger.info("path is " + requestPath); - strSortKey = input.getPathParameters().get("street"); - strSortKey = strSortKey.replace(' ', '-').toLowerCase(); - - try { - List result = queryTable(strPartitionKey, strSortKey); - responseString = objectMapper.writeValueAsString(result); - } catch (Exception e) { - return response - .withStatusCode(500) - .withBody( - "{ \"message\": \"ErrorInRequest\", \"requestdetails\": \"Cannot Process Request\" }"); - } - break; + return pathParams.get("street").replace(' ', '-').toLowerCase(); case "/properties/{country}/{city}/{street}/{number}": - logger.info("path is " + requestPath); - strSortKey = input.getPathParameters().get("street") + "#" + input.getPathParameters().get("number"); - strSortKey = strSortKey.replace(' ', '-').toLowerCase(); - - try { - List result = queryTable(strPartitionKey, strSortKey); - responseString = objectMapper.writeValueAsString(result); - } catch (Exception e) { - return response - .withStatusCode(500) - .withBody( - "{ \"message\": \"ErrorInRequest\", \"requestdetails\": \"Cannot Process Request\" }"); - } - break; + return (pathParams.get("street") + "#" + pathParams.get("number")).replace(' ', '-').toLowerCase(); default: - return response - .withStatusCode(400) - .withBody("{ \"message\": \"ErrorInRequest\", \"requestdetails\": \"Input Invalid\" }"); - + throw new IllegalArgumentException("Unsupported resource path: " + resource); } + } - return response + private APIGatewayProxyResponseEvent createSuccessResponse(String body) { + return new APIGatewayProxyResponseEvent() .withStatusCode(200) - .withBody(responseString); + .withHeaders(Map.of("Content-Type", CONTENT_TYPE)) + .withBody(body); } - public List queryTable(String partitionkey, String sortKey) throws Exception { + private APIGatewayProxyResponseEvent createErrorResponse(int statusCode, String message) { + String errorBody = String.format("{\"error\":\"%s\"}", message); + return new APIGatewayProxyResponseEvent() + .withStatusCode(statusCode) + .withHeaders(Map.of("Content-Type", CONTENT_TYPE)) + .withBody(errorBody); + } - try { - if (partitionkey == null) { - throw new Exception("Invalid Input"); - } - List result = new ArrayList(); - SdkPublisher properties = null; - - AttributeValue attributeValue = AttributeValue.builder() - .s("APPROVED") - .build(); - Map expressionValues = new HashMap<>(); - expressionValues.put(":value", attributeValue); - - Map expressionNames = new HashMap<>(); - expressionNames.put("#property_status", "status"); - - Expression expression = Expression.builder() - .expressionNames(expressionNames) - .expression("#property_status = :value") - .expressionValues(expressionValues) - .build(); - - if (sortKey != null) { - Key key = Key.builder().partitionValue(partitionkey).sortValue(sortKey).build(); - - QueryConditional queryConditional = QueryConditional.sortBeginsWith(key); - QueryEnhancedRequest request = QueryEnhancedRequest.builder().queryConditional(queryConditional) - .filterExpression(expression).build(); - properties = propertyTable.query(request).items(); - - } else { - Key key = Key.builder().partitionValue(partitionkey).build(); - QueryConditional queryConditional = QueryConditional.keyEqualTo(key); - QueryEnhancedRequest request = QueryEnhancedRequest.builder().queryConditional(queryConditional) - .filterExpression(expression).build(); - properties = propertyTable.query(request).items(); - } + private List queryTable(String partitionKey, String sortKey) throws Exception { + if (partitionKey == null) { + throw new IllegalArgumentException("Partition key cannot be null"); + } - CompletableFuture future = properties.subscribe(res -> { - // Add response to the list - result.add(res); - }); - future.get(); + List result = new ArrayList<>(); + + Expression filterExpression = Expression.builder() + .expressionNames(Map.of("#property_status", "status")) + .expression("#property_status = :value") + .expressionValues(Map.of(":value", AttributeValue.builder().s(APPROVED_STATUS).build())) + .build(); + + QueryConditional queryConditional; + if (sortKey != null) { + Key key = Key.builder().partitionValue(partitionKey).sortValue(sortKey).build(); + queryConditional = QueryConditional.sortBeginsWith(key); + } else { + Key key = Key.builder().partitionValue(partitionKey).build(); + queryConditional = QueryConditional.keyEqualTo(key); + } - return result; + QueryEnhancedRequest request = QueryEnhancedRequest.builder() + .queryConditional(queryConditional) + .filterExpression(filterExpression) + .build(); + try { + SdkPublisher properties = propertyTable.query(request).items(); + CompletableFuture future = properties.subscribe(result::add); + future.get(); + return result; } catch (DynamoDbException | InterruptedException | ExecutionException e) { - throw new Exception(e.getMessage()); + logger.error("Error querying DynamoDB", e); + throw new Exception("Database query failed: " + e.getMessage()); } } - }