using Microsoft.EntityFrameworkCore; using ScadaLink.Commons.Entities.Deployment; using ScadaLink.Commons.Entities.Instances; using ScadaLink.Commons.Interfaces.Repositories; namespace ScadaLink.ConfigurationDatabase.Repositories; /// /// EF Core implementation of IDeploymentManagerRepository. /// Provides storage/query of deployed configuration snapshots per instance, /// current deployment status, and optimistic concurrency on deployment status records. /// /// WP-24: Stub level sufficient for diff/staleness support. /// public class DeploymentManagerRepository : IDeploymentManagerRepository { private readonly ScadaLinkDbContext _dbContext; public DeploymentManagerRepository(ScadaLinkDbContext dbContext) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); } // --- DeploymentRecord --- public async Task GetDeploymentRecordByIdAsync(int id, CancellationToken cancellationToken = default) { return await _dbContext.DeploymentRecords.FindAsync([id], cancellationToken); } public async Task> GetAllDeploymentRecordsAsync(CancellationToken cancellationToken = default) { return await _dbContext.DeploymentRecords .OrderByDescending(d => d.DeployedAt) .ToListAsync(cancellationToken); } public async Task> GetDeploymentsByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default) { return await _dbContext.DeploymentRecords .Where(d => d.InstanceId == instanceId) .OrderByDescending(d => d.DeployedAt) .ToListAsync(cancellationToken); } /// /// Gets the most recent deployment record for an instance (current deployment status). /// Used for staleness detection by comparing revision hashes. /// public async Task GetCurrentDeploymentStatusAsync(int instanceId, CancellationToken cancellationToken = default) { return await _dbContext.DeploymentRecords .Where(d => d.InstanceId == instanceId) .OrderByDescending(d => d.DeployedAt) .FirstOrDefaultAsync(cancellationToken); } public async Task GetDeploymentByDeploymentIdAsync(string deploymentId, CancellationToken cancellationToken = default) { return await _dbContext.DeploymentRecords .FirstOrDefaultAsync(d => d.DeploymentId == deploymentId, cancellationToken); } public async Task AddDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default) { await _dbContext.DeploymentRecords.AddAsync(record, cancellationToken); } /// /// Updates a deployment record. Uses optimistic concurrency on deployment status records — /// EF Core's change tracking will detect concurrent modifications via the row version/concurrency token /// configured in the entity configuration. /// public Task UpdateDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default) { _dbContext.DeploymentRecords.Update(record); return Task.CompletedTask; } public Task DeleteDeploymentRecordAsync(int id, CancellationToken cancellationToken = default) { var record = _dbContext.DeploymentRecords.Local.FirstOrDefault(d => d.Id == id); if (record != null) { _dbContext.DeploymentRecords.Remove(record); } else { var stub = new DeploymentRecord("stub", "stub") { Id = id }; _dbContext.DeploymentRecords.Attach(stub); _dbContext.DeploymentRecords.Remove(stub); } return Task.CompletedTask; } // --- SystemArtifactDeploymentRecord --- public async Task GetSystemArtifactDeploymentByIdAsync(int id, CancellationToken cancellationToken = default) { return await _dbContext.SystemArtifactDeploymentRecords.FindAsync([id], cancellationToken); } public async Task> GetAllSystemArtifactDeploymentsAsync(CancellationToken cancellationToken = default) { return await _dbContext.SystemArtifactDeploymentRecords .OrderByDescending(d => d.DeployedAt) .ToListAsync(cancellationToken); } public async Task AddSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default) { await _dbContext.SystemArtifactDeploymentRecords.AddAsync(record, cancellationToken); } public Task UpdateSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default) { _dbContext.SystemArtifactDeploymentRecords.Update(record); return Task.CompletedTask; } public Task DeleteSystemArtifactDeploymentAsync(int id, CancellationToken cancellationToken = default) { var record = _dbContext.SystemArtifactDeploymentRecords.Local.FirstOrDefault(d => d.Id == id); if (record != null) { _dbContext.SystemArtifactDeploymentRecords.Remove(record); } else { var stub = new SystemArtifactDeploymentRecord("stub", "stub") { Id = id }; _dbContext.SystemArtifactDeploymentRecords.Attach(stub); _dbContext.SystemArtifactDeploymentRecords.Remove(stub); } return Task.CompletedTask; } // --- WP-8: DeployedConfigSnapshot --- public async Task GetDeployedSnapshotByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default) { return await _dbContext.Set() .FirstOrDefaultAsync(s => s.InstanceId == instanceId, cancellationToken); } public async Task AddDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default) { await _dbContext.Set().AddAsync(snapshot, cancellationToken); } public Task UpdateDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default) { _dbContext.Set().Update(snapshot); return Task.CompletedTask; } public async Task DeleteDeployedSnapshotAsync(int instanceId, CancellationToken cancellationToken = default) { var snapshot = await _dbContext.Set() .FirstOrDefaultAsync(s => s.InstanceId == instanceId, cancellationToken); if (snapshot != null) { _dbContext.Set().Remove(snapshot); } } // --- Instance lookups for deployment pipeline --- public async Task GetInstanceByIdAsync(int instanceId, CancellationToken cancellationToken = default) { return await _dbContext.Set() .Include(i => i.AttributeOverrides) .Include(i => i.AlarmOverrides) .Include(i => i.ConnectionBindings) .FirstOrDefaultAsync(i => i.Id == instanceId, cancellationToken); } public async Task GetInstanceByUniqueNameAsync(string uniqueName, CancellationToken cancellationToken = default) { return await _dbContext.Set() .Include(i => i.AttributeOverrides) .Include(i => i.AlarmOverrides) .Include(i => i.ConnectionBindings) .FirstOrDefaultAsync(i => i.UniqueName == uniqueName, cancellationToken); } public Task UpdateInstanceAsync(Instance instance, CancellationToken cancellationToken = default) { _dbContext.Set().Update(instance); return Task.CompletedTask; } public async Task SaveChangesAsync(CancellationToken cancellationToken = default) { return await _dbContext.SaveChangesAsync(cancellationToken); } }