fast fertig
This commit is contained in:
+149
-8
@@ -1,4 +1,5 @@
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { hashSync } from "bcryptjs";
|
||||
import { getDatabasePath, getUploadDir } from "./env";
|
||||
|
||||
@@ -27,6 +28,8 @@ function ensureSchema() {
|
||||
email TEXT NOT NULL COLLATE NOCASE UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL CHECK (role IN ('admin', 'member')),
|
||||
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1)),
|
||||
deleted_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) STRICT;
|
||||
|
||||
@@ -39,11 +42,32 @@ function ensureSchema() {
|
||||
vin TEXT,
|
||||
plate TEXT,
|
||||
current_km INTEGER NOT NULL DEFAULT 0 CHECK (current_km >= 0),
|
||||
tire_pressure_front_bar REAL CHECK (tire_pressure_front_bar IS NULL OR tire_pressure_front_bar > 0),
|
||||
tire_pressure_rear_bar REAL CHECK (tire_pressure_rear_bar IS NULL OR tire_pressure_rear_bar > 0),
|
||||
chain_tension_mm INTEGER CHECK (chain_tension_mm IS NULL OR chain_tension_mm >= 0),
|
||||
chain_tension_min_mm INTEGER CHECK (chain_tension_min_mm IS NULL OR chain_tension_min_mm >= 0),
|
||||
chain_tension_max_mm INTEGER CHECK (chain_tension_max_mm IS NULL OR chain_tension_max_mm >= 0),
|
||||
oil_type TEXT,
|
||||
oil_capacity_liters REAL CHECK (oil_capacity_liters IS NULL OR oil_capacity_liters > 0),
|
||||
oil_check_temp TEXT CHECK (oil_check_temp IS NULL OR oil_check_temp IN ('warm', 'cold')),
|
||||
oil_check_run_minutes INTEGER CHECK (oil_check_run_minutes IS NULL OR oil_check_run_minutes >= 0),
|
||||
oil_check_wait_min_minutes INTEGER CHECK (oil_check_wait_min_minutes IS NULL OR oil_check_wait_min_minutes >= 0),
|
||||
oil_check_wait_max_minutes INTEGER CHECK (oil_check_wait_max_minutes IS NULL OR oil_check_wait_max_minutes >= 0),
|
||||
oil_check_wait_minutes INTEGER CHECK (oil_check_wait_minutes IS NULL OR oil_check_wait_minutes >= 0),
|
||||
oil_check_method TEXT CHECK (oil_check_method IS NULL OR oil_check_method IN ('dipstick', 'sight_glass')),
|
||||
notes TEXT,
|
||||
created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS motorcycle_shares (
|
||||
motorcycle_id INTEGER NOT NULL REFERENCES motorcycles(id) ON DELETE CASCADE,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
shared_by INTEGER NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (motorcycle_id, user_id)
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS maintenance_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
motorcycle_id INTEGER NOT NULL REFERENCES motorcycles(id) ON DELETE CASCADE,
|
||||
@@ -87,11 +111,95 @@ function ensureSchema() {
|
||||
ON maintenance_events(motorcycle_id, type, event_date DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_trips_motorcycle
|
||||
ON trips(motorcycle_id, trip_date DESC, id DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_motorcycle_shares_user
|
||||
ON motorcycle_shares(user_id, motorcycle_id);
|
||||
`);
|
||||
|
||||
const userColumns = db.prepare("PRAGMA table_info(users)").all() as { name: string }[];
|
||||
if (!userColumns.some((column) => column.name === "active")) {
|
||||
db.exec("ALTER TABLE users ADD COLUMN active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1))");
|
||||
}
|
||||
if (!userColumns.some((column) => column.name === "deleted_at")) {
|
||||
db.exec("ALTER TABLE users ADD COLUMN deleted_at TEXT");
|
||||
}
|
||||
|
||||
const motorcycleColumns = db.prepare("PRAGMA table_info(motorcycles)").all() as { name: string }[];
|
||||
if (!motorcycleColumns.some((column) => column.name === "tire_pressure_front_bar")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN tire_pressure_front_bar REAL");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "tire_pressure_rear_bar")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN tire_pressure_rear_bar REAL");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "chain_tension_mm")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN chain_tension_mm INTEGER");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "chain_tension_min_mm")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN chain_tension_min_mm INTEGER");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "chain_tension_max_mm")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN chain_tension_max_mm INTEGER");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "oil_type")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN oil_type TEXT");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "oil_capacity_liters")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN oil_capacity_liters REAL");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "oil_check_temp")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN oil_check_temp TEXT");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "oil_check_run_minutes")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN oil_check_run_minutes INTEGER");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "oil_check_wait_min_minutes")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN oil_check_wait_min_minutes INTEGER");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "oil_check_wait_max_minutes")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN oil_check_wait_max_minutes INTEGER");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "oil_check_wait_minutes")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN oil_check_wait_minutes INTEGER");
|
||||
}
|
||||
if (!motorcycleColumns.some((column) => column.name === "oil_check_method")) {
|
||||
db.exec("ALTER TABLE motorcycles ADD COLUMN oil_check_method TEXT");
|
||||
}
|
||||
|
||||
const motorcycleColumnsAfter = db.prepare("PRAGMA table_info(motorcycles)").all() as { name: string }[];
|
||||
if (
|
||||
motorcycleColumnsAfter.some((column) => column.name === "chain_tension_mm") &&
|
||||
motorcycleColumnsAfter.some((column) => column.name === "chain_tension_min_mm") &&
|
||||
motorcycleColumnsAfter.some((column) => column.name === "chain_tension_max_mm")
|
||||
) {
|
||||
db.exec(`
|
||||
UPDATE motorcycles
|
||||
SET chain_tension_min_mm = chain_tension_mm,
|
||||
chain_tension_max_mm = chain_tension_mm
|
||||
WHERE chain_tension_mm IS NOT NULL
|
||||
AND chain_tension_min_mm IS NULL
|
||||
AND chain_tension_max_mm IS NULL
|
||||
`);
|
||||
}
|
||||
|
||||
if (
|
||||
motorcycleColumnsAfter.some((column) => column.name === "oil_check_wait_minutes") &&
|
||||
motorcycleColumnsAfter.some((column) => column.name === "oil_check_wait_min_minutes") &&
|
||||
motorcycleColumnsAfter.some((column) => column.name === "oil_check_wait_max_minutes")
|
||||
) {
|
||||
db.exec(`
|
||||
UPDATE motorcycles
|
||||
SET oil_check_wait_min_minutes = oil_check_wait_minutes,
|
||||
oil_check_wait_max_minutes = oil_check_wait_minutes
|
||||
WHERE oil_check_wait_minutes IS NOT NULL
|
||||
AND oil_check_wait_min_minutes IS NULL
|
||||
AND oil_check_wait_max_minutes IS NULL
|
||||
`);
|
||||
}
|
||||
|
||||
db.exec("CREATE INDEX IF NOT EXISTS idx_users_active ON users(active, name COLLATE NOCASE)");
|
||||
}
|
||||
|
||||
function ensureInitialAdmin() {
|
||||
const row = db.prepare("SELECT COUNT(*) AS count FROM users").get() as
|
||||
const row = db.prepare("SELECT COUNT(*) AS count FROM users WHERE active = 1").get() as
|
||||
| { count: number }
|
||||
| undefined;
|
||||
|
||||
@@ -99,13 +207,12 @@ function ensureInitialAdmin() {
|
||||
return;
|
||||
}
|
||||
|
||||
const email = process.env.ADMIN_EMAIL?.trim().toLowerCase();
|
||||
const password = process.env.ADMIN_PASSWORD;
|
||||
const name = process.env.ADMIN_NAME?.trim();
|
||||
|
||||
if (!email || !password || !name) {
|
||||
if (!password || !name) {
|
||||
throw new Error(
|
||||
"Keine Benutzer vorhanden. ADMIN_EMAIL, ADMIN_PASSWORD und ADMIN_NAME müssen gesetzt sein.",
|
||||
"Keine Benutzer vorhanden. ADMIN_PASSWORD und ADMIN_NAME müssen gesetzt sein.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,18 +220,52 @@ function ensureInitialAdmin() {
|
||||
throw new Error("ADMIN_PASSWORD muss mindestens 10 Zeichen lang sein.");
|
||||
}
|
||||
|
||||
const adminSlug =
|
||||
name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "konto";
|
||||
db.prepare(
|
||||
"INSERT INTO users (name, email, password_hash, role) VALUES (?, ?, ?, 'admin')",
|
||||
).run(name, email, hashSync(password, 12));
|
||||
).run(name, `admin-${adminSlug}@local.invalid`, hashSync(password, 12));
|
||||
}
|
||||
|
||||
function ensureReferencedUsersExist() {
|
||||
const missingUserIds = db
|
||||
.prepare(
|
||||
`SELECT DISTINCT refs.user_id AS id
|
||||
FROM (
|
||||
SELECT created_by AS user_id FROM motorcycles
|
||||
UNION
|
||||
SELECT created_by AS user_id FROM maintenance_events
|
||||
UNION
|
||||
SELECT driver_user_id AS user_id FROM trips
|
||||
) refs
|
||||
LEFT JOIN users u ON u.id = refs.user_id
|
||||
WHERE refs.user_id IS NOT NULL AND u.id IS NULL
|
||||
ORDER BY refs.user_id`,
|
||||
)
|
||||
.all() as { id: number }[];
|
||||
|
||||
const insertMissingUser = db.prepare(
|
||||
`INSERT INTO users (id, name, email, password_hash, role, active, deleted_at)
|
||||
VALUES (?, ?, ?, ?, 'member', 0, CURRENT_TIMESTAMP)`,
|
||||
);
|
||||
|
||||
for (const missing of missingUserIds) {
|
||||
insertMissingUser.run(
|
||||
missing.id,
|
||||
`Gelöschter Benutzer #${missing.id}`,
|
||||
`deleted-${missing.id}-${randomUUID()}@invalid.local`,
|
||||
hashSync(randomUUID(), 12),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function initDatabase() {
|
||||
getUploadDir();
|
||||
ensureSchema();
|
||||
if (globalThis.__mopedDatabaseInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
getUploadDir();
|
||||
ensureSchema();
|
||||
ensureReferencedUsersExist();
|
||||
ensureInitialAdmin();
|
||||
globalThis.__mopedDatabaseInitialized = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user