feat(T-561): loadIdeaSyncData server-loader voor Sync-tab

Joint Idea → PBI → Stories → Tasks → ClaudeJobs + StoryLog in één
prisma.findFirst-call. user_id-scope conform M12-keuze 2 (strikt
user_id-only). Filtert ClaudeJob op kind=TASK_IMPLEMENTATION en
neemt laatste 20 story-logs per story.

Returns null als idea geen pbi_id heeft — caller render geen tab.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-06 00:18:21 +02:00
parent e1da9aae43
commit f4f02bd0d2

View file

@ -0,0 +1,85 @@
import 'server-only'
import { prisma } from '@/lib/prisma'
// Server-only loader voor de Sync-tab op /ideas/[id].
// Joint Idea → PBI → Stories → Tasks → ClaudeJobs + StoryLog.
// Auth-scope: strikt user_id-only conform M12-keuze 2.
//
// Returns null wanneer:
// - idea bestaat niet of behoort niet aan user
// - idea heeft geen pbi_id (status !== PLANNED, dus sync-tab niet relevant)
//
// Caller (page.tsx) moet de tab niet renderen als deze null retourneert.
export async function loadIdeaSyncData(ideaId: string, userId: string) {
const idea = await prisma.idea.findFirst({
where: { id: ideaId, user_id: userId },
select: {
id: true,
code: true,
title: true,
status: true,
pbi_id: true,
product: { select: { id: true, name: true, repo_url: true } },
pbi: {
select: {
id: true,
code: true,
title: true,
pr_url: true,
pr_merged_at: true,
stories: {
orderBy: { sort_order: 'asc' },
select: {
id: true,
code: true,
title: true,
status: true,
tasks: {
orderBy: { sort_order: 'asc' },
select: {
id: true,
code: true,
title: true,
status: true,
claude_jobs: {
where: { kind: 'TASK_IMPLEMENTATION' },
orderBy: { created_at: 'desc' },
select: {
id: true,
status: true,
branch: true,
pushed_at: true,
pr_url: true,
error: true,
summary: true,
created_at: true,
finished_at: true,
},
},
},
},
logs: {
orderBy: { created_at: 'desc' },
take: 20,
select: {
id: true,
type: true,
content: true,
status: true,
commit_hash: true,
commit_message: true,
created_at: true,
},
},
},
},
},
},
},
})
if (!idea || !idea.pbi) return null
return idea
}
export type IdeaSyncData = NonNullable<Awaited<ReturnType<typeof loadIdeaSyncData>>>