save development progress
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user