diff --git a/backend/ControlCenter.Api.csproj b/backend/ControlCenter.Api.csproj index 7d43500..a509f52 100644 --- a/backend/ControlCenter.Api.csproj +++ b/backend/ControlCenter.Api.csproj @@ -6,6 +6,11 @@ enable + + + + + diff --git a/backend/Models/AgentState.cs b/backend/Models/AgentState.cs new file mode 100644 index 0000000..7c5f4a6 --- /dev/null +++ b/backend/Models/AgentState.cs @@ -0,0 +1,19 @@ +namespace ControlCenter.Api.Models; + +/// +/// Read-only model representing an agent's current state. +/// Used as the return type from the Agent State Repository +/// to decouple consumers from the persistence layer. +/// +public class AgentState +{ + public Guid Id { get; set; } + public string Status { get; set; } = string.Empty; + public string? Task { get; set; } + public int? Progress { get; set; } + public string SessionKey { get; set; } = string.Empty; + public string Channel { get; set; } = string.Empty; + public DateTime LastActivity { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/backend/Program.cs b/backend/Program.cs index c45c355..d23abf3 100644 --- a/backend/Program.cs +++ b/backend/Program.cs @@ -1,5 +1,6 @@ using ControlCenter.Api.Data; using ControlCenter.Api.Hubs; +using ControlCenter.Api.Repositories; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -10,6 +11,9 @@ builder.Services.AddOpenApi(); // Register SignalR for real-time agent status updates builder.Services.AddSignalR(); +// Register Agent State Repository +builder.Services.AddScoped(); + // Register DbContext with PostgreSQL builder.Services.AddDbContext(options => { diff --git a/backend/Repositories/AgentStateRepository.cs b/backend/Repositories/AgentStateRepository.cs new file mode 100644 index 0000000..ca452ff --- /dev/null +++ b/backend/Repositories/AgentStateRepository.cs @@ -0,0 +1,76 @@ +using ControlCenter.Api.Data; +using ControlCenter.Api.Entities; +using ControlCenter.Api.Models; +using Microsoft.EntityFrameworkCore; + +namespace ControlCenter.Api.Repositories; + +/// +/// EF Core implementation of the Agent State Repository. +/// Maps between the persisted entity and the +/// read-oriented model. +/// +public class AgentStateRepository : IAgentStateRepository +{ + private readonly AppDbContext _db; + + public AgentStateRepository(AppDbContext db) + { + _db = db; + } + + /// + public async Task> GetAllAsync(CancellationToken ct = default) + { + var agents = await _db.Agents + .AsNoTracking() + .OrderByDescending(a => a.LastActivity) + .ToListAsync(ct); + + return agents.Select(ToModel).ToList(); + } + + /// + public async Task GetBySessionKeyAsync(string sessionKey, CancellationToken ct = default) + { + var agent = await _db.Agents + .AsNoTracking() + .FirstOrDefaultAsync(a => a.SessionKey == sessionKey, ct); + + return agent is null ? null : ToModel(agent); + } + + /// + public async Task UpdateStatusAsync(Guid id, string status, CancellationToken ct = default) + { + if (!Enum.TryParse(status, ignoreCase: true, out var parsedStatus)) + return false; + + var agent = await _db.Agents.FindAsync([id], ct); + if (agent is null) + return false; + + agent.Status = parsedStatus; + agent.UpdatedAt = DateTime.UtcNow; + agent.LastActivity = DateTime.UtcNow; + + await _db.SaveChangesAsync(ct); + return true; + } + + /// + /// Maps a persisted entity to a model. + /// + private static AgentState ToModel(Agent agent) => new() + { + Id = agent.Id, + Status = agent.Status.ToString(), + Task = agent.Task, + Progress = agent.Progress, + SessionKey = agent.SessionKey, + Channel = agent.Channel, + LastActivity = agent.LastActivity, + CreatedAt = agent.CreatedAt, + UpdatedAt = agent.UpdatedAt, + }; +} \ No newline at end of file diff --git a/backend/Repositories/IAgentStateRepository.cs b/backend/Repositories/IAgentStateRepository.cs new file mode 100644 index 0000000..4c0c2b9 --- /dev/null +++ b/backend/Repositories/IAgentStateRepository.cs @@ -0,0 +1,27 @@ +using ControlCenter.Api.Models; + +namespace ControlCenter.Api.Repositories; + +/// +/// Repository interface for accessing and mutating Agent State. +/// Provides a clean abstraction over the EF Core data access layer. +/// +public interface IAgentStateRepository +{ + /// + /// Retrieve all agent states. + /// + Task> GetAllAsync(CancellationToken ct = default); + + /// + /// Retrieve a single agent state by its session key. + /// Returns null if no agent is found with the given session key. + /// + Task GetBySessionKeyAsync(string sessionKey, CancellationToken ct = default); + + /// + /// Update the status of an agent by its primary key. + /// Returns true if the agent was found and updated, false otherwise. + /// + Task UpdateStatusAsync(Guid id, string status, CancellationToken ct = default); +} \ No newline at end of file