// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 using System.Text.Json; using System.Web; using Amazon; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.DataModel; using Amazon.DynamoDBv2.DocumentModel; 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.Metrics; using AWS.Lambda.Powertools.Tracing; using Unicorn.Web.Common; 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.Web.SearchService; public class PropertySearchFunction { private readonly IDynamoDBContext _dynamoDbContext; /// /// Default constructor. Initialises global variables for function. /// /// Init exception public PropertySearchFunction() { // Instrument all AWS SDK calls AWSSDKHandler.RegisterXRayForAllServices(); var dynamodbTable = Environment.GetEnvironmentVariable("DYNAMODB_TABLE") ?? ""; if (string.IsNullOrEmpty(dynamodbTable)) throw new Exception("Environment variable DYNAMODB_TABLE is not defined."); AWSConfigsDynamoDB.Context.TypeMappings[typeof(PropertyRecord)] = new TypeMapping(typeof(PropertyRecord), dynamodbTable); var config = new DynamoDBContextConfig { Conversion = DynamoDBEntryConversion.V2 }; _dynamoDbContext = new DynamoDBContext(new AmazonDynamoDBClient(), config); } /// /// Testing constructor for PropertySearchFunction /// /// public PropertySearchFunction(IDynamoDBContext dynamoDbContext) { _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) { var response = new APIGatewayProxyResponse { Body = string.Empty, StatusCode = 200, Headers = new Dictionary { { "Content-Type", "application/json" }, { "X-Custom-Header", "application/json" } } }; if (!string.Equals(apigProxyEvent.HttpMethod, "GET", StringComparison.CurrentCultureIgnoreCase)) { var body = new Dictionary { { "message", "ErrorInRequest" }, { "requestDetails", "Input Invalid" } }; response.StatusCode = 400; response.Body = JsonSerializer.Serialize(body); return response; } try { var requestPath = (apigProxyEvent.Resource ?? "").ToLower(); var partitionKey = string.Empty; var sortKey = string.Empty; switch (requestPath) { case "/search/{country}/{city}": partitionKey = PropertyRecordHelper.GetPartitionKey ( GetPathParameter(apigProxyEvent, "country"), GetPathParameter(apigProxyEvent, "city") ); break; case "/search/{country}/{city}/{street}": partitionKey = PropertyRecordHelper.GetPartitionKey ( GetPathParameter(apigProxyEvent, "country"), GetPathParameter(apigProxyEvent, "city") ); sortKey = GetPathParameter(apigProxyEvent, "street").Replace(" ", "-").ToLower(); break; case "/properties/{country}/{city}/{street}/{number}": partitionKey = PropertyRecordHelper.GetPartitionKey ( GetPathParameter(apigProxyEvent, "country"), GetPathParameter(apigProxyEvent, "city") ); sortKey = PropertyRecordHelper.GetSortKey( GetPathParameter(apigProxyEvent, "street"), GetPathParameter(apigProxyEvent, "number") ); break; } Logger.LogInformation($"Path is: {requestPath}"); Logger.LogInformation($"PartitionKey is: {partitionKey} and SortKey is: {sortKey}"); if (string.IsNullOrEmpty(partitionKey)) { var body = new Dictionary { { "message", "ErrorInRequest" }, { "requestDetails", "Cannot Process Request" } }; response.StatusCode = 500; response.Body = JsonSerializer.Serialize(body); return response; } var result = await QueryTableAsync(partitionKey, sortKey).ConfigureAwait(false); response.Body = JsonSerializer.Serialize(result.Select(PropertyRecordHelper.ToDto)); } catch (Exception e) { Logger.LogError(e); var body = new Dictionary { { "message", "ErrorInRequest" }, { "requestDetails", "Cannot Process Request" } }; response.StatusCode = 500; response.Body = JsonSerializer.Serialize(body); } return response; } private static string GetPathParameter(APIGatewayProxyRequest apigProxyEvent, string parameterName) { var parameterValue = apigProxyEvent.PathParameters.ContainsKey(parameterName) ? apigProxyEvent.PathParameters[parameterName] : apigProxyEvent.PathParameters .Where(x => string.Equals(x.Key, parameterName, StringComparison.CurrentCultureIgnoreCase)) .Select(x => x.Value).FirstOrDefault(); return string.IsNullOrWhiteSpace(parameterValue) ? string.Empty : HttpUtility.UrlDecode(parameterValue); } private async Task> QueryTableAsync(string partitionKey, string sortKey) { var filter = new QueryFilter(PropertyNames.PrimaryKey, QueryOperator.Equal, partitionKey); if (!string.IsNullOrWhiteSpace(sortKey)) filter.AddCondition(PropertyNames.SortKey, QueryOperator.BeginsWith, sortKey); filter.AddCondition(PropertyNames.Status, QueryOperator.Equal, PropertyStatus.Approved); return await _dynamoDbContext .FromQueryAsync(new QueryOperationConfig { Filter = filter }) .GetRemainingAsync() .ConfigureAwait(false); } }