/** * Development/Test fixture seeding. Idempotent: running it twice must not * duplicate anything. Never run against production data. * * Fixture matrix (documented in apps/web/e2e/README.md): * fixture-admin active, Site Admin * fixture-user active, regular account * fixture-pending registered but e-mail not verified * * All fixture accounts share the password below — they exist only on * dev machines and disposable CI/Test databases. On shared stages * (test/int), set FIXTURE_ADMIN_PASSWORD / FIXTURE_USER_PASSWORD to give * those two accounts non-public passwords. */ import { slugify } from '@dorfteich/shared'; import { PrismaClient, UserStatus } from '@prisma/client'; import { hashPassword } from '../src/users/password'; export const FIXTURE_PASSWORD = 'fixture passwort 123'; const PASSWORD_OVERRIDES: Record = { 'fixture-admin': process.env.FIXTURE_ADMIN_PASSWORD, 'fixture-user': process.env.FIXTURE_USER_PASSWORD, }; const prisma = new PrismaClient(); interface FixtureUser { username: string; displayName: string; status: UserStatus; isSiteAdmin: boolean; } const FIXTURES: FixtureUser[] = [ { username: 'fixture-admin', displayName: 'Fixture Admin', status: 'ACTIVE', isSiteAdmin: true }, { username: 'fixture-user', displayName: 'Fixture User', status: 'ACTIVE', isSiteAdmin: false }, { username: 'fixture-pending', displayName: 'Fixture Pending', status: 'PENDING_VERIFICATION', isSiteAdmin: false, }, ]; async function upsertFixtureUser(fixture: FixtureUser): Promise { const email = `${fixture.username}@dorfteich.test`; const user = await prisma.user.upsert({ where: { username: fixture.username }, create: { username: fixture.username, email, displayName: fixture.displayName, locale: 'de', status: fixture.status, isSiteAdmin: fixture.isSiteAdmin, emailVerifiedAt: fixture.status === 'ACTIVE' ? new Date() : null, }, update: { status: fixture.status, isSiteAdmin: fixture.isSiteAdmin, }, }); // Re-hash on every run so a changed override takes effect on re-seed. const credential = await hashPassword(PASSWORD_OVERRIDES[fixture.username] ?? FIXTURE_PASSWORD); await prisma.userIdentity.upsert({ where: { provider_subject: { provider: 'password', subject: user.id } }, create: { userId: user.id, provider: 'password', subject: user.id, credential, }, update: { credential }, }); // Active accounts get their personal pond, mirroring what e-mail // verification does for real signups (issue #21). if (fixture.status === 'ACTIVE') { const existing = await prisma.pond.findFirst({ where: { ownerId: user.id, type: 'PERSONAL' }, select: { id: true }, }); if (!existing) { await prisma.pond.create({ data: { slug: slugify(fixture.displayName) || fixture.username, name: fixture.displayName, type: 'PERSONAL', ownerId: user.id, }, }); } // The instance default for additional_ponds is 0 (ADR 0011) — give // the fixtures headroom so pond flows are exercisable in dev/e2e. await prisma.quotaOverride.upsert({ where: { subjectType_subjectId_quotaKey: { subjectType: 'USER', subjectId: user.id, quotaKey: 'additional_ponds', }, }, create: { subjectType: 'USER', subjectId: user.id, quotaKey: 'additional_ponds', value: 100, }, update: { value: 100 }, }); } } async function main(): Promise { // Fresh rate-limit budget for e2e runs — seed targets are always // disposable dev/CI databases, never production. await prisma.rateLimit.deleteMany({}); for (const fixture of FIXTURES) { await upsertFixtureUser(fixture); } await prisma.instanceSetting.upsert({ where: { key: 'seed.marker' }, create: { key: 'seed.marker', value: { seededAt: new Date().toISOString() } }, update: { value: { seededAt: new Date().toISOString() } }, }); console.log(`seed: done (${FIXTURES.length} fixture users)`); } main() .catch((error) => { console.error(error); process.exitCode = 1; }) .finally(() => prisma.$disconnect());