diff options
11 files changed, 152 insertions, 33 deletions
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TableInfoTest.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TableInfoTest.cs index 62fb9e5..4d4cc36 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TableInfoTest.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TableInfoTest.cs @@ -1,5 +1,3 @@ -using Xunit.Abstractions; - namespace CrupestApi.Commons.Crud.Tests; public class TableInfoTest diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs index f1fb99b..545397d 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs @@ -158,6 +158,12 @@ public class ColumnInfo public bool IsNotNull => IsPrimaryKey || Metadata.GetValueOrDefault(ColumnMetadataKeys.NotNull) is true; public bool IsClientGenerate => Metadata.GetValueOrDefault(ColumnMetadataKeys.ClientGenerate) is true; public bool IsNoUpdate => Metadata.GetValueOrDefault(ColumnMetadataKeys.NoUpdate) is true; + /// <summary> + /// This only returns metadata value. It doesn't not fall back to primary column. If you want to get the real key column, go to table info. + /// </summary> + /// <seealso cref="ColumnMetadataKeys.ActAsKey"/> + /// <seealso cref="TableInfo.KeyColumn"/> + public bool IsSpecifiedAsKey => Metadata.GetValueOrDefault(ColumnMetadataKeys.ActAsKey) is true; public ColumnIndexType Index => Metadata.GetValueOrDefault<ColumnIndexType?>(ColumnMetadataKeys.Index) ?? ColumnIndexType.None; diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs index 1ca2ce8..9fb3999 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs @@ -29,6 +29,11 @@ public static class ColumnMetadataKeys /// </summary> /// <returns></returns> public const string NoUpdate = nameof(ColumnAttribute.NoUpdate); + + /// <summary> + /// This column acts as key when get one entity for http get method in path. + /// </summary> + public const string ActAsKey = nameof(ColumnAttribute.ActAsKey); } public interface IColumnMetadata @@ -105,6 +110,11 @@ public class ColumnAttribute : Attribute, IColumnMetadata /// <see cref="ColumnMetadataKeys.NoUpdate"/> public bool NoUpdate { get; init; } + /// <see cref="ColumnMetadataKeys.ActAsKey"/> + public bool ActAsKey { get; init; } + + + public bool TryGetValue(string key, out object? value) { var property = GetType().GetProperty(key); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs index 811b2e6..af6a8d5 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs @@ -1,13 +1,16 @@ using System.Data; +using System.Text.Json; using Dapper; namespace CrupestApi.Commons.Crud; +// TODO: Register this. public class CrudService<TEntity> : IDisposable where TEntity : class { protected readonly TableInfo _table; protected readonly string? _connectionName; protected readonly IDbConnection _dbConnection; + protected readonly EntityJsonHelper _jsonHelper; private readonly ILogger<CrudService<TEntity>> _logger; public CrudService(string? connectionName, ITableInfoFactory tableInfoFactory, IDbConnectionFactory dbConnectionFactory, ILoggerFactory loggerFactory) @@ -15,6 +18,7 @@ public class CrudService<TEntity> : IDisposable where TEntity : class _connectionName = connectionName; _table = tableInfoFactory.Get(typeof(TEntity)); _dbConnection = dbConnectionFactory.Get(_connectionName); + _jsonHelper = new EntityJsonHelper(_table); _logger = loggerFactory.CreateLogger<CrudService<TEntity>>(); if (!_table.CheckExistence(_dbConnection)) @@ -50,12 +54,14 @@ public class CrudService<TEntity> : IDisposable where TEntity : class return _table.SelectCount(_dbConnection, filter); } - public int Insert(IInsertClause insertClause) + // Return the key. + public object Insert(IInsertClause insertClause) { return _table.Insert(_dbConnection, insertClause); } - public int Insert(TEntity entity) + // Return the key. + public object Insert(TEntity entity) { return _table.Insert(_dbConnection, _table.GenerateInsertClauseFromEntity(entity)); } @@ -69,4 +75,25 @@ public class CrudService<TEntity> : IDisposable where TEntity : class { return _table.Delete(_dbConnection, filter); } + + public TEntity SelectByKey(object key) + { + return Select(WhereClause.Create().Eq(_table.KeyColumn.ColumnName, key)).Single(); + } + + public List<JsonDocument> SelectAsJson(IWhereClause? filter) + { + return Select(filter).Select(e => _jsonHelper.ConvertEntityToJson(e)).ToList(); + } + + public JsonDocument SelectAsJsonByKey(object key) + { + return SelectAsJson(WhereClause.Create().Eq(_table.KeyColumn.ColumnName, key)).Single(); + } + + public object InsertFromJson(JsonDocument? json) + { + // TODO: Implement this. + throw new NotImplementedException(); + } } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs index 3467625..46b2e5b 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs @@ -2,10 +2,20 @@ namespace CrupestApi.Commons.Crud; public static class CrudWebApplicationExtensions { - public static WebApplication UseCrud(this WebApplication app, string path) + public static WebApplication UseCrud<TEntity>(this WebApplication app, string path) where TEntity : class { app.MapGet(path, async (context) => { + var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>(); + + var result = crudService.SelectAsJson(null); + + await context.ResponseJsonAsync(result); + }); + + app.MapPost(path, async (context) => + { + var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>(); }); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs new file mode 100644 index 0000000..a1e4583 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs @@ -0,0 +1,36 @@ +using System.Text.Json; + +namespace CrupestApi.Commons.Crud; + +public class EntityJsonHelper +{ + private readonly TableInfo _table; + + public EntityJsonHelper(TableInfo table) + { + _table = table; + } + + public virtual JsonDocument ConvertEntityToJson(object? entity) + { + if (entity is null) return JsonSerializer.SerializeToDocument<object?>(null); + + var result = new Dictionary<string, object?>(); + + foreach (var column in _table.ColumnInfos) + { + if (column.PropertyInfo is not null) + { + result.Add(column.ColumnName, column.PropertyInfo.GetValue(entity)); + } + } + + return JsonSerializer.SerializeToDocument(result); + } + + public virtual object? ConvertJsonToEntity(JsonDocument? json) + { + // TODO: Implement this. + throw new NotImplementedException(); + } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs index 5bb19ad..498529c 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs @@ -27,8 +27,9 @@ public class TableInfo var columnInfos = new List<ColumnInfo>(); - bool hasPrimaryKey = false; bool hasId = false; + ColumnInfo? primaryKeyColumn = null; + ColumnInfo? keyColumn = null; List<PropertyInfo> columnProperties = new(); List<PropertyInfo> nonColumnProperties = new(); @@ -40,11 +41,21 @@ public class TableInfo var columnInfo = new ColumnInfo(this, property, _columnTypeProvider); columnInfos.Add(columnInfo); if (columnInfo.IsPrimaryKey) - hasPrimaryKey = true; + { + primaryKeyColumn = columnInfo; + } if (columnInfo.ColumnName.Equals("id", StringComparison.OrdinalIgnoreCase)) { hasId = true; } + if (columnInfo.IsSpecifiedAsKey) + { + if (keyColumn is not null) + { + throw new Exception("Already exists a key column."); + } + keyColumn = columnInfo; + } columnProperties.Add(property); } else @@ -53,14 +64,21 @@ public class TableInfo } } - if (!hasPrimaryKey) + if (primaryKeyColumn is null) { if (hasId) throw new Exception("A column named id already exists but is not primary key."); - var columnInfo = CreateAutoIdColumn(); - columnInfos.Add(columnInfo); + primaryKeyColumn = CreateAutoIdColumn(); + columnInfos.Add(primaryKeyColumn); + } + + if (keyColumn is null) + { + keyColumn = primaryKeyColumn; } ColumnInfos = columnInfos; + PrimaryKeyColumn = primaryKeyColumn; + KeyColumn = keyColumn; ColumnProperties = columnProperties; NonColumnProperties = nonColumnProperties; @@ -85,6 +103,12 @@ public class TableInfo public Type EntityType { get; } public string TableName { get; } public IReadOnlyList<ColumnInfo> ColumnInfos { get; } + public ColumnInfo PrimaryKeyColumn { get; } + /// <summary> + /// Maybe not the primary key. But acts as primary key. + /// </summary> + /// <seealso cref="ColumnMetadataKeys.ActAsKey"/> + public ColumnInfo KeyColumn { get; } public IReadOnlyList<PropertyInfo> ColumnProperties { get; } public IReadOnlyList<PropertyInfo> NonColumnProperties { get; } public IReadOnlyList<string> ColumnNameList => _lazyColumnNameList.Value; @@ -412,10 +436,10 @@ CREATE TABLE {tableName}( }); } - public virtual int Insert(IDbConnection dbConnection, IInsertClause insert) + // Returns the insert entity's key. + public object Insert(IDbConnection dbConnection, IInsertClause insert) { - var (sql, parameters) = GenerateInsertSql(insert); - + object? key = null; foreach (var column in ColumnInfos) { InsertItem? item = insert.Items.FirstOrDefault(i => i.ColumnName == column.ColumnName); @@ -423,16 +447,25 @@ CREATE TABLE {tableName}( column.Hooks.BeforeInsert(column, ref value); if (item is null) { - if (value is not null) - insert.Items.Add(new InsertItem(column.ColumnName, value)); + item = new InsertItem(column.ColumnName, value); + insert.Items.Add(item); } else { item.Value = value; } + + if (item.ColumnName == KeyColumn.ColumnName) + { + key = item.Value; + } } - return dbConnection.Execute(sql, ConvertParameters(parameters)); + var (sql, parameters) = GenerateInsertSql(insert); + + dbConnection.Execute(sql, ConvertParameters(parameters)); + + return key ?? throw new Exception("No key???"); } public virtual int Update(IDbConnection dbConnection, IWhereClause? where, IUpdateClause update) diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpResponseAction.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpResponseAction.cs index 768a6d2..9023f4e 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpResponseAction.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpResponseAction.cs @@ -1,4 +1,3 @@ namespace CrupestApi.Commons; public delegate void HttpResponseAction(HttpResponse response); -public delegate Task AsyncHttpResponseAction(HttpResponse response); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs index 4f5862f..8c4b34d 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs @@ -46,35 +46,38 @@ public static class CrupestApiJsonExtensions return services; } - public static async Task WriteJsonAsync<T>(this HttpResponse response, T bodyObject, int statusCode, HttpResponseAction? beforeWriteBody, CancellationToken cancellationToken = default) - { - await response.WriteJsonAsync(bodyObject, statusCode, (context) => - { - beforeWriteBody?.Invoke(context); - return Task.CompletedTask; - }, cancellationToken); - } - - public static async Task WriteJsonAsync<T>(this HttpResponse response, T bodyObject, int statusCode = 200, AsyncHttpResponseAction? beforeWriteBody = null, CancellationToken cancellationToken = default) + public static async Task WriteJsonAsync<T>(this HttpResponse response, T bodyObject, int statusCode = 200, HttpResponseAction? beforeWriteBody = null, CancellationToken cancellationToken = default) { var jsonOptions = response.HttpContext.RequestServices.GetRequiredService<IOptionsSnapshot<JsonSerializerOptions>>(); byte[] json = JsonSerializer.SerializeToUtf8Bytes<T>(bodyObject, jsonOptions.Value); var byteCount = json.Length; + response.StatusCode = statusCode; response.Headers.ContentType = "application/json; charset=utf-8"; response.Headers.ContentLength = byteCount; if (beforeWriteBody is not null) { - await beforeWriteBody(response); + beforeWriteBody(response); } await response.Body.WriteAsync(json, cancellationToken); } - public static async Task WriteMessageAsync(this HttpResponse response, string message, int statusCode = 200, HttpResponseAction? beforeWriteBody = null, CancellationToken cancellationToken = default) + public static async Task WriteMessageAsync(this HttpResponse response, string message, int statusCode = 400, HttpResponseAction? beforeWriteBody = null, CancellationToken cancellationToken = default) { await response.WriteJsonAsync(new ErrorBody(message), statusCode: statusCode, beforeWriteBody, cancellationToken); } + + public static Task ResponseJsonAsync<T>(this HttpContext context, T bodyObject, int statusCode = 200, HttpResponseAction? beforeWriteBody = null, CancellationToken cancellationToken = default) + { + return context.Response.WriteJsonAsync<T>(bodyObject, statusCode, beforeWriteBody, cancellationToken); + } + + public static Task ResponseMessageAsync<T>(this HttpContext context, string message, int statusCode = 400, HttpResponseAction? beforeWriteBody = null, CancellationToken cancellationToken = default) + { + return context.Response.WriteMessageAsync(message, statusCode, beforeWriteBody, cancellationToken); + } + } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs index e6af39b..12fd3da 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs @@ -8,7 +8,7 @@ public class SecretInfo { [Column(NotNull = true)] public string Key { get; set; } = default!; - [Column(NotNull = true, ClientGenerate = true, NoUpdate = true)] + [Column(NotNull = true, ClientGenerate = true, NoUpdate = true, ActAsKey = true)] public string Secret { get; set; } = default!; [Column(DefaultEmptyForString = true)] public string Description { get; set; } = default!; diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsService.cs index b8912cb..7051602 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsService.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsService.cs @@ -1,7 +1,4 @@ -using System.Diagnostics; -using CrupestApi.Commons; using CrupestApi.Commons.Crud; -using Dapper; namespace CrupestApi.Secrets; |