package api import ( "database/sql" "encoding/json" "fmt" "log" "net/http" "jiggablend/pkg/types" ) // handleSubmitMetadata handles metadata submission from runner func (s *Server) handleSubmitMetadata(w http.ResponseWriter, r *http.Request) { jobID, err := parseID(r, "jobId") if err != nil { s.respondError(w, http.StatusBadRequest, err.Error()) return } // Get runner ID from context (set by runnerAuthMiddleware) runnerID, ok := r.Context().Value(runnerIDContextKey).(int64) if !ok { s.respondError(w, http.StatusUnauthorized, "runner_id not found in context") return } var metadata types.BlendMetadata if err := json.NewDecoder(r.Body).Decode(&metadata); err != nil { s.respondError(w, http.StatusBadRequest, "Invalid metadata JSON") return } // Verify job exists var jobUserID int64 err = s.db.QueryRow("SELECT user_id FROM jobs WHERE id = ?", jobID).Scan(&jobUserID) if err == sql.ErrNoRows { s.respondError(w, http.StatusNotFound, "Job not found") return } if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to verify job: %v", err)) return } // Find the metadata extraction task for this job // First try to find task assigned to this runner, then fall back to any metadata task for this job var taskID int64 err = s.db.QueryRow( `SELECT id FROM tasks WHERE job_id = ? AND task_type = ? AND runner_id = ?`, jobID, types.TaskTypeMetadata, runnerID, ).Scan(&taskID) if err == sql.ErrNoRows { // Fall back to any metadata task for this job (in case assignment changed) err = s.db.QueryRow( `SELECT id FROM tasks WHERE job_id = ? AND task_type = ? ORDER BY created_at DESC LIMIT 1`, jobID, types.TaskTypeMetadata, ).Scan(&taskID) if err == sql.ErrNoRows { s.respondError(w, http.StatusNotFound, "Metadata extraction task not found") return } if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to find task: %v", err)) return } // Update the task to be assigned to this runner if it wasn't already s.db.Exec( `UPDATE tasks SET runner_id = ? WHERE id = ? AND runner_id IS NULL`, runnerID, taskID, ) } else if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to find task: %v", err)) return } // Convert metadata to JSON metadataJSON, err := json.Marshal(metadata) if err != nil { s.respondError(w, http.StatusInternalServerError, "Failed to marshal metadata") return } // Update job with metadata _, err = s.db.Exec( `UPDATE jobs SET blend_metadata = ? WHERE id = ?`, string(metadataJSON), jobID, ) if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to update job metadata: %v", err)) return } // Mark task as completed _, err = s.db.Exec( `UPDATE tasks SET status = ?, completed_at = CURRENT_TIMESTAMP WHERE id = ?`, types.TaskStatusCompleted, taskID, ) if err != nil { log.Printf("Failed to mark metadata task as completed: %v", err) } else { // Update job status and progress after metadata task completes s.updateJobStatusFromTasks(jobID) } log.Printf("Metadata extracted for job %d: frame_start=%d, frame_end=%d", jobID, metadata.FrameStart, metadata.FrameEnd) s.respondJSON(w, http.StatusOK, map[string]string{"message": "Metadata submitted successfully"}) } // handleGetJobMetadata retrieves metadata for a job func (s *Server) handleGetJobMetadata(w http.ResponseWriter, r *http.Request) { userID, err := getUserID(r) if err != nil { s.respondError(w, http.StatusUnauthorized, err.Error()) return } jobID, err := parseID(r, "id") if err != nil { s.respondError(w, http.StatusBadRequest, err.Error()) return } // Verify job belongs to user var jobUserID int64 var blendMetadataJSON sql.NullString err = s.db.QueryRow( `SELECT user_id, blend_metadata FROM jobs WHERE id = ?`, jobID, ).Scan(&jobUserID, &blendMetadataJSON) if err == sql.ErrNoRows { s.respondError(w, http.StatusNotFound, "Job not found") return } if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query job: %v", err)) return } if jobUserID != userID { s.respondError(w, http.StatusForbidden, "Access denied") return } if !blendMetadataJSON.Valid || blendMetadataJSON.String == "" { s.respondJSON(w, http.StatusOK, nil) return } var metadata types.BlendMetadata if err := json.Unmarshal([]byte(blendMetadataJSON.String), &metadata); err != nil { s.respondError(w, http.StatusInternalServerError, "Failed to parse metadata") return } s.respondJSON(w, http.StatusOK, metadata) }