Scrum4Me/prisma/seed.ts
janpeter visser 08de004ae7 feat: PostgreSQL support en Vercel CI/CD deployment
- Prisma schema: sqlite → postgresql provider met directUrl
- prisma.config.ts: directUrl toegevoegd
- seed.ts: dynamisch SQLite of PostgreSQL adapter op basis van DATABASE_URL
- ci.yml: preview deploy op PR, productie deploy op push naar main

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:59:12 +02:00

257 lines
20 KiB
TypeScript

import { PrismaClient } from '@prisma/client'
import * as dotenv from 'dotenv'
import * as path from 'path'
import * as bcrypt from 'bcryptjs'
// 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')
if (url.startsWith('file:')) {
// SQLite (local development)
const { PrismaBetterSqlite3 } = await import('@prisma/adapter-better-sqlite3')
const adapter = new PrismaBetterSqlite3({ url })
prisma = new PrismaClient({ adapter })
} else {
// PostgreSQL (Neon / production)
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})`)
// 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: 'DevPlanner',
description: 'Een lichtgewicht Scrum-gebaseerde projectplanner voor solo developers en kleine Scrum Teams.',
repo_url: 'https://github.com/devplanner/devplanner',
definition_of_done: 'Feature is geïmplementeerd, getest (unit + integratie), gedocumenteerd in code, en gedeployed naar de staging-omgeving zonder regressies.',
archived: false,
},
})
console.log(`Product created: ${product.name} (id: ${product.id}, owner: demo)`)
// PBI data from the product backlog document
const pbis = [
{
title: 'Authenticatie & gebruikersbeheer',
description: 'Het Scrum Team kan een account aanmaken en inloggen met gebruikersnaam en wachtwoord. Een demo-gebruiker heeft alleen leesrechten. Gebruikers kunnen één of meerdere Scrum-rollen aannemen.',
priority: 1,
stories: [
{ title: 'Account aanmaken', description: 'Als bezoeker wil ik een account aanmaken met gebruikersnaam en wachtwoord, zodat ik toegang krijg tot de app.', acceptance_criteria: '- Gebruikersnaam en wachtwoord zijn verplicht\n- Gebruikersnaam is uniek; dubbele aanmelding geeft foutmelding\n- Wachtwoord heeft minimaal 8 tekens\n- Na aanmaken wordt de gebruiker direct ingelogd\n- Geen e-mailverificatie vereist in v1', priority: 1 },
{ title: 'Inloggen', description: 'Als geregistreerde gebruiker wil ik inloggen met gebruikersnaam en wachtwoord, zodat ik mijn projecten kan beheren.', acceptance_criteria: '- Incorrecte combinatie geeft generieke foutmelding\n- Na inloggen wordt de gebruiker doorgestuurd naar het dashboard\n- Sessie blijft actief totdat de gebruiker uitlogt', priority: 1 },
{ title: 'Uitloggen', description: 'Als ingelogde gebruiker wil ik kunnen uitloggen, zodat mijn sessie veilig afgesloten wordt.', acceptance_criteria: '- Uitlogknop altijd zichtbaar in de navigatie\n- Na uitloggen wordt de gebruiker naar de loginpagina gestuurd\n- Sessiedata wordt gewist', priority: 1 },
{ title: 'Demo-gebruiker (read-only)', description: 'Als bezoeker wil ik kunnen inloggen als demo-gebruiker, zodat ik de app kan verkennen zonder een account aan te maken.', acceptance_criteria: '- Vaste inloggegevens voor de demo-gebruiker zijn beschikbaar op de loginpagina\n- Demo-gebruiker ziet alle data maar kan niets aanmaken, aanpassen of verwijderen\n- Alle actieknoppen zijn zichtbaar maar uitgeschakeld', priority: 2 },
{ title: 'Roltoewijzing', description: 'Als gebruiker wil ik één of meerdere Scrum-rollen kunnen aannemen (Product Owner, Scrum Master, Developer).', acceptance_criteria: '- Gebruiker kan bij registratie of in instellingen rollen selecteren\n- Minimaal één rol is verplicht\n- Alle drie de rollen tegelijk zijn toegestaan', priority: 3 },
],
},
{
title: 'Productbeheer',
description: 'Het Scrum Team kan producten aanmaken, bekijken, bewerken en archiveren.',
priority: 1,
stories: [
{ title: 'Product aanmaken', description: 'Als Product Owner wil ik een nieuw product aanmaken met naam, beschrijving en git-repo URL.', acceptance_criteria: '- Naam is verplicht en uniek per gebruiker\n- Beschrijving is optioneel\n- Git-repo URL is optioneel maar wordt gevalideerd als geldige URL', priority: 1 },
{ title: 'Product bewerken', description: 'Als Product Owner wil ik de naam, beschrijving en git-repo URL van een product kunnen aanpassen.', acceptance_criteria: '- Alle velden zijn bewerkbaar\n- Wijzigingen worden opgeslagen zonder de pagina te verlaten', priority: 2 },
{ title: 'Product archiveren', description: 'Als Product Owner wil ik een product kunnen archiveren.', acceptance_criteria: '- Gearchiveerde producten verschijnen niet in de standaardlijst\n- Archiveren is omkeerbaar', priority: 2 },
{ title: 'Productenlijst bekijken', description: 'Als gebruiker wil ik een overzicht zien van alle actieve producten.', acceptance_criteria: '- Lijst toont naam, beschrijving (ingekort) en git-repo link\n- Klikken op een product opent de Product Backlog', priority: 1 },
],
},
{
title: 'Product Backlog',
description: 'Het Scrum Team kan de Product Backlog beheren via een gesplitst scherm: links de PBIs, rechts de bijbehorende stories.',
priority: 1,
stories: [
{ title: 'PBI aanmaken', description: 'Als Product Owner wil ik een PBI aanmaken in de Product Backlog.', acceptance_criteria: '- PBI heeft een titel (verplicht) en omschrijving (optioneel)\n- PBI krijgt een prioriteit (1 t/m 4)\n- Nieuw PBI verschijnt onderaan de lijst', priority: 1 },
{ title: 'PBI bewerken', description: 'Als Product Owner wil ik de titel, omschrijving en prioriteit van een PBI kunnen aanpassen.', acceptance_criteria: '- Dubbelklikken of via contextmenu opent bewerkingsmodus\n- Alle velden zijn inline bewerkbaar', priority: 2 },
{ title: 'PBI verwijderen', description: 'Als Product Owner wil ik een PBI kunnen verwijderen.', acceptance_criteria: '- Verwijderen vereist bevestiging\n- Cascade verwijdering van bijbehorende stories', priority: 2 },
{ title: 'PBI prioriteit instellen', description: 'Als Product Owner wil ik per PBI een prioriteit kunnen instellen (1 t/m 4).', acceptance_criteria: '- Prioriteit is instelbaar via dropdown of inline label\n- PBIs worden gegroepeerd per prioriteit', priority: 1 },
{ title: 'PBI volgorde aanpassen via drag-and-drop', description: 'Als Product Owner wil ik de volgorde van PBIs binnen dezelfde prioriteit kunnen aanpassen via drag-and-drop.', acceptance_criteria: '- Drag-and-drop werkt vloeiend via dnd-kit\n- Volgorde wordt direct opgeslagen na loslaten', priority: 2 },
{ title: 'PBI filteren', description: 'Als gebruiker wil ik PBIs kunnen filteren op prioriteit.', acceptance_criteria: '- Filteropties beschikbaar in de navigatiebar\n- Filter werkt realtime', priority: 3 },
{ title: 'Gesplitst scherm Product Backlog', description: 'Als gebruiker wil ik de Product Backlog bekijken als gesplitst scherm.', acceptance_criteria: '- Scherm is standaard 50/50 verdeeld\n- Splitter is horizontaal versleepbaar', priority: 1 },
],
},
{
title: 'Story-beheer',
description: 'Stories kunnen worden aangemaakt, bewerkt, geprioriteerd en gerangschikt binnen een PBI.',
priority: 1,
stories: [
{ title: 'Story aanmaken', description: 'Als Product Owner wil ik een story aanmaken binnen een PBI.', acceptance_criteria: '- Story heeft een titel (verplicht), omschrijving (optioneel) en prioriteit\n- Nieuwe story verschijnt als blok rechts', priority: 1 },
{ title: 'Story weergave als blokken', description: 'Als gebruiker wil ik stories zien als compacte blokken (~10% schermbreedte).', acceptance_criteria: '- Elk blok toont: storytitel, prioriteit, status\n- Blokken zijn gerangschikt op prioriteit', priority: 1 },
{ title: 'Story prioriteit instellen', description: 'Als Product Owner wil ik per story een prioriteit instellen.', acceptance_criteria: '- Prioriteit instelbaar via het storyblok\n- Prioriteitswijziging herplaatst het blok in de juiste groep', priority: 2 },
{ title: 'Story volgorde aanpassen via drag-and-drop', description: 'Als Product Owner wil ik de volgorde van stories aanpassen via drag-and-drop.', acceptance_criteria: '- Drag-and-drop werkt via dnd-kit\n- Volgorde wordt direct opgeslagen', priority: 2 },
{ title: 'Story bewerken', description: 'Als Product Owner wil ik de titel, omschrijving en prioriteit van een story kunnen aanpassen.', acceptance_criteria: '- Bewerkbaar via klikken op het storyblok\n- Wijzigingen opgeslagen zonder paginaverversing', priority: 2 },
{ title: 'Story verwijderen', description: 'Als Product Owner wil ik een story kunnen verwijderen.', acceptance_criteria: '- Verwijderen vereist bevestiging\n- Cascade verwijdering van gekoppelde taken', priority: 2 },
],
},
{
title: 'Todo-lijst',
description: 'Gebruikers kunnen een snelle todo-lijst bijhouden voor ongeplande of kortstondige taken.',
priority: 2,
stories: [
{ title: 'Todo-item aanmaken', description: 'Als gebruiker wil ik snel een todo-item aanmaken zonder het aan een product te koppelen.', acceptance_criteria: '- Todo heeft alleen een titel (verplicht)\n- Aanmaken via een snel-invoerveld (Enter om op te slaan)', priority: 1 },
{ title: 'Todo-item afvinken', description: 'Als gebruiker wil ik een todo-item kunnen afvinken.', acceptance_criteria: '- Afgevinkte items zijn visueel doorgestreept\n- Afgevinkte items kunnen worden gearchiveerd', priority: 1 },
{ title: 'Todo promoveren naar PBI', description: 'Als Product Owner wil ik een todo-item promoveren naar een PBI.', acceptance_criteria: '- Promoten opent een dialoog om product en prioriteit te kiezen\n- Todo wordt omgezet naar een PBI', priority: 2 },
{ title: 'Todo promoveren naar story', description: 'Als Product Owner wil ik een todo-item promoveren naar een story.', acceptance_criteria: '- Promoten opent een dialoog om product, PBI en prioriteit te kiezen\n- Todo wordt omgezet naar een story', priority: 2 },
],
},
{
title: 'Sprint Backlog & Sprint Planning',
description: 'Het Scrum Team kan een Sprint aanmaken met een Sprint Goal, stories slepen en de volgorde bepalen.',
priority: 2,
stories: [
{ title: 'Sprint aanmaken', description: 'Als Scrum Master wil ik een nieuwe Sprint aanmaken met een Sprint Goal.', acceptance_criteria: '- Sprint heeft een Sprint Goal (verplicht)\n- Sprint is gekoppeld aan een product\n- Er kan maar één actieve Sprint per product zijn', priority: 1 },
{ title: 'Sprint Backlog scherm (gesplitst)', description: 'Als gebruiker wil ik de Sprint Backlog kunnen beheren via een gesplitst scherm.', acceptance_criteria: '- Links: Sprint Backlog met geselecteerde stories\n- Rechts: stories uit de Product Backlog', priority: 1 },
{ title: 'Story naar Sprint slepen', description: 'Als Developer wil ik een story vanuit de Product Backlog naar de Sprint Backlog kunnen slepen.', acceptance_criteria: '- Drag-and-drop werkt via dnd-kit\n- Story verschijnt in de Sprint Backlog op de gesleepte positie', priority: 1 },
{ title: 'Volgorde stories in Sprint bepalen', description: 'Als Developer wil ik de volgorde van stories in de Sprint Backlog kunnen aanpassen.', acceptance_criteria: '- Drag-and-drop werkt binnen de Sprint Backlog\n- Volgorde wordt direct opgeslagen', priority: 2 },
{ title: 'Story uit Sprint verwijderen', description: 'Als Developer wil ik een story uit de Sprint Backlog kunnen verwijderen.', acceptance_criteria: '- Story verdwijnt uit de Sprint Backlog\n- Story is weer beschikbaar in de Product Backlog', priority: 2 },
],
},
{
title: 'Sprint Planning (taken per story)',
description: 'Tijdens Sprint Planning worden stories opgedeeld in taken via een gesplitst scherm.',
priority: 2,
stories: [
{ title: 'Sprint Planning scherm', description: 'Als Developer wil ik een Sprint Planning scherm zien met stories links en taken rechts.', acceptance_criteria: '- Links: stories in de Sprint Backlog\n- Rechts: taken van de geselecteerde story', priority: 1 },
{ title: 'Taak aanmaken', description: 'Als Developer wil ik een taak aanmaken onder een story.', acceptance_criteria: '- Taak heeft een titel (verplicht), omschrijving (optioneel) en prioriteit\n- Nieuwe taak verschijnt onderaan de takenlijst', priority: 1 },
{ title: 'Taak prioriteit instellen', description: 'Als Developer wil ik per taak een prioriteit instellen.', acceptance_criteria: '- Prioriteit instelbaar via taakregel\n- Taken gegroepeerd op prioriteit', priority: 2 },
{ title: 'Taak volgorde aanpassen via drag-and-drop', description: 'Als Developer wil ik de volgorde van taken kunnen aanpassen via drag-and-drop.', acceptance_criteria: '- Drag-and-drop via dnd-kit binnen de takenlijst\n- Volgorde direct opgeslagen na loslaten', priority: 2 },
{ title: 'Taakstatus bijhouden', description: 'Als Developer wil ik de status van een taak kunnen bijhouden (To Do, In Progress, Done).', acceptance_criteria: '- Status is instelbaar via de UI\n- Story toont een voortgangsindicator op basis van taakstatussen', priority: 1 },
],
},
{
title: 'Claude Code integratie',
description: 'Claude Code kan via een REST API stories en taken ophalen, implementatieplannen vastleggen en commits registreren.',
priority: 2,
stories: [
{ title: 'REST API — story ophalen', description: 'Als Developer (via Claude Code) wil ik de hoogst geprioriteerde open story ophalen via een API-endpoint.', acceptance_criteria: '- Endpoint: GET /api/products/:id/next-story\n- Authentiseerd via API-token\n- Geeft 404 als er geen open stories zijn', priority: 1 },
{ title: 'REST API — eerste 10 taken ophalen', description: 'Als Developer (via Claude Code) wil ik de eerste 10 taken van de Sprint Backlog kunnen ophalen.', acceptance_criteria: '- Endpoint: GET /api/sprints/:id/tasks?limit=10\n- Retourneert taken in huidige volgorde', priority: 1 },
{ title: 'REST API — taakvolgorde aanpassen', description: 'Als Developer (via Claude Code) wil ik de volgorde van taken kunnen aanpassen via de API.', acceptance_criteria: '- Endpoint: PATCH /api/stories/:id/tasks/reorder\n- Accepteert een geordende lijst van taak-ids', priority: 2 },
{ title: 'Implementatieplan vastleggen', description: 'Als Developer (via Claude Code) wil ik een implementatieplan kunnen schrijven naar een story.', acceptance_criteria: '- Endpoint: POST /api/stories/:id/log\n- type: "IMPLEMENTATION_PLAN"', priority: 1 },
{ title: 'Teststatus vastleggen', description: 'Als Developer (via Claude Code) wil ik de uitkomst van testruns kunnen vastleggen in een story.', acceptance_criteria: '- Endpoint: POST /api/stories/:id/log\n- type: "TEST_RESULT", status: "PASSED" | "FAILED"', priority: 1 },
{ title: 'Commit-hash vastleggen', description: 'Als Developer (via Claude Code) wil ik de commit-hash na een succesvolle commit kunnen vastleggen.', acceptance_criteria: '- Endpoint: POST /api/stories/:id/log\n- type: "COMMIT", commit_hash, commit_message', priority: 1 },
{ title: 'Story activiteitenlog in UI', description: 'Als gebruiker wil ik per story een activiteitenlog zien met alle vastgelegde plannen, testresultaten en commits.', acceptance_criteria: '- Log toont alle entries in chronologische volgorde\n- Elk type entry heeft een eigen visuele stijl', priority: 2 },
],
},
{
title: 'Infrastructuur & deployment',
description: 'De app is deployable op Vercel + Neon en volledig lokaal draaibaar zonder externe afhankelijkheden.',
priority: 1,
stories: [
{ title: 'Cloud deployment (Vercel + Neon)', description: 'Als Developer wil ik de app deployen op Vercel met een Neon PostgreSQL-database.', acceptance_criteria: '- next build slaagt zonder fouten\n- Database-migraties worden uitgevoerd via Prisma', priority: 1 },
{ title: 'Lokale modus', description: 'Als Developer wil ik de app volledig lokaal kunnen draaien met een lokale SQLite-database.', acceptance_criteria: '- npm run dev start de app lokaal\n- Database wordt aangemaakt via prisma db push', priority: 1 },
{ title: 'API-token authenticatie', description: 'Als Developer wil ik een API-token kunnen genereren in de app.', acceptance_criteria: '- Gebruiker kan een API-token aanmaken\n- Token wordt eenmalig getoond\n- Alle API-endpoints vereisen een geldig token', priority: 1 },
],
},
]
// Create all PBIs and their stories
for (let pbiIdx = 0; pbiIdx < pbis.length; pbiIdx++) {
const pbiData = pbis[pbiIdx]
const pbi = await prisma.pbi.create({
data: {
product_id: product.id,
title: pbiData.title,
description: pbiData.description,
priority: pbiData.priority,
sort_order: (pbiIdx + 1) * 1.0,
},
})
console.log(` PBI: ${pbi.title} (priority ${pbi.priority})`)
for (let storyIdx = 0; storyIdx < pbiData.stories.length; storyIdx++) {
const storyData = pbiData.stories[storyIdx]
await prisma.story.create({
data: {
pbi_id: pbi.id,
product_id: product.id,
title: storyData.title,
description: storyData.description,
acceptance_criteria: storyData.acceptance_criteria,
priority: storyData.priority,
sort_order: (storyIdx + 1) * 1.0,
status: 'OPEN',
},
})
}
}
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()
})