// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 using System.Text.Json; using Amazon; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.DataModel; using Amazon.Lambda.Core; using Amazon.Lambda.DynamoDBEvents; using Amazon.StepFunctions; using Amazon.StepFunctions.Model; using Amazon.Util; using Amazon.XRay.Recorder.Handlers.AwsSdk; using AWS.Lambda.Powertools.Logging; using AWS.Lambda.Powertools.Metrics; using AWS.Lambda.Powertools.Tracing; using DynamoDBContextConfig = Amazon.DynamoDBv2.DataModel.DynamoDBContextConfig; namespace Unicorn.Properties.PropertiesService; /// /// Represents the AWS Lambda function that processes stream events from the /// Unicorn Properties Contract Status table /// public class PropertiesApprovalSyncFunction { private readonly AmazonStepFunctionsClient _amazonStepFunctionsClient; private readonly IDynamoDBContext _dynamoDbContext; /// /// Default constructor. Initialises global variables for function. /// public PropertiesApprovalSyncFunction() { // Instrument all AWS SDK calls AWSSDKHandler.RegisterXRayForAllServices(); // Initialise DynamoDB client var dynamodbTable = Environment.GetEnvironmentVariable("CONTRACT_STATUS_TABLE"); if (string.IsNullOrEmpty(dynamodbTable)) { throw new Exception("Environment variable CONTRACT_STATUS_TABLE is not defined."); } AWSConfigsDynamoDB.Context.TypeMappings[typeof(ContractStatusItem)] = new TypeMapping(typeof(ContractStatusItem), dynamodbTable); var config = new DynamoDBContextConfig { Conversion = DynamoDBEntryConversion.V2 }; _dynamoDbContext = new DynamoDBContext(new AmazonDynamoDBClient(), config); // Initialise Step Functions client _amazonStepFunctionsClient = new AmazonStepFunctionsClient(); } /// /// Testing constructor /// /// /// public PropertiesApprovalSyncFunction(AmazonStepFunctionsClient mockStepFunctionsClient, IDynamoDBContext mockDynamoDbContext) { _dynamoDbContext = mockDynamoDbContext; _amazonStepFunctionsClient = mockStepFunctionsClient; } /// /// Event handler for processing DynamoDB stream data for changes to Contract Status /// /// Instance of /// Lambda Context runtime methods and attributes [Logging(LogEvent = true)] [Metrics(CaptureColdStart = true)] [Tracing(CaptureMode = TracingCaptureMode.ResponseAndError)] public async Task FunctionHandler(DynamoDBEvent? dynamoEvent, ILambdaContext context) { // process DDB foreach (var record in dynamoEvent.Records) { var propertyId = record.Dynamodb.Keys["PropertyId"].S; var item = await GetContractStatusItem(propertyId).ConfigureAwait(false); if (string.IsNullOrEmpty(item.SfnWaitApprovedTaskToken)) { Logger.LogInformation("Contract status has no approval token, nothing to sync"); return; } if (item.ContractStatus != "APPROVED") { Logger.LogInformation("Contract status for property is not APPROVED, cannot sync."); return; } if (item.ContractStatus == "APPROVED") { var sendTaskSuccessRequest = new SendTaskSuccessRequest { TaskToken = item.SfnWaitApprovedTaskToken, Output = JsonSerializer.Serialize(item) }; try { await _amazonStepFunctionsClient.SendTaskSuccessAsync(sendTaskSuccessRequest).ConfigureAwait(false); } catch (Exception e) { Logger.LogError(e); throw; } } else { Logger.LogInformation("Contract status for property is not APPROVED, cannot sync."); return; } } Logger.LogInformation("Property approval sync complete."); } /// /// Retrieves the contract status for a specifies property /// /// Property ID /// Instance of /// [Tracing(SegmentName = "Get Contract Status")] private async Task GetContractStatusItem(string propertyId) { ContractStatusItem? item; try { Logger.LogInformation($"Getting Contract Status for {propertyId}"); item = await _dynamoDbContext.LoadAsync(propertyId).ConfigureAwait(false); Logger.LogInformation($"Found contact: {item != null}"); } catch (Exception e) { Logger.LogInformation($"Error loading contract status {propertyId}: {e.Message}"); item = null; } if (item == null) { throw new ContractStatusNotFoundException($"Could not find property with ID: {propertyId}"); } return item; } }