Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<SQSEvent, Void> {
public class ContractEventHandlerFunction implements RequestHandler<SQSEvent, Void> {

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;
}

Expand All @@ -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;
Expand Down Expand Up @@ -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<String, AttributeValue> 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()
Expand All @@ -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);
Expand All @@ -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<String, AttributeValue> key = Map.of(
Expand All @@ -144,7 +148,7 @@ private void updateContract(String contractJson) throws JsonProcessingException
Map<String, AttributeValue> 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()
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}

Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ public Contract parseResponse(Map<String, AttributeValue> 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"))
Expand All @@ -42,7 +41,6 @@ public Contract parseResponse(Map<String, AttributeValue> 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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -48,6 +51,8 @@ public class RequestApprovalFunction {
private static final Set<String> 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");
Expand Down Expand Up @@ -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)
Expand All @@ -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;
}
Expand All @@ -227,6 +239,21 @@ class RequestApproval {
String propertyId;
Address address;

@JsonProperty("status")
String status;

@JsonProperty("listprice")
Float listprice;

@JsonProperty("images")
java.util.List<String> images;

@JsonProperty("description")
String description;

@JsonProperty("currency")
String currency;

public String getPropertyId() {
return propertyId;
}
Expand All @@ -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<String> getImages() {
return images;
}

public void setImages(java.util.List<String> 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 {
Expand Down
Loading