package db import ( "context" "fmt" "github.com/flatrender/render-svc/internal/models" ) const jobCols = `id, tenant_id, user_id, saved_project_id, original_project_id, project_name, title, name, external_job_id, priority_queue::text, priority_score, step::text, render_progress, convert_progress, image_preview_b64, price_type::text, paid_price_minor, discount_code, support_flatrender, mode, quality::text, resolution, r_height, frame_rate, is_60_fps, duration_sec, export_duration_sec, has_music, has_sfx, has_voiceover, music_volume, sfx_volume, voiceover_volume, render_node_count, current_active_nodes, region, tell_me_when_done, retry_count, max_retries, repair_attempts, failed_message, failed_at_step::text, export_id, task_start_date, queued_at, started_at, completed_at, created_at, updated_at` // ListAllJobs returns render jobs across all users (admin view), optional status filter. func (s *Store) ListAllJobs(ctx context.Context, status string, page, pageSize int) ([]*models.RenderJob, int64, error) { where := "" args := []any{} idx := 1 if status != "" { where = fmt.Sprintf(" WHERE step::text = $%d", idx) args = append(args, status) idx++ } var total int64 _ = s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM render.render_jobs"+where, args...).Scan(&total) args = append(args, pageSize, (page-1)*pageSize) q := "SELECT " + jobCols + " FROM render.render_jobs" + where + fmt.Sprintf(" ORDER BY created_at DESC LIMIT $%d OFFSET $%d", idx, idx+1) rows, err := s.pool.Query(ctx, q, args...) if err != nil { return nil, 0, err } defer rows.Close() jobs, err := scanJobs(rows) if err != nil { return nil, 0, err } s.attachUserInfo(ctx, jobs) return jobs, total, err } // attachUserInfo fills UserName/UserEmail on each job via one cross-schema lookup // against identity.users (same Postgres DB). Best-effort — failures leave names blank. func (s *Store) attachUserInfo(ctx context.Context, jobs []*models.RenderJob) { if len(jobs) == 0 { return } ids := make([]string, 0, len(jobs)) seen := map[string]bool{} for _, j := range jobs { id := j.UserID.String() if !seen[id] { seen[id] = true ids = append(ids, id) } } rows, err := s.pool.Query(ctx, `SELECT id::text, COALESCE(full_name,''), COALESCE(email,'') FROM identity.users WHERE id = ANY($1)`, ids) if err != nil { return } defer rows.Close() type ui struct{ name, email string } m := map[string]ui{} for rows.Next() { var id, name, email string if rows.Scan(&id, &name, &email) == nil { m[id] = ui{name, email} } } for _, j := range jobs { if info, ok := m[j.UserID.String()]; ok { j.UserName = info.name j.UserEmail = info.email } } }