Uploads werden nach AVIF gewandelt statt nach WebP und heissen jetzt nach dem Inhalt statt nach Zeitstempel und Zufall: - Events uebernehmen den Event-Titel, die Gallery den Alt-Text, Bilder in den Textbereichen ihren Zweck, PDFs den urspruenglichen Dateinamen. - Ohne solche Angabe wird der Dateiname vor dem Upload verwendet. - Umlaute werden ausgeschrieben (Getraenkekarte, nicht Getrnkekarte), Sonderzeichen und Pfadanteile fallen weg. - Gleiche Namen werden durchnummeriert, damit zwei Events namens "Karaoke" sich nicht gegenseitig ueberschreiben. Besitz statt Namensmuster: Die Loeschsicherheit haing bisher am Namensmuster <zeitstempel>-<zufall>. Mit sprechenden Namen traegt der Name diese Information nicht mehr - ein hochgeladenes karaoke-abend.avif ist von einem handgepflegten event_karaoke.jpg nicht zu unterscheiden. Deshalb fuehrt das CMS jetzt in managed_assets Buch darueber, welche Dateien es selbst angelegt hat, und loescht ausschliesslich diese. Uploads von vor der Umstellung werden weiterhin am alten Muster erkannt, damit sie aufraeumbar bleiben. initDatabase() legte Tabellen nur an, wenn users noch fehlte. Auf einer bestehenden Datenbank waere managed_assets damit nie entstanden. Der Block laeuft jetzt bei jedem Start; alle Anweisungen sind IF NOT EXISTS. Nebenbei repariert: die Formulare schickten den Alt-Text NACH der Datei. Zu dem Zeitpunkt hat der Server ihn noch nicht geparst, in der Gallery landete deshalb immer der Dateiname als Alt-Text. Textfelder gehen jetzt vor der Datei raus. Gemessen an einem 4032x3024-Bild: 0.90 MB rein, 36 KB AVIF bei 1600x1200 raus, 579 ms. Das Testbild ist synthetisch und komprimiert besser als ein echtes Foto - die Verkleinerung auf 1600px greift aber immer. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
110 lines
3.3 KiB
TypeScript
110 lines
3.3 KiB
TypeScript
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
import Database from 'better-sqlite3';
|
|
import * as schema from '../db/schema.js';
|
|
import { env } from './env.js';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
if (!env.DATABASE_PATH) {
|
|
throw new Error('DATABASE_PATH environment variable is not set');
|
|
}
|
|
|
|
// Ensure directory exists BEFORE opening the database file
|
|
const dbDir = path.dirname(env.DATABASE_PATH);
|
|
if (!fs.existsSync(dbDir)) {
|
|
fs.mkdirSync(dbDir, { recursive: true });
|
|
}
|
|
|
|
const sqlite = new Database(env.DATABASE_PATH);
|
|
|
|
// Enable WAL mode for better concurrent access
|
|
sqlite.pragma('journal_mode = WAL');
|
|
|
|
export const db = drizzle(sqlite, { schema });
|
|
|
|
// Auto-create tables if they don't exist
|
|
export function initDatabase() {
|
|
console.log('🔧 Initializing database...');
|
|
|
|
try {
|
|
// Nur zur Protokollierung - angelegt wird immer, siehe unten
|
|
const tableCheck = sqlite
|
|
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
|
|
.get();
|
|
|
|
console.log(tableCheck ? '📝 Checking database schema...' : '📝 Creating database schema...');
|
|
|
|
// Laeuft bei JEDEM Start. Alle Anweisungen sind IF NOT EXISTS, und nur so
|
|
// bekommen bestehende Datenbanken spaeter ergaenzte Tabellen ueberhaupt.
|
|
{
|
|
sqlite.exec(`
|
|
PRAGMA foreign_keys = ON;
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
gitea_id TEXT UNIQUE NOT NULL,
|
|
gitea_username TEXT NOT NULL,
|
|
gitea_email TEXT,
|
|
display_name TEXT,
|
|
avatar_url TEXT,
|
|
role TEXT DEFAULT 'admin',
|
|
created_at INTEGER DEFAULT (unixepoch()),
|
|
last_login INTEGER
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
date TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
image_url TEXT NOT NULL,
|
|
display_order INTEGER NOT NULL,
|
|
is_published INTEGER DEFAULT 1,
|
|
created_at INTEGER DEFAULT (unixepoch()),
|
|
updated_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS gallery_images (
|
|
id TEXT PRIMARY KEY,
|
|
image_url TEXT NOT NULL,
|
|
alt_text TEXT NOT NULL,
|
|
display_order INTEGER NOT NULL,
|
|
is_published INTEGER DEFAULT 1,
|
|
created_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS content_sections (
|
|
id TEXT PRIMARY KEY,
|
|
section_name TEXT UNIQUE NOT NULL,
|
|
content_json TEXT NOT NULL,
|
|
updated_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS site_settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
updated_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS managed_assets (
|
|
path TEXT PRIMARY KEY,
|
|
created_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS publish_history (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT REFERENCES users(id),
|
|
commit_hash TEXT,
|
|
commit_message TEXT,
|
|
published_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
`);
|
|
|
|
console.log('✅ Database schema is up to date.');
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ Error initializing database:', error);
|
|
throw error;
|
|
}
|
|
}
|