| 1 | using AgentForge.Data; |
| 2 | using AgentForge.Data.Entities; |
| 3 | using AgentForge.Plugin.Ci.Ir; |
| 4 | using Microsoft.EntityFrameworkCore; |
| 5 | |
| 6 | namespace AgentForge.Plugin.Ci.Data; |
| 7 | |
| 8 | public sealed class ForgeCiDefinitionStore(ForgeDbContext db) |
| 9 | { |
| 10 | public IReadOnlyList<ForgeCiDefinitionRecord> List(string repoId) => |
| 11 | db.CiDefinitions.AsNoTracking() |
| 12 | .Where(d => d.RepoId == repoId) |
| 13 | .OrderBy(d => d.Name) |
| 14 | .AsEnumerable() |
| 15 | .Select(ToRecord) |
| 16 | .ToList(); |
| 17 | |
| 18 | public ForgeCiDefinitionRecord? GetByName(string repoId, string name) |
| 19 | { |
| 20 | var entity = db.CiDefinitions.AsNoTracking() |
| 21 | .FirstOrDefault(d => d.RepoId == repoId && d.Name == name); |
| 22 | return entity is null ? null : ToRecord(entity); |
| 23 | } |
| 24 | |
| 25 | public ForgeCiDefinitionRecord Insert( |
| 26 | string repoId, |
| 27 | ForgeCiDocument document, |
| 28 | string createdBy) |
| 29 | { |
| 30 | var canonical = ForgeCiDocumentValidator.Canonicalize(document); |
| 31 | canonical.Name = ForgeCiTemplates.NormalizeDefinitionName(canonical.Name); |
| 32 | if (GetByName(repoId, canonical.Name) is not null) |
| 33 | throw new InvalidOperationException($"CI definition '{canonical.Name}' already exists."); |
| 34 | |
| 35 | var now = DateTimeOffset.UtcNow; |
| 36 | var entity = new ForgeCiDefinitionEntity |
| 37 | { |
| 38 | Id = Guid.NewGuid().ToString("N"), |
| 39 | RepoId = repoId, |
| 40 | Name = canonical.Name, |
| 41 | BodyJson = canonical.ToJson(), |
| 42 | CreatedAt = now, |
| 43 | UpdatedAt = now, |
| 44 | CreatedBy = createdBy ?? "", |
| 45 | }; |
| 46 | |
| 47 | db.CiDefinitions.Add(entity); |
| 48 | db.SaveChanges(); |
| 49 | return ToRecord(entity); |
| 50 | } |
| 51 | |
| 52 | public ForgeCiDefinitionRecord Update(string repoId, string name, ForgeCiDocument document) |
| 53 | { |
| 54 | var entity = db.CiDefinitions |
| 55 | .FirstOrDefault(d => d.RepoId == repoId && d.Name == name) |
| 56 | ?? throw new KeyNotFoundException($"CI definition '{name}' not found."); |
| 57 | |
| 58 | var canonical = ForgeCiDocumentValidator.Canonicalize(document); |
| 59 | if (!string.Equals(canonical.Name, entity.Name, StringComparison.Ordinal)) |
| 60 | throw new ForgeCiValidationException("definition name cannot change on update."); |
| 61 | |
| 62 | entity.BodyJson = canonical.ToJson(); |
| 63 | entity.UpdatedAt = DateTimeOffset.UtcNow; |
| 64 | db.SaveChanges(); |
| 65 | return ToRecord(entity); |
| 66 | } |
| 67 | |
| 68 | public ForgeCiDocument LoadDocument(ForgeCiDefinitionRecord record) => |
| 69 | ForgeCiDocument.FromJson(record.BodyJson); |
| 70 | |
| 71 | private static ForgeCiDefinitionRecord ToRecord(ForgeCiDefinitionEntity entity) => |
| 72 | new( |
| 73 | entity.Id, |
| 74 | entity.RepoId, |
| 75 | entity.Name, |
| 76 | entity.BodyJson, |
| 77 | entity.CreatedAt, |
| 78 | entity.UpdatedAt, |
| 79 | entity.CreatedBy); |
| 80 | } |
| 81 | |