using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PrivaPub.Api.Mastodon.Infrastructure; using PrivaPub.Domain.Statistics; namespace PrivaPub.Api.Mastodon.Controllers { // The CDNs in front of the servers this server knows, each named by its own domain (owner decision 2026-10-04): how many // servers are behind it, how it is recognised, and week by week who joined and who left. public class CdnsController : MastodonController { const int MaxWeeks = 260; readonly IServerPlaces _places; public CdnsController(IServerPlaces places) => _places = places; [HttpGet("/api/privapub/v1/cdns"), Scope("read", requiresUser: false), AllowAnonymous] public async Task List(CancellationToken token) => Json((await _places.Cdns(token)).Select(Summary).ToList()); [HttpGet("/api/privapub/v1/cdns/{key}"), Scope("read", requiresUser: false), AllowAnonymous] public async Task Detail(string key, CancellationToken token) { var history = await _places.Cdn(key, Math.Clamp(Params.Int("weeks") ?? 52, 1, MaxWeeks), token); if (history == default) return NotFoundError(); var detail = new CdnDetail { Hosts = history.Hosts, HostsTruncated = history.Group.Servers > history.Hosts.Count, Weeks = history.Weeks.Select(w => new CdnWeekEntry { Week = w.Week, Servers = w.Servers, Joined = w.Joined, Left = w.Left }).ToList() }; Fill(detail, history.Group); return Json(detail); } static CdnSummary Summary(CdnGroup group) { var summary = new CdnSummary(); Fill(summary, group); return summary; } static void Fill(CdnSummary summary, CdnGroup group) { summary.Key = group.Key; summary.Name = group.Name; summary.Domain = group.Domain; summary.Servers = group.Servers; summary.Ranges = group.Ranges; summary.RangesUpdatedAt = group.RangesUpdatedAt is { } at ? MastodonJson.Time(at) : default; summary.DetectedBy = group.DetectedBy; } public class CdnSummary { public string Key { get; set; } public string Name { get; set; } public string Domain { get; set; } public int Servers { get; set; } public int Ranges { get; set; } public string RangesUpdatedAt { get; set; } public List DetectedBy { get; set; } = new(); } public class CdnDetail : CdnSummary { public List Hosts { get; set; } = new(); public bool HostsTruncated { get; set; } public List Weeks { get; set; } = new(); } public class CdnWeekEntry { public string Week { get; set; } public int Servers { get; set; } public List Joined { get; set; } = new(); public List Left { get; set; } = new(); } } }