save development progress

This commit is contained in:
StellaOps Bot
2025-12-25 23:09:58 +02:00
parent d71853ad7e
commit aa70af062e
351 changed files with 37683 additions and 150156 deletions

View File

@@ -0,0 +1,132 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Federation.Export;
using StellaOps.Concelier.Federation.Models;
using StellaOps.Concelier.WebService.Options;
using StellaOps.Concelier.WebService.Results;
using HttpResults = Microsoft.AspNetCore.Http.Results;
namespace StellaOps.Concelier.WebService.Extensions;
/// <summary>
/// Endpoint extensions for Federation functionality.
/// Per SPRINT_8200_0014_0002_CONCEL_delta_bundle_export.
/// </summary>
internal static class FederationEndpointExtensions
{
public static void MapConcelierFederationEndpoints(this WebApplication app)
{
var group = app.MapGroup("/api/v1/federation")
.WithTags("Federation");
// GET /api/v1/federation/export - Export delta bundle
group.MapGet("/export", async (
HttpContext context,
IBundleExportService exportService,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
CancellationToken cancellationToken,
[FromQuery(Name = "since_cursor")] string? sinceCursor = null,
[FromQuery] bool sign = true,
[FromQuery(Name = "max_items")] int maxItems = 10000,
[FromQuery(Name = "compress_level")] int compressLevel = 3) =>
{
var options = optionsMonitor.CurrentValue;
if (!options.Federation.Enabled)
{
return ConcelierProblemResultFactory.FederationDisabled(context);
}
// Validate parameters
if (maxItems < 1 || maxItems > 100_000)
{
return HttpResults.BadRequest(new { error = "max_items must be between 1 and 100000" });
}
if (compressLevel < 1 || compressLevel > 19)
{
return HttpResults.BadRequest(new { error = "compress_level must be between 1 and 19" });
}
var exportOptions = new BundleExportOptions
{
Sign = sign,
MaxItems = maxItems,
CompressionLevel = compressLevel
};
// Set response headers for streaming
context.Response.ContentType = "application/zstd";
context.Response.Headers.ContentDisposition =
$"attachment; filename=\"feedser-bundle-{DateTime.UtcNow:yyyyMMdd-HHmmss}.zst\"";
// Export directly to response stream
var result = await exportService.ExportToStreamAsync(
context.Response.Body,
sinceCursor,
exportOptions,
cancellationToken);
// Add metadata headers
context.Response.Headers.Append("X-Bundle-Hash", result.BundleHash);
context.Response.Headers.Append("X-Export-Cursor", result.ExportCursor);
context.Response.Headers.Append("X-Items-Count", result.Counts.Total.ToString());
return HttpResults.Empty;
})
.WithName("ExportFederationBundle")
.WithSummary("Export delta bundle for federation sync")
.Produces(200, contentType: "application/zstd")
.ProducesProblem(400)
.ProducesProblem(503);
// GET /api/v1/federation/export/preview - Preview export statistics
group.MapGet("/export/preview", async (
HttpContext context,
IBundleExportService exportService,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
CancellationToken cancellationToken,
[FromQuery(Name = "since_cursor")] string? sinceCursor = null) =>
{
var options = optionsMonitor.CurrentValue;
if (!options.Federation.Enabled)
{
return ConcelierProblemResultFactory.FederationDisabled(context);
}
var preview = await exportService.PreviewAsync(sinceCursor, cancellationToken);
return HttpResults.Ok(new
{
since_cursor = sinceCursor,
estimated_canonicals = preview.EstimatedCanonicals,
estimated_edges = preview.EstimatedEdges,
estimated_deletions = preview.EstimatedDeletions,
estimated_size_bytes = preview.EstimatedSizeBytes,
estimated_size_mb = Math.Round(preview.EstimatedSizeBytes / 1024.0 / 1024.0, 2)
});
})
.WithName("PreviewFederationExport")
.WithSummary("Preview export statistics without creating bundle")
.Produces<object>(200)
.ProducesProblem(503);
// GET /api/v1/federation/status - Federation status
group.MapGet("/status", (
HttpContext context,
IOptionsMonitor<ConcelierOptions> optionsMonitor) =>
{
var options = optionsMonitor.CurrentValue;
return HttpResults.Ok(new
{
enabled = options.Federation.Enabled,
site_id = options.Federation.SiteId,
default_compression_level = options.Federation.DefaultCompressionLevel,
default_max_items = options.Federation.DefaultMaxItems
});
})
.WithName("GetFederationStatus")
.WithSummary("Get federation configuration status")
.Produces<object>(200);
}
}