708d983429
- internal/daemon/server.go: HTTP Server with lifecycle, logging middleware
- internal/daemon/health.go: /healthz (liveness), /readyz (db+ready), /v1/status
- internal/daemon/jobs_handler.go: GET /v1/jobs, /v1/jobs/{id}, /v1/jobs/{id}/tasks
- internal/daemon/nodes_handler.go: GET /v1/nodes
- internal/daemon/tasks_handler.go: GET /v1/tasks (with ?job_id and ?limit)
- internal/daemon/validate.go: input validation for path IDs
- internal/daemon/version.go: ldflags-friendly version var
- internal/store: added TaskRepo.ListRecent for unfiltered task listing
- internal/cli/daemon.go: CLI wiring with signal.NotifyContext shutdown
Personas: backend-engineer (handlers), cli-engineer (CLI wiring),
security-engineer (input validation, no secrets in access logs, slog JSON).
---ci---
project: orca
phase: 5
milestone: v0.1
status: execute
requirements:
covered: [REQ-006, REQ-017, REQ-019]
partial: []
---/ci---
67 lines
1.5 KiB
Go
67 lines
1.5 KiB
Go
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.cloudinit.dev/coreci/orca/internal/model"
|
|
"git.cloudinit.dev/coreci/orca/internal/store"
|
|
)
|
|
|
|
// handleTasksCollection handles /v1/tasks (GET only).
|
|
// Optional query param: ?job_id=<id> to filter by job.
|
|
// Optional: ?limit=<n> (default 100, max 1000).
|
|
func (s *Server) handleTasksCollection(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
jobID := r.URL.Query().Get("job_id")
|
|
if jobID != "" {
|
|
if err := validateID(jobID); err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
limit := 100
|
|
if v := r.URL.Query().Get("limit"); v != "" {
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil || n <= 0 {
|
|
writeError(w, http.StatusBadRequest, "invalid limit")
|
|
return
|
|
}
|
|
if n > 1000 {
|
|
n = 1000
|
|
}
|
|
limit = n
|
|
}
|
|
|
|
repo := store.NewTaskRepo(s.db)
|
|
var tasks []*model.Task
|
|
var err error
|
|
if jobID != "" {
|
|
tasks, err = repo.ListByJob(ctx, jobID)
|
|
} else {
|
|
tasks, err = repo.ListRecent(ctx, limit)
|
|
}
|
|
if err != nil {
|
|
s.log.Error("list tasks",
|
|
slog.String("component", "daemon"),
|
|
slog.String("job_id", jobID),
|
|
slog.String("error", err.Error()))
|
|
writeError(w, http.StatusInternalServerError, "failed to list tasks")
|
|
return
|
|
}
|
|
if tasks == nil {
|
|
tasks = []*model.Task{}
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"tasks": tasks, "count": len(tasks)})
|
|
}
|