diff options
author | crupest <crupest@outlook.com> | 2022-12-21 11:51:25 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2022-12-21 11:51:25 +0800 |
commit | 13a1aef3364c3ecfed41d30ceebdcd255ae8141c (patch) | |
tree | 411350f425f767fe6a8b3d1f753c713ea2b0a0e6 /docker | |
parent | 4264d8135c066081dbabb412db17bf537ceab86e (diff) | |
download | crupest-13a1aef3364c3ecfed41d30ceebdcd255ae8141c.tar.gz crupest-13a1aef3364c3ecfed41d30ceebdcd255ae8141c.tar.bz2 crupest-13a1aef3364c3ecfed41d30ceebdcd255ae8141c.zip |
Develop secret api. v51
Diffstat (limited to 'docker')
8 files changed, 155 insertions, 189 deletions
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudServiceTest.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudServiceTest.cs new file mode 100644 index 0000000..0c42c67 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudServiceTest.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Logging.Abstractions; + +namespace CrupestApi.Commons.Crud.Tests; + +public class CrudServiceTest +{ + private readonly SqliteMemoryConnectionFactory _memoryConnectionFactory = new SqliteMemoryConnectionFactory(); + + private readonly CrudService<TestEntity> _crudService; + + public CrudServiceTest() + { + var columnTypeProvider = new ColumnTypeProvider(); + var tableInfoFactory = new TableInfoFactory(columnTypeProvider, NullLoggerFactory.Instance); + var dbConnectionFactory = new SqliteMemoryConnectionFactory(); + + _crudService = new CrudService<TestEntity>( + tableInfoFactory, dbConnectionFactory, NullLoggerFactory.Instance); + } + + [Fact] + public void CrudTest() + { + _crudService.Create(new TestEntity() + { + Name = "crupest", + Age = 18, + }); + } + + +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudTestBase.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudTestBase.cs deleted file mode 100644 index 98c0dfd..0000000 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudTestBase.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Net; -using CrupestApi.Commons.Secrets; -using Microsoft.AspNetCore.TestHost; - -namespace CrupestApi.Commons.Crud.Tests; - -public abstract class CrudTestBase<TEntity> : IAsyncDisposable where TEntity : class -{ - protected readonly WebApplication _app; - - protected readonly string _path; - protected readonly string? _authKey; - - protected readonly HttpClient _client; - - public CrudTestBase(string path, string? authKey = null) - { - _path = path; - _authKey = authKey; - - var builder = WebApplication.CreateBuilder(); - builder.WebHost.UseTestServer(); - builder.Services.AddCrud<TEntity>(); - ConfigureApplication(builder); - _app = builder.Build(); - - if (authKey is not null) - { - using (var scope = _app.Services.CreateScope()) - { - var secretService = scope.ServiceProvider.GetRequiredService<ISecretService>(); - secretService.CreateTestSecret(authKey, "test-secret"); - } - } - - _client = CreateHttpClient(); - } - - protected abstract void ConfigureApplication(WebApplicationBuilder builder); - - public virtual async ValueTask DisposeAsync() - { - await _app.DisposeAsync(); - } - - public TestServer GetTestServer() - { - return _app.GetTestServer(); - } - - public HttpClient CreateHttpClient() - { - return GetTestServer().CreateClient(); - } - - public async Task TestAuth() - { - if (_authKey is null) - { - return; - } - - { - using var response = await _client.GetAsync(_path); - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } - - { - var entity = Activator.CreateInstance<TEntity>(); - using var response = await _client.PostAsJsonAsync(_path, entity); - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } - } -} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TestEntity.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TestEntity.cs index ca84d5a..7cc19ed 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TestEntity.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TestEntity.cs @@ -2,7 +2,7 @@ namespace CrupestApi.Commons.Crud.Tests; public class TestEntity { - [Column(NotNull = true)] + [Column(ActAsKey = true, NotNull = true)] public string Name { get; set; } = default!; [Column(NotNull = true)] @@ -11,5 +11,13 @@ public class TestEntity [Column] public float? Height { get; set; } + [Column(Generated = true, NotNull = true, NoUpdate = true)] + public string Secret { get; set; } = default!; + + public static string SecretDefaultValueGenerator() + { + return "secret"; + } + public string NonColumn { get; set; } = "Not A Column"; } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs index 1a2a055..33ff2ed 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs @@ -1,23 +1,29 @@ using System.Data; -using System.Text.Json; using Dapper; namespace CrupestApi.Commons.Crud; +[Flags] +public enum UpdateBehavior +{ + None = 0, + SaveNull = 1 +} + public class CrudService<TEntity> : IDisposable where TEntity : class { protected readonly TableInfo _table; protected readonly string? _connectionName; protected readonly IDbConnection _dbConnection; - protected readonly EntityJsonHelper<TEntity> _jsonHelper; + private readonly bool _shouldDisposeConnection; private readonly ILogger<CrudService<TEntity>> _logger; - public CrudService(ITableInfoFactory tableInfoFactory, IDbConnectionFactory dbConnectionFactory, EntityJsonHelper<TEntity> jsonHelper, ILoggerFactory loggerFactory) + public CrudService(ITableInfoFactory tableInfoFactory, IDbConnectionFactory dbConnectionFactory, ILoggerFactory loggerFactory) { _connectionName = GetConnectionName(); _table = tableInfoFactory.Get(typeof(TEntity)); _dbConnection = dbConnectionFactory.Get(_connectionName); - _jsonHelper = jsonHelper; + _shouldDisposeConnection = dbConnectionFactory.ShouldDisposeConnection; _logger = loggerFactory.CreateLogger<CrudService<TEntity>>(); CheckDatabase(_dbConnection); @@ -28,8 +34,6 @@ public class CrudService<TEntity> : IDisposable where TEntity : class return typeof(TEntity).Name; } - public EntityJsonHelper<TEntity> JsonHelper => _jsonHelper; - protected virtual void CheckDatabase(IDbConnection dbConnection) { if (!_table.CheckExistence(dbConnection)) @@ -47,7 +51,8 @@ public class CrudService<TEntity> : IDisposable where TEntity : class public void Dispose() { - _dbConnection.Dispose(); + if (_shouldDisposeConnection) + _dbConnection.Dispose(); } public List<TEntity> GetAll() @@ -80,17 +85,23 @@ public class CrudService<TEntity> : IDisposable where TEntity : class return (string)key; } - public string Create(JsonElement jsonElement) + public IUpdateClause ConvertEntityToUpdateClauses(TEntity entity, UpdateBehavior behavior) { - var insertClauses = _jsonHelper.ConvertJsonElementToInsertClauses(jsonElement); - var key = _table.Insert(_dbConnection, insertClauses); - return (string)key; + var result = UpdateClause.Create(); + var saveNull = behavior.HasFlag(UpdateBehavior.SaveNull); + foreach (var column in _table.PropertyColumns) + { + var value = column.PropertyInfo!.GetValue(entity); + if (!saveNull && value is null) continue; + result.Add(column.ColumnName, value); + } + return result; } - public void UpdateByKey(object key, JsonElement jsonElement) + public void UpdateByKey(object key, TEntity entity, UpdateBehavior behavior) { - var updateClauses = _jsonHelper.ConvertJsonElementToUpdateClause(jsonElement); - _table.Update(_dbConnection, WhereClause.Create().Eq(_table.KeyColumn.ColumnName, key), updateClauses); + _table.Update(_dbConnection, WhereClause.Create().Eq(_table.KeyColumn.ColumnName, key), + ConvertEntityToUpdateClauses(entity, behavior)); } public void DeleteByKey(object key) diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs index b7bc6f1..c91c969 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs @@ -8,14 +8,16 @@ public static class CrudWebApplicationExtensions { if (!context.RequirePermission(permission)) return; var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>(); + var entityJsonHelper = context.RequestServices.GetRequiredService<EntityJsonHelper<TEntity>>(); var allEntities = crudService.GetAll(); - await context.ResponseJsonAsync(allEntities.Select(e => crudService.JsonHelper.ConvertEntityToDictionary(e))); + await context.ResponseJsonAsync(allEntities.Select(e => entityJsonHelper.ConvertEntityToDictionary(e))); }); app.MapGet(path + "/{key}", async (context) => { if (!context.RequirePermission(permission)) return; var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>(); + var entityJsonHelper = context.RequestServices.GetRequiredService<EntityJsonHelper<TEntity>>(); var key = context.Request.RouteValues["key"]?.ToString(); if (key == null) { @@ -24,23 +26,25 @@ public static class CrudWebApplicationExtensions } var entity = crudService.GetByKey(key); - await context.ResponseJsonAsync(crudService.JsonHelper.ConvertEntityToDictionary(entity)); + await context.ResponseJsonAsync(entityJsonHelper.ConvertEntityToDictionary(entity)); }); app.MapPost(path, async (context) => { if (!context.RequirePermission(permission)) return; var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>(); + var entityJsonHelper = context.RequestServices.GetRequiredService<EntityJsonHelper<TEntity>>(); var jsonDocument = await context.Request.ReadJsonAsync(); - var key = crudService.Create(jsonDocument.RootElement); - await context.ResponseJsonAsync(crudService.JsonHelper.ConvertEntityToDictionary(crudService.GetByKey(key))); + var key = crudService.Create(entityJsonHelper.ConvertJsonToEntityForInsert(jsonDocument.RootElement)); + await context.ResponseJsonAsync(entityJsonHelper.ConvertEntityToDictionary(crudService.GetByKey(key))); }); app.MapPatch(path + "/{key}", async (context) => { if (!context.RequirePermission(permission)) return; - var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>(); var key = context.Request.RouteValues["key"]?.ToString(); + var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>(); + var entityJsonHelper = context.RequestServices.GetRequiredService<EntityJsonHelper<TEntity>>(); if (key == null) { await context.ResponseMessageAsync("Please specify a key in path."); @@ -48,9 +52,9 @@ public static class CrudWebApplicationExtensions } var jsonDocument = await context.Request.ReadJsonAsync(); - crudService.UpdateByKey(key, jsonDocument.RootElement); - - await context.ResponseJsonAsync(crudService.JsonHelper.ConvertEntityToDictionary(crudService.GetByKey(key))); + var entity = entityJsonHelper.ConvertJsonToEntityForUpdate(jsonDocument.RootElement, out var updateBehavior); + crudService.UpdateByKey(key, entity, updateBehavior); + await context.ResponseJsonAsync(entityJsonHelper.ConvertEntityToDictionary(crudService.GetByKey(key))); }); app.MapDelete(path + "/{key}", async (context) => diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/DbConnectionFactory.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/DbConnectionFactory.cs index e8f8abf..85b818b 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/DbConnectionFactory.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/DbConnectionFactory.cs @@ -7,6 +7,7 @@ namespace CrupestApi.Commons.Crud; public interface IDbConnectionFactory { IDbConnection Get(string? name = null); + bool ShouldDisposeConnection { get; } } public class SqliteConnectionFactory : IDbConnectionFactory @@ -28,4 +29,44 @@ public class SqliteConnectionFactory : IDbConnectionFactory return new SqliteConnection(connectionString); } + + public bool ShouldDisposeConnection => true; +} + +public class SqliteMemoryConnectionFactory : IDbConnectionFactory, IDisposable +{ + private readonly Dictionary<string, IDbConnection> _connections = new(); + + public IDbConnection Get(string? name = null) + { + name = name ?? "crupest-api"; + + if (_connections.TryGetValue(name, out var connection)) + { + return connection; + } + else + { + var connectionString = new SqliteConnectionStringBuilder() + { + DataSource = ":memory:", + Mode = SqliteOpenMode.ReadWriteCreate + }.ToString(); + + connection = new SqliteConnection(connectionString); + _connections.Add(name, connection); + return connection; + } + } + + public bool ShouldDisposeConnection => false; + + + public void Dispose() + { + foreach (var connection in _connections.Values) + { + connection.Dispose(); + } + } } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs index 1265fe9..4489307 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs @@ -17,7 +17,7 @@ public class EntityJsonHelper<TEntity> where TEntity : class _jsonSerializerOptions = jsonSerializerOptions; } - public virtual Dictionary<string, object?> ConvertEntityToDictionary(TEntity entity, bool includeNonColumnProperties = false) + public Dictionary<string, object?> ConvertEntityToDictionary(TEntity entity, bool includeNonColumnProperties = false) { var result = new Dictionary<string, object?>(); @@ -40,131 +40,75 @@ public class EntityJsonHelper<TEntity> where TEntity : class return result; } - public virtual string ConvertEntityToJson(TEntity entity, bool includeNonColumnProperties = false) + public string ConvertEntityToJson(TEntity entity, bool includeNonColumnProperties = false) { var dictionary = ConvertEntityToDictionary(entity, includeNonColumnProperties); return JsonSerializer.Serialize(dictionary, _jsonSerializerOptions.CurrentValue); } - public virtual IInsertClause ConvertJsonElementToInsertClauses(JsonElement rootElement) + public TEntity ConvertJsonToEntityForInsert(JsonElement jsonElement) { - var insertClause = InsertClause.Create(); - - if (rootElement.ValueKind != JsonValueKind.Object) - { - throw new UserException("The root element must be an object."); - } + if (jsonElement.ValueKind is not JsonValueKind.Object) + throw new ArgumentException("The jsonElement must be an object."); + var result = Activator.CreateInstance<TEntity>(); foreach (var column in _table.PropertyColumns) { - object? value = null; - if (rootElement.TryGetProperty(column.ColumnName, out var propertyElement)) - { - value = propertyElement.ValueKind switch - { - JsonValueKind.Null or JsonValueKind.Undefined => null, - JsonValueKind.Number => propertyElement.GetDouble(), - JsonValueKind.True => true, - JsonValueKind.False => false, - JsonValueKind.String => propertyElement.GetString(), - _ => throw new Exception($"Bad json value of property {column.ColumnName}.") - }; - } - - if (column.IsGenerated && value is not null) - { - throw new UserException($"The property {column.ColumnName} is generated. You cannot specify its value."); - } - - if (column.IsNotNull && !column.CanBeGenerated && value is null) + if (jsonElement.TryGetProperty(column.ColumnName, out var value)) { - throw new UserException($"The property {column.ColumnName} can't be null or generated. But you specify a null value."); + var realValue = column.ColumnType.ConvertFromDatabase(value); + column.PropertyInfo!.SetValue(result, realValue); } - - insertClause.Add(column.ColumnName, value); } - return insertClause; + return result; } - public IInsertClause ConvertJsonToInsertClauses(string json) + public TEntity ConvertJsonToEntityForInsert(string json) { - var document = JsonSerializer.Deserialize<JsonDocument>(json, _jsonSerializerOptions.CurrentValue)!; - return ConvertJsonElementToInsertClauses(document.RootElement); + var jsonElement = JsonSerializer.Deserialize<JsonElement>(json, _jsonSerializerOptions.CurrentValue); + return ConvertJsonToEntityForInsert(jsonElement!); } - public IUpdateClause ConvertJsonElementToUpdateClause(JsonElement rootElement, bool saveNull) + public TEntity ConvertJsonToEntityForUpdate(JsonElement jsonElement, out UpdateBehavior updateBehavior) { - var updateClause = UpdateClause.Create(); + if (jsonElement.ValueKind is not JsonValueKind.Object) + throw new UserException("The jsonElement must be an object."); - if (rootElement.ValueKind != JsonValueKind.Object) - { - throw new UserException("The root element must be an object."); - } + updateBehavior = UpdateBehavior.None; - foreach (var column in _table.PropertyColumns) + if (jsonElement.TryGetProperty("$saveNull", out var saveNullValue)) { - object? value = null; - - if (rootElement.TryGetProperty(column.ColumnName, out var propertyElement)) + if (saveNullValue.ValueKind is JsonValueKind.True) { - value = propertyElement.ValueKind switch - { - JsonValueKind.Null or JsonValueKind.Undefined => null, - JsonValueKind.Number => propertyElement.GetDouble(), - JsonValueKind.True => true, - JsonValueKind.False => false, - JsonValueKind.String => propertyElement.GetString(), - _ => throw new Exception($"Bad json value of property {column.ColumnName}.") - }; - - if (column.IsNoUpdate && (value is not null || saveNull)) - { - throw new UserException($"The property {column.ColumnName} is not updatable. You cannot specify its value."); - } + updateBehavior |= UpdateBehavior.SaveNull; } + else if (saveNullValue.ValueKind is JsonValueKind.False) + { - if (value is null && !saveNull) + } + else { - continue; + throw new UserException("The $saveNull must be a boolean."); } - - updateClause.Add(column.ColumnName, value ?? DbNullValue.Instance); } - return updateClause; - } - - public IUpdateClause ConvertJsonElementToUpdateClause(JsonElement rootElement) - { - var updateClause = UpdateClause.Create(); - - if (rootElement.ValueKind != JsonValueKind.Object) - { - throw new UserException("The root element must be an object."); - } - - bool saveNull = false; - - if (rootElement.TryGetProperty("$saveNull", out var propertyElement)) + var result = Activator.CreateInstance<TEntity>(); + foreach (var column in _table.PropertyColumns) { - if (propertyElement.ValueKind is not JsonValueKind.True or JsonValueKind.False) + if (jsonElement.TryGetProperty(column.ColumnName, out var value)) { - throw new UserException("$saveNull can only be true or false."); - } - - if (propertyElement.ValueKind is JsonValueKind.True) - { - saveNull = true; + var realValue = column.ColumnType.ConvertFromDatabase(value); + column.PropertyInfo!.SetValue(result, realValue); } } - return ConvertJsonElementToUpdateClause(rootElement, saveNull); + return result; } - public IUpdateClause ConvertJsonToUpdateClause(string json) + public TEntity ConvertJsonToEntityForUpdate(string json, out UpdateBehavior updateBehavior) { - var document = JsonSerializer.Deserialize<JsonDocument>(json, _jsonSerializerOptions.CurrentValue)!; - return ConvertJsonElementToUpdateClause(document.RootElement); + var jsonElement = JsonSerializer.Deserialize<JsonElement>(json, _jsonSerializerOptions.CurrentValue); + return ConvertJsonToEntityForUpdate(jsonElement!, out updateBehavior); } } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs index 69acad3..c2242ff 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs @@ -5,8 +5,8 @@ namespace CrupestApi.Commons.Secrets; public class SecretService : CrudService<SecretInfo>, ISecretService { - public SecretService(ITableInfoFactory tableInfoFactory, IDbConnectionFactory dbConnectionFactory, EntityJsonHelper<SecretInfo> jsonHelper, ILoggerFactory loggerFactory) - : base(tableInfoFactory, dbConnectionFactory, jsonHelper, loggerFactory) + public SecretService(ITableInfoFactory tableInfoFactory, IDbConnectionFactory dbConnectionFactory, ILoggerFactory loggerFactory) + : base(tableInfoFactory, dbConnectionFactory, loggerFactory) { } |