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