aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TableInfoTest.cs2
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs6
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs10
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs31
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs12
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs36
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs55
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpResponseAction.cs1
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs27
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs2
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsService.cs3
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;