using System.Text.Json;
using Fleet.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Monitor.Backend.Application;
using Monitor.Protocol;
namespace Monitor.Backend.Api;
/// API 令牌、站点设置与保留期、用户。令牌明文只在创建的响应里出现一次。
public static class SystemEndpoints
{
private const string TokensTag = "admin-api-tokens";
private const string SettingsTag = "admin-settings";
private const string UsersTag = "admin-users";
public static void Map(RouteGroupBuilder group)
{
group.MapGet("/api-tokens", ListTokensAsync)
.Api("api_tokens_list", "API tokens", TokensTag).Returns>().ProducesProblems(401, 403);
group.MapPost("/api-tokens", CreateTokenAsync)
.Api("api_tokens_create", "Create an API token (scope read or admin); the plaintext is returned only once", TokensTag)
.Returns(StatusCodes.Status201Created).ProducesProblems(400, 401, 403);
group.MapDelete("/api-tokens/{id}", RevokeTokenAsync)
.Api("api_tokens_revoke", "Revoke an API token", TokensTag).Returns(StatusCodes.Status204NoContent).ProducesProblems(401, 403);
group.MapGet("/settings", ListSettingsAsync)
.Api("settings_list", "Site settings and retention periods", SettingsTag).Returns>().ProducesProblems(401, 403);
group.MapPatch("/settings", PatchSettingsAsync)
.Accepts>("application/merge-patch+json", "application/json")
.Api("settings_update", "Change settings (merge-patch)", SettingsTag).Returns>().ProducesProblems(400, 401, 403);
group.MapGet("/users", ListUsersAsync)
.Api("users_list", "Panel users", UsersTag).Returns>().ProducesProblems(401, 403);
group.MapPost("/users", CreateUserAsync)
.Api("users_create", "Create a panel user", UsersTag).Returns(StatusCodes.Status201Created).ProducesProblems(400, 401, 403, 409);
group.MapPatch("/users/{id}", ChangePasswordAsync)
.Api("users_update", "Change a user's password", UsersTag).Returns(StatusCodes.Status204NoContent).ProducesProblems(400, 401, 403);
}
private static async Task ListTokensAsync(AuthService auth, CancellationToken ct) =>
TypedResults.Json(new PageDto([.. (await auth.ListApiTokensAsync(ct)).Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoApiTokenDto);
private static async Task CreateTokenAsync(HttpContext http, AuthService auth, CreateApiTokenRequest request)
{
Result result = await auth.CreateApiTokenAsync(request.Name ?? "", request.Scope ?? "", http.RequestAborted);
if (!result.TryGet(out CreatedApiToken? created, out FleetError? error))
{
return Problems.From(http, error);
}
http.Response.Headers.CacheControl = "no-store";
return TypedResults.Json(new CreatedApiTokenDto(Mappers.ToDto(created.Record), created.Plaintext), ApiJsonContext.Default.CreatedApiTokenDto, statusCode: StatusCodes.Status201Created);
}
private static async Task RevokeTokenAsync(AuthService auth, string id, CancellationToken ct)
{
await auth.RevokeApiTokenAsync(id, ct);
return TypedResults.NoContent();
}
private static async Task ListSettingsAsync(SettingsService settings, CancellationToken ct) =>
TypedResults.Json(new PageDto([.. (await settings.ListAsync(ct)).Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoSettingDto);
private static async Task PatchSettingsAsync(HttpContext http, SettingsService settings, AgentService agents, Dictionary changes)
{
Result> result = await settings.PatchAsync(changes, http.RequestAborted);
if (result.IsSuccess && changes.ContainsKey(SettingsService.AutoLink))
{
await agents.AfterAgentSetChangedAsync(http.RequestAborted); // 自动连接开关变了:重新下发各 Probe 的 Agent 列表和各 Agent 允许的 Probe
}
return result.TryGet(out IReadOnlyList? views, out FleetError? error)
? TypedResults.Json(new PageDto([.. views.Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoSettingDto)
: Problems.From(http, error);
}
private static async Task ListUsersAsync(AuthService auth, CancellationToken ct) =>
TypedResults.Json(new PageDto([.. (await auth.ListUsersAsync(ct)).Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoUserDto);
private static async Task CreateUserAsync(HttpContext http, AuthService auth, CreateUserRequest request)
{
Result result = await auth.CreateUserAsync(request.Username ?? "", request.Password ?? "", http.RequestAborted);
if (!result.TryGet(out UserRecord? user, out FleetError? error))
{
return Problems.From(http, error);
}
http.Response.Headers.Location = "/api/admin/users";
return TypedResults.Json(Mappers.ToDto(user), ApiJsonContext.Default.UserDto, statusCode: StatusCodes.Status201Created);
}
private static async Task ChangePasswordAsync(HttpContext http, AuthService auth, ChangePasswordRequest request, string id)
{
Result result = await auth.ChangePasswordAsync(id, request.Password ?? "", http.RequestAborted);
return result.IsSuccess ? TypedResults.NoContent() : Problems.From(http, result.Error);
}
}