Adds a Solo Demo PBI under the active sprint with 4 stories: - 3 claimed by demo user, tasks covering TO_DO / IN_PROGRESS / DONE columns - 1 unassigned story so the sheet demo shows a claimable item Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
281 lines
8.4 KiB
TypeScript
281 lines
8.4 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 story = await prisma.story.create({
|
|
data: {
|
|
pbi_id: pbi.id,
|
|
product_id: product.id,
|
|
sprint_id: inSprint ? sprint.id : null,
|
|
title: s.title,
|
|
description: s.description,
|
|
acceptance_criteria: s.acceptance_criteria,
|
|
priority: ms.priority,
|
|
sort_order: s.sort_order,
|
|
status: storyStatus,
|
|
},
|
|
})
|
|
|
|
await prisma.task.create({
|
|
data: {
|
|
story_id: story.id,
|
|
sprint_id: inSprint ? sprint.id : null,
|
|
title: 'Implementatie',
|
|
description: s.description,
|
|
priority: ms.priority,
|
|
sort_order: 1.0,
|
|
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()
|
|
})
|