namespace marlin.web.Controllers; using core.models; using core.models.meta; using core.util; using Data; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Models; using Services; using Util; [Route("api/dispatch")] [ApiController] [Authorize(Policy = "Agent")] public class DispatchController(ApplicationDbContext dbContext) : ControllerBase { [HttpPost("handshake")] public async Task> Handshake(AgentStatus status, CancellationToken ct) { var agentId = User.RequireAgentId(); var agent = await dbContext.Agents .FirstAsync(x => x.Id == agentId, ct); foreach (var capability in status.CapabilitySet.Items) agent.Capabilities.Items[capability.Key] = capability.Value; await dbContext.SaveChangesAsync(ct); return new AgentConfiguration( Name: agent.Name, MaxConcurrentJobs: agent.MaxConcurrentJobs ); } [HttpGet("queue")] public async Task> Queue(CancellationToken ct) { var agentId = User.RequireAgentId(); var nextJob = await dbContext .Jobs .Include(x => x.Source) .Include(x => x.Branch) .Include(x => x.Commit) .Where(x => x.Status == JobStatus.Pending && x.AgentId == null) .OrderBy(x => x.CreatedAt) .FirstOrDefaultAsync(ct); if (nextJob == null) return (DispatchJob?) null; string claimToken = SecureTokenGenerator.CreateClaimToken(); nextJob.ClaimToken = claimToken; nextJob.AgentId = agentId; await dbContext.SaveChangesAsync(ct); return new DispatchJob( Id: nextJob.Id, Token: claimToken, Vcs: nextJob.Source.UpstreamUrl, Ref: nextJob.Branch.Name, Commit: nextJob.Commit.Hash, Recipe: nextJob.Recipe ); } }