feat(scanner): POST /api/scanner/cancel/:jobId (admin)

Cancels a pending or running scan job. Responses: 200 with the job id
and status on acceptance, 404 for unknown jobs, 409 for jobs already
in a terminal state (completed/failed/cancelled).

Additive route in the existing admin scanner group; no contract pins
affected.
This commit is contained in:
John O'Keefe committed 2026-10-18 18:02:25 -04:00
1 parent 30ecdd4bf3
commit 5b81e19f2f
2 files changed
+24

No files matched your search

+23
View File
@@ -3,6 +3,7 @@ package handlers
import (
"bookhoard/internal/services"
"context"
"errors"
"fmt"
"net/http"
@@ -209,6 +210,28 @@ func (h *Handler) GetScanSummary(c *echo.Context) error {
})
}
// CancelScan handles POST /api/scanner/cancel/:jobId (admin): requests
// cancellation of a pending or running scan job. Cancellation is
// cooperative — the file currently being processed finishes, the walk
// stops at its next context check, and the archive pass is skipped so
// unvisited files are never treated as missing.
func (h *Handler) CancelScan(c *echo.Context) error {
jobID := c.Param("jobId")
if err := h.worker.CancelJob(jobID); err != nil {
if errors.Is(err, services.ErrJobNotFound) {
return c.JSON(http.StatusNotFound, map[string]string{"error": "job not found"})
}
return c.JSON(http.StatusConflict, map[string]string{"error": err.Error()})
}
return c.JSON(http.StatusOK, map[string]interface{}{
"message": "scan cancellation requested",
"job_id": jobID,
"status": "cancelled",
})
}
// StartWatchModeForLibrary starts watching a specific library's folders
func (h *Handler) StartWatchModeForLibrary(ctx context.Context, libraryID pgtype.UUID, adminID pgtype.UUID) error {
h.mu.Lock()
+1
View File
@@ -17,6 +17,7 @@ func registerScannerRoutes(protected, admin *echo.Group, h *handlers.Handler) {
admin.POST("/scanner/scan", h.ScanLibrary)
admin.POST("/scanner/start", h.StartScanner)
admin.POST("/scanner/stop", h.StopScanner)
admin.POST("/scanner/cancel/:jobId", h.CancelScan)
admin.GET("/scanner/status/:jobId", h.GetScanStatus)
admin.GET("/scanner/active", h.GetActiveScans)