219 lines
11 KiB
C#
219 lines
11 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Nexus.Api.Data;
|
|
|
|
public sealed class NexusDbContext(DbContextOptions<NexusDbContext> options) : DbContext(options)
|
|
{
|
|
public DbSet<Project> Projects => Set<Project>();
|
|
public DbSet<WorkTask> Tasks => Set<WorkTask>();
|
|
public DbSet<Notification> Notifications => Set<Notification>();
|
|
public DbSet<ActivityEvent> Activity => Set<ActivityEvent>();
|
|
public DbSet<NexusUser> Users => Set<NexusUser>();
|
|
public DbSet<RefreshToken> RefreshTokens => Set<RefreshToken>();
|
|
public DbSet<SeedAudit> SeedAudits => Set<SeedAudit>();
|
|
public DbSet<OpenClawRun> OpenClawRuns => Set<OpenClawRun>();
|
|
public DbSet<OpenClawRunHistory> OpenClawRunHistory => Set<OpenClawRunHistory>();
|
|
public DbSet<OpenClawConnectionProfile> OpenClawConnectionProfiles =>
|
|
Set<OpenClawConnectionProfile>();
|
|
public DbSet<AgentProposal> AgentProposals => Set<AgentProposal>();
|
|
public DbSet<AgentProvisionRequest> AgentProvisionRequests =>
|
|
Set<AgentProvisionRequest>();
|
|
public DbSet<OperationClaim> OperationClaims => Set<OperationClaim>();
|
|
public DbSet<OutboxEvent> OutboxEvents => Set<OutboxEvent>();
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
modelBuilder.ApplyConfiguration(new OpenClawConnectionProfileConfiguration());
|
|
ConfigureAgentProvisioning(modelBuilder);
|
|
modelBuilder.Entity<Project>().Property(x => x.Name).HasMaxLength(160);
|
|
modelBuilder.Entity<WorkTask>(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<Notification>(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<ActivityEvent>(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<NexusUser>().HasIndex(u => u.NormalizedEmail).IsUnique();
|
|
modelBuilder.Entity<RefreshToken>().HasIndex(r => r.TokenHash).IsUnique();
|
|
modelBuilder.Entity<RefreshToken>().HasIndex(r => new { r.UserId, r.FamilyId });
|
|
modelBuilder.Entity<RefreshToken>().Property(r => r.ConcurrencyStamp).IsConcurrencyToken();
|
|
modelBuilder.Entity<RefreshToken>()
|
|
.HasOne(r => r.User)
|
|
.WithMany(u => u.RefreshTokens)
|
|
.HasForeignKey(r => r.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
modelBuilder.Entity<ActivityEvent>().HasIndex(x => x.CreatedAt);
|
|
|
|
modelBuilder.Entity<OpenClawRun>(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<WorkTask>()
|
|
.WithMany()
|
|
.HasForeignKey(x => x.TaskId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
entity.HasOne<Project>()
|
|
.WithMany()
|
|
.HasForeignKey(x => x.ProjectId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
});
|
|
|
|
modelBuilder.Entity<OpenClawRunHistory>(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<AgentProposal>(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<AgentProvisionRequest>(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<OperationClaim>(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<OutboxEvent>(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);
|
|
});
|
|
}
|
|
}
|