Files
mystuff_backend/api/database.php
Lars Behrends eeff824701 Add Jellyfin mappings and optimize cast queries
Performance and settings updates:

- Add a new jellyfin_library_mappings column to the settings table and wire it into the Settings model (update handling and default value). This enables storing Jellyfin library mappings in settings.
- Optimize Cast::list by loading all cast filmography in a single joined query and grouping results per cast to avoid N+1 queries.
- Remove per-item cast/staff loading in Media model to avoid repeated queries during list/search operations.
- Remove game-specific enrichment from MediaController search to stop extra game info lookups during search responses.

These changes reduce repeated DB calls and centralize Jellyfin mapping storage.
2026-04-12 02:07:59 +02:00

390 lines
13 KiB
PHP

<?php
require_once __DIR__ . '/config.php';
class Database {
private $pdo;
public function __construct() {
try {
$dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4';
$this->pdo = new PDO($dsn, DB_USER, DB_PASS);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$this->initializeTables();
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Database connection failed: ' . $e->getMessage()]);
exit;
}
}
private function initializeTables() {
// Media-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS media (
id INT AUTO_INCREMENT PRIMARY KEY,
title TEXT NOT NULL,
cleanname TEXT,
year TEXT,
poster TEXT,
banner TEXT,
description TEXT,
rating FLOAT,
category TEXT,
type TEXT,
status TEXT,
aspectRatio TEXT,
runtime INT,
director TEXT,
writer TEXT,
releaseDate TEXT,
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
");
// cleanname Feld zu bestehender Tabelle hinzufügen, falls nicht vorhanden
try {
$this->pdo->exec("ALTER TABLE media ADD COLUMN cleanname TEXT");
} catch (Exception $e) {
// Feld existiert bereits
}
// Index für cleanname erstellen
try {
$this->pdo->exec("CREATE INDEX idx_media_cleanname ON media(cleanname)");
} catch (Exception $e) {
// Index existiert bereits
}
// Genres-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS genres (
id INT AUTO_INCREMENT PRIMARY KEY,
media_id INT,
genre TEXT,
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
)
");
// Tags-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS tags (
id INT AUTO_INCREMENT PRIMARY KEY,
media_id INT,
tag TEXT,
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
)
");
// Studios-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS studios (
id INT AUTO_INCREMENT PRIMARY KEY,
media_id INT,
studio TEXT,
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
)
");
// Cast/Staff-Tabelle (Stammdaten - ohne media_id)
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS cast_staff (
id INT AUTO_INCREMENT PRIMARY KEY,
name TEXT NOT NULL,
cleanname TEXT,
photo TEXT,
bio TEXT,
birthDate TEXT,
birthPlace TEXT,
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
");
// cleanname Feld zu bestehender Tabelle hinzufügen, falls nicht vorhanden
try {
$this->pdo->exec("ALTER TABLE cast_staff ADD COLUMN cleanname TEXT");
} catch (Exception $e) {
// Feld existiert bereits
}
// Index für cleanname erstellen
try {
$this->pdo->exec("CREATE INDEX idx_cast_staff_cleanname ON cast_staff(cleanname)");
} catch (Exception $e) {
// Index existiert bereits
}
// n:m Beziehung zwischen Media und Cast
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS media_cast (
id INT AUTO_INCREMENT PRIMARY KEY,
media_id INT,
cast_id INT,
role TEXT,
characterName TEXT,
characterImage TEXT,
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE,
FOREIGN KEY (cast_id) REFERENCES cast_staff(id) ON DELETE CASCADE
)
");
// Occupations-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS occupations (
id INT AUTO_INCREMENT PRIMARY KEY,
cast_id INT,
occupation TEXT,
FOREIGN KEY (cast_id) REFERENCES cast_staff(id) ON DELETE CASCADE
)
");
// Episodes-Tabelle (für Series)
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS episodes (
id INT AUTO_INCREMENT PRIMARY KEY,
media_id INT,
season INT,
episode_number INT,
title TEXT,
description TEXT,
air_date TEXT,
duration INT,
thumbnail TEXT,
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
)
");
// Tracks-Tabelle (für Music/Albums)
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS tracks (
id INT AUTO_INCREMENT PRIMARY KEY,
media_id INT,
track_number INT,
title TEXT,
duration INT,
artist TEXT,
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
)
");
// Adult-spezifische Cast-Infos
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS adult_cast_specifics (
id INT AUTO_INCREMENT PRIMARY KEY,
cast_id INT UNIQUE,
bust_size TEXT,
cup_size TEXT,
waist_size TEXT,
hip_size TEXT,
height INT,
weight INT,
hair_color TEXT,
eye_color TEXT,
ethnicity TEXT,
tattoos TEXT,
piercings TEXT,
measurements TEXT,
shoe_size TEXT,
FOREIGN KEY (cast_id) REFERENCES cast_staff(id) ON DELETE CASCADE
)
");
// Media-Games Tabelle (1:1 Relation zu media für spiel-spezifische Daten)
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS media_games (
id INT AUTO_INCREMENT PRIMARY KEY,
media_id INT UNIQUE,
sortingName TEXT,
notes TEXT,
completionStatus TEXT,
source TEXT,
gameId TEXT,
pluginId TEXT,
isInstalled BOOLEAN DEFAULT 0,
installDirectory TEXT,
installSize BIGINT DEFAULT 0,
hidden BOOLEAN DEFAULT 0,
favorite BOOLEAN DEFAULT 0,
playCount INT DEFAULT 0,
lastActivity TIMESTAMP NULL,
added TIMESTAMP NULL,
modified TIMESTAMP NULL,
communityScore INT DEFAULT 0,
criticScore INT DEFAULT 0,
userScore INT DEFAULT 0,
hasIcon BOOLEAN DEFAULT 0,
hasCover BOOLEAN DEFAULT 0,
hasBackground BOOLEAN DEFAULT 0,
version TEXT,
playtime INT DEFAULT 0,
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
)
");
// Achievements-Tabelle (für Games - referenziert media_games)
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS achievements (
id INT AUTO_INCREMENT PRIMARY KEY,
media_game_id INT,
name TEXT NOT NULL,
description TEXT,
icon TEXT,
unlocked BOOLEAN DEFAULT 0,
unlocked_date TIMESTAMP NULL,
FOREIGN KEY (media_game_id) REFERENCES media_games(id) ON DELETE CASCADE
)
");
// Game Categories-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS game_categories (
id INT AUTO_INCREMENT PRIMARY KEY,
media_game_id INT,
category TEXT,
FOREIGN KEY (media_game_id) REFERENCES media_games(id) ON DELETE CASCADE
)
");
// Game Features-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS game_features (
id INT AUTO_INCREMENT PRIMARY KEY,
media_game_id INT,
feature TEXT,
FOREIGN KEY (media_game_id) REFERENCES media_games(id) ON DELETE CASCADE
)
");
// Game Platforms-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS game_platforms (
id INT AUTO_INCREMENT PRIMARY KEY,
media_game_id INT,
platform TEXT,
FOREIGN KEY (media_game_id) REFERENCES media_games(id) ON DELETE CASCADE
)
");
// Game Developers-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS game_developers (
id INT AUTO_INCREMENT PRIMARY KEY,
media_game_id INT,
developer TEXT,
FOREIGN KEY (media_game_id) REFERENCES media_games(id) ON DELETE CASCADE
)
");
// Game Publishers-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS game_publishers (
id INT AUTO_INCREMENT PRIMARY KEY,
media_game_id INT,
publisher TEXT,
FOREIGN KEY (media_game_id) REFERENCES media_games(id) ON DELETE CASCADE
)
");
// Game Series-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS game_series (
id INT AUTO_INCREMENT PRIMARY KEY,
media_game_id INT,
series TEXT,
FOREIGN KEY (media_game_id) REFERENCES media_games(id) ON DELETE CASCADE
)
");
// Game Age Ratings-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS game_age_ratings (
id INT AUTO_INCREMENT PRIMARY KEY,
media_game_id INT,
age_rating TEXT,
FOREIGN KEY (media_game_id) REFERENCES media_games(id) ON DELETE CASCADE
)
");
// Game Regions-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS game_regions (
id INT AUTO_INCREMENT PRIMARY KEY,
media_game_id INT,
region TEXT,
FOREIGN KEY (media_game_id) REFERENCES media_games(id) ON DELETE CASCADE
)
");
// Game Links-Tabelle (mit name und url)
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS game_links (
id INT AUTO_INCREMENT PRIMARY KEY,
media_game_id INT,
name TEXT,
url TEXT,
FOREIGN KEY (media_game_id) REFERENCES media_games(id) ON DELETE CASCADE
)
");
// API Logs-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS api_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
type TEXT NOT NULL,
method TEXT,
path TEXT,
params JSON,
body JSON,
status_code INT,
response JSON,
error TEXT,
INDEX idx_timestamp (timestamp),
INDEX idx_type (type)
)
");
// Settings-Tabelle
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS settings (
id INT AUTO_INCREMENT PRIMARY KEY,
enabled_categories JSON,
items_per_page INT DEFAULT 20,
default_view TEXT DEFAULT 'grid',
show_adult_content BOOLEAN DEFAULT 0,
auto_play_trailers BOOLEAN DEFAULT 0,
language TEXT DEFAULT 'en',
theme TEXT DEFAULT 'system',
jellyfin_library_mappings JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
");
// Bestehende Einträge mit cleanname aktualisieren
$this->updateExistingCleanNames();
}
private function updateExistingCleanNames() {
// Media cleanname aktualisieren
$this->pdo->exec("
UPDATE media
SET cleanname = LOWER(REPLACE(REPLACE(REPLACE(REPLACE(title, ' ', '-'), '.', '-'), ',', '-'), '--', '-'))
WHERE cleanname IS NULL OR cleanname = ''
");
// Cast cleanname aktualisieren
$this->pdo->exec("
UPDATE cast_staff
SET cleanname = LOWER(REPLACE(REPLACE(REPLACE(REPLACE(name, ' ', '-'), '.', '-'), ',', '-'), '--', '-'))
WHERE cleanname IS NULL OR cleanname = ''
");
}
public function getConnection() {
return $this->pdo;
}
}