liftman/scripts/seed.js
2026-05-22 23:05:37 +02:00

420 lines
12 KiB
JavaScript

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);
}