import Database from 'better-sqlite3'; function createTables(db: Database.Database): void { db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', maps_api_key TEXT, unsplash_api_key TEXT, openweather_api_key TEXT, avatar TEXT, oidc_sub TEXT, oidc_issuer TEXT, last_login DATETIME, mfa_enabled INTEGER DEFAULT 0, mfa_secret TEXT, mfa_backup_codes TEXT, immich_url TEXT, immich_access_token TEXT, synology_url TEXT, synology_username TEXT, synology_password TEXT, synology_sid TEXT, must_change_password INTEGER DEFAULT 0, password_version INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS password_reset_tokens ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at DATETIME NOT NULL, consumed_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_ip TEXT ); CREATE INDEX IF NOT EXISTS idx_prt_user ON password_reset_tokens(user_id); CREATE INDEX IF NOT EXISTS idx_prt_hash ON password_reset_tokens(token_hash); CREATE TABLE IF NOT EXISTS webauthn_credentials ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, credential_id TEXT NOT NULL UNIQUE, public_key BLOB NOT NULL, counter INTEGER NOT NULL DEFAULT 0, transports TEXT, device_type TEXT, backed_up INTEGER NOT NULL DEFAULT 0, name TEXT, aaguid TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_used_at DATETIME ); CREATE INDEX IF NOT EXISTS idx_webauthn_credentials_user ON webauthn_credentials(user_id); CREATE TABLE IF NOT EXISTS webauthn_challenges ( id INTEGER PRIMARY KEY AUTOINCREMENT, challenge TEXT NOT NULL UNIQUE, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, type TEXT NOT NULL, expires_at INTEGER NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_webauthn_challenges_expires ON webauthn_challenges(expires_at); CREATE TABLE IF NOT EXISTS settings ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, key TEXT NOT NULL, value TEXT, UNIQUE(user_id, key) ); CREATE TABLE IF NOT EXISTS trips ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, title TEXT NOT NULL, description TEXT, start_date TEXT, end_date TEXT, currency TEXT DEFAULT 'EUR', cover_image TEXT, is_archived INTEGER DEFAULT 0, reminder_days INTEGER DEFAULT 3, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS days ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, day_number INTEGER NOT NULL, date TEXT, notes TEXT, title TEXT, UNIQUE(trip_id, day_number) ); CREATE TABLE IF NOT EXISTS categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, color TEXT DEFAULT '#6366f1', icon TEXT DEFAULT '📍', user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, color TEXT DEFAULT '#10b981', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS places ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, name TEXT NOT NULL, description TEXT, lat REAL, lng REAL, address TEXT, category_id INTEGER REFERENCES categories(id) ON DELETE SET NULL, price REAL, currency TEXT, reservation_status TEXT DEFAULT 'none', reservation_notes TEXT, reservation_datetime TEXT, place_time TEXT, end_time TEXT, duration_minutes INTEGER DEFAULT 60, notes TEXT, image_url TEXT, google_place_id TEXT, website TEXT, phone TEXT, transport_mode TEXT DEFAULT 'walking', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS place_tags ( place_id INTEGER NOT NULL REFERENCES places(id) ON DELETE CASCADE, tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE, PRIMARY KEY (place_id, tag_id) ); CREATE TABLE IF NOT EXISTS day_assignments ( id INTEGER PRIMARY KEY AUTOINCREMENT, day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE, place_id INTEGER NOT NULL REFERENCES places(id) ON DELETE CASCADE, order_index INTEGER DEFAULT 0, notes TEXT, reservation_status TEXT DEFAULT 'none', reservation_notes TEXT, reservation_datetime TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS packing_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, name TEXT NOT NULL, checked INTEGER DEFAULT 0, category TEXT, sort_order INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS photos ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, day_id INTEGER REFERENCES days(id) ON DELETE SET NULL, place_id INTEGER REFERENCES places(id) ON DELETE SET NULL, filename TEXT NOT NULL, original_name TEXT NOT NULL, file_size INTEGER, mime_type TEXT, caption TEXT, taken_at TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS trip_files ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, place_id INTEGER REFERENCES places(id) ON DELETE SET NULL, reservation_id INTEGER REFERENCES reservations(id) ON DELETE SET NULL, filename TEXT NOT NULL, original_name TEXT NOT NULL, file_size INTEGER, mime_type TEXT, description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS reservations ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, day_id INTEGER REFERENCES days(id) ON DELETE SET NULL, end_day_id INTEGER REFERENCES days(id) ON DELETE SET NULL, place_id INTEGER REFERENCES places(id) ON DELETE SET NULL, assignment_id INTEGER REFERENCES day_assignments(id) ON DELETE SET NULL, title TEXT NOT NULL, accommodation_id TEXT, reservation_time TEXT, reservation_end_time TEXT, location TEXT, confirmation_number TEXT, notes TEXT, status TEXT DEFAULT 'pending', type TEXT DEFAULT 'other', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS trip_members ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, invited_by INTEGER REFERENCES users(id), added_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(trip_id, user_id) ); CREATE TABLE IF NOT EXISTS day_notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, text TEXT NOT NULL, time TEXT, icon TEXT DEFAULT '📝', sort_order REAL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS app_settings ( key TEXT PRIMARY KEY, value TEXT ); CREATE TABLE IF NOT EXISTS budget_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, category TEXT NOT NULL DEFAULT 'Other', name TEXT NOT NULL, total_price REAL NOT NULL DEFAULT 0, persons INTEGER DEFAULT NULL, days INTEGER DEFAULT NULL, note TEXT, sort_order INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- Addon system CREATE TABLE IF NOT EXISTS addons ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, type TEXT NOT NULL DEFAULT 'global', icon TEXT DEFAULT 'Puzzle', enabled INTEGER DEFAULT 0, config TEXT DEFAULT '{}', sort_order INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS photo_providers ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, icon TEXT DEFAULT 'Image', enabled INTEGER DEFAULT 0, sort_order INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS photo_provider_fields ( id INTEGER PRIMARY KEY AUTOINCREMENT, provider_id TEXT NOT NULL REFERENCES photo_providers(id) ON DELETE CASCADE, field_key TEXT NOT NULL, label TEXT NOT NULL, input_type TEXT NOT NULL DEFAULT 'text', placeholder TEXT, hint TEXT, required INTEGER DEFAULT 0, secret INTEGER DEFAULT 0, settings_key TEXT, payload_key TEXT, sort_order INTEGER DEFAULT 0, UNIQUE(provider_id, field_key) ); -- Vacay addon tables CREATE TABLE IF NOT EXISTS vacay_plans ( id INTEGER PRIMARY KEY AUTOINCREMENT, owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, block_weekends INTEGER DEFAULT 1, holidays_enabled INTEGER DEFAULT 0, holidays_region TEXT DEFAULT '', company_holidays_enabled INTEGER DEFAULT 1, carry_over_enabled INTEGER DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(owner_id) ); CREATE TABLE IF NOT EXISTS vacay_plan_members ( id INTEGER PRIMARY KEY AUTOINCREMENT, plan_id INTEGER NOT NULL REFERENCES vacay_plans(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, status TEXT DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(plan_id, user_id) ); CREATE TABLE IF NOT EXISTS vacay_user_colors ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, plan_id INTEGER NOT NULL REFERENCES vacay_plans(id) ON DELETE CASCADE, color TEXT DEFAULT '#6366f1', UNIQUE(user_id, plan_id) ); CREATE TABLE IF NOT EXISTS vacay_years ( id INTEGER PRIMARY KEY AUTOINCREMENT, plan_id INTEGER NOT NULL REFERENCES vacay_plans(id) ON DELETE CASCADE, year INTEGER NOT NULL, UNIQUE(plan_id, year) ); CREATE TABLE IF NOT EXISTS vacay_user_years ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, plan_id INTEGER NOT NULL REFERENCES vacay_plans(id) ON DELETE CASCADE, year INTEGER NOT NULL, vacation_days INTEGER DEFAULT 30, carried_over INTEGER DEFAULT 0, UNIQUE(user_id, plan_id, year) ); CREATE TABLE IF NOT EXISTS vacay_entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, plan_id INTEGER NOT NULL REFERENCES vacay_plans(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, date TEXT NOT NULL, note TEXT DEFAULT '', UNIQUE(user_id, plan_id, date) ); CREATE TABLE IF NOT EXISTS vacay_company_holidays ( id INTEGER PRIMARY KEY AUTOINCREMENT, plan_id INTEGER NOT NULL REFERENCES vacay_plans(id) ON DELETE CASCADE, date TEXT NOT NULL, note TEXT DEFAULT '', UNIQUE(plan_id, date) ); CREATE TABLE IF NOT EXISTS vacay_holiday_calendars ( id INTEGER PRIMARY KEY AUTOINCREMENT, plan_id INTEGER NOT NULL REFERENCES vacay_plans(id) ON DELETE CASCADE, region TEXT NOT NULL, label TEXT, color TEXT NOT NULL DEFAULT '#fecaca', sort_order INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS day_accommodations ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, place_id INTEGER REFERENCES places(id) ON DELETE SET NULL, start_day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE, end_day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE, check_in TEXT, check_in_end TEXT, check_out TEXT, confirmation TEXT, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- Collab addon tables CREATE TABLE IF NOT EXISTS collab_notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, category TEXT DEFAULT 'General', title TEXT NOT NULL, content TEXT, color TEXT DEFAULT '#6366f1', pinned INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS collab_polls ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, question TEXT NOT NULL, options TEXT NOT NULL, multiple INTEGER DEFAULT 0, closed INTEGER DEFAULT 0, deadline TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS collab_poll_votes ( id INTEGER PRIMARY KEY AUTOINCREMENT, poll_id INTEGER NOT NULL REFERENCES collab_polls(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, option_index INTEGER NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(poll_id, user_id, option_index) ); CREATE TABLE IF NOT EXISTS collab_messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, text TEXT NOT NULL, reply_to INTEGER REFERENCES collab_messages(id) ON DELETE SET NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_collab_notes_trip ON collab_notes(trip_id); CREATE INDEX IF NOT EXISTS idx_collab_polls_trip ON collab_polls(trip_id); CREATE INDEX IF NOT EXISTS idx_collab_messages_trip ON collab_messages(trip_id); `); db.exec(` CREATE INDEX IF NOT EXISTS idx_places_trip_id ON places(trip_id); CREATE INDEX IF NOT EXISTS idx_places_category_id ON places(category_id); CREATE INDEX IF NOT EXISTS idx_days_trip_id ON days(trip_id); CREATE INDEX IF NOT EXISTS idx_day_assignments_day_id ON day_assignments(day_id); CREATE INDEX IF NOT EXISTS idx_day_assignments_place_id ON day_assignments(place_id); CREATE INDEX IF NOT EXISTS idx_place_tags_place_id ON place_tags(place_id); CREATE INDEX IF NOT EXISTS idx_place_tags_tag_id ON place_tags(tag_id); CREATE INDEX IF NOT EXISTS idx_trip_members_trip_id ON trip_members(trip_id); CREATE INDEX IF NOT EXISTS idx_trip_members_user_id ON trip_members(user_id); CREATE INDEX IF NOT EXISTS idx_packing_items_trip_id ON packing_items(trip_id); CREATE INDEX IF NOT EXISTS idx_budget_items_trip_id ON budget_items(trip_id); CREATE INDEX IF NOT EXISTS idx_reservations_trip_id ON reservations(trip_id); CREATE INDEX IF NOT EXISTS idx_trip_files_trip_id ON trip_files(trip_id); CREATE INDEX IF NOT EXISTS idx_day_notes_day_id ON day_notes(day_id); CREATE INDEX IF NOT EXISTS idx_photos_trip_id ON photos(trip_id); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); CREATE INDEX IF NOT EXISTS idx_day_accommodations_trip_id ON day_accommodations(trip_id); CREATE TABLE IF NOT EXISTS assignment_participants ( id INTEGER PRIMARY KEY AUTOINCREMENT, assignment_id INTEGER NOT NULL REFERENCES day_assignments(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, UNIQUE(assignment_id, user_id) ); CREATE INDEX IF NOT EXISTS idx_assignment_participants_assignment ON assignment_participants(assignment_id); CREATE TABLE IF NOT EXISTS audit_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, action TEXT NOT NULL, resource TEXT, details TEXT, ip TEXT ); CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at DESC); CREATE TABLE IF NOT EXISTS notifications ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL CHECK(type IN ('simple', 'boolean', 'navigate')), scope TEXT NOT NULL CHECK(scope IN ('trip', 'user', 'admin')), target INTEGER NOT NULL, sender_id INTEGER REFERENCES users(id) ON DELETE SET NULL, recipient_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, title_key TEXT NOT NULL, title_params TEXT DEFAULT '{}', text_key TEXT NOT NULL, text_params TEXT DEFAULT '{}', positive_text_key TEXT, negative_text_key TEXT, positive_callback TEXT, negative_callback TEXT, response TEXT CHECK(response IN ('positive', 'negative')), navigate_text_key TEXT, navigate_target TEXT, is_read INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_notifications_recipient ON notifications(recipient_id, is_read, created_at DESC); CREATE INDEX IF NOT EXISTS idx_notifications_recipient_created ON notifications(recipient_id, created_at DESC); CREATE TABLE IF NOT EXISTS notification_channel_preferences ( user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, event_type TEXT NOT NULL, channel TEXT NOT NULL, enabled INTEGER NOT NULL DEFAULT 1, PRIMARY KEY (user_id, event_type, channel) ); CREATE INDEX IF NOT EXISTS idx_ncp_user ON notification_channel_preferences(user_id); CREATE TABLE IF NOT EXISTS migrations (id integer PRIMARY KEY AUTOINCREMENT NOT NULL, timestamp bigint NOT NULL, name varchar NOT NULL); `); } export { createTables };