V13-FE-011: finalize search list layout slice
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using Dapper;
|
||||
using FastEndpoints;
|
||||
using KBX.Shared.Experience;
|
||||
using Npgsql;
|
||||
|
||||
namespace KBX.Modules.Common.Suggestions.Submit;
|
||||
|
||||
public sealed record Request(
|
||||
string Category,
|
||||
string Message,
|
||||
bool IncludeScreenContext,
|
||||
SuggestionContext Context);
|
||||
|
||||
public sealed record Response(Guid SuggestionId, DateTimeOffset ReceivedAt);
|
||||
|
||||
public sealed class Endpoint(NpgsqlDataSource dataSource) : Endpoint<Request, Response>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/common/suggestions");
|
||||
Permissions("common.suggestion.create");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(Request req, CancellationToken ct)
|
||||
{
|
||||
if (req.Message.Trim().Length is < 3 or > 2000)
|
||||
{
|
||||
AddError(r => r.Message, "의견 내용은 3~2000자로 입력하세요.");
|
||||
await Send.ErrorsAsync(cancellation: ct);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.Category is not ("inconvenience" or "bug" or "improvement"))
|
||||
{
|
||||
AddError(r => r.Category, "지원하지 않는 의견 유형입니다.");
|
||||
await Send.ErrorsAsync(cancellation: ct);
|
||||
return;
|
||||
}
|
||||
|
||||
// Host application must replace these claims adapters with its authenticated tenant/user resolver.
|
||||
var tenantId = Guid.Empty;
|
||||
var userId = Guid.Empty;
|
||||
var id = Guid.NewGuid();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
object? context = null;
|
||||
if (req.IncludeScreenContext)
|
||||
{
|
||||
// Deliberately persist diagnostic keys only, not arbitrary screen/business values.
|
||||
context = new
|
||||
{
|
||||
req.Context.ActiveFilters,
|
||||
req.Context.GridLayoutVersion,
|
||||
};
|
||||
}
|
||||
|
||||
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
||||
await connection.ExecuteAsync(new CommandDefinition("""
|
||||
insert into kbx.user_suggestions
|
||||
(id, tenant_id, user_id, category, message, screen_id, screen_version,
|
||||
route, app_version, user_role, context, status, created_at)
|
||||
values
|
||||
(@Id, @TenantId, @UserId, @Category, @Message, @ScreenId, @ScreenVersion,
|
||||
@Route, @AppVersion, @UserRole, cast(@Context as jsonb), 'NEW', @CreatedAt)
|
||||
""", new
|
||||
{
|
||||
Id = id,
|
||||
TenantId = tenantId,
|
||||
UserId = userId,
|
||||
req.Category,
|
||||
Message = req.Message.Trim(),
|
||||
req.Context.ScreenId,
|
||||
req.Context.ScreenVersion,
|
||||
req.Context.Route,
|
||||
req.Context.AppVersion,
|
||||
req.Context.UserRole,
|
||||
Context = context is null ? null : System.Text.Json.JsonSerializer.Serialize(context),
|
||||
CreatedAt = now,
|
||||
}, cancellationToken: ct));
|
||||
|
||||
await Send.OkAsync(new Response(id, now), ct);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user