using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using Amazon.CognitoIdentity; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.DataModel; using Amazon.DynamoDBv2.Model; using RMC.Core.Exceptions; using UnityEngine; #pragma warning disable CS1998 // async/await namespace RMC.Backend.Baas.Aws { // Class Attributes ---------------------------------- /// /// Backend subsystem related to database tables. /// public class JawsDatabaseSubsystem : IDatabaseSubsystem { // Events ---------------------------------------- public DatabaseEvent OnInitialized { get; } = new DatabaseEvent(); public DatabaseEvent OnTableRead { get; } = new DatabaseEvent(); public DatabaseEvent OnItemCreated { get; } = new DatabaseEvent(); public DatabaseEvent OnItemRead { get; } = new DatabaseEvent(); public DatabaseEvent OnItemUpdated { get; } = new DatabaseEvent(); // Properties ------------------------------------ public bool IsInitialized { get { return _isInitialized; } } public Configuration Configuration { get { return _configuration; } } /// /// Provide RAW access to power users /// public AmazonDynamoDBClient RawAmazonDynamoDBClient { get { RequireIsInitialized(); RequireConfiguration(); return _amazonDynamoDBClient; } } // Fields ---------------------------------------- private bool _isInitialized = false; private JawsConfiguration _configuration; private AmazonDynamoDBClient _amazonDynamoDBClient; private DynamoDBContext _databaseContext; // Initialization -------------------------------- public JawsDatabaseSubsystem() { } public async Task InitializeAsync() { if (_isInitialized) { return; } _configuration = Resources.Load("JawsConfiguration"); RequireConfiguration(); var credentials = new CognitoAWSCredentials ( _configuration.IdentityPoolId, _configuration.RegionEndpoint ); _amazonDynamoDBClient = new AmazonDynamoDBClient(credentials, _configuration.RegionEndpoint); _isInitialized = true; OnInitialized.Invoke(this); } public void RequireIsInitialized() { if (!IsInitialized) { throw new NotInitializedException(this); } } public void RequireConfiguration() { if (_configuration == null) { throw new Exception("RequireConfiguration() failed. Configuration must exist."); } if (!_configuration.IsValid()) { throw new Exception("RequireConfiguration() failed. Configuration.IsValid() must be true."); } } // Methods --------------------------------------- public async Task TableReadAsync(string tableName) { TableReadResponse response = new TableReadResponse(); var request = new DescribeTableRequest { TableName = tableName }; try { var describeTableResponse = await _amazonDynamoDBClient.DescribeTableAsync(request); if (describeTableResponse.HttpStatusCode == HttpStatusCode.OK) { response.IsSuccess = true; response.Table = new Table(); response.Table.TableName = describeTableResponse.Table.TableName; response.Table.ItemCount = describeTableResponse.Table.ItemCount; } else { response.IsSuccess = false; response.ErrorMessage = $"TableReadAsync() failed. Not Ok."; } } catch (Exception e) { response.IsSuccess = false; response.ErrorMessage = $"TableReadAsync() failed. error = {e.Message}."; } OnTableRead.Invoke(this); return response; } public async Task ItemReadAsync(Table table, User user, InventoryItem item) { ItemReadResponse response = new ItemReadResponse(); if (table == null || string.IsNullOrEmpty(table.TableName)) { response.IsSuccess = false; response.ErrorMessage = $"ItemReadAsync() failed. Table.TableName must not be '{table?.TableName}'."; return response; } if (user == null || string.IsNullOrEmpty(user.Email)) { response.IsSuccess = false; response.ErrorMessage = $"ItemReadAsync() failed. User.Email must not be '{user?.Email}'."; return response; } if (item == null || string.IsNullOrEmpty(item.Name)) { response.IsSuccess = false; response.ErrorMessage = $"ItemReadAsync() failed. Item.Name must not be '{item?.Name}'."; return response; } var key = new Dictionary { { "email".ToLower(), new AttributeValue { S = user.Email } } }; var request = new GetItemRequest() { TableName = table.TableName, Key = key, }; try { var getItemResponse = await _amazonDynamoDBClient.GetItemAsync(request); if (getItemResponse.HttpStatusCode == HttpStatusCode.OK && getItemResponse.Item.ContainsKey(item.Name.ToLower())) { var itemOfInterest = getItemResponse.Item[item.Name.ToLower()]; response.IsSuccess = true; response.Table = table; //Caution: This is not FRESH from the server response.Item = new InventoryItem(); response.Item.Name = itemOfInterest.S; response.Item.Quantity = int.Parse(itemOfInterest.N); } else { response.IsSuccess = false; response.ErrorMessage = $"ItemReadAsync() failed. Not Ok."; } } catch (Exception e) { response.IsSuccess = false; response.ErrorMessage = $"ItemReadAsync() failed. error = {e.Message}."; } OnItemRead.Invoke(this); return response; } public async Task ItemCreateAsync(Table table, User user, InventoryItem item) { ItemCreateResponse response = new ItemCreateResponse(); if (table == null || string.IsNullOrEmpty(table.TableName)) { response.IsSuccess = false; response.ErrorMessage = $"ItemCreateAsync() failed. Table.TableName must not be '{table?.TableName}'."; return response; } if (user == null || string.IsNullOrEmpty(user.Email)) { response.IsSuccess = false; response.ErrorMessage = $"ItemCreateAsync() failed. User.Email must not be '{user?.Email}'."; return response; } if (item == null || string.IsNullOrEmpty(item.Name)) { response.IsSuccess = false; response.ErrorMessage = $"ItemCreateAsync() failed. Item.Name must not be '{item?.Name}'."; return response; } var itemAttributes = new Dictionary { { "email".ToLower(), new AttributeValue { S = user.Email } }, { item.Name.ToLower(), new AttributeValue { N = item.Quantity.ToString() } } }; var request = new PutItemRequest { TableName = table.TableName, Item = itemAttributes }; try { var putItemResponse = await _amazonDynamoDBClient.PutItemAsync(request); if (putItemResponse.HttpStatusCode == HttpStatusCode.OK) { response.IsSuccess = true; response.Table = table; //Caution: This is not FRESH from the server response.Item = item; //Caution: This is not FRESH from the server // Check the attributes that were changed if (putItemResponse.Attributes != null && putItemResponse.Attributes.Count > 0) { foreach (var attribute in putItemResponse.Attributes) { Debug.Log($"Attribute {attribute.Key} was changed to {attribute.Value}"); } } } else { response.IsSuccess = false; response.ErrorMessage = $"ItemCreateAsync() failed. Not Ok."; } } catch (Exception e) { response.IsSuccess = false; response.ErrorMessage = $"ItemCreateAsync() failed. error = {e.Message}."; } OnItemCreated.Invoke(this); return response; } public async Task ItemUpdateAsync(Table table, User user, InventoryItem item) { ItemUpdateResponse response = new ItemUpdateResponse(); if (table == null || string.IsNullOrEmpty(table.TableName)) { response.IsSuccess = false; response.ErrorMessage = $"ItemUpdateAsync() failed. Table.TableName must not be '{table?.TableName}'."; return response; } if (user == null || string.IsNullOrEmpty(user.Email)) { response.IsSuccess = false; response.ErrorMessage = $"ItemUpdateAsync() failed. User.Email must not be '{user?.Email}'."; return response; } if (item == null || string.IsNullOrEmpty(item.Name)) { response.IsSuccess = false; response.ErrorMessage = $"ItemUpdateAsync() failed. Item.Name must not be '{item?.Name}'."; return response; } var key = new Dictionary { { "email".ToLower(), new AttributeValue { S = user.Email } } }; var attributeUpdates = new Dictionary { { item.Name.ToLower(), new AttributeValueUpdate { Action = AttributeAction.PUT, Value = new AttributeValue { N = item.Quantity.ToString() } } } }; var request = new UpdateItemRequest { TableName = table.TableName, Key = key, AttributeUpdates = attributeUpdates }; try { var updateItemResponse = await _amazonDynamoDBClient.UpdateItemAsync(request); if (updateItemResponse.HttpStatusCode == HttpStatusCode.OK) { response.IsSuccess = true; response.Table = table; //Caution: This is not FRESH from the server response.Item = item; //Caution: This is not FRESH from the server } else { response.IsSuccess = false; response.ErrorMessage = $"ItemUpdateAsync() failed. Not Ok."; } } catch (Exception e) { response.IsSuccess = false; response.ErrorMessage = $"ItemUpdateAsync() failed. error = {e.Message}."; } OnItemUpdated.Invoke(this); return response; } } }