feat: wire API queue projection to Redis

This commit is contained in:
Josh Creek
2026-09-01 09:28:49 +01:00
parent fe9f6f3cb5
commit 18538e833b
5 changed files with 119 additions and 5 deletions
+27
View File
@@ -34,6 +34,13 @@ type QueueBackend interface {
Get(context.Context, string, string, time.Time) (domain.QueueTicket, error)
}
// CandidateIndex is a transient projection of durable queue ownership. Index
// failures must never change the result of an already successful mutation.
type CandidateIndex interface {
Upsert(context.Context, domain.Candidate) error
Remove(context.Context, string) error
}
type SessionBackend interface {
Authenticate(context.Context, string, string, time.Time) (domain.Session, error)
}
@@ -77,6 +84,7 @@ type Service struct {
Candidate CandidateProvider
CandidateV2 CandidateProviderV2
QueueBackend QueueBackend
CandidateIndex CandidateIndex
Probe ProbeProvider
Assignment AssignmentProvider
Now func() time.Time
@@ -220,6 +228,7 @@ func (s *Service) queueCreate(w http.ResponseWriter, r *http.Request) {
writeDomainError(w, err)
return
}
s.projectCandidate(r.Context(), ticket)
s.publishTicketEvent(ticket, now)
writeJSON(w, http.StatusCreated, toQueueResponse(ticket))
return
@@ -249,10 +258,23 @@ func (s *Service) queueCreate(w http.ResponseWriter, r *http.Request) {
writeDomainError(w, err)
return
}
s.projectCandidate(r.Context(), ticket)
s.publishTicketEvent(ticket, now)
writeJSON(w, http.StatusCreated, toQueueResponse(ticket))
}
func (s *Service) projectCandidate(ctx context.Context, ticket domain.QueueTicket) {
if s.CandidateIndex != nil {
_ = s.CandidateIndex.Upsert(ctx, ticket.Candidate)
}
}
func (s *Service) removeCandidate(ctx context.Context, ticketID string) {
if s.CandidateIndex != nil {
_ = s.CandidateIndex.Remove(ctx, ticketID)
}
}
func (s *Service) contractQueueCreate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
s.queueCreate(w, r)
@@ -391,6 +413,11 @@ func (s *Service) queueMutation(w http.ResponseWriter, r *http.Request) {
writeDomainError(w, err)
return
}
if ticket.State == domain.Cancelled {
s.removeCandidate(r.Context(), ticket.TicketID)
} else {
s.projectCandidate(r.Context(), ticket)
}
s.publishTicketEvent(ticket, now)
if r.Header.Get("X-Contract-Delete") == "1" {
w.WriteHeader(http.StatusNoContent)