Add generic AI job queue lifecycle
This commit is contained in:
@@ -39,10 +39,16 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleCreateJob(w, r)
|
||||
case r.Method == http.MethodPost && path == "/api/v1/jobs/batch":
|
||||
s.handleCreateBatch(w, r)
|
||||
case r.Method == http.MethodPost && path == "/api/v1/jobs/claim":
|
||||
s.handleClaimJobs(w, r)
|
||||
case r.Method == http.MethodGet && strings.HasPrefix(path, "/api/v1/jobs/"):
|
||||
s.handleGetJob(w, r, path)
|
||||
case r.Method == http.MethodPost && strings.HasPrefix(path, "/api/v1/jobs/") && strings.HasSuffix(path, "/retry"):
|
||||
s.handleRetryJob(w, r, path)
|
||||
case r.Method == http.MethodPost && strings.HasPrefix(path, "/api/v1/jobs/") && strings.HasSuffix(path, "/complete"):
|
||||
s.handleCompleteJob(w, r, path)
|
||||
case r.Method == http.MethodPost && strings.HasPrefix(path, "/api/v1/jobs/") && strings.HasSuffix(path, "/fail"):
|
||||
s.handleFailJob(w, r, path)
|
||||
case r.Method == http.MethodGet && path == "/api/v1/stats":
|
||||
s.handleStats(w, r)
|
||||
default:
|
||||
@@ -136,6 +142,26 @@ func (s *Server) handleCreateBatch(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusCreated, out)
|
||||
}
|
||||
|
||||
type claimJobsResponse struct {
|
||||
Jobs []*model.Job `json:"jobs"`
|
||||
}
|
||||
|
||||
func (s *Server) handleClaimJobs(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.ClaimJobs
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "bad json")
|
||||
return
|
||||
}
|
||||
ctx, cancel := contextWithTimeout(r, 8*time.Second)
|
||||
defer cancel()
|
||||
jobs, err := s.store.ClaimJobs(ctx, req)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, claimJobsResponse{Jobs: jobs})
|
||||
}
|
||||
|
||||
func (s *Server) handleGetJob(w http.ResponseWriter, r *http.Request, path string) {
|
||||
id, err := jobIDFromPath(path, false)
|
||||
if err != nil {
|
||||
@@ -176,6 +202,56 @@ func (s *Server) handleRetryJob(w http.ResponseWriter, r *http.Request, path str
|
||||
writeJSON(w, http.StatusOK, job)
|
||||
}
|
||||
|
||||
func (s *Server) handleCompleteJob(w http.ResponseWriter, r *http.Request, path string) {
|
||||
id, err := jobIDFromActionPath(path, "complete")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
var req model.CompleteJob
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "bad json")
|
||||
return
|
||||
}
|
||||
ctx, cancel := contextWithTimeout(r, 8*time.Second)
|
||||
defer cancel()
|
||||
job, err := s.store.CompleteJob(ctx, id, req)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if job == nil {
|
||||
writeError(w, http.StatusNotFound, "running job not found")
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, job)
|
||||
}
|
||||
|
||||
func (s *Server) handleFailJob(w http.ResponseWriter, r *http.Request, path string) {
|
||||
id, err := jobIDFromActionPath(path, "fail")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
var req model.FailJob
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "bad json")
|
||||
return
|
||||
}
|
||||
ctx, cancel := contextWithTimeout(r, 8*time.Second)
|
||||
defer cancel()
|
||||
job, err := s.store.FailJob(ctx, id, req)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if job == nil {
|
||||
writeError(w, http.StatusNotFound, "running job not found")
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, job)
|
||||
}
|
||||
|
||||
func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := contextWithTimeout(r, 8*time.Second)
|
||||
defer cancel()
|
||||
@@ -199,6 +275,16 @@ func jobIDFromPath(path string, retry bool) (uuid.UUID, error) {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func jobIDFromActionPath(path string, action string) (uuid.UUID, error) {
|
||||
raw := strings.TrimPrefix(path, "/api/v1/jobs/")
|
||||
raw = strings.TrimSuffix(raw, "/"+action)
|
||||
id, err := uuid.Parse(strings.Trim(raw, "/"))
|
||||
if err != nil {
|
||||
return uuid.Nil, errors.New("bad job id")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func isValidationError(err error) bool {
|
||||
msg := err.Error()
|
||||
return strings.Contains(msg, " is required")
|
||||
|
||||
Reference in New Issue
Block a user