using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using MoShiro.Backend.Application; namespace MoShiro.Backend.Api; /// /// 节点的展示信息:系统、架构、国家、运营商。只有这几项,没有任何 IP 地址、城市、ASN、经纬度—— /// 这些在 Backend 里只在查询的那一刻用到,存起来的记录里也没有,所以访客(和任何别的接口)都拿不到。 /// public static class NodeEndpoints { private const string Tag = "public-nodes"; public static void Map(RouteGroupBuilder group) { group.MapGet("/nodes", ListAsync) .Api("nodes_list", "Display info of the visible agents and probes: system, architecture, country and ISP (never an address)", Tag) .Returns>().ProducesProblems(403); group.MapGet("/about", AboutAsync) .Api("about_get", "The backend's version, tag and build time, if the site shows build info", Tag) .Returns().ProducesProblems(403); } /// 这个后端自己的构建信息;没有(本地构建)的字段是 null。 internal static BuildDto Backend(bool withCommit) => new(MoShiro.Protocol.BuildInfo.Version, Empty(MoShiro.Protocol.BuildInfo.Tag), withCommit ? Empty(MoShiro.Protocol.BuildInfo.Commit) : null, Empty(MoShiro.Protocol.BuildInfo.BuiltAt)); private static string? Empty(string value) => value.Length == 0 ? null : value; private static async Task AboutAsync(HttpContext http, SettingsService settings) { bool show = await settings.IsShowBuildAsync(http.RequestAborted); http.Response.Headers.CacheControl = "no-store"; return TypedResults.Json(new AboutDto(show, show ? Backend(withCommit: false) : null), ApiJsonContext.Default.AboutDto); } private static async Task ListAsync(HttpContext http, AgentService agents, ProbeService probes, NodeInfoService nodes, SettingsService settings, CancellationToken ct) { // 和别的公开接口一样:看得见的 Agent(隐藏的只有登录后才看得见)和没有被吊销的 Probe var visible = new HashSet(StringComparer.Ordinal); foreach (AgentView view in await agents.ListAsync(ct)) { if (view.Agent.Status == Fleet.Server.NodeStatus.Active && (view.Agent.PublicVisible || PublicGuard.CanSeeHidden(http))) { visible.Add(view.Agent.Id.Value); } } foreach (ProbeView probe in await probes.ListAsync(ct)) { if (!probe.Revoked) { visible.Add(probe.Probe.Id); } } // 版本和 tag 只在站点打开「显示构建信息」时才给访客;关着就是 null bool showBuild = await settings.IsShowBuildAsync(ct); var items = (await nodes.ListAsync(ct)) .Where(n => visible.Contains(n.NodeId)) .OrderBy(n => n.NodeId, StringComparer.Ordinal) .Select(n => new NodeInfoDto(n.NodeId, n.Kind, n.Os, n.OsName, n.Arch, n.Country, n.IspKey, n.IspName, showBuild ? n.Version : null, showBuild ? n.Tag : null)) .ToList(); http.Response.Headers.CacheControl = "no-store"; return TypedResults.Json(new PageDto(items, null), ApiJsonContext.Default.PageDtoNodeInfoDto); } }