Files
nexus/backend/Data/NexusDbContext.cs
AzuTear f5552218bc
CI - Build & Test / Backend (.NET) (push) Successful in 42s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m46s
CI - Build & Test / Security Check (push) Successful in 3s
CI - Build & Test / Deploy Nexus (push) Successful in 56s
feat: ship agent-first mission control v0.2.57
2026-07-31 22:39:47 +02:00

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);
});
}
}