feat: Implement provisioning pipelines for subscription management

- Add ReactivatePipeline to handle subscription reactivation, including scaling Docker services, health verification, status updates, audit logging, and broadcasting status changes.
- Introduce RotateCredentialsPipeline for OAuth2 credential rotation, managing the deletion of old apps, creation of new ones, credential storage, access verification, and audit logging.
- Create StepRunner to manage job step execution, including lifecycle management and progress broadcasting via SignalR.
- Implement SuspendPipeline for subscription suspension, scaling down services, updating statuses, logging audits, and broadcasting changes.
- Add UpdateScreenLimitPipeline to update Xibo CMS screen limits and record snapshots.
- Introduce XiboFeatureManifests for hardcoded feature ACLs per role.
- Add docker-compose.dev.yml for local development with PostgreSQL setup.
This commit is contained in:
Matt Batchelder
2026-03-18 10:27:26 -04:00
parent c2e03de8bb
commit c6d46098dd
77 changed files with 9412 additions and 29 deletions

View File

@@ -0,0 +1,13 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public class AuditLog
{
public Guid Id { get; set; }
public Guid? InstanceId { get; set; }
public string Actor { get; set; } = string.Empty;
public string Action { get; set; } = string.Empty;
public string Target { get; set; } = string.Empty;
public string? Outcome { get; set; }
public string? Detail { get; set; }
public DateTime OccurredAt { get; set; }
}

View File

@@ -0,0 +1,17 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public enum AuthentikMetricsStatus
{
Healthy,
Degraded,
Critical
}
public class AuthentikMetrics
{
public Guid Id { get; set; }
public DateTime CheckedAt { get; set; }
public AuthentikMetricsStatus Status { get; set; }
public int LatencyMs { get; set; }
public string? ErrorMessage { get; set; }
}

View File

@@ -0,0 +1,16 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public class ByoiConfig
{
public Guid Id { get; set; }
public Guid InstanceId { get; set; }
public string Slug { get; set; } = string.Empty;
public string EntityId { get; set; } = string.Empty;
public string SsoUrl { get; set; } = string.Empty;
public string CertPem { get; set; } = string.Empty;
public DateTime CertExpiry { get; set; }
public bool Enabled { get; set; }
public DateTime CreatedAt { get; set; }
public Instance Instance { get; set; } = null!;
}

View File

@@ -0,0 +1,38 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public enum CustomerPlan
{
Essentials,
Pro
}
public enum CustomerStatus
{
PendingPayment,
Provisioning,
Active,
Suspended,
Decommissioned
}
public class Customer
{
public Guid Id { get; set; }
public string Abbreviation { get; set; } = string.Empty;
public string CompanyName { get; set; } = string.Empty;
public string AdminEmail { get; set; } = string.Empty;
public string AdminFirstName { get; set; } = string.Empty;
public string AdminLastName { get; set; } = string.Empty;
public CustomerPlan Plan { get; set; }
public int ScreenCount { get; set; }
public string? StripeCustomerId { get; set; }
public string? StripeSubscriptionId { get; set; }
public string? StripeCheckoutSessionId { get; set; }
public CustomerStatus Status { get; set; }
public int FailedPaymentCount { get; set; }
public DateTime? FirstPaymentFailedAt { get; set; }
public DateTime CreatedAt { get; set; }
public ICollection<Instance> Instances { get; set; } = [];
public ICollection<Job> Jobs { get; set; } = [];
}

View File

@@ -0,0 +1,21 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public enum HealthEventStatus
{
Healthy,
Degraded,
Critical
}
public class HealthEvent
{
public Guid Id { get; set; }
public Guid InstanceId { get; set; }
public string CheckName { get; set; } = string.Empty;
public HealthEventStatus Status { get; set; }
public string? Message { get; set; }
public bool Remediated { get; set; }
public DateTime OccurredAt { get; set; }
public Instance Instance { get; set; } = null!;
}

View File

@@ -0,0 +1,30 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public enum HealthStatus
{
Unknown,
Healthy,
Degraded,
Critical
}
public class Instance
{
public Guid Id { get; set; }
public Guid CustomerId { get; set; }
public string XiboUrl { get; set; } = string.Empty;
public string DockerStackName { get; set; } = string.Empty;
public string MysqlDatabase { get; set; } = string.Empty;
public string NfsPath { get; set; } = string.Empty;
public string? CmsAdminPassRef { get; set; }
public string? AuthentikProviderId { get; set; }
public HealthStatus HealthStatus { get; set; }
public DateTime? LastHealthCheck { get; set; }
public DateTime CreatedAt { get; set; }
public Customer Customer { get; set; } = null!;
public ICollection<HealthEvent> HealthEvents { get; set; } = [];
public ICollection<ScreenSnapshot> ScreenSnapshots { get; set; } = [];
public ICollection<OauthAppRegistry> OauthAppRegistries { get; set; } = [];
public ICollection<ByoiConfig> ByoiConfigs { get; set; } = [];
}

View File

@@ -0,0 +1,26 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public enum JobStatus
{
Queued,
Running,
Completed,
Failed
}
public class Job
{
public Guid Id { get; set; }
public Guid CustomerId { get; set; }
public string JobType { get; set; } = string.Empty;
public JobStatus Status { get; set; }
public string? TriggeredBy { get; set; }
public string? Parameters { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? StartedAt { get; set; }
public DateTime? CompletedAt { get; set; }
public string? ErrorMessage { get; set; }
public Customer Customer { get; set; } = null!;
public ICollection<JobStep> Steps { get; set; } = [];
}

View File

@@ -0,0 +1,22 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public enum JobStepStatus
{
Queued,
Running,
Completed,
Failed
}
public class JobStep
{
public Guid Id { get; set; }
public Guid JobId { get; set; }
public string StepName { get; set; } = string.Empty;
public JobStepStatus Status { get; set; }
public string? LogOutput { get; set; }
public DateTime? StartedAt { get; set; }
public DateTime? CompletedAt { get; set; }
public Job Job { get; set; } = null!;
}

View File

@@ -0,0 +1,11 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public class OauthAppRegistry
{
public Guid Id { get; set; }
public Guid InstanceId { get; set; }
public string ClientId { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public Instance Instance { get; set; } = null!;
}

View File

@@ -0,0 +1,18 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public enum OperatorRole
{
Admin,
Viewer
}
public class Operator
{
public Guid Id { get; set; }
public string Email { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
public OperatorRole Role { get; set; }
public DateTime CreatedAt { get; set; }
public ICollection<RefreshToken> RefreshTokens { get; set; } = [];
}

View File

@@ -0,0 +1,12 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public class RefreshToken
{
public Guid Id { get; set; }
public Guid OperatorId { get; set; }
public string Token { get; set; } = string.Empty;
public DateTime ExpiresAt { get; set; }
public DateTime? RevokedAt { get; set; }
public Operator Operator { get; set; } = null!;
}

View File

@@ -0,0 +1,12 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public class ScreenSnapshot
{
public Guid Id { get; set; }
public Guid InstanceId { get; set; }
public DateOnly SnapshotDate { get; set; }
public int ScreenCount { get; set; }
public DateTime CreatedAt { get; set; }
public Instance Instance { get; set; } = null!;
}

View File

@@ -0,0 +1,9 @@
namespace OTSSignsOrchestrator.Server.Data.Entities;
public class StripeEvent
{
public string StripeEventId { get; set; } = string.Empty;
public string EventType { get; set; } = string.Empty;
public DateTime ProcessedAt { get; set; }
public string? Payload { get; set; }
}