aboutsummaryrefslogtreecommitdiff
path: root/docker/crupest-api/CrupestApi
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2022-12-23 11:48:11 +0800
committercrupest <crupest@outlook.com>2022-12-23 11:48:11 +0800
commitd1e2a504b0543f00a9c9d9602ba7f120bb0e8895 (patch)
treeeff2982e0e8ea24f7952bba4678a18d7dcb250d5 /docker/crupest-api/CrupestApi
parent93924947fecca4ddad670f1f1768f32063cfec3a (diff)
downloadcrupest-d1e2a504b0543f00a9c9d9602ba7f120bb0e8895.tar.gz
crupest-d1e2a504b0543f00a9c9d9602ba7f120bb0e8895.tar.bz2
crupest-d1e2a504b0543f00a9c9d9602ba7f120bb0e8895.zip
Develop secret api. v60
Diffstat (limited to 'docker/crupest-api/CrupestApi')
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudIntegratedTest.cs89
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs12
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs4
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs146
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs25
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpContextExtensions.cs4
-rw-r--r--docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs4
7 files changed, 206 insertions, 78 deletions
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudIntegratedTest.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudIntegratedTest.cs
index ddda5f6..179048d 100644
--- a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudIntegratedTest.cs
+++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudIntegratedTest.cs
@@ -15,7 +15,8 @@ public class CrudIntegratedTest : IAsyncLifetime
public CrudIntegratedTest()
{
var builder = WebApplication.CreateBuilder();
- builder.Services.AddSingleton<SqliteMemoryConnectionFactory>();
+ builder.Logging.ClearProviders();
+ builder.Services.AddSingleton<IDbConnectionFactory, SqliteMemoryConnectionFactory>();
builder.Services.AddCrud<TestEntity>();
builder.WebHost.UseTestServer();
_app = builder.Build();
@@ -48,12 +49,94 @@ public class CrudIntegratedTest : IAsyncLifetime
[Fact]
- public async Task Test()
+ public async Task EmptyTest()
{
- var response = await _authorizedHttpClient.GetAsync($"/test?secret={_token}");
+ using var response = await _authorizedHttpClient.GetAsync("/test");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<List<TestEntity>>();
Assert.NotNull(body);
Assert.Empty(body);
}
+
+ [Fact]
+ public async Task CrudTest()
+ {
+ {
+ using var response = await _authorizedHttpClient.PostAsJsonAsync("/test", new TestEntity
+ {
+ Name = "test",
+ Age = 22
+ });
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var body = await response.Content.ReadFromJsonAsync<TestEntity>();
+ Assert.NotNull(body);
+ Assert.Equal("test", body.Name);
+ Assert.Equal(22, body.Age);
+ Assert.Null(body.Height);
+ Assert.NotEmpty(body.Secret);
+ }
+
+ {
+ using var response = await _authorizedHttpClient.GetAsync("/test");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var body = await response.Content.ReadFromJsonAsync<List<TestEntity>>();
+ Assert.NotNull(body);
+ var entity = Assert.Single(body);
+ Assert.Equal("test", entity.Name);
+ Assert.Equal(22, entity.Age);
+ Assert.Null(entity.Height);
+ Assert.NotEmpty(entity.Secret);
+ }
+
+ {
+ using var response = await _authorizedHttpClient.GetAsync("/test/test");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var body = await response.Content.ReadFromJsonAsync<TestEntity>();
+ Assert.NotNull(body);
+ Assert.Equal("test", body.Name);
+ Assert.Equal(22, body.Age);
+ Assert.Null(body.Height);
+ Assert.NotEmpty(body.Secret);
+ }
+
+ {
+ using var response = await _authorizedHttpClient.PatchAsJsonAsync("/test/test", new TestEntity
+ {
+ Name = "test-2",
+ Age = 23,
+ Height = 188.0f
+ });
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var body = await response.Content.ReadFromJsonAsync<TestEntity>();
+ Assert.NotNull(body);
+ Assert.Equal("test-2", body.Name);
+ Assert.Equal(23, body.Age);
+ Assert.Equal(188.0f, body.Height);
+ Assert.NotEmpty(body.Secret);
+ }
+
+ {
+ using var response = await _authorizedHttpClient.GetAsync("/test/test-2");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var body = await response.Content.ReadFromJsonAsync<TestEntity>();
+ Assert.NotNull(body);
+ Assert.Equal("test-2", body.Name);
+ Assert.Equal(23, body.Age);
+ Assert.Equal(188.0f, body.Height);
+ Assert.NotEmpty(body.Secret);
+ }
+
+ {
+ using var response = await _authorizedHttpClient.DeleteAsync("/test/test-2");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ {
+ using var response = await _authorizedHttpClient.GetAsync("/test");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var body = await response.Content.ReadFromJsonAsync<List<TestEntity>>();
+ Assert.NotNull(body);
+ Assert.Empty(body);
+ }
+ }
}
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs
index dc32387..a56790a 100644
--- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs
+++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs
@@ -72,7 +72,7 @@ public class CrudService<TEntity> : IDisposable where TEntity : class
var result = _table.Select<TEntity>(_dbConnection, null, WhereClause.Create().Eq(_table.KeyColumn.ColumnName, key)).SingleOrDefault();
if (result is null)
{
- throw new EntityNotExistException("Required entity not found.");
+ throw new EntityNotExistException($"Required entity for key {key} not found.");
}
return result;
}
@@ -91,7 +91,7 @@ public class CrudService<TEntity> : IDisposable where TEntity : class
public object Create(TEntity entity)
{
var insertClause = ConvertEntityToInsertClauses(entity);
- var key = _table.Insert(_dbConnection, insertClause);
+ _table.Insert(_dbConnection, insertClause, out var key);
return key;
}
@@ -108,14 +108,16 @@ public class CrudService<TEntity> : IDisposable where TEntity : class
return result;
}
- public void UpdateByKey(object key, TEntity entity, UpdateBehavior behavior = UpdateBehavior.None)
+ // Return new key.
+ public object UpdateByKey(object key, TEntity entity, UpdateBehavior behavior = UpdateBehavior.None)
{
var affectedCount = _table.Update(_dbConnection, WhereClause.Create().Eq(_table.KeyColumn.ColumnName, key),
- ConvertEntityToUpdateClauses(entity, behavior));
+ ConvertEntityToUpdateClauses(entity, behavior), out var newKey);
if (affectedCount == 0)
{
- throw new EntityNotExistException("Required entity not found.");
+ throw new EntityNotExistException($"Required entity for key {key} not found.");
}
+ return newKey ?? key;
}
public bool 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 dfb67d1..c9e43f2 100644
--- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs
+++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs
@@ -53,8 +53,8 @@ public static class CrudWebApplicationExtensions
var jsonDocument = await context.Request.ReadJsonAsync();
var entity = entityJsonHelper.ConvertJsonToEntityForUpdate(jsonDocument.RootElement, out var updateBehavior);
- crudService.UpdateByKey(key, entity, updateBehavior);
- await context.ResponseJsonAsync(entityJsonHelper.ConvertEntityToDictionary(crudService.GetByKey(key)));
+ var newKey = crudService.UpdateByKey(key, entity, updateBehavior);
+ await context.ResponseJsonAsync(entityJsonHelper.ConvertEntityToDictionary(crudService.GetByKey(newKey)));
});
app.MapDelete(path + "/{key}", async (context) =>
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs
index 406e214..cf3f178 100644
--- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs
+++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs
@@ -1,3 +1,4 @@
+using System.Globalization;
using System.Text.Json;
using Microsoft.Extensions.Options;
@@ -46,34 +47,63 @@ public class EntityJsonHelper<TEntity> where TEntity : class
return JsonSerializer.Serialize(dictionary, _jsonSerializerOptions.CurrentValue);
}
- private static Type MapJsonValueKindToType(JsonElement jsonElement, out object? value)
+ private object? ConvertJsonValue(JsonElement? optionalJsonElement, Type type, string propertyName)
{
- switch (jsonElement.ValueKind)
+ if (optionalJsonElement is null)
{
- case JsonValueKind.String:
- {
- value = jsonElement.GetString()!;
- return typeof(string);
- }
- case JsonValueKind.Number:
- {
- value = jsonElement.GetDouble();
- return typeof(double);
- }
- case JsonValueKind.True:
- case JsonValueKind.False:
- {
- value = jsonElement.GetBoolean();
- return typeof(bool);
- }
- case JsonValueKind.Null:
- {
- value = null;
- return typeof(object);
- }
- default:
- throw new UserException("Unsupported json value type.");
+ return null;
+ }
+
+ var jsonElement = optionalJsonElement.Value;
+
+ if (jsonElement.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
+ {
+ return null;
+ }
+
+ if (jsonElement.ValueKind is JsonValueKind.String)
+ {
+ if (type != typeof(string))
+ {
+ throw new UserException($"Property {propertyName} must be a string.");
+ }
+ return jsonElement.GetString()!;
+ }
+
+ if (jsonElement.ValueKind is JsonValueKind.True or JsonValueKind.False)
+ {
+ if (type != typeof(bool))
+ {
+ throw new UserException($"Property {propertyName} must be a boolean.");
+ }
+ return jsonElement.GetBoolean();
+ }
+
+ if (jsonElement.ValueKind is JsonValueKind.Number)
+ {
+ try
+ {
+ return Convert.ChangeType(jsonElement.GetRawText(), type, CultureInfo.InvariantCulture);
+ }
+ catch (Exception)
+ {
+ throw new UserException($"Property {propertyName} must be a valid number.");
+ }
+ }
+
+ throw new UserException($"Property {propertyName} is of wrong type.");
+ }
+
+ public Dictionary<string, JsonElement> ConvertJsonObjectToDictionary(JsonElement jsonElement)
+ {
+ var result = new Dictionary<string, JsonElement>();
+
+ foreach (var property in jsonElement.EnumerateObject())
+ {
+ result[property.Name.ToLower()] = property.Value;
}
+
+ return result;
}
public TEntity ConvertJsonToEntityForInsert(JsonElement jsonElement)
@@ -82,31 +112,23 @@ public class EntityJsonHelper<TEntity> where TEntity : class
throw new ArgumentException("The jsonElement must be an object.");
var result = Activator.CreateInstance<TEntity>();
+
+ Dictionary<string, JsonElement> jsonProperties = ConvertJsonObjectToDictionary(jsonElement);
+
foreach (var column in _table.PropertyColumns)
{
- if (jsonElement.TryGetProperty(column.ColumnName, out var jsonValue))
+ var jsonPropertyValue = jsonProperties.GetValueOrDefault(column.ColumnName.ToLower());
+ var value = ConvertJsonValue(jsonPropertyValue, column.ColumnType.DatabaseClrType, column.ColumnName);
+ if (column.IsOnlyGenerated && value is not null)
{
- if (column.IsOnlyGenerated)
- {
- throw new UserException($"Property {column.ColumnName} is auto generated, you cannot set it.");
- }
-
- var valueType = MapJsonValueKindToType(jsonValue, out var value);
- if (!valueType.IsAssignableTo(column.ColumnType.DatabaseClrType))
- {
- throw new UserException($"Property {column.ColumnName} is of wrong type.");
- }
-
- var realValue = column.ColumnType.ConvertFromDatabase(value);
- column.PropertyInfo!.SetValue(result, realValue);
+ throw new UserException($"Property {column.ColumnName} is auto generated, you cannot set it.");
}
- else
+ if (!column.CanBeGenerated && value is null && column.IsNotNull)
{
- if (!column.CanBeGenerated)
- {
- throw new UserException($"Property {column.ColumnName} is not auto generated, you must set it.");
- }
+ throw new UserException($"Property {column.ColumnName} can NOT be generated, you must set it.");
}
+ var realValue = column.ColumnType.ConvertFromDatabase(value);
+ column.PropertyInfo!.SetValue(result, realValue);
}
return result;
@@ -125,11 +147,15 @@ public class EntityJsonHelper<TEntity> where TEntity : class
updateBehavior = UpdateBehavior.None;
- if (jsonElement.TryGetProperty("$saveNull", out var saveNullValue))
+ Dictionary<string, JsonElement> jsonProperties = ConvertJsonObjectToDictionary(jsonElement);
+
+ bool saveNull = false;
+ if (jsonProperties.TryGetValue("$saveNull".ToLower(), out var saveNullValue))
{
if (saveNullValue.ValueKind is JsonValueKind.True)
{
updateBehavior |= UpdateBehavior.SaveNull;
+ saveNull = true;
}
else if (saveNullValue.ValueKind is JsonValueKind.False)
{
@@ -144,26 +170,28 @@ public class EntityJsonHelper<TEntity> where TEntity : class
var result = Activator.CreateInstance<TEntity>();
foreach (var column in _table.PropertyColumns)
{
- if (jsonElement.TryGetProperty(column.ColumnName, out var jsonValue))
+ if (jsonProperties.TryGetValue(column.ColumnName.ToLower(), out var jsonPropertyValue))
{
- if (column.IsOnlyGenerated)
+ if (jsonPropertyValue.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
{
- throw new UserException($"Property {column.ColumnName} is auto generated, you cannot set it.");
- }
+ if ((column.IsOnlyGenerated || column.IsNoUpdate) && saveNull)
+ {
+ throw new UserException($"Property {column.ColumnName} is auto generated or not updatable, you cannot set it.");
+ }
- if (column.IsNoUpdate)
- {
- throw new UserException($"Property {column.ColumnName} is not updatable, you cannot set it.");
+ column.PropertyInfo!.SetValue(result, null);
}
-
- var valueType = MapJsonValueKindToType(jsonValue, out var value);
- if (!valueType.IsAssignableTo(column.ColumnType.DatabaseClrType))
+ else
{
- throw new UserException($"Property {column.ColumnName} is of wrong type.");
+ if (column.IsOnlyGenerated || column.IsNoUpdate)
+ {
+ throw new UserException($"Property {column.ColumnName} is auto generated or not updatable, you cannot set it.");
+ }
+
+ var value = ConvertJsonValue(jsonPropertyValue, column.ColumnType.DatabaseClrType, column.ColumnName);
+ var realValue = column.ColumnType.ConvertFromDatabase(value);
+ column.PropertyInfo!.SetValue(result, realValue);
}
-
- var realValue = column.ColumnType.ConvertFromDatabase(value);
- column.PropertyInfo!.SetValue(result, realValue);
}
}
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs
index bc580f2..41ef097 100644
--- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs
+++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs
@@ -492,9 +492,9 @@ CREATE TABLE {tableName}(
/// Insert a entity and call hooks.
/// </summary>
/// <returns>The key of insert entity.</returns>
- public object Insert(IDbConnection dbConnection, IInsertClause insert)
+ public int Insert(IDbConnection dbConnection, IInsertClause insert, out object key)
{
- object? key = null;
+ object? finalKey = null;
var realInsert = InsertClause.Create();
@@ -546,23 +546,31 @@ CREATE TABLE {tableName}(
if (realInsertItem.ColumnName == KeyColumn.ColumnName)
{
- key = realInsertItem.Value;
+ finalKey = realInsertItem.Value;
}
}
+ if (finalKey is null) throw new Exception("No key???");
+ key = finalKey;
+
var (sql, parameters) = GenerateInsertSql(realInsert);
- dbConnection.Execute(sql, ConvertParameters(parameters));
+ var affectedRowCount = dbConnection.Execute(sql, ConvertParameters(parameters));
+
+ if (affectedRowCount != 1)
+ throw new Exception("Failed to insert.");
- return key ?? throw new Exception("No key???");
+ return affectedRowCount;
}
/// <summary>
/// Upgrade a entity and call hooks.
/// </summary>
/// <returns>The key of insert entity.</returns>
- public virtual int Update(IDbConnection dbConnection, IWhereClause? where, IUpdateClause update)
+ public virtual int Update(IDbConnection dbConnection, IWhereClause? where, IUpdateClause update, out object? newKey)
{
+ newKey = null;
+
var realUpdate = UpdateClause.Create();
foreach (var column in Columns)
@@ -580,6 +588,11 @@ CREATE TABLE {tableName}(
column.InvokeValidator(value);
realUpdate.Add(column.ColumnName, value);
+
+ if (column.ColumnName == KeyColumn.ColumnName)
+ {
+ newKey = value;
+ }
}
}
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpContextExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpContextExtensions.cs
index 2ad2c1f..a0b2d89 100644
--- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpContextExtensions.cs
+++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpContextExtensions.cs
@@ -33,8 +33,10 @@ public static class CrupestApiJsonExtensions
public static async Task<JsonDocument> ReadJsonAsync(this HttpRequest request)
{
+ var jsonOptions = request.HttpContext.RequestServices.GetRequiredService<IOptionsSnapshot<JsonSerializerOptions>>();
using var stream = request.Body;
- return await JsonDocument.ParseAsync(stream);
+ var body = await JsonSerializer.DeserializeAsync<JsonDocument>(stream, jsonOptions.Value);
+ return body!;
}
public static async Task WriteJsonAsync<T>(this HttpResponse response, T bodyObject, int statusCode = 200, HttpResponseAction? beforeWriteBody = null, CancellationToken cancellationToken = default)
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs
index c2242ff..9a0ec95 100644
--- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs
+++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs
@@ -19,7 +19,7 @@ public class SecretService : CrudService<SecretInfo>, ISecretService
.Add(nameof(SecretInfo.Key), SecretsConstants.SecretManagementKey)
.Add(nameof(SecretInfo.Secret), "crupest")
.Add(nameof(SecretInfo.Description), "This is the init key. Please revoke it immediately after creating a new one.");
- _table.Insert(connection, insertClause);
+ _table.Insert(connection, insertClause, out var _);
transaction.Commit();
}
@@ -30,7 +30,7 @@ public class SecretService : CrudService<SecretInfo>, ISecretService
.Add(nameof(SecretInfo.Key), key)
.Add(nameof(SecretInfo.Secret), secret)
.Add(nameof(SecretInfo.Description), "Test secret.");
- _table.Insert(connection, insertClause);
+ _table.Insert(connection, insertClause, out var _);
}
public List<string> GetPermissions(string secret)