diff --git a/unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandler.java b/unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandlerFunction.java similarity index 90% rename from unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandler.java rename to unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandlerFunction.java index 4de4200..7e26d13 100644 --- a/unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandler.java +++ b/unicorn_contracts/ContractsService/src/main/java/contracts/ContractEventHandlerFunction.java @@ -13,28 +13,31 @@ import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.*; import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.tracing.Tracing; import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.tracing.Tracing; import java.time.Instant; import java.util.Map; import java.util.Optional; import java.util.UUID; -public class ContractEventHandler implements RequestHandler { +public class ContractEventHandlerFunction implements RequestHandler { 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 Logger LOGGER = LogManager.getLogger(ContractEventHandlerFunction.class); private static final String HTTP_METHOD_ATTR = "HttpMethod"; private final DynamoDbClient dynamodbClient; - public ContractEventHandler() { + public ContractEventHandlerFunction() { this(DynamoDbClient.builder().build()); } - public ContractEventHandler(DynamoDbClient dynamodbClient) { + public ContractEventHandlerFunction(DynamoDbClient dynamodbClient) { this.dynamodbClient = dynamodbClient; } @@ -56,11 +59,11 @@ public Void handleRequest(SQSEvent event, Context context) { 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; @@ -95,15 +98,15 @@ private String extractHttpMethod(SQSMessage msg) { private void createContract(String contractJson) throws JsonProcessingException { Contract contract = OBJECT_MAPPER.readValue(contractJson, Contract.class); validateContract(contract); - + String contractId = UUID.randomUUID().toString(); - long timestamp = Instant.now().toEpochMilli(); + String timestamp = Instant.now().toString(); 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_created", AttributeValue.builder().s(timestamp).build(), + "contract_last_modified_on", AttributeValue.builder().s(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() @@ -124,6 +127,7 @@ private void createContract(String contractJson) throws JsonProcessingException try { dynamodbClient.putItem(request); + MetricsFactory.getMetricsInstance().addMetric("ContractCreated", 1, MetricUnit.COUNT); } catch (ConditionalCheckFailedException e) { LOGGER.error("Active contract already exists for property: {}", contract.getPropertyId()); throw new IllegalStateException("Contract already exists for property: " + contract.getPropertyId(), e); @@ -134,7 +138,7 @@ private void createContract(String contractJson) throws JsonProcessingException 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( @@ -144,7 +148,7 @@ private void updateContract(String contractJson) throws JsonProcessingException 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() + ":modifiedDate", AttributeValue.builder().s(Instant.now().toString()).build() ); UpdateItemRequest request = UpdateItemRequest.builder() @@ -157,6 +161,7 @@ private void updateContract(String contractJson) throws JsonProcessingException try { dynamodbClient.updateItem(request); + MetricsFactory.getMetricsInstance().addMetric("ContractUpdated", 1, MetricUnit.COUNT); } 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); 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 14069c3..379af4d 100644 --- a/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Contract.java +++ b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/Contract.java @@ -28,11 +28,11 @@ public class Contract { @JsonProperty("contract_created") @JsonAlias("contract_created") - private Long contractCreated; - + private String contractCreated; + @JsonProperty("contract_last_modified_on") @JsonAlias("contract_last_modified_on") - private Long contractLastModifiedOn; + private String contractLastModifiedOn; public Contract() {} @@ -76,19 +76,19 @@ public void setContractStatus(ContractStatusEnum contractStatus) { this.contractStatus = contractStatus; } - public Long getContractCreated() { + public String getContractCreated() { return contractCreated; } - public void setContractCreated(Long contractCreated) { + public void setContractCreated(String contractCreated) { this.contractCreated = contractCreated; } - public Long getContractLastModifiedOn() { + public String getContractLastModifiedOn() { return contractLastModifiedOn; } - public void setContractLastModifiedOn(Long contractLastModifiedOn) { + public void setContractLastModifiedOn(String contractLastModifiedOn) { this.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 a17201a..d40abe8 100644 --- a/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ResponseParser.java +++ b/unicorn_contracts/ContractsService/src/main/java/contracts/utils/ResponseParser.java @@ -33,7 +33,6 @@ public Contract parseResponse(Map queryResponse) throws // Parse other fields Optional.ofNullable(queryResponse.get("contract_created")) .map(AttributeValue::s) - .map(Long::valueOf) .ifPresent(contract::setContractCreated); Optional.ofNullable(queryResponse.get("contract_id")) @@ -42,7 +41,6 @@ public Contract parseResponse(Map queryResponse) throws Optional.ofNullable(queryResponse.get("contract_last_modified_on")) .map(AttributeValue::s) - .map(Long::valueOf) .ifPresent(contract::setContractLastModifiedOn); Optional.ofNullable(queryResponse.get("contract_status")) diff --git a/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java b/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java index aec7b53..8f3d2be 100644 --- a/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java +++ b/unicorn_contracts/ContractsService/src/test/java/contracts/CreateContractTests.java @@ -28,11 +28,11 @@ public class CreateContractTests { @Mock private DynamoDbClient dynamoDbClient; - private ContractEventHandler handler; + private ContractEventHandlerFunction handler; @Before public void setUp() { - handler = new ContractEventHandler(dynamoDbClient); + handler = new ContractEventHandlerFunction(dynamoDbClient); } @Test diff --git a/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationEvaluationEventHandler.java b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationEvaluationEventHandler.java index 964cb3f..eb63cdc 100644 --- a/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationEvaluationEventHandler.java +++ b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/PublicationEvaluationEventHandler.java @@ -22,6 +22,9 @@ import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; import software.amazon.lambda.powertools.tracing.Tracing; import schema.unicorn_approvals.publicationevaluationcompleted.marshaller.Marshaller; import schema.unicorn_approvals.publicationevaluationcompleted.AWSEvent; @@ -98,6 +101,11 @@ public void handleRequest(InputStream inputStream, OutputStream outputStream, Co private void updatePropertyStatus(String evaluationResult, String propertyId) { logger.info("Updating property status for property ID: {}", propertyId); logger.info("Evaluation result: {}", evaluationResult); + if (!"APPROVED".equalsIgnoreCase(evaluationResult) && !"DECLINED".equalsIgnoreCase(evaluationResult)) { + logger.warn("Unknown evaluation result '{}', skipping DynamoDB update", evaluationResult); + return; + } + try { String[] parts = propertyId.split("/"); if (parts.length != 4) { @@ -124,7 +132,8 @@ private void updatePropertyStatus(String evaluationResult, String propertyId) { logger.info("Updating property {} with status: {}", propertyId, evaluationResult); propertyTable.putItem(existingProperty).join(); - + MetricsFactory.getMetricsInstance().addMetric("PropertiesApproved", 1, MetricUnit.COUNT); + } 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 83773a1..29551be 100644 --- a/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/RequestApprovalFunction.java +++ b/unicorn_web/PublicationManagerService/src/main/java/publicationmanager/RequestApprovalFunction.java @@ -37,6 +37,9 @@ import software.amazon.awssdk.services.eventbridge.model.PutEventsRequestEntry; import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; import software.amazon.lambda.powertools.tracing.Tracing; /** @@ -48,6 +51,8 @@ public class RequestApprovalFunction { 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 SERVICE_NAMESPACE = System.getenv("SERVICE_NAMESPACE"); + 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"); @@ -188,19 +193,25 @@ 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); + event.setStatus("PENDING"); + event.setListprice(property.getListprice()); + event.setImages(property.getImages()); + event.setDescription(property.getDescription()); + event.setCurrency(property.getCurrency()); + String eventString = objectMapper.writeValueAsString(event); logger.info("Event payload created: {}", eventString); PutEventsRequestEntry requestEntry = PutEventsRequestEntry.builder() .eventBusName(eventBus) - .source("Unicorn.Web") + .source(SERVICE_NAMESPACE) .resources(property.getId()) .detailType("PublicationApprovalRequested") .detail(eventString) @@ -213,9 +224,10 @@ private void sendEvent(Property property) throws JsonProcessingException { logger.debug("Sending event to EventBridge bus: {}", eventBus); try { eventBridgeClient.putEvents(eventsRequest).join(); + MetricsFactory.getMetricsInstance().addMetric("ApprovalsRequested", 1, MetricUnit.COUNT); logger.info("Event sent successfully for property: {}", property.getId()); } catch (Exception e) { - logger.error("Failed to send event to EventBridge for property: {}, bus: {}", + logger.error("Failed to send event to EventBridge for property: {}, bus: {}", property.getId(), eventBus, e); throw e; } @@ -227,6 +239,21 @@ class RequestApproval { String propertyId; Address address; + @JsonProperty("status") + String status; + + @JsonProperty("listprice") + Float listprice; + + @JsonProperty("images") + java.util.List images; + + @JsonProperty("description") + String description; + + @JsonProperty("currency") + String currency; + public String getPropertyId() { return propertyId; } @@ -242,6 +269,46 @@ public Address getAddress() { public void setAddress(Address address) { this.address = address; } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Float getListprice() { + return listprice; + } + + public void setListprice(Float listprice) { + this.listprice = listprice; + } + + public java.util.List getImages() { + return images; + } + + public void setImages(java.util.List images) { + this.images = images; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } } class Address {