// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using Amazon;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using Amazon.Util;
using Amazon.XRay.Recorder.Handlers.AwsSdk;
using AWS.Lambda.Powertools.Logging;
using AWS.Lambda.Powertools.Tracing;
using AWS.Lambda.Powertools.Metrics;
using DynamoDBContextConfig = Amazon.DynamoDBv2.DataModel.DynamoDBContextConfig;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace Unicorn.Contracts.ContractService;
public class CreateContractFunction
{
private readonly IDynamoDBContext _dynamoDbContext;
private readonly IPublisher _publisher;
///
/// Default constructor for CreateContractFunction
///
public CreateContractFunction()
{
// Instrument all AWS SDK calls
AWSSDKHandler.RegisterXRayForAllServices();
_publisher = new Publisher();
var dynamodbTable = Environment.GetEnvironmentVariable("DYNAMODB_TABLE");
if (string.IsNullOrEmpty(dynamodbTable))
{
throw new Exception("Environment variable DYNAMODB_TABLE is not defined.");
}
AWSConfigsDynamoDB.Context.TypeMappings[typeof(Contract)] =
new TypeMapping(typeof(Contract), dynamodbTable);
var config = new DynamoDBContextConfig { Conversion = DynamoDBEntryConversion.V2 };
_dynamoDbContext = new DynamoDBContext(new AmazonDynamoDBClient(), config);
}
///
/// Testing constructor for CreateContractFunction
///
///
///
public CreateContractFunction(IDynamoDBContext dynamoDbContext, IPublisher publisher)
{
_publisher = publisher;
_dynamoDbContext = dynamoDbContext;
}
///
/// Lambda Handler for creating new Contracts.
///
/// API Gateway Lambda Proxy Request that triggers the function.
/// The context for the Lambda function.
/// API Gateway Lambda Proxy Response.
[Logging(LogEvent = true)]
[Metrics(CaptureColdStart = true)]
[Tracing(CaptureMode = TracingCaptureMode.ResponseAndError)]
public async Task FunctionHandler(APIGatewayProxyRequest apigProxyEvent,
ILambdaContext context)
{
// Validate request body
CreateContractRequest contractRequest;
try
{
contractRequest = ValidateRequestBody(apigProxyEvent);
}
catch (EventValidationException e)
{
Logger.LogError(e.Message);
throw;
}
// check to see if we already have a contract with the same ID
var existingContract = await TryGetContractById(contractRequest.PropertyId ?? "").ConfigureAwait(false);
if (existingContract != null)
{
return new APIGatewayProxyResponse
{
Body = JsonSerializer.Serialize(existingContract),
StatusCode = (int)HttpStatusCode.OK,
Headers = new Dictionary { { "Content-Type", "application/json" } }
};
}
// new contract
var contract = new Contract
{
PropertyId = contractRequest.PropertyId,
ContractId = Guid.NewGuid(),
Address = contractRequest.Address,
SellerName = contractRequest.SellerName
};
Logger.AppendKey("Contract", contract);
Logger.LogInformation("Creating new contract");
// Create entry in DDB for new contract
await CreateContract(contract).ConfigureAwait(false);
// Publish ContractStatusChanged event
await _publisher.PublishEvent(contract).ConfigureAwait(false);
// return generated contract ID back to user:
return new APIGatewayProxyResponse
{
Body = JsonSerializer.Serialize(contract),
StatusCode = (int)HttpStatusCode.OK,
Headers = new Dictionary { { "Content-Type", "application/json" } }
};
}
///
/// Parse data to create a new contract.
///
/// The API Gateway Proxy Request
/// Instance of , the strongly-typed representation of the API Gateway body
private CreateContractRequest ValidateRequestBody(APIGatewayProxyRequest apigProxyEvent)
{
Logger.LogInformation("Parsing API Gateway request body to CreateContractRequest type.");
CreateContractRequest? request;
try
{
if (apigProxyEvent.Body == null)
throw new EventValidationException("API Gateway.");
request = JsonSerializer.Deserialize(apigProxyEvent.Body, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
});
}
catch (Exception e)
{
throw new EventValidationException("Unable to convert APIGatewayProxyRequest to CreateContractRequest.", e);
}
if (request == null)
throw new EventValidationException("Unable to convert APIGatewayProxyRequest to CreateContractRequest.");
if (string.IsNullOrEmpty(request.PropertyId))
throw new EventValidationException("Request does not contain a Property ID.");
return request;
}
///
/// Queries database for existing contract
///
/// Property ID
/// Instance of
[Tracing(SegmentName = "Existing contract")]
private async Task TryGetContractById(string propertyId)
{
try
{
Logger.LogInformation($"Contract for Property ID: `{propertyId}` already exists");
var contract = await _dynamoDbContext.LoadAsync(propertyId);
Logger.LogInformation($"Contract for Property ID: `{propertyId}` already exists");
return contract;
}
catch (Exception e)
{
Logger.LogInformation($"Error loading contract {propertyId}: {e.Message}");
}
return null;
}
///
///
///
///
///
[Tracing(SegmentName = "Create Contract")]
private async Task CreateContract(Contract contract)
{
try
{
Logger.LogInformation($"Saving contract for Property ID: {contract.PropertyId}");
await _dynamoDbContext.SaveAsync(contract);
}
catch (Exception e)
{
Logger.LogError(e.Message);
throw;
}
}
}