tenant fixes
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
|
||||
namespace StellaOps.ExportCenter.Core.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for managing export distributions.
|
||||
/// </summary>
|
||||
public interface IExportDistributionRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a distribution by ID.
|
||||
/// </summary>
|
||||
Task<ExportDistribution?> GetByIdAsync(
|
||||
Guid tenantId,
|
||||
Guid distributionId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a distribution by idempotency key.
|
||||
/// </summary>
|
||||
Task<ExportDistribution?> GetByIdempotencyKeyAsync(
|
||||
Guid tenantId,
|
||||
string idempotencyKey,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists distributions for a run.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ExportDistribution>> ListByRunAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists distributions by status.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ExportDistribution>> ListByStatusAsync(
|
||||
Guid tenantId,
|
||||
ExportDistributionStatus status,
|
||||
int limit = 100,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists distributions due for retention deletion.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ExportDistribution>> ListExpiredAsync(
|
||||
DateTimeOffset asOf,
|
||||
int limit = 100,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new distribution record.
|
||||
/// </summary>
|
||||
Task<ExportDistribution> CreateAsync(
|
||||
ExportDistribution distribution,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates a distribution record.
|
||||
/// Returns the updated record, or null if not found.
|
||||
/// </summary>
|
||||
Task<ExportDistribution?> UpdateAsync(
|
||||
ExportDistribution distribution,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Performs an idempotent upsert based on idempotency key.
|
||||
/// Returns existing distribution if key matches, otherwise creates new.
|
||||
/// </summary>
|
||||
Task<(ExportDistribution Distribution, bool WasCreated)> UpsertByIdempotencyKeyAsync(
|
||||
ExportDistribution distribution,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Marks a distribution for deletion.
|
||||
/// </summary>
|
||||
Task<bool> MarkForDeletionAsync(
|
||||
Guid tenantId,
|
||||
Guid distributionId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a distribution record and returns whether it existed.
|
||||
/// </summary>
|
||||
Task<bool> DeleteAsync(
|
||||
Guid tenantId,
|
||||
Guid distributionId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets distribution statistics for a run.
|
||||
/// </summary>
|
||||
Task<ExportDistributionStats> GetStatsAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Statistics for distributions of a run.
|
||||
/// </summary>
|
||||
public sealed record ExportDistributionStats
|
||||
{
|
||||
public int Total { get; init; }
|
||||
public int Pending { get; init; }
|
||||
public int Distributing { get; init; }
|
||||
public int Distributed { get; init; }
|
||||
public int Verified { get; init; }
|
||||
public int Failed { get; init; }
|
||||
public int Cancelled { get; init; }
|
||||
public long TotalSizeBytes { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
|
||||
namespace StellaOps.ExportCenter.Core.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for managing export profiles.
|
||||
/// </summary>
|
||||
public interface IExportProfileRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a profile by ID for a tenant.
|
||||
/// </summary>
|
||||
Task<ExportProfile?> GetByIdAsync(
|
||||
Guid tenantId,
|
||||
Guid profileId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists profiles for a tenant with optional filtering.
|
||||
/// </summary>
|
||||
Task<(IReadOnlyList<ExportProfile> Items, int TotalCount)> ListAsync(
|
||||
Guid tenantId,
|
||||
ExportProfileStatus? status = null,
|
||||
ExportProfileKind? kind = null,
|
||||
string? search = null,
|
||||
int offset = 0,
|
||||
int limit = 50,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new profile.
|
||||
/// </summary>
|
||||
Task<ExportProfile> CreateAsync(
|
||||
ExportProfile profile,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing profile.
|
||||
/// </summary>
|
||||
Task<ExportProfile?> UpdateAsync(
|
||||
ExportProfile profile,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Archives a profile (soft delete).
|
||||
/// </summary>
|
||||
Task<bool> ArchiveAsync(
|
||||
Guid tenantId,
|
||||
Guid profileId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a profile name is unique within a tenant.
|
||||
/// </summary>
|
||||
Task<bool> IsNameUniqueAsync(
|
||||
Guid tenantId,
|
||||
string name,
|
||||
Guid? excludeProfileId = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets active scheduled profiles for processing.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ExportProfile>> GetScheduledProfilesAsync(
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
|
||||
namespace StellaOps.ExportCenter.Core.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for managing export runs.
|
||||
/// </summary>
|
||||
public interface IExportRunRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a run by ID for a tenant.
|
||||
/// </summary>
|
||||
Task<ExportRun?> GetByIdAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists runs for a tenant with optional filtering.
|
||||
/// </summary>
|
||||
Task<(IReadOnlyList<ExportRun> Items, int TotalCount)> ListAsync(
|
||||
Guid tenantId,
|
||||
Guid? profileId = null,
|
||||
ExportRunStatus? status = null,
|
||||
ExportRunTrigger? trigger = null,
|
||||
DateTimeOffset? createdAfter = null,
|
||||
DateTimeOffset? createdBefore = null,
|
||||
string? correlationId = null,
|
||||
int offset = 0,
|
||||
int limit = 50,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new run.
|
||||
/// </summary>
|
||||
Task<ExportRun> CreateAsync(
|
||||
ExportRun run,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates run status and progress.
|
||||
/// </summary>
|
||||
Task<ExportRun?> UpdateAsync(
|
||||
ExportRun run,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Cancels a run if it's in a cancellable state.
|
||||
/// </summary>
|
||||
Task<bool> CancelAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets active runs count for concurrency checks.
|
||||
/// </summary>
|
||||
Task<int> GetActiveRunsCountAsync(
|
||||
Guid tenantId,
|
||||
Guid? profileId = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets queued runs count.
|
||||
/// </summary>
|
||||
Task<int> GetQueuedRunsCountAsync(
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next queued run to execute.
|
||||
/// </summary>
|
||||
Task<ExportRun?> DequeueNextRunAsync(
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repository for managing export artifacts.
|
||||
/// </summary>
|
||||
public interface IExportArtifactRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an artifact by ID.
|
||||
/// </summary>
|
||||
Task<ExportArtifact?> GetByIdAsync(
|
||||
Guid tenantId,
|
||||
Guid artifactId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists artifacts for a run.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ExportArtifact>> ListByRunAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new artifact record.
|
||||
/// </summary>
|
||||
Task<ExportArtifact> CreateAsync(
|
||||
ExportArtifact artifact,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes artifacts for a run.
|
||||
/// </summary>
|
||||
Task<int> DeleteByRunAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an export artifact.
|
||||
/// </summary>
|
||||
public sealed record ExportArtifact
|
||||
{
|
||||
public required Guid ArtifactId { get; init; }
|
||||
public required Guid RunId { get; init; }
|
||||
public required Guid TenantId { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required string Kind { get; init; }
|
||||
public required string Path { get; init; }
|
||||
public long SizeBytes { get; init; }
|
||||
public string? ContentType { get; init; }
|
||||
public required string Checksum { get; init; }
|
||||
public string ChecksumAlgorithm { get; init; } = "SHA-256";
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Npgsql;
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
using StellaOps.ExportCenter.Infrastructure.Db;
|
||||
using StellaOps.ExportCenter.Infrastructure.EfCore.Models;
|
||||
using StellaOps.ExportCenter.WebService.Distribution;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
|
||||
namespace StellaOps.ExportCenter.Infrastructure.Postgres.Repositories;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using Npgsql;
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
using StellaOps.ExportCenter.Infrastructure.Db;
|
||||
using StellaOps.ExportCenter.Infrastructure.EfCore.Models;
|
||||
using StellaOps.ExportCenter.WebService.Api;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
|
||||
namespace StellaOps.ExportCenter.Infrastructure.Postgres.Repositories;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using Npgsql;
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
using StellaOps.ExportCenter.Infrastructure.Db;
|
||||
using StellaOps.ExportCenter.Infrastructure.EfCore.Models;
|
||||
using StellaOps.ExportCenter.WebService.Api;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
|
||||
namespace StellaOps.ExportCenter.Infrastructure.Postgres.Repositories;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
using StellaOps.ExportCenter.WebService.Api;
|
||||
using StellaOps.TestKit;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
using StellaOps.ExportCenter.WebService.Api;
|
||||
using Xunit;
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
using StellaOps.ExportCenter.Core.Planner;
|
||||
using IExportProfileRepository = StellaOps.ExportCenter.Core.Persistence.IExportProfileRepository;
|
||||
using StellaOps.ExportCenter.WebService.Telemetry;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Claims;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
|
||||
namespace StellaOps.ExportCenter.WebService.Api;
|
||||
|
||||
|
||||
@@ -1,66 +1,3 @@
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
|
||||
namespace StellaOps.ExportCenter.WebService.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for managing export profiles.
|
||||
/// </summary>
|
||||
public interface IExportProfileRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a profile by ID for a tenant.
|
||||
/// </summary>
|
||||
Task<ExportProfile?> GetByIdAsync(
|
||||
Guid tenantId,
|
||||
Guid profileId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists profiles for a tenant with optional filtering.
|
||||
/// </summary>
|
||||
Task<(IReadOnlyList<ExportProfile> Items, int TotalCount)> ListAsync(
|
||||
Guid tenantId,
|
||||
ExportProfileStatus? status = null,
|
||||
ExportProfileKind? kind = null,
|
||||
string? search = null,
|
||||
int offset = 0,
|
||||
int limit = 50,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new profile.
|
||||
/// </summary>
|
||||
Task<ExportProfile> CreateAsync(
|
||||
ExportProfile profile,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing profile.
|
||||
/// </summary>
|
||||
Task<ExportProfile?> UpdateAsync(
|
||||
ExportProfile profile,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Archives a profile (soft delete).
|
||||
/// </summary>
|
||||
Task<bool> ArchiveAsync(
|
||||
Guid tenantId,
|
||||
Guid profileId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a profile name is unique within a tenant.
|
||||
/// </summary>
|
||||
Task<bool> IsNameUniqueAsync(
|
||||
Guid tenantId,
|
||||
string name,
|
||||
Guid? excludeProfileId = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets active scheduled profiles for processing.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ExportProfile>> GetScheduledProfilesAsync(
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
// This interface has been moved to StellaOps.ExportCenter.Core.Persistence.
|
||||
// Import that namespace instead of StellaOps.ExportCenter.WebService.Api for IExportProfileRepository.
|
||||
// This file is kept for reference only.
|
||||
|
||||
@@ -1,133 +1,4 @@
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
|
||||
namespace StellaOps.ExportCenter.WebService.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for managing export runs.
|
||||
/// </summary>
|
||||
public interface IExportRunRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a run by ID for a tenant.
|
||||
/// </summary>
|
||||
Task<ExportRun?> GetByIdAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists runs for a tenant with optional filtering.
|
||||
/// </summary>
|
||||
Task<(IReadOnlyList<ExportRun> Items, int TotalCount)> ListAsync(
|
||||
Guid tenantId,
|
||||
Guid? profileId = null,
|
||||
ExportRunStatus? status = null,
|
||||
ExportRunTrigger? trigger = null,
|
||||
DateTimeOffset? createdAfter = null,
|
||||
DateTimeOffset? createdBefore = null,
|
||||
string? correlationId = null,
|
||||
int offset = 0,
|
||||
int limit = 50,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new run.
|
||||
/// </summary>
|
||||
Task<ExportRun> CreateAsync(
|
||||
ExportRun run,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates run status and progress.
|
||||
/// </summary>
|
||||
Task<ExportRun?> UpdateAsync(
|
||||
ExportRun run,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Cancels a run if it's in a cancellable state.
|
||||
/// </summary>
|
||||
Task<bool> CancelAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets active runs count for concurrency checks.
|
||||
/// </summary>
|
||||
Task<int> GetActiveRunsCountAsync(
|
||||
Guid tenantId,
|
||||
Guid? profileId = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets queued runs count.
|
||||
/// </summary>
|
||||
Task<int> GetQueuedRunsCountAsync(
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next queued run to execute.
|
||||
/// </summary>
|
||||
Task<ExportRun?> DequeueNextRunAsync(
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repository for managing export artifacts.
|
||||
/// </summary>
|
||||
public interface IExportArtifactRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an artifact by ID.
|
||||
/// </summary>
|
||||
Task<ExportArtifact?> GetByIdAsync(
|
||||
Guid tenantId,
|
||||
Guid artifactId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists artifacts for a run.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ExportArtifact>> ListByRunAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new artifact record.
|
||||
/// </summary>
|
||||
Task<ExportArtifact> CreateAsync(
|
||||
ExportArtifact artifact,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes artifacts for a run.
|
||||
/// </summary>
|
||||
Task<int> DeleteByRunAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an export artifact.
|
||||
/// </summary>
|
||||
public sealed record ExportArtifact
|
||||
{
|
||||
public required Guid ArtifactId { get; init; }
|
||||
public required Guid RunId { get; init; }
|
||||
public required Guid TenantId { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required string Kind { get; init; }
|
||||
public required string Path { get; init; }
|
||||
public long SizeBytes { get; init; }
|
||||
public string? ContentType { get; init; }
|
||||
public required string Checksum { get; init; }
|
||||
public string ChecksumAlgorithm { get; init; } = "SHA-256";
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
}
|
||||
// The IExportRunRepository, IExportArtifactRepository interfaces and ExportArtifact record
|
||||
// have been moved to StellaOps.ExportCenter.Core.Persistence.
|
||||
// Import that namespace instead of StellaOps.ExportCenter.WebService.Api.
|
||||
// This file is kept for reference only.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace StellaOps.ExportCenter.WebService.Api;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
using StellaOps.ExportCenter.WebService.Distribution.Oci;
|
||||
|
||||
namespace StellaOps.ExportCenter.WebService.Distribution;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
|
||||
namespace StellaOps.ExportCenter.WebService.Distribution;
|
||||
|
||||
|
||||
@@ -1,112 +1,4 @@
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
|
||||
namespace StellaOps.ExportCenter.WebService.Distribution;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for managing export distributions.
|
||||
/// </summary>
|
||||
public interface IExportDistributionRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a distribution by ID.
|
||||
/// </summary>
|
||||
Task<ExportDistribution?> GetByIdAsync(
|
||||
Guid tenantId,
|
||||
Guid distributionId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a distribution by idempotency key.
|
||||
/// </summary>
|
||||
Task<ExportDistribution?> GetByIdempotencyKeyAsync(
|
||||
Guid tenantId,
|
||||
string idempotencyKey,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists distributions for a run.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ExportDistribution>> ListByRunAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists distributions by status.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ExportDistribution>> ListByStatusAsync(
|
||||
Guid tenantId,
|
||||
ExportDistributionStatus status,
|
||||
int limit = 100,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists distributions due for retention deletion.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ExportDistribution>> ListExpiredAsync(
|
||||
DateTimeOffset asOf,
|
||||
int limit = 100,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new distribution record.
|
||||
/// </summary>
|
||||
Task<ExportDistribution> CreateAsync(
|
||||
ExportDistribution distribution,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates a distribution record.
|
||||
/// Returns the updated record, or null if not found.
|
||||
/// </summary>
|
||||
Task<ExportDistribution?> UpdateAsync(
|
||||
ExportDistribution distribution,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Performs an idempotent upsert based on idempotency key.
|
||||
/// Returns existing distribution if key matches, otherwise creates new.
|
||||
/// </summary>
|
||||
Task<(ExportDistribution Distribution, bool WasCreated)> UpsertByIdempotencyKeyAsync(
|
||||
ExportDistribution distribution,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Marks a distribution for deletion.
|
||||
/// </summary>
|
||||
Task<bool> MarkForDeletionAsync(
|
||||
Guid tenantId,
|
||||
Guid distributionId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a distribution record and returns whether it existed.
|
||||
/// </summary>
|
||||
Task<bool> DeleteAsync(
|
||||
Guid tenantId,
|
||||
Guid distributionId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets distribution statistics for a run.
|
||||
/// </summary>
|
||||
Task<ExportDistributionStats> GetStatsAsync(
|
||||
Guid tenantId,
|
||||
Guid runId,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Statistics for distributions of a run.
|
||||
/// </summary>
|
||||
public sealed record ExportDistributionStats
|
||||
{
|
||||
public int Total { get; init; }
|
||||
public int Pending { get; init; }
|
||||
public int Distributing { get; init; }
|
||||
public int Distributed { get; init; }
|
||||
public int Verified { get; init; }
|
||||
public int Failed { get; init; }
|
||||
public int Cancelled { get; init; }
|
||||
public long TotalSizeBytes { get; init; }
|
||||
}
|
||||
// The IExportDistributionRepository interface and ExportDistributionStats record
|
||||
// have been moved to StellaOps.ExportCenter.Core.Persistence.
|
||||
// Import that namespace instead of StellaOps.ExportCenter.WebService.Distribution.
|
||||
// This file is kept for reference only.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.ExportCenter.Core.Domain;
|
||||
using StellaOps.ExportCenter.Core.Persistence;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace StellaOps.ExportCenter.WebService.Distribution;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using StellaOps.AirGap.Policy;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
using StellaOps.ExportCenter.WebService;
|
||||
using StellaOps.ExportCenter.WebService.Api;
|
||||
using StellaOps.ExportCenter.WebService.Attestation;
|
||||
@@ -104,6 +105,7 @@ builder.Services.AddExportApiServices(options =>
|
||||
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
builder.Services.AddStellaOpsTenantServices();
|
||||
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
|
||||
|
||||
// Stella Router integration
|
||||
@@ -125,6 +127,7 @@ if (app.Environment.IsDevelopment())
|
||||
app.UseStellaOpsCors();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseStellaOpsTenantMiddleware();
|
||||
app.TryUseStellaRouter(routerEnabled);
|
||||
|
||||
// OpenAPI discovery endpoints (anonymous)
|
||||
@@ -162,19 +165,22 @@ app.MapGet("/exports", () => Results.Ok(Array.Empty<object>()))
|
||||
.RequireAuthorization(StellaOpsResourceServerPolicies.ExportViewer)
|
||||
.WithDeprecation(DeprecatedEndpointsRegistry.ListExports)
|
||||
.WithSummary("List exports (DEPRECATED)")
|
||||
.WithDescription("This endpoint is deprecated. Use GET /v1/exports/profiles instead.");
|
||||
.WithDescription("This endpoint is deprecated. Use GET /v1/exports/profiles instead.")
|
||||
.RequireTenant();
|
||||
|
||||
app.MapPost("/exports", () => Results.Accepted("/exports", new { status = "scheduled" }))
|
||||
.RequireAuthorization(StellaOpsResourceServerPolicies.ExportOperator)
|
||||
.WithDeprecation(DeprecatedEndpointsRegistry.CreateExport)
|
||||
.WithSummary("Create export (DEPRECATED)")
|
||||
.WithDescription("This endpoint is deprecated. Use POST /v1/exports/evidence or /v1/exports/attestations instead.");
|
||||
.WithDescription("This endpoint is deprecated. Use POST /v1/exports/evidence or /v1/exports/attestations instead.")
|
||||
.RequireTenant();
|
||||
|
||||
app.MapDelete("/exports/{id}", (string id) => Results.NoContent())
|
||||
.RequireAuthorization(StellaOpsResourceServerPolicies.ExportAdmin)
|
||||
.WithDeprecation(DeprecatedEndpointsRegistry.DeleteExport)
|
||||
.WithSummary("Delete export (DEPRECATED)")
|
||||
.WithDescription("This endpoint is deprecated. Use POST /v1/exports/runs/{id}/cancel instead.");
|
||||
.WithDescription("This endpoint is deprecated. Use POST /v1/exports/runs/{id}/cancel instead.")
|
||||
.RequireTenant();
|
||||
|
||||
// Refresh Router endpoint cache
|
||||
app.TryRefreshStellaRouterEndpoints(routerEnabled);
|
||||
|
||||
Reference in New Issue
Block a user