Scrum4Me/prisma/seed.ts
Madhura68 a5f81fce70 feat(ST-004): emit one task per backlog sub-bullet, not per story
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:39:11 +02:00

284 lines
8.5 KiB
TypeScript

// Seed bron: docs/scrum4me-backlog.md → prisma/seed-data/parse-backlog.ts.
// Pas alléén de backlog-markdown aan als je seed-data wilt wijzigen, niet deze file.
import { PrismaClient } from '@prisma/client'
import * as dotenv from 'dotenv'
import * as path from 'path'
import * as bcrypt from 'bcryptjs'
import { loadBacklog } from './seed-data/parse-backlog'
// Load env from project root
const root = path.resolve(__dirname, '..')
dotenv.config({ path: path.join(root, '.env.local'), override: true })
dotenv.config({ path: path.join(root, '.env') })
let prisma: PrismaClient
async function main() {
const url = process.env.DATABASE_URL
if (!url) throw new Error('DATABASE_URL is not set. Check .env.local')
const { Pool } = await import('pg')
const { PrismaPg } = await import('@prisma/adapter-pg')
const pool = new Pool({ connectionString: url })
const adapter = new PrismaPg(pool)
prisma = new PrismaClient({ adapter })
console.log('Seeding database...')
// Create main demo user
const demoHash = await bcrypt.hash('demo1234', 12)
const demo = await prisma.user.upsert({
where: { username: 'demo' },
update: {},
create: {
username: 'demo',
password_hash: demoHash,
is_demo: true,
roles: {
create: [
{ role: 'PRODUCT_OWNER' },
{ role: 'DEVELOPER' },
],
},
},
})
console.log(`Demo user: ${demo.username} (id: ${demo.id})`)
// Create seed user for the product
const userHash = await bcrypt.hash('scrum4me123', 12)
const user = await prisma.user.upsert({
where: { username: 'lars' },
update: {},
create: {
username: 'lars',
password_hash: userHash,
is_demo: false,
roles: {
create: [
{ role: 'PRODUCT_OWNER' },
{ role: 'SCRUM_MASTER' },
{ role: 'DEVELOPER' },
],
},
},
})
console.log(`Main user: ${user.username} (id: ${user.id})`)
// Create tester user for cross-user isolation tests (no products)
const testerHash = await bcrypt.hash('tester123', 12)
const tester = await prisma.user.upsert({
where: { username: 'tester' },
update: {},
create: {
username: 'tester',
password_hash: testerHash,
is_demo: false,
roles: {
create: [
{ role: 'PRODUCT_OWNER' },
{ role: 'DEVELOPER' },
],
},
},
})
console.log(`Tester user: ${tester.username} (id: ${tester.id})`)
// Reset demo product data — delete in dependency order to avoid FK violations
const existingProducts = await prisma.product.findMany({ where: { user_id: demo.id }, select: { id: true } })
for (const p of existingProducts) {
// Stories reference product_id directly (no cascade), so delete PBIs first (cascades to stories via pbi_id)
const existingPbis = await prisma.pbi.findMany({ where: { product_id: p.id }, select: { id: true } })
for (const pbi of existingPbis) {
await prisma.story.deleteMany({ where: { pbi_id: pbi.id } })
}
await prisma.pbi.deleteMany({ where: { product_id: p.id } })
await prisma.sprint.deleteMany({ where: { product_id: p.id } })
}
await prisma.product.deleteMany({ where: { user_id: demo.id } })
const product = await prisma.product.create({
data: {
user_id: demo.id,
name: 'Scrum4Me',
description:
'Een desktop-first fullstack webapplicatie voor solo developers en kleine Scrum Teams die meerdere softwareprojecten parallel beheren.',
repo_url: 'https://github.com/madhura68/Scrum4Me',
definition_of_done:
'Code is geïmplementeerd, type-checked, getest, gedocumenteerd in code en docs, en gedeployed naar Vercel zonder regressies.',
archived: false,
},
})
console.log(`Product created: ${product.name} (id: ${product.id}, owner: demo)`)
await prisma.productMember.create({
data: { product_id: product.id, user_id: user.id },
})
console.log(` Added ${user.username} as member of ${product.name}`)
const milestones = await loadBacklog(root)
console.log(`Loaded backlog: ${milestones.length} milestones, ${milestones.reduce((acc, m) => acc + m.stories.length, 0)} stories`)
for (const ms of milestones) {
const pbi = await prisma.pbi.create({
data: {
product_id: product.id,
title: `${ms.key}: ${ms.title}`,
description: ms.goal,
priority: ms.priority,
sort_order: ms.sort_order,
},
})
const sprint = await prisma.sprint.create({
data: {
product_id: product.id,
sprint_goal: `${ms.key}${ms.goal}`,
status: ms.sprint_status,
},
})
console.log(
` PBI ${pbi.title} (priority ${pbi.priority}) + sprint ${ms.sprint_status}`,
)
for (const s of ms.stories) {
const isActive = ms.sprint_status === 'ACTIVE'
const inSprint = isActive || s.status === 'DONE'
const storyStatus =
s.status === 'DONE' ? 'DONE' : isActive ? 'IN_SPRINT' : 'OPEN'
const storySummary = s.tasks.map((t) => t.title).join('; ')
const story = await prisma.story.create({
data: {
pbi_id: pbi.id,
product_id: product.id,
sprint_id: inSprint ? sprint.id : null,
title: s.title,
description: storySummary,
acceptance_criteria: s.acceptance_criteria,
priority: ms.priority,
sort_order: s.sort_order,
status: storyStatus,
},
})
for (const t of s.tasks) {
await prisma.task.create({
data: {
story_id: story.id,
sprint_id: inSprint ? sprint.id : null,
title: t.title,
description: t.description,
priority: ms.priority,
sort_order: t.sort_order,
status: s.status === 'DONE' ? 'DONE' : 'TO_DO',
},
})
}
}
}
// Solo board demo data — claimed stories for demo user + 1 unassigned for the sheet
const activeSprint = await prisma.sprint.findFirst({
where: { product_id: product.id, status: 'ACTIVE' },
})
if (activeSprint) {
const soloPbi = await prisma.pbi.create({
data: {
product_id: product.id,
title: 'Solo Demo',
description: 'Voorbeeldtaken voor het Solo bord.',
priority: 3,
sort_order: 99,
},
})
const soloData = [
{
title: 'Gebruikersauthenticatie opzetten',
tasks: [
{ title: 'JWT middleware schrijven', status: 'TO_DO' as const, priority: 1 },
{ title: 'Login endpoint testen', status: 'TO_DO' as const, priority: 2 },
],
assignee_id: demo.id,
sortOrder: 1,
},
{
title: 'REST API endpoints implementeren',
tasks: [
{ title: 'Route handlers aanmaken', status: 'IN_PROGRESS' as const, priority: 2 },
{ title: 'Zod-validatie toevoegen', status: 'TO_DO' as const, priority: 3 },
],
assignee_id: demo.id,
sortOrder: 2,
},
{
title: 'Database schema migreren',
tasks: [
{ title: 'Prisma schema bijwerken', status: 'DONE' as const, priority: 2 },
{ title: 'Migratietest uitvoeren', status: 'DONE' as const, priority: 3 },
],
assignee_id: demo.id,
sortOrder: 3,
},
{
title: 'Frontend unit tests schrijven',
tasks: [
{ title: 'Vitest opzetten', status: 'TO_DO' as const, priority: 3 },
],
assignee_id: null,
sortOrder: 4,
},
]
for (const s of soloData) {
const story = await prisma.story.create({
data: {
pbi_id: soloPbi.id,
product_id: product.id,
sprint_id: activeSprint.id,
title: s.title,
priority: 2,
sort_order: 90 + s.sortOrder,
status: 'IN_SPRINT',
assignee_id: s.assignee_id,
},
})
for (let i = 0; i < s.tasks.length; i++) {
const t = s.tasks[i]
await prisma.task.create({
data: {
story_id: story.id,
sprint_id: activeSprint.id,
title: t.title,
priority: t.priority,
sort_order: i + 1.0,
status: t.status,
},
})
}
}
console.log(' Solo demo stories created (3 claimed, 1 unassigned)')
}
console.log('\nSeeding complete!')
console.log('Demo user: username=demo password=demo1234')
console.log('Main user: username=lars password=scrum4me123')
}
main()
.catch((e) => {
console.error(e)
process.exit(1)
})
.finally(async () => {
await prisma?.$disconnect()
})