420 lines
12 KiB
JavaScript
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);
|
|
}
|