npm run screenshots startet eine Wegwerf-Instanz in einem eigenen Compose-Projekt mit eigener Datenbank, spielt ausschließlich erfundene Daten ein, fährt die Seiten mit Playwright ab und räumt am Ende alles wieder weg. Je Seite entstehen vier Bilder: hell und dunkel, Schreibtisch und Handy. Die Uhr steht fest, damit sich zwischen zwei Läufen nur das ändert, was sich wirklich geändert hat. Der Demo-Seed verweigert den Dienst ohne DEMO_MODUS=true — er würde sonst erfundene Daten in eine echte Datenbank schreiben. Test- und Werkzeugordner sind jetzt aus dem Docker-Kontext ausgenommen. Ein Typfehler in scripts/screenshots.ts hatte den Bau des Abbilds blockiert, obwohl die Datei dort nichts zu suchen hat. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
173 lines
4.3 KiB
TypeScript
173 lines
4.3 KiB
TypeScript
/**
|
|
* Daten für die Screenshot-Instanz.
|
|
*
|
|
* Alles hier ist frei erfunden. Diese Datei darf niemals gegen eine
|
|
* Datenbank mit echten Daten laufen — deshalb bricht sie ab, wenn
|
|
* DEMO_MODUS nicht ausdrücklich auf true steht.
|
|
*
|
|
* Aufruf: über npm run screenshots, nicht von Hand.
|
|
*/
|
|
import 'dotenv/config'
|
|
import { PrismaPg } from '@prisma/adapter-pg'
|
|
import bcrypt from 'bcryptjs'
|
|
import { PrismaClient } from '../src/generated/prisma/client.js'
|
|
|
|
if (process.env.DEMO_MODUS !== 'true') {
|
|
console.error(
|
|
'Der Demo-Seed läuft nur mit DEMO_MODUS=true. Das ist Absicht: er würde ' +
|
|
'sonst erfundene Daten in eine echte Datenbank schreiben.',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
const verbindung = process.env.DATABASE_URL
|
|
if (!verbindung) {
|
|
console.error('DATABASE_URL ist nicht gesetzt.')
|
|
process.exit(1)
|
|
}
|
|
|
|
const db = new PrismaClient({ adapter: new PrismaPg({ connectionString: verbindung }) })
|
|
|
|
export const DEMO_EMAIL = 'martina.beispiel@planer.invalid'
|
|
export const DEMO_PASSWORT = 'demo-instanz-nur-fuer-bilder'
|
|
|
|
const BEREICHE = [
|
|
{
|
|
name: 'Firma',
|
|
farbe: '#2563eb',
|
|
reihenfolge: 1,
|
|
arbeitsfensterVon: 450,
|
|
arbeitsfensterBis: 990,
|
|
kapazitaetMo: 360,
|
|
kapazitaetDi: 360,
|
|
kapazitaetMi: 360,
|
|
kapazitaetDo: 360,
|
|
kapazitaetFr: 240,
|
|
kapazitaetSa: 0,
|
|
kapazitaetSo: 0,
|
|
},
|
|
{
|
|
name: 'Gewerbe',
|
|
farbe: '#7c3aed',
|
|
reihenfolge: 2,
|
|
arbeitsfensterVon: 1020,
|
|
arbeitsfensterBis: 1260,
|
|
kapazitaetMo: 90,
|
|
kapazitaetDi: 90,
|
|
kapazitaetMi: 90,
|
|
kapazitaetDo: 90,
|
|
kapazitaetFr: 60,
|
|
kapazitaetSa: 180,
|
|
kapazitaetSo: 0,
|
|
},
|
|
{
|
|
name: 'Privat',
|
|
farbe: '#16a34a',
|
|
reihenfolge: 3,
|
|
arbeitsfensterVon: 1020,
|
|
arbeitsfensterBis: 1320,
|
|
kapazitaetMo: 60,
|
|
kapazitaetDi: 60,
|
|
kapazitaetMi: 60,
|
|
kapazitaetDo: 60,
|
|
kapazitaetFr: 120,
|
|
kapazitaetSa: 240,
|
|
kapazitaetSo: 240,
|
|
},
|
|
{
|
|
name: 'Homelab',
|
|
farbe: '#ea580c',
|
|
reihenfolge: 4,
|
|
arbeitsfensterVon: 1140,
|
|
arbeitsfensterBis: 1380,
|
|
kapazitaetMo: 0,
|
|
kapazitaetDi: 60,
|
|
kapazitaetMi: 0,
|
|
kapazitaetDo: 60,
|
|
kapazitaetFr: 0,
|
|
kapazitaetSa: 120,
|
|
kapazitaetSo: 60,
|
|
},
|
|
]
|
|
|
|
/** Erfundene Protokolleinträge, damit die Protokollseite nicht leer wirkt. */
|
|
const PROTOKOLL = [
|
|
{ aktion: 'bereich.angelegt', objektTyp: 'Bereich', details: { name: 'Homelab' } },
|
|
{
|
|
aktion: 'bereich.geaendert',
|
|
objektTyp: 'Bereich',
|
|
details: { vorher: 'Nebenerwerb', nachher: 'Gewerbe' },
|
|
},
|
|
{
|
|
aktion: 'passkey.angelegt',
|
|
objektTyp: 'Passkey',
|
|
details: { bezeichnung: 'Diensthandy' },
|
|
},
|
|
]
|
|
|
|
async function main(): Promise<void> {
|
|
console.log('Demo-Daten einspielen.')
|
|
|
|
for (const bereich of BEREICHE) {
|
|
await db.bereich.upsert({
|
|
where: { name: bereich.name },
|
|
update: bereich,
|
|
create: bereich,
|
|
})
|
|
}
|
|
|
|
const benutzer = await db.benutzer.upsert({
|
|
where: { email: DEMO_EMAIL },
|
|
update: { rolle: 'ADMIN', name: 'Martina Beispiel' },
|
|
create: {
|
|
email: DEMO_EMAIL,
|
|
name: 'Martina Beispiel',
|
|
rolle: 'ADMIN',
|
|
passwortHash: await bcrypt.hash(DEMO_PASSWORT, 10),
|
|
},
|
|
})
|
|
|
|
await db.auditEintrag.deleteMany({})
|
|
// Feste Zeitpunkte, damit die Bilder zwischen zwei Läufen gleich bleiben.
|
|
let versatz = 0
|
|
for (const eintrag of PROTOKOLL) {
|
|
versatz += 37
|
|
await db.auditEintrag.create({
|
|
data: {
|
|
zeitpunkt: new Date(Date.UTC(2026, 2, 17, 9, versatz % 60)),
|
|
benutzerId: benutzer.id,
|
|
benutzerEmail: benutzer.email,
|
|
aktion: eintrag.aktion,
|
|
objektTyp: eintrag.objektTyp,
|
|
details: eintrag.details,
|
|
instanz: 'daheim',
|
|
},
|
|
})
|
|
}
|
|
|
|
await db.passkey.deleteMany({ where: { benutzerId: benutzer.id } })
|
|
await db.passkey.create({
|
|
data: {
|
|
benutzerId: benutzer.id,
|
|
credentialId: 'demo-passkey-diensthandy',
|
|
publicKey: Buffer.from([0]),
|
|
bezeichnung: 'Diensthandy',
|
|
gesichert: true,
|
|
erstelltAm: new Date(Date.UTC(2026, 1, 3, 8, 15)),
|
|
zuletztBenutzt: new Date(Date.UTC(2026, 2, 17, 7, 42)),
|
|
},
|
|
})
|
|
|
|
console.log(`Demo-Daten stehen bereit. Anmeldung: ${DEMO_EMAIL}`)
|
|
}
|
|
|
|
main()
|
|
.then(async () => {
|
|
await db.$disconnect()
|
|
})
|
|
.catch(async (fehler: unknown) => {
|
|
console.error(fehler)
|
|
await db.$disconnect()
|
|
process.exit(1)
|
|
})
|