45 lines
2.0 KiB
C#
45 lines
2.0 KiB
C#
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Routing;
|
|
using MoShiro.Backend.Application;
|
|
using MoShiro.Protocol;
|
|
|
|
namespace MoShiro.Backend.Api;
|
|
|
|
/// <summary>审计日志:管理接口里每个写操作、登录都会记一行,这里只读。</summary>
|
|
public static class AuditEndpoints
|
|
{
|
|
private const string Tag = "admin-audit";
|
|
|
|
/// <summary>handler 往这里放一句「这次具体做了什么」(例如新建令牌的名字),守卫记审计时一起写进去。</summary>
|
|
public const string DetailItem = "moshiro.audit.detail";
|
|
|
|
public static void Map(RouteGroupBuilder guarded) =>
|
|
guarded.MapGet("/audit", ListAsync)
|
|
.Api("audit_list", "Audit log of admin actions, newest first", Tag).Returns<PageDto<AuditDto>>().ProducesProblems(400, 401, 403)
|
|
.Requires(Capability.AuditRead);
|
|
|
|
public static void SetDetail(this HttpContext http, string detail) => http.Items[DetailItem] = detail;
|
|
|
|
private static async Task<IResult> ListAsync(HttpContext http, AuditService audit, string? actor, string? action, int? limit, string? cursor)
|
|
{
|
|
long? before = null;
|
|
if (cursor is not null)
|
|
{
|
|
if (!long.TryParse(cursor, out long parsed))
|
|
{
|
|
return Problems.From(http, ValidationError.Single("cursor", "is not a valid cursor"));
|
|
}
|
|
|
|
before = parsed;
|
|
}
|
|
|
|
int size = Math.Clamp(limit ?? 50, 1, 500);
|
|
IReadOnlyList<AuditRecord> rows = await audit.ListAsync(new AuditQuery(before, size + 1, string.IsNullOrEmpty(actor) ? null : actor, string.IsNullOrEmpty(action) ? null : action), http.RequestAborted);
|
|
bool more = rows.Count > size;
|
|
IReadOnlyList<AuditRecord> page = [.. rows.Take(size)];
|
|
string? next = more ? page[^1].Id.ToString(System.Globalization.CultureInfo.InvariantCulture) : null;
|
|
return TypedResults.Json(new PageDto<AuditDto>([.. page.Select(Mappers.ToDto)], next), ApiJsonContext.Default.PageDtoAuditDto);
|
|
}
|
|
}
|