const bcrypt = require("bcrypt"); const { Pool } = require("pg"); const DATABASE_URL = process.env.DATABASE_URL || "postgresql://lift_admin:lift_secret@127.0.0.1:5432/lift_manager"; const SALT_ROUNDS = 10; const DEFAULT_PASSWORD = process.env.SEED_PASSWORD || "teszt123"; const RUN_TOKEN = Date.now().toString(36).slice(-6); const ROUTES = [ "Észak-Buda", "Dél-Buda", "Észak-Pest", "Dél-Pest", "Belváros", "Külváros", "Keleti zóna", "Nyugati zóna" ]; const FIRST_NAMES = [ "Ádám", "Bence", "Csaba", "Dávid", "Erika", "Ferenc", "Gábor", "Hanna", "István", "Judit", "Katalin", "Levente", "Márk", "Noémi", "Orsolya", "Péter", "Réka", "Sándor", "Tamás", "Zoltán" ]; const LAST_NAMES = [ "Kovács", "Szabó", "Tóth", "Nagy", "Varga", "Kiss", "Molnár", "Németh", "Farkas", "Balogh", "Takács", "Juhász" ]; const STREETS = [ "Kossuth Lajos utca", "Rákóczi út", "Bartók Béla út", "Alkotás utca", "Petőfi Sándor utca", "Szent István körút", "Andrássy út", "József Attila utca", "Kerepesi út", "Váci út", "Móricz Zsigmond körtér", "Fehérvári út" ]; const BUILDING_TYPES = [ "Társasház", "Irodaház", "Rendelőintézet", "Szálloda", "Kereskedelmi központ", "Logisztikai csarnok", "Önkormányzati épület", "Parkolóház" ]; const STATUS_VALUES = ["rendben", "rendben", "rendben", "figyelmeztetes", "lejart"]; const NOTE_TEMPLATES = [ "Rendszeres időszakos ellenőrzés, jelentős eltérés nem tapasztalható.", "Ajtózár finombeállítás javasolt a következő szerviz alkalmával.", "A gépház tiszta, az üzemi paraméterek megfelelő tartományban vannak.", "Kisebb kopás látható a vezetősínek környezetében, megfigyelés javasolt.", "A vészvilágítás működik, a dokumentáció rendben.", "Szintbeállítás megfelelő, utasoldali panasz nem érkezett.", "Biztonsági áramkör ellenőrizve, működés megfelelő.", "Esztétikai sérülések láthatók a fülkében, műszaki hibát nem okoznak." ]; const pool = new Pool({ connectionString: DATABASE_URL }); const options = parseArgs(process.argv.slice(2)); main().catch((error) => { console.error("A seed futtatása sikertelen:", error.message); process.exitCode = 1; }).finally(async () => { await pool.end(); }); async function main() { await initializeDatabase(); if (options.reset) { await resetDatabase(); } const passwordHash = await bcrypt.hash(DEFAULT_PASSWORD, SALT_ROUNDS); const client = await pool.connect(); try { await client.query("BEGIN"); const createdUsers = []; const createdElevators = []; let createdLogs = 0; for (let routeIndex = 0; routeIndex < options.routeCount; routeIndex += 1) { const routeName = ROUTES[routeIndex % ROUTES.length]; for (let userIndex = 0; userIndex < options.usersPerRoute; userIndex += 1) { const user = await createUser(client, routeName, routeIndex, userIndex, passwordHash); createdUsers.push(user); } const routeUsers = createdUsers.filter((user) => user.route_name === routeName); for (let elevatorIndex = 0; elevatorIndex < options.elevatorsPerRoute; elevatorIndex += 1) { const elevator = await createElevator(client, routeName, routeIndex, elevatorIndex); createdElevators.push(elevator); createdLogs += await createLogsForElevator(client, elevator, routeUsers, elevatorIndex, options.logsPerElevator); } } await client.query("COMMIT"); console.log("Seed kész."); console.log(`Felhasználók: ${createdUsers.length}`); console.log(`Eszközök: ${createdElevators.length}`); console.log(`Naplóbejegyzések: ${createdLogs}`); console.log(`Teszt jelszó: ${DEFAULT_PASSWORD}`); } catch (error) { await client.query("ROLLBACK"); throw error; } finally { client.release(); } } function parseArgs(args) { const parsed = { reset: args.includes("--reset"), routeCount: 6, usersPerRoute: 3, elevatorsPerRoute: 18, logsPerElevator: 14 }; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; const nextValue = args[index + 1]; if (arg === "--routes") { parsed.routeCount = toPositiveInt(nextValue, parsed.routeCount); } if (arg === "--users-per-route") { parsed.usersPerRoute = toPositiveInt(nextValue, parsed.usersPerRoute); } if (arg === "--elevators-per-route") { parsed.elevatorsPerRoute = toPositiveInt(nextValue, parsed.elevatorsPerRoute); } if (arg === "--logs-per-elevator") { parsed.logsPerElevator = toPositiveInt(nextValue, parsed.logsPerElevator); } } return parsed; } function toPositiveInt(value, fallback) { const parsed = Number(value); return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; } async function initializeDatabase() { await pool.query(` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, full_name TEXT NOT NULL, route_name TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS elevators ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, serial_number TEXT NOT NULL UNIQUE, location TEXT NOT NULL, route_name TEXT NOT NULL, last_inspection_date DATE, next_inspection_date DATE, inspection_interval_days INTEGER NOT NULL DEFAULT 30, last_status TEXT NOT NULL DEFAULT 'rendben', updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS logs ( id SERIAL PRIMARY KEY, elevator_id INTEGER REFERENCES elevators(id) ON DELETE SET NULL, elevator_name TEXT NOT NULL, serial_number TEXT NOT NULL, location TEXT NOT NULL, route_name TEXT NOT NULL, inspection_date DATE NOT NULL, next_inspection_date DATE NOT NULL, inspection_interval_days INTEGER NOT NULL DEFAULT 30, status TEXT NOT NULL, notes TEXT NOT NULL DEFAULT '', technician_name TEXT NOT NULL, created_by_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS log_files ( id SERIAL PRIMARY KEY, log_id INTEGER NOT NULL REFERENCES logs(id) ON DELETE CASCADE, original_name TEXT NOT NULL, stored_name TEXT NOT NULL, size INTEGER NOT NULL, mime_type TEXT NOT NULL ); `); await pool.query(` ALTER TABLE logs ADD COLUMN IF NOT EXISTS elevator_id INTEGER REFERENCES elevators(id) ON DELETE SET NULL; `); } async function resetDatabase() { await pool.query(` TRUNCATE TABLE log_files, logs, elevators, users RESTART IDENTITY CASCADE; `); } async function createUser(client, routeName, routeIndex, userIndex, passwordHash) { const firstName = FIRST_NAMES[(routeIndex * 3 + userIndex) % FIRST_NAMES.length]; const lastName = LAST_NAMES[(routeIndex * 5 + userIndex) % LAST_NAMES.length]; const fullName = `${lastName} ${firstName}`; const username = normalizeUsername(`${lastName}.${firstName}.${routeIndex + 1}.${userIndex + 1}.${RUN_TOKEN}`); const result = await client.query( `INSERT INTO users (username, password_hash, full_name, route_name) VALUES ($1, $2, $3, $4) RETURNING id, username, full_name, route_name`, [username, passwordHash, fullName, routeName] ); return result.rows[0]; } async function createElevator(client, routeName, routeIndex, elevatorIndex) { const buildingType = BUILDING_TYPES[(routeIndex + elevatorIndex) % BUILDING_TYPES.length]; const street = STREETS[(routeIndex * 7 + elevatorIndex) % STREETS.length]; const buildingNumber = 10 + ((routeIndex * 13 + elevatorIndex * 3) % 89); const elevatorName = `${buildingType} ${routeIndex + 1}/${elevatorIndex + 1}. lift`; const serialNumber = buildSerialNumber(routeIndex, elevatorIndex); const location = `Budapest, ${street} ${buildingNumber}.`; const result = await client.query( `INSERT INTO elevators ( name, serial_number, location, route_name, last_inspection_date, next_inspection_date, inspection_interval_days, last_status ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, name, serial_number, location, route_name, last_inspection_date, next_inspection_date, inspection_interval_days, last_status, updated_at`, [elevatorName, serialNumber, location, routeName, null, null, 30, "rendben"] ); return result.rows[0]; } async function createLogsForElevator(client, elevator, routeUsers, elevatorIndex, logCount) { const createdLogs = []; const latestLogProfile = buildLatestLogProfile(elevatorIndex); for (let logIndex = 0; logIndex < logCount; logIndex += 1) { const isLatestLog = logIndex === logCount - 1; const logProfile = isLatestLog ? latestLogProfile : buildHistoricalLogProfile(elevatorIndex, logIndex); const user = routeUsers[(elevatorIndex + logIndex) % routeUsers.length]; const inspectionDate = dateToIso(logProfile.inspectionDate); const nextInspectionDate = dateToIso(logProfile.nextInspectionDate); const result = await client.query( `INSERT INTO logs ( elevator_id, elevator_name, serial_number, location, route_name, inspection_date, next_inspection_date, inspection_interval_days, status, notes, technician_name, created_by_user_id, created_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id`, [ elevator.id, elevator.name, elevator.serial_number, elevator.location, elevator.route_name, inspectionDate, nextInspectionDate, logProfile.inspectionIntervalDays, logProfile.status, logProfile.notes, user.full_name, user.id, logProfile.createdAt.toISOString() ] ); createdLogs.push(result.rows[0].id); } await client.query( `UPDATE elevators SET last_inspection_date = $1, next_inspection_date = $2, inspection_interval_days = $3, last_status = $4, updated_at = $5 WHERE id = $6`, [ dateToIso(latestLogProfile.inspectionDate), dateToIso(latestLogProfile.nextInspectionDate), latestLogProfile.inspectionIntervalDays, latestLogProfile.status, latestLogProfile.createdAt.toISOString(), elevator.id ] ); return createdLogs.length; } function buildLatestLogProfile(elevatorIndex) { const today = startOfToday(); const nextInspectionOffset = elevatorIndex % 10; const intervalDays = 30 + ((elevatorIndex % 4) * 15); const inspectionDate = addDays(today, -((elevatorIndex % 18) + 3)); const nextInspectionDate = addDays(today, nextInspectionOffset); const status = nextInspectionOffset <= 1 ? "figyelmeztetes" : "rendben"; return { inspectionDate, nextInspectionDate, inspectionIntervalDays: intervalDays, status, notes: NOTE_TEMPLATES[elevatorIndex % NOTE_TEMPLATES.length], createdAt: new Date(`${dateToIso(inspectionDate)}T09:00:00Z`) }; } function buildHistoricalLogProfile(elevatorIndex, logIndex) { const intervalDays = 30 + (((elevatorIndex + logIndex) % 4) * 15); const inspectionDate = addDays(startOfToday(), -((logIndex + 2) * intervalDays)); const nextInspectionDate = addDays(inspectionDate, intervalDays); const status = STATUS_VALUES[(elevatorIndex + logIndex) % STATUS_VALUES.length]; return { inspectionDate, nextInspectionDate, inspectionIntervalDays: intervalDays, status, notes: NOTE_TEMPLATES[(elevatorIndex + logIndex) % NOTE_TEMPLATES.length], createdAt: new Date(`${dateToIso(inspectionDate)}T08:30:00Z`) }; } function buildSerialNumber(routeIndex, elevatorIndex) { const routePart = String(routeIndex + 1).padStart(2, "0"); const elevatorPart = String(elevatorIndex + 1).padStart(3, "0"); return `LIFT-${RUN_TOKEN}-${routePart}-${elevatorPart}`; } function normalizeUsername(value) { return value .normalize("NFD") .replaceAll(/[\u0300-\u036f]/g, "") .replaceAll(/[^a-zA-Z0-9.]/g, "") .toLowerCase(); } function startOfToday() { const now = new Date(); return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())); } function addDays(date, days) { const copy = new Date(date.getTime()); copy.setUTCDate(copy.getUTCDate() + days); return copy; } function dateToIso(date) { return date.toISOString().slice(0, 10); }