CUB-54: implement AgentState entity, repository, and DI registration

- Add AgentState entity mapping to 'agents' table (snake_case columns)
- Add IAgentStateRepository interface (GetAllAsync, GetBySessionKeyAsync, UpdateStatusAsync)
- Add AgentStateRepository with EF Core implementation
- Add ControlCenterDbContext with ApplyConfigurationsFromAssembly
- Add AgentStateConfiguration with snake_case column mappings and indexes
- Register DbContext (Npgsql) and repository in Program.cs DI
- Add ConnectionStrings to appsettings.json
- Add EF Core 9.0.7 and Npgsql.EntityFrameworkCore.PostgreSQL 9.0.4 packages
This commit is contained in:
cubecraft-agents[bot]
2026-04-26 11:44:17 +00:00
parent 69df1562c7
commit eb08a0bc90
8 changed files with 289 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
using ControlCenter.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ControlCenter.Data.Configurations;
/// <summary>
/// EF Core entity configuration for <see cref="AgentState"/>.
///
/// <para>Maps to the <c>agents</c> table with snake_case column naming,
/// consistent with the Extrudex project conventions.</para>
///
/// <para>CUB-56 will create the migration; this configuration only
/// defines the entity-to-table mapping and constraints.</para>
/// </summary>
public class AgentStateConfiguration : IEntityTypeConfiguration<AgentState>
{
public void Configure(EntityTypeBuilder<AgentState> builder)
{
// Table name
builder.ToTable("agents");
// Primary key
builder.HasKey(a => a.Id);
// Properties with snake_case column names
builder.Property(a => a.Id)
.HasColumnName("id")
.ValueGeneratedOnAdd();
builder.Property(a => a.Status)
.HasColumnName("status")
.IsRequired()
.HasMaxLength(20)
.HasDefaultValue("idle");
builder.Property(a => a.Task)
.HasColumnName("task")
.IsRequired(false)
.HasMaxLength(500);
builder.Property(a => a.Progress)
.HasColumnName("progress")
.IsRequired(false);
builder.Property(a => a.SessionKey)
.HasColumnName("session_key")
.IsRequired()
.HasMaxLength(255);
builder.Property(a => a.Channel)
.HasColumnName("channel")
.IsRequired()
.HasMaxLength(50);
builder.Property(a => a.LastActivity)
.HasColumnName("last_activity")
.IsRequired()
.HasDefaultValueSql("NOW()");
// Indexes
builder.HasIndex(a => a.SessionKey)
.IsUnique()
.HasDatabaseName("ix_agents_session_key");
builder.HasIndex(a => a.Status)
.HasDatabaseName("ix_agents_status");
}
}

View File

@@ -0,0 +1,39 @@
using ControlCenter.Models;
using Microsoft.EntityFrameworkCore;
namespace ControlCenter.Data;
/// <summary>
/// EF Core DbContext for the Control Center database.
///
/// <para>Provides <see cref="DbSet{T}"/> access to all persistent entities.
/// Uses snake_case naming conventions for PostgreSQL columns,
/// applied via <see cref="Configurations.AgentStateConfiguration"/>.</para>
///
/// <para>Connection string is configured in <c>appsettings.json</c>
/// under <c>ConnectionStrings:ControlCenterDb</c>.</para>
/// </summary>
public class ControlCenterDbContext : DbContext
{
/// <summary>
/// Agent state records — one row per active or recently active agent.
/// Maps to the <c>agents</c> table.
/// </summary>
public DbSet<AgentState> AgentStates => Set<AgentState>();
public ControlCenterDbContext(DbContextOptions<ControlCenterDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Apply all entity configurations from this assembly
modelBuilder.ApplyConfigurationsFromAssembly(
typeof(Configurations.AgentStateConfiguration).Assembly);
// Global snake_case naming convention for any unmapped columns
// (explicit configurations take precedence)
base.OnModelCreating(modelBuilder);
}
}