using Microsoft.EntityFrameworkCore; namespace Nexus.Api.Data; public sealed class NexusDbContext(DbContextOptions options) : DbContext(options) { public DbSet Projects => Set(); public DbSet Tasks => Set(); public DbSet Notifications => Set(); public DbSet Activity => Set(); public DbSet Users => Set(); public DbSet RefreshTokens => Set(); public DbSet SeedAudits => Set(); public DbSet OpenClawRuns => Set(); public DbSet OpenClawRunHistory => Set(); public DbSet OpenClawConnectionProfiles => Set(); public DbSet AgentProposals => Set(); public DbSet AgentProvisionRequests => Set(); public DbSet OperationClaims => Set(); public DbSet OutboxEvents => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new OpenClawConnectionProfileConfiguration()); ConfigureAgentProvisioning(modelBuilder); modelBuilder.Entity().Property(x => x.Name).HasMaxLength(160); modelBuilder.Entity(entity => { entity.Property(x => x.Title).HasMaxLength(240); entity.Property(x => x.Detail).HasMaxLength(2000); entity.Property(x => x.Source).HasMaxLength(60); entity.Property(x => x.AssignedTo).HasMaxLength(60); entity.Property(x => x.ExpectedFrom).HasMaxLength(60); entity.HasIndex(x => x.Source); entity.HasIndex(x => x.AssignedTo); entity.HasIndex(x => x.IsAgentTask); entity.HasIndex(x => x.ExpectedFrom); entity.HasIndex(x => new { x.State, x.UpdatedAt, x.Id }) .IsDescending(false, true, false) .HasDatabaseName("IX_Tasks_State_UpdatedAt_Id_Board"); entity.HasIndex(x => new { x.UpdatedAt, x.Id }) .IsDescending(true, true) .HasFilter("\"State\" = 'Done'") .HasDatabaseName("IX_Tasks_Done_UpdatedAt_Id"); entity.HasIndex(x => new { x.ParentTaskId, x.State }) .HasFilter("\"ParentTaskId\" IS NOT NULL") .HasDatabaseName("IX_Tasks_ParentTaskId_State"); entity.HasIndex(x => new { x.ExpectedFrom, x.State, x.UpdatedAt, x.Id }) .IsDescending(false, false, true, false) .HasFilter("\"IsAgentTask\" = TRUE") .HasDatabaseName("IX_Tasks_AgentWorkflow"); entity.HasOne(x => x.ParentTask) .WithMany(x => x.ChildTasks) .HasForeignKey(x => x.ParentTaskId) .OnDelete(DeleteBehavior.SetNull); }); modelBuilder.Entity(entity => { entity.Property(x => x.Title).HasMaxLength(240); entity.Property(x => x.Message).HasMaxLength(1000); entity.Property(x => x.Type).HasMaxLength(60); entity.Property(x => x.ForUser).HasMaxLength(60); entity.HasIndex(x => new { x.ForUser, x.IsRead, x.CreatedAt }); }); modelBuilder.Entity(entity => { entity.Property(x => x.Message).HasMaxLength(1000); entity.HasIndex(x => x.TaskId); entity.HasIndex(x => new { x.TaskId, x.CreatedAt, x.Id }) .IsDescending(false, true, true) .HasFilter("\"TaskId\" IS NOT NULL") .HasDatabaseName("IX_Activity_TaskId_CreatedAt_Id"); }); modelBuilder.Entity().HasIndex(u => u.NormalizedEmail).IsUnique(); modelBuilder.Entity().HasIndex(r => r.TokenHash).IsUnique(); modelBuilder.Entity().HasIndex(r => new { r.UserId, r.FamilyId }); modelBuilder.Entity().Property(r => r.ConcurrencyStamp).IsConcurrencyToken(); modelBuilder.Entity() .HasOne(r => r.User) .WithMany(u => u.RefreshTokens) .HasForeignKey(r => r.UserId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasIndex(x => x.CreatedAt); modelBuilder.Entity(entity => { entity.Property(x => x.Title).HasMaxLength(160); entity.Property(x => x.Prompt).HasColumnType("text"); entity.Property(x => x.AgentId).HasMaxLength(200); entity.Property(x => x.SessionKey).HasMaxLength(500); entity.Property(x => x.Status).HasMaxLength(40); entity.Property(x => x.OpenClawRunId).HasMaxLength(240); entity.Property(x => x.StartIdempotencyKey).HasMaxLength(200); entity.Property(x => x.CorrelationId).HasMaxLength(200); entity.Property(x => x.Actor).HasMaxLength(240); entity.Property(x => x.TraceParent).HasMaxLength(128); entity.Property(x => x.LastError).HasMaxLength(2000); entity.Property(x => x.Revision).IsConcurrencyToken(); entity.HasIndex(x => x.StartIdempotencyKey).IsUnique(); entity.HasIndex(x => x.OpenClawRunId).IsUnique(); entity.HasIndex(x => new { x.Status, x.UpdatedAt }); entity.HasIndex(x => x.TaskId); entity.HasIndex(x => x.ProjectId); entity.HasIndex(x => x.SessionKey); entity.HasIndex(x => x.RetriedFromRunId); entity.HasOne() .WithMany() .HasForeignKey(x => x.TaskId) .OnDelete(DeleteBehavior.SetNull); entity.HasOne() .WithMany() .HasForeignKey(x => x.ProjectId) .OnDelete(DeleteBehavior.SetNull); }); modelBuilder.Entity(entity => { entity.Property(x => x.Action).HasMaxLength(80); entity.Property(x => x.FromStatus).HasMaxLength(40); entity.Property(x => x.ToStatus).HasMaxLength(40); entity.Property(x => x.Message).HasMaxLength(2000); entity.Property(x => x.Actor).HasMaxLength(240); entity.Property(x => x.CorrelationId).HasMaxLength(200); entity.Property(x => x.IdempotencyKey).HasMaxLength(200); entity.Property(x => x.TraceParent).HasMaxLength(128); entity.Property(x => x.GatewayEventId).HasMaxLength(120); entity.HasIndex(x => new { x.RunId, x.OccurredAt }); entity.HasIndex(x => new { x.RunId, x.Action, x.IdempotencyKey }).IsUnique(); entity.HasIndex(x => x.GatewayEventId).IsUnique(); entity.HasOne(x => x.Run) .WithMany(x => x.History) .HasForeignKey(x => x.RunId) .OnDelete(DeleteBehavior.Cascade); }); } private static void ConfigureAgentProvisioning(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { entity.Property(x => x.Source).HasMaxLength(40); entity.Property(x => x.RequestedName).HasMaxLength(80); entity.Property(x => x.RequestedAgentId).HasMaxLength(64); entity.Property(x => x.Role).HasMaxLength(160); entity.Property(x => x.Description).HasMaxLength(4000); entity.Property(x => x.Model).HasMaxLength(240); entity.Property(x => x.Emoji).HasMaxLength(32); entity.Property(x => x.Avatar).HasMaxLength(2048); entity.Property(x => x.Workspace).HasMaxLength(2048); entity.Property(x => x.StandardFilesJson).HasColumnType("jsonb"); entity.Property(x => x.StandardFilesHash).HasMaxLength(64); entity.Property(x => x.Status).HasMaxLength(40); entity.Property(x => x.RequestedBy).HasMaxLength(240); entity.Property(x => x.ApprovedBy).HasMaxLength(240); entity.Property(x => x.RejectedBy).HasMaxLength(240); entity.Property(x => x.RejectionReason).HasMaxLength(1000); entity.Property(x => x.OpenClawAgentId).HasMaxLength(128); entity.Property(x => x.OpenClawWorkspace).HasMaxLength(2048); entity.Property(x => x.LastErrorCode).HasMaxLength(120); entity.Property(x => x.LastErrorMessage).HasMaxLength(2000); entity.Property(x => x.Revision).IsConcurrencyToken(); entity.HasIndex(x => new { x.Status, x.UpdatedAt }); entity.HasIndex(x => x.RequestedAgentId); entity.HasIndex(x => x.OpenClawAgentId); }); modelBuilder.Entity(entity => { entity.Property(x => x.Stage).HasMaxLength(40); entity.Property(x => x.Status).HasMaxLength(40); entity.Property(x => x.IdempotencyKeyHash).HasMaxLength(64); entity.Property(x => x.Actor).HasMaxLength(240); entity.Property(x => x.CorrelationId).HasMaxLength(200); entity.Property(x => x.TraceParent).HasMaxLength(128); entity.Property(x => x.OpenClawAgentId).HasMaxLength(128); entity.Property(x => x.LastErrorCode).HasMaxLength(120); entity.Property(x => x.LastErrorMessage).HasMaxLength(2000); entity.Property(x => x.LeaseOwner).HasMaxLength(160); entity.Property(x => x.Revision).IsConcurrencyToken(); entity.HasIndex(x => new { x.ProposalId, x.Attempt }).IsUnique(); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasIndex(x => x.LeaseUntil); entity.HasOne(x => x.Proposal) .WithMany(x => x.ProvisionRequests) .HasForeignKey(x => x.ProposalId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { entity.Property(x => x.Operation).HasMaxLength(100); entity.Property(x => x.IdempotencyKeyHash).HasMaxLength(64); entity.Property(x => x.RequestHash).HasMaxLength(64); entity.Property(x => x.State).HasMaxLength(40); entity.Property(x => x.ResultCode).HasMaxLength(120); entity.HasIndex(x => new { x.Operation, x.IdempotencyKeyHash }).IsUnique(); entity.HasIndex(x => x.ExpiresAt); }); modelBuilder.Entity(entity => { entity.HasKey(x => x.Sequence); entity.Property(x => x.Sequence).ValueGeneratedOnAdd(); entity.Property(x => x.Type).HasMaxLength(120); entity.Property(x => x.AggregateType).HasMaxLength(80); entity.Property(x => x.AggregateId).HasMaxLength(128); entity.Property(x => x.PayloadJson).HasColumnType("jsonb"); entity.Property(x => x.LastErrorCode).HasMaxLength(120); entity.HasIndex(x => x.EventId).IsUnique(); entity.HasIndex(x => new { x.PublishedAt, x.Sequence }); entity.HasIndex(x => x.OccurredAt); }); } }