package jobs import ( "context" "fmt" "log" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) // Queue enqueues processing and feed-sync work for the worker poller (SKIP LOCKED claim). // ASSUMPTION: full River client + river migrations are deferred; Postgres pending // jobs + FOR UPDATE SKIP LOCKED is the production queue for P0-4 MVP. type Queue struct { Pool *pgxpool.Pool } func NewQueue(pool *pgxpool.Pool) *Queue { return &Queue{Pool: pool} } // EnqueueProcessingJob ensures the job is pending and wakes listeners via NOTIFY. func (q *Queue) EnqueueProcessingJob(ctx context.Context, jobID uuid.UUID) error { if q == nil || q.Pool == nil { return fmt.Errorf("jobs queue not configured") } ct, err := q.Pool.Exec(ctx, ` UPDATE processing_jobs SET status = 'pending', updated_at = now(), error = CASE WHEN status = 'failed' THEN NULL ELSE error END WHERE id = $1 AND status IN ('pending', 'failed')`, jobID) if err != nil { return err } if ct.RowsAffected() == 0 { // Already running/completed/cancelled — still notify in case worker is idle. var status string _ = q.Pool.QueryRow(ctx, `SELECT status FROM processing_jobs WHERE id = $1`, jobID).Scan(&status) log.Printf("jobs: enqueue %s status=%s (no status change)", jobID, status) } _, _ = q.Pool.Exec(ctx, `SELECT pg_notify('processing_jobs', $1)`, jobID.String()) return nil } // EnqueueFeedSyncJob wakes listeners for an already-pending feed_sync_jobs row. // Row creation + mapping gates live in feeds.EnqueueSync; this mirrors processing NOTIFY. func (q *Queue) EnqueueFeedSyncJob(ctx context.Context, jobID uuid.UUID) error { if q == nil || q.Pool == nil { return fmt.Errorf("jobs queue not configured") } var status string err := q.Pool.QueryRow(ctx, `SELECT status FROM feed_sync_jobs WHERE id = $1`, jobID).Scan(&status) if err != nil { return err } if status != "pending" && status != "running" { log.Printf("jobs: feed sync enqueue %s status=%s (notify only)", jobID, status) } _, _ = q.Pool.Exec(ctx, `SELECT pg_notify('feed_sync_jobs', $1)`, jobID.String()) return nil }