- server/: Express-API (Auth, Bootstrap, Tickets, Threads, Benutzerverwaltung, Codes) mit pg-Schema (11 Tabellen), idempotem Seed (50 Wohnungen, 552 Buchungen) - mp-db (PostgreSQL 16, Volume mp_pgdata) + mp-api im Compose-Stack, nginx proxiet /api/ an mp-api; deploy.sh baut API-Image auf dem Server - Frontend: Store laedt /api/bootstrap, Schreibzugriff async, kein localStorage mehr (ausser Theme), Ladebildschirm beim Session-Check, vite-Proxy fuer Dev - Rollenrechte serverseitig: Mieter nur eigene Wohnung (getestet, 403-Verweis) - Mail-Benachrichtigungen komplett serverseitig (server/src/mail.js) - beispieldaten.py schreibt server/seed/generated.json; Verwaltungs-Mail = axel@hocks.eu - E2E live verifiziert: Login, Scoping, Ticket + Zustellung (status=sent), Registrierung, Codes; Testdaten bereinigt; Doku nachgefuehrt
101 lines
3.7 KiB
JavaScript
101 lines
3.7 KiB
JavaScript
/** Idempotenter Seed: füllt die Datenbank beim ersten Start mit den
|
||
* Demo-Daten (50 Wohnungen, 552 Buchungen, Tickets, Dokumente, Threads,
|
||
* Ankündigungen, Demo-Benutzer, Registrierungscode). Läuft nur, wenn die
|
||
* users-Tabelle leer ist – bestehende Daten werden nie überschrieben.
|
||
*/
|
||
|
||
const { pool } = require('./db')
|
||
const { users, registrationCodes, tickets, documents, threads, announcements } = require('./seed-data')
|
||
const generated = require('../seed/generated.json')
|
||
|
||
async function seed() {
|
||
const { rows } = await pool.query('SELECT COUNT(*)::int AS n FROM users')
|
||
if (rows[0].n > 0) return false
|
||
|
||
const client = await pool.connect()
|
||
try {
|
||
await client.query('BEGIN')
|
||
|
||
for (const u of generated.units) {
|
||
await client.query(
|
||
`INSERT INTO units (id, building, number, floor, size, cold_rent, warm_rent, tenant, status)
|
||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`,
|
||
[u.id, u.building, u.number, u.floor, u.size, u.coldRent, u.warmRent, u.tenant, u.status],
|
||
)
|
||
}
|
||
|
||
for (const t of generated.transactions) {
|
||
await client.query(
|
||
`INSERT INTO transactions (id, unit_id, tenant, month, year, amount, type, status, paid_at)
|
||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`,
|
||
[t.id, t.unitId, t.tenant, t.month, t.year, t.amount, t.type, t.status, t.paidAt ?? null],
|
||
)
|
||
}
|
||
|
||
// Mieter-Namen in Tickets/Threads aus der units-Tabelle nachziehen
|
||
const { rows: unitRows } = await client.query('SELECT id, tenant FROM units')
|
||
const mieterVon = Object.fromEntries(unitRows.map((u) => [u.id, u.tenant]))
|
||
|
||
for (const t of tickets) {
|
||
await client.query(
|
||
`INSERT INTO tickets (id, unit_id, tenant, title, description, category, priority, status, created_at)
|
||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`,
|
||
[t.id, t.unit_id, mieterVon[t.unit_id] ?? t.tenant, t.title, t.description, t.category, t.priority, t.status, t.created_at],
|
||
)
|
||
}
|
||
|
||
for (const d of documents) {
|
||
await client.query(
|
||
`INSERT INTO documents (id, unit_id, title, category, size, date) VALUES ($1,$2,$3,$4,$5,$6)`,
|
||
[d.id, d.unit_id, d.title, d.category, d.size, d.date],
|
||
)
|
||
}
|
||
|
||
for (const th of threads) {
|
||
await client.query(
|
||
`INSERT INTO threads (id, unit_id, tenant, subject, unread_by_admin, unread_by_tenant)
|
||
VALUES ($1,$2,$3,$4,$5,$6)`,
|
||
[th.id, th.unit_id, mieterVon[th.unit_id] ?? th.tenant, th.subject, th.unread_by_admin, th.unread_by_tenant],
|
||
)
|
||
for (const m of th.messages) {
|
||
await client.query(
|
||
`INSERT INTO messages (id, thread_id, from_role, author, text, at) VALUES ($1,$2,$3,$4,$5,$6)`,
|
||
[m.id, th.id, m.from_role, m.author, m.text, m.at],
|
||
)
|
||
}
|
||
}
|
||
|
||
for (const a of announcements) {
|
||
await client.query(
|
||
`INSERT INTO announcements (id, title, text, date, tag) VALUES ($1,$2,$3,$4,$5)`,
|
||
[a.id, a.title, a.text, a.date, a.tag],
|
||
)
|
||
}
|
||
|
||
for (const b of users) {
|
||
await client.query(
|
||
`INSERT INTO users (id, benutzername, name, passwort_hash, rolle, email, einheit_id, aktiv, erstellt_am, letzter_login)
|
||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)`,
|
||
[b.id, b.benutzername, b.name, b.passwort_hash, b.rolle, b.email, b.einheit_id, b.aktiv, b.erstellt_am, b.letzter_login ?? null],
|
||
)
|
||
}
|
||
|
||
for (const c of registrationCodes) {
|
||
await client.query(
|
||
`INSERT INTO registration_codes (code, einheit_id, erstellt_am, verwendet_am) VALUES ($1,$2,$3,$4)`,
|
||
[c.code, c.einheit_id, c.erstellt_am, c.verwendet_am],
|
||
)
|
||
}
|
||
|
||
await client.query('COMMIT')
|
||
return true
|
||
} catch (e) {
|
||
await client.query('ROLLBACK')
|
||
throw e
|
||
} finally {
|
||
client.release()
|
||
}
|
||
}
|
||
|
||
module.exports = { seed }
|