diff --git a/actions/jobs-page.ts b/actions/jobs-page.ts new file mode 100644 index 0000000..ae58804 --- /dev/null +++ b/actions/jobs-page.ts @@ -0,0 +1,114 @@ +'use server' + +import { prisma } from '@/lib/prisma' +import { getSession } from '@/lib/auth' +import type { ClaudeJobKind, ClaudeJobStatus, VerifyResult } from '@prisma/client' + +export type JobWithRelations = { + id: string + kind: ClaudeJobKind + status: ClaudeJobStatus + taskCode: string | null + taskTitle: string | null + ideaCode: string | null + ideaTitle: string | null + sprintGoal: string | null + sprintCode: string | null + productName: string + modelId: string | null + inputTokens: number | null + outputTokens: number | null + cacheReadTokens: number | null + cacheWriteTokens: number | null + branch: string | null + prUrl: string | null + error: string | null + summary: string | null + verifyResult: VerifyResult | null + startedAt: Date | null + finishedAt: Date | null + createdAt: Date + sprintRunId: string | null +} + +const JOB_INCLUDE = { + task: { select: { code: true, title: true } }, + idea: { select: { code: true, title: true } }, + product: { select: { name: true } }, + sprint_run: { include: { sprint: { select: { sprint_goal: true, code: true } } } }, +} as const + +function mapJob(j: { + id: string + kind: ClaudeJobKind + status: ClaudeJobStatus + model_id: string | null + input_tokens: number | null + output_tokens: number | null + cache_read_tokens: number | null + cache_write_tokens: number | null + branch: string | null + pr_url: string | null + error: string | null + summary: string | null + verify_result: VerifyResult | null + started_at: Date | null + finished_at: Date | null + created_at: Date + sprint_run_id: string | null + task: { code: string | null; title: string } | null + idea: { code: string | null; title: string } | null + product: { name: string } + sprint_run: { sprint: { sprint_goal: string; code: string | null } } | null +}): JobWithRelations { + return { + id: j.id, + kind: j.kind, + status: j.status, + taskCode: j.task?.code ?? null, + taskTitle: j.task?.title ?? null, + ideaCode: j.idea?.code ?? null, + ideaTitle: j.idea?.title ?? null, + sprintGoal: j.sprint_run?.sprint.sprint_goal ?? null, + sprintCode: j.sprint_run?.sprint.code ?? null, + productName: j.product.name, + modelId: j.model_id, + inputTokens: j.input_tokens, + outputTokens: j.output_tokens, + cacheReadTokens: j.cache_read_tokens, + cacheWriteTokens: j.cache_write_tokens, + branch: j.branch, + prUrl: j.pr_url, + error: j.error, + summary: j.summary, + verifyResult: j.verify_result, + startedAt: j.started_at, + finishedAt: j.finished_at, + createdAt: j.created_at, + sprintRunId: j.sprint_run_id, + } +} + +export async function fetchJobsPageData(): Promise<{ activeJobs: JobWithRelations[]; doneJobs: JobWithRelations[] } | null> { + const session = await getSession() + if (!session.userId) return null + + const [active, done] = await Promise.all([ + prisma.claudeJob.findMany({ + where: { user_id: session.userId, status: { notIn: ['DONE'] } }, + include: JOB_INCLUDE, + orderBy: { created_at: 'asc' }, + }), + prisma.claudeJob.findMany({ + where: { user_id: session.userId, status: 'DONE' }, + include: JOB_INCLUDE, + orderBy: { finished_at: 'desc' }, + take: 100, + }), + ]) + + return { + activeJobs: active.map(mapJob), + doneJobs: done.map(mapJob), + } +}