namespace marlin.web.Controllers; using System.Text.Json; using core; using core.models; using Data; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Models; using Services; [Route("vcs")] public class VCSController(ApplicationDbContext dbContext, VersionControlSyncService syncService) : Controller { [HttpGet("list")] public async Task List(CancellationToken ct) { var items = await dbContext.Sources .Include(x => x.Branches) .ToListAsync(ct); return this.View(items); } [HttpGet("details/{id}")] public async Task Details(Guid id, CancellationToken ct) { var item = await dbContext .Sources .Include(x => x.Branches) .ThenInclude(x => x.Commit) .ThenInclude(x => x.WorkflowSnapshot) .FirstOrDefaultAsync(x => x.Id == id, ct); if (item == null) return this.NotFound(); return this.View(item); } [HttpGet("details/{sourceId}/{commitId}/blobs")] public async Task Blobs(Guid sourceId, Guid commitId) { var blobs = await dbContext .SourceBlobs .Include(x => x.Commit) .ThenInclude(x => x.Source) .Where(x => x.CommitId == commitId && x.SourceId == sourceId) .ToListAsync(); if (!blobs.Any()) return this.NotFound(); return this.View(blobs); } [HttpGet("details/{sourceId}/{commitId}/workflow")] public async Task Workflow(Guid sourceId, Guid commitId) { var snapshot = await dbContext .WorkflowSnapshots .Include(x => x.Commit) .ThenInclude(x => x.Source) .FirstOrDefaultAsync(x => x.CommitId == commitId && x.Commit.SourceId == sourceId); if (snapshot == null) return this.NotFound(); return this.View(snapshot); } [HttpPost("edit/{id}")] public async Task Edit(Guid id, VersionControlSource vcs, CancellationToken ct) { var item = await dbContext.Sources .FirstOrDefaultAsync(x => x.Id == id, ct); if (item == null) return this.NotFound(); item.UpstreamUrl = vcs.UpstreamUrl; item.BranchFilter = vcs.BranchFilter; item.MinPollingInterval = vcs.MinPollingInterval; await dbContext.SaveChangesAsync(ct); await syncService.FetchSources(ct); return this.RedirectToAction("List"); } [HttpPost("details/{sourceId}/secrets/access")] public async Task SetAccessSecret(Guid sourceId, Guid? secretId, CancellationToken ct) { var item = await dbContext.Sources .FirstOrDefaultAsync(x => x.Id == sourceId, ct); if (item == null) return this.NotFound(); item.AccessSecretId = secretId; await dbContext.SaveChangesAsync(ct); await syncService.FetchSources(ct); return this.RedirectToAction("ListSecret", new { sourceId }); } [HttpPost("details/{sourceId}/main-branch")] public async Task SetMainBranch(Guid sourceId, string branchName, CancellationToken ct) { var item = await dbContext.Sources .FirstOrDefaultAsync(x => x.Id == sourceId, ct); if (item == null) return this.NotFound(); item.MainBranch = branchName; await dbContext.SaveChangesAsync(ct); return this.RedirectToAction("Details", new { id = sourceId }); } [HttpGet("create")] public async Task Create() { return this.View(); } [HttpPost("create")] public async Task CreatePost(VersionControlSource vcs, CancellationToken ct) { vcs.Id = Guid.Empty; vcs.MainBranch = ""; await dbContext.AddAsync(vcs, ct); await dbContext.SaveChangesAsync(ct); await syncService.FetchSources(ct); return this.RedirectToAction("Details", new { id = vcs.Id }); } public record SecretCreateRequest(string Label, string? Kind, bool MayReveal, string ContentJson); [HttpGet("details/{sourceId}/secrets/create")] public async Task CreateSecret(Guid sourceId, CancellationToken ct) { var source = await dbContext.Sources .FirstOrDefaultAsync(x => x.Id == sourceId, ct); if (source == null) return this.NotFound(); return this.View(source); } [HttpPost("details/{sourceId}/secrets/create")] public async Task CreateSecretPost(Guid sourceId, SecretCreateRequest request, CancellationToken ct) { var source = await dbContext.Sources .FirstOrDefaultAsync(x => x.Id == sourceId, ct); if (source == null) return this.NotFound(); var content = JsonSerializer.Deserialize>(request.ContentJson) ?? new(); var secret = new VersionControlSecret { SourceId = sourceId, Label = request.Label, Kind = request.Kind, MayReveal = request.MayReveal, Content = content }; await dbContext.Secrets.AddAsync(secret, ct); await dbContext.SaveChangesAsync(ct); return this.RedirectToAction("ListSecret", new { sourceId }); } [HttpGet("details/{sourceId}/secrets/list")] public async Task ListSecret(Guid sourceId, CancellationToken ct) { var source = await dbContext.Sources .FirstOrDefaultAsync(x => x.Id == sourceId, ct); if (source == null) return this.NotFound(); var items = await dbContext.Secrets .Where(x => x.SourceId == source.Id) .ToListAsync(ct); return this.View((source, items)); } [HttpPost("details/{sourceId}/secrets/delete/{id}")] public async Task DeleteSecret(Guid sourceId, Guid id, CancellationToken ct) { var secret = await dbContext.Secrets .FirstOrDefaultAsync(x => x.SourceId == sourceId && x.Id == id, ct); if (secret == null) return this.NotFound(); dbContext.Secrets.Remove(secret); await dbContext.SaveChangesAsync(ct); return this.RedirectToAction("ListSecret", new { sourceId = sourceId }); } }