diff --git a/cmd/server/main.go b/cmd/server/main.go index febc3e6..adac24a 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -106,6 +106,7 @@ func main() { // Create worker for background tasks worker := services.NewWorker(3) + services.WorkerInstance = worker koreaderHandler := handlers.NewKOReaderHandler(queries, connManager, queueProcessor) wsHandler := handlers.NewWSHandler(queries, connManager, cfg.JWTSecret, deviceAuthMiddleware) @@ -123,6 +124,7 @@ func main() { dashboardHandler := handlers.NewDashboardHandler(queries) mediaHandler := handlers.NewMediaHandler(queries, libraryService, worker) matchingHandler := handlers.NewMatchingHandler(queries, connManager) + jobsHandler := handlers.NewJobsHandler(queries, worker) e := echo.New() @@ -177,6 +179,7 @@ func main() { ConnManager: connManager, QueueProcessor: queueProcessor, DeviceAuthMiddleware: deviceAuthMiddleware, + JobsHandler: jobsHandler, LoginTracker: loginAttemptTracker, } diff --git a/internal/handlers/jobs.go b/internal/handlers/jobs.go new file mode 100644 index 0000000..1252ef4 --- /dev/null +++ b/internal/handlers/jobs.go @@ -0,0 +1,87 @@ +package handlers + +import ( + "net/http" + + "github.com/google/uuid" + "github.com/labstack/echo/v4" + + "bookhoard/internal/database" + "bookhoard/internal/services" +) + +type JobsHandler struct { + db *database.Queries + worker *services.Worker +} + +func NewJobsHandler(db *database.Queries, worker *services.Worker) *JobsHandler { + return &JobsHandler{ + db: db, + worker: worker, + } +} + +// CreateJob creates a new job based on type +func (h *JobsHandler) CreateJob(c echo.Context) error { + var req struct { + Type string `json:"type"` + Params map[string]interface{} `json:"params"` + } + + if err := c.Bind(&req); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": "Invalid request body", + }) + } + + // Validate job type + var jobType services.JobType + switch req.Type { + case "import", "convert", "thumbnails", "backup", "analytics", "sync": + jobType = services.JobType(req.Type) + default: + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": "Invalid job type", + }) + } + + // Add database to params + req.Params["db"] = h.db + + // Create job + job := &services.Job{ + ID: uuid.New().String(), + Type: jobType, + Params: req.Params, + Status: services.JobStatusPending, + } + + // Enqueue job + if err := h.worker.EnqueueJob(job); err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": "Failed to enqueue job", + }) + } + + return c.JSON(http.StatusAccepted, map[string]interface{}{ + "message": "Job created", + "job_id": job.ID, + "type": req.Type, + "status": "pending", + }) +} + +// GetJobStatus returns the status of a specific job +func (h *JobsHandler) GetJobStatus(c echo.Context) error { + jobID := c.Param("jobId") + + result, exists := h.worker.GetJobStatus(jobID) + if !exists { + return c.JSON(http.StatusNotFound, map[string]string{ + "error": "Job not found", + }) + } + + return c.JSON(http.StatusOK, result) +} diff --git a/internal/router/jobs.go b/internal/router/jobs.go new file mode 100644 index 0000000..6a63ce1 --- /dev/null +++ b/internal/router/jobs.go @@ -0,0 +1,10 @@ +package router + +func registerJobRoutes(cfg *Config) { + e := cfg.Echo + jwtMiddleware := createJWTMiddleware(cfg) + protected := e.Group("/api", jwtMiddleware) + jobs := protected.Group("/jobs") + jobs.POST("", cfg.JobsHandler.CreateJob) + jobs.GET("/:jobId", cfg.JobsHandler.GetJobStatus) +} diff --git a/internal/router/router.go b/internal/router/router.go index 658b69b..beec0c2 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -61,6 +61,7 @@ type Config struct { DeviceAuthMiddleware *middleware.DeviceAuthMiddleware LoginTracker *ratelimit.LoginAttemptTracker ScannerHandler *handlers.Handler + JobsHandler *handlers.JobsHandler } // createJWTMiddleware creates a JWT middleware with proper user context setup @@ -217,6 +218,7 @@ func RegisterRoutes(cfg *Config) *handlers.Handler { registerConflictRoutes(cfg) registerAnalyticsRoutes(cfg) registerQueueRoutes(cfg) + registerJobRoutes(cfg) registerOPDSRoutes(cfg) registerWebSocketRoutes(cfg) registerFrontendRoutes(cfg)