You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
366 lines
17 KiB
PHP
366 lines
17 KiB
PHP
<?php
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
require_once __DIR__ . '/multimedia-settings.php';
|
|
|
|
// --- CORS ---
|
|
function mm_origin_allowed(string $origin): bool {
|
|
foreach (MM_ALLOWED_ORIGINS as $pat) {
|
|
$rx = preg_quote($pat, '/');
|
|
$rx = str_replace(['\\*', '\\:\\*'], ['.*', '(?::\\d+)?'], $rx);
|
|
if (preg_match('/^'.$rx.'$/i', $origin)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
|
if ($origin && mm_origin_allowed($origin)) {
|
|
header('Access-Control-Allow-Origin: ' . $origin);
|
|
header('Vary: Origin');
|
|
} else {
|
|
header('Access-Control-Allow-Origin: https://windesign.at'); // Fallback
|
|
}
|
|
|
|
// WICHTIG: weit gefasste Allow-Headers (Browser senden oft zusätzliche)
|
|
$reqHeaders = $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] ?? '';
|
|
$allowHeaders = 'Content-Type, Accept, X-Requested-With, Authorization';
|
|
if ($reqHeaders) { $allowHeaders .= ', ' . $reqHeaders; }
|
|
|
|
header('Access-Control-Allow-Headers: ' . $allowHeaders);
|
|
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
|
header('Access-Control-Max-Age: 86400'); // Preflights cachen
|
|
header('Access-Control-Allow-Credentials: false');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
|
|
|
|
function jsonBody(): array {
|
|
$raw = file_get_contents('php://input');
|
|
if (!$raw) return [];
|
|
$data = json_decode($raw, true);
|
|
return is_array($data) ? $data : [];
|
|
}
|
|
|
|
// Accept both JSON and form-urlencoded. Also decode JSON-like strings in keys we expect.
|
|
function inputBody(): array {
|
|
$in = jsonBody();
|
|
if (!$in) {
|
|
// Fallback to form POST
|
|
$in = $_POST ?: [];
|
|
}
|
|
// Normalize: decode JSON strings for nested payloads (e.g., tmdb)
|
|
foreach (['tmdb'] as $k) {
|
|
if (isset($in[$k]) && is_string($in[$k])) {
|
|
$d = json_decode($in[$k], true);
|
|
if (is_array($d)) $in[$k] = $d;
|
|
}
|
|
}
|
|
// Cast common numeric fields when present
|
|
foreach (['show_id','season_id','tmdb_id','ref_id','limit','offset'] as $k) {
|
|
if (isset($in[$k])) $in[$k] = (int)$in[$k];
|
|
}
|
|
return $in;
|
|
}
|
|
function resp($data, int $code = 200) { http_response_code($code); echo json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); exit; }
|
|
function fail($msg, int $code = 400) { resp(['ok'=>false,'error'=>$msg], $code); }
|
|
|
|
$dsn = sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', DATABASE_HOST, DATABASE_NAME);
|
|
try {
|
|
$pdo = new PDO($dsn, DATABASE_USER, DATABASE_PASSWORD, [
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
]);
|
|
} catch (Throwable $e) {
|
|
if (MM_DEBUG) fail('DB connection failed: '.$e->getMessage(), 500);
|
|
fail('DB connection failed', 500);
|
|
}
|
|
|
|
$in = inputBody();
|
|
$action = $in['action'] ?? null;
|
|
|
|
try {
|
|
switch ($action) {
|
|
case 'upsert_show': {
|
|
$tmdb = $in['tmdb'] ?? null; if (!$tmdb || !isset($tmdb['id'])) fail('missing tmdb payload');
|
|
try {
|
|
$stmt = $pdo->prepare("INSERT INTO shows (tmdb_id, name, original_name, first_air_year, poster_path, backdrop_path, json)
|
|
VALUES (?,?,?,?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE name=VALUES(name), original_name=VALUES(original_name), first_air_year=VALUES(first_air_year), poster_path=VALUES(poster_path), backdrop_path=VALUES(backdrop_path), json=VALUES(json)");
|
|
$stmt->execute([
|
|
$tmdb['id'],
|
|
$tmdb['name'] ?? '',
|
|
$tmdb['original_name'] ?? null,
|
|
isset($tmdb['first_air_date']) ? intval(substr($tmdb['first_air_date'],0,4)) : null,
|
|
$tmdb['poster_path'] ?? null,
|
|
$tmdb['backdrop_path'] ?? null,
|
|
json_encode($tmdb, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
|
|
]);
|
|
} catch (Throwable $e) {
|
|
// Fallback for schemas without original_name/first_air_year
|
|
if (strpos($e->getMessage(), 'Unknown column') !== false || ($e instanceof PDOException && $e->getCode()==='42S22')) {
|
|
$stmt = $pdo->prepare("INSERT INTO shows (tmdb_id, name, poster_path, backdrop_path, json)
|
|
VALUES (?,?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE name=VALUES(name), poster_path=VALUES(poster_path), backdrop_path=VALUES(backdrop_path), json=VALUES(json)");
|
|
$stmt->execute([
|
|
$tmdb['id'],
|
|
$tmdb['name'] ?? '',
|
|
$tmdb['poster_path'] ?? null,
|
|
$tmdb['backdrop_path'] ?? null,
|
|
json_encode($tmdb, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
|
|
]);
|
|
} else {
|
|
throw $e;
|
|
}
|
|
}
|
|
$id = $pdo->lastInsertId();
|
|
if (!$id) { $q=$pdo->prepare('SELECT id FROM shows WHERE tmdb_id=?'); $q->execute([$tmdb['id']]); $id=$q->fetchColumn(); }
|
|
resp(['ok'=>true,'id'=>(int)$id]);
|
|
}
|
|
|
|
case 'upsert_season': {
|
|
$showId = (int)($in['show_id'] ?? 0);
|
|
$tmdb = $in['tmdb'] ?? null; if (!$showId || !$tmdb) fail('bad params');
|
|
$seasonNo = isset($tmdb['season_number']) ? (int)$tmdb['season_number'] : null; if ($seasonNo===null) fail('missing season_number');
|
|
$name = $tmdb['name'] ?? (isset($seasonNo) ? ('Season '.$seasonNo) : '');
|
|
$airDate = $tmdb['air_date'] ?? null;
|
|
try {
|
|
$stmt = $pdo->prepare("INSERT INTO seasons (show_id, season_number, name, air_date, json)
|
|
VALUES (?,?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE name=VALUES(name), air_date=VALUES(air_date), json=VALUES(json)");
|
|
$stmt->execute([$showId, $seasonNo, $name, $airDate, json_encode($tmdb, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)]);
|
|
} catch (Throwable $e) {
|
|
if (strpos($e->getMessage(), 'Unknown column') !== false || ($e instanceof PDOException && $e->getCode()==='42S22')) {
|
|
$stmt = $pdo->prepare("INSERT INTO seasons (show_id, season_number, name, json)
|
|
VALUES (?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE name=VALUES(name), json=VALUES(json)");
|
|
$stmt->execute([$showId, $seasonNo, $name, json_encode($tmdb, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)]);
|
|
} else { throw $e; }
|
|
}
|
|
$id = $pdo->lastInsertId();
|
|
if (!$id) { $q=$pdo->prepare('SELECT id FROM seasons WHERE show_id=? AND season_number=?'); $q->execute([$showId,$seasonNo]); $id=$q->fetchColumn(); }
|
|
resp(['ok'=>true,'id'=>(int)$id]);
|
|
}
|
|
|
|
case 'upsert_episode': {
|
|
$seasonId = (int)($in['season_id'] ?? 0);
|
|
$tmdb = $in['tmdb'] ?? null; if (!$seasonId || !$tmdb) fail('bad params');
|
|
$epNo = isset($tmdb['episode_number']) ? (int)$tmdb['episode_number'] : null; if ($epNo===null) fail('missing episode_number');
|
|
$name = $tmdb['name'] ?? ('Episode '.$epNo);
|
|
$runtime = isset($tmdb['runtime']) ? (int)$tmdb['runtime'] : null;
|
|
$tmdbId = $tmdb['id'] ?? null;
|
|
try {
|
|
if ($tmdbId) {
|
|
$stmt = $pdo->prepare("INSERT INTO episodes (season_id, tmdb_id, episode_number, name, runtime, json)
|
|
VALUES (?,?,?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE episode_number=VALUES(episode_number), name=VALUES(name), runtime=VALUES(runtime), json=VALUES(json)");
|
|
$stmt->execute([$seasonId, $tmdbId, $epNo, $name, $runtime, json_encode($tmdb, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)]);
|
|
} else {
|
|
// No tmdb_id
|
|
$stmt = $pdo->prepare("INSERT INTO episodes (season_id, episode_number, name, runtime, json)
|
|
VALUES (?,?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE name=VALUES(name), runtime=VALUES(runtime), json=VALUES(json)");
|
|
$stmt->execute([$seasonId, $epNo, $name, $runtime, json_encode($tmdb, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)]);
|
|
}
|
|
} catch (Throwable $e) {
|
|
// Fallback if tmdb_id column doesn't exist: always use (season_id, episode_number)
|
|
if (strpos($e->getMessage(), 'Unknown column') !== false || ($e instanceof PDOException && $e->getCode()==='42S22')) {
|
|
$stmt = $pdo->prepare("INSERT INTO episodes (season_id, episode_number, name, runtime, json)
|
|
VALUES (?,?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE name=VALUES(name), runtime=VALUES(runtime), json=VALUES(json)");
|
|
$stmt->execute([$seasonId, $epNo, $name, $runtime, json_encode($tmdb, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)]);
|
|
} else { throw $e; }
|
|
}
|
|
$id = $pdo->lastInsertId();
|
|
if (!$id) {
|
|
if ($tmdbId) { $q=$pdo->prepare('SELECT id FROM episodes WHERE tmdb_id=?'); $q->execute([$tmdbId]); $id=$q->fetchColumn(); }
|
|
if (!$id) { $q=$pdo->prepare('SELECT id FROM episodes WHERE season_id=? AND episode_number=?'); $q->execute([$seasonId,$epNo]); $id=$q->fetchColumn(); }
|
|
}
|
|
resp(['ok'=>true,'id'=>(int)$id]);
|
|
}
|
|
case 'upsert_movie': {
|
|
$tmdb = $in['tmdb'] ?? null; if (!$tmdb || !isset($tmdb['id'])) fail('missing tmdb payload');
|
|
$stmt = $pdo->prepare("INSERT INTO movies (tmdb_id, title, original_title, release_year, poster_path, backdrop_path, runtime, json)
|
|
VALUES (?,?,?,?,?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE title=VALUES(title), original_title=VALUES(original_title), release_year=VALUES(release_year), poster_path=VALUES(poster_path), backdrop_path=VALUES(backdrop_path), runtime=VALUES(runtime), json=VALUES(json)");
|
|
$stmt->execute([
|
|
$tmdb['id'],
|
|
$tmdb['title'] ?? $tmdb['name'] ?? '',
|
|
$tmdb['original_title'] ?? $tmdb['original_name'] ?? null,
|
|
isset($tmdb['release_date']) ? intval(substr($tmdb['release_date'],0,4)) : null,
|
|
$tmdb['poster_path'] ?? null,
|
|
$tmdb['backdrop_path'] ?? null,
|
|
$tmdb['runtime'] ?? null,
|
|
json_encode($tmdb, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
|
|
]);
|
|
$id = $pdo->lastInsertId();
|
|
if (!$id) { $q=$pdo->prepare('SELECT id FROM movies WHERE tmdb_id=?'); $q->execute([$tmdb['id']]); $id=$q->fetchColumn(); }
|
|
resp(['ok'=>true,'id'=>(int)$id]);
|
|
}
|
|
|
|
case 'set_status': {
|
|
$type=$in['type'] ?? null;
|
|
$ref=(int)($in['ref_id'] ?? 0);
|
|
$st=$in['status'] ?? null; // may be string or int
|
|
if (!in_array($type,['movie','episode'],true) || !$ref || $st===null) fail('bad params');
|
|
// Map string statuses to int codes: 0 Init, 1 Progress, 2 Done
|
|
$stInt = is_numeric($st) ? (int)$st : (function($s){
|
|
$s = strtolower((string)$s);
|
|
if ($s==='progress') return 1; if ($s==='done') return 2; return 0;
|
|
})($st);
|
|
if ($type==='movie') {
|
|
$stmt=$pdo->prepare("UPDATE movies SET status=? WHERE id=?");
|
|
$stmt->execute([$stInt,$ref]);
|
|
} else {
|
|
$stmt=$pdo->prepare("UPDATE episodes SET status=? WHERE id=?");
|
|
$stmt->execute([$stInt,$ref]);
|
|
}
|
|
resp(['ok'=>true]);
|
|
}
|
|
|
|
case 'get_list': {
|
|
$type=$in['type'] ?? 'movie';
|
|
$status=$in['status'] ?? null;
|
|
$q=$in['q'] ?? '';
|
|
$limit=max(1,(int)($in['limit']??50));
|
|
$offset=max(0,(int)($in['offset']??0));
|
|
|
|
if ($type === 'episode') {
|
|
$statusVal = null;
|
|
if ($status) {
|
|
$s = strtolower($status);
|
|
if ($s==='init') $statusVal = 0; elseif ($s==='progress') $statusVal = 1; elseif ($s==='done') $statusVal = 2;
|
|
}
|
|
$base = "FROM episodes e
|
|
JOIN seasons se ON se.id = e.season_id
|
|
JOIN shows sh ON sh.id = se.show_id
|
|
WHERE 1=1";
|
|
// Prefer extracting show status directly from JSON and include cliffhanger column if present
|
|
$selectWithYearJson = "SELECT e.*,
|
|
CASE e.status WHEN 1 THEN 'Progress' WHEN 2 THEN 'Done' ELSE 'Init' END AS status,
|
|
sh.resolution AS resolution,
|
|
sh.poster_path AS poster_path,
|
|
sh.first_air_year AS first_air_year,
|
|
JSON_UNQUOTE(JSON_EXTRACT(sh.json, '$.status')) AS show_status,
|
|
sh.cliffhanger AS show_cliffhanger,
|
|
sh.json AS show_json,
|
|
se.season_number, sh.name AS show_name ".$base;
|
|
$selectNoYearJson = "SELECT e.*,
|
|
CASE e.status WHEN 1 THEN 'Progress' WHEN 2 THEN 'Done' ELSE 'Init' END AS status,
|
|
sh.resolution AS resolution,
|
|
sh.poster_path AS poster_path,
|
|
NULL AS first_air_year,
|
|
JSON_UNQUOTE(JSON_EXTRACT(sh.json, '$.status')) AS show_status,
|
|
sh.cliffhanger AS show_cliffhanger,
|
|
sh.json AS show_json,
|
|
se.season_number, sh.name AS show_name ".$base;
|
|
// Fallback selects without JSON_EXTRACT (older MySQL) — frontend will parse from show_json
|
|
$selectWithYear = "SELECT e.*,
|
|
CASE e.status WHEN 1 THEN 'Progress' WHEN 2 THEN 'Done' ELSE 'Init' END AS status,
|
|
sh.resolution AS resolution,
|
|
sh.poster_path AS poster_path,
|
|
sh.first_air_year AS first_air_year,
|
|
sh.cliffhanger AS show_cliffhanger,
|
|
sh.json AS show_json,
|
|
se.season_number, sh.name AS show_name ".$base;
|
|
$selectNoYear = "SELECT e.*,
|
|
CASE e.status WHEN 1 THEN 'Progress' WHEN 2 THEN 'Done' ELSE 'Init' END AS status,
|
|
sh.resolution AS resolution,
|
|
sh.poster_path AS poster_path,
|
|
NULL AS first_air_year,
|
|
sh.cliffhanger AS show_cliffhanger,
|
|
sh.json AS show_json,
|
|
se.season_number, sh.name AS show_name ".$base;
|
|
|
|
$run = function(string $sql) use ($pdo, $statusVal, $q, $offset, $limit) {
|
|
$params = [];
|
|
if ($statusVal !== null) { $sql .= " AND e.status = ?"; $params[] = $statusVal; }
|
|
if ($q) { $sql .= " AND e.name LIKE ?"; $params[] = "%$q%"; }
|
|
$sql .= " ORDER BY sh.name ASC, se.season_number ASC, e.episode_number ASC LIMIT ?, ?";
|
|
$params[] = $offset; $params[] = $limit;
|
|
$stmt = $pdo->prepare($sql);
|
|
$i=1; foreach ($params as $p) { $stmt->bindValue($i++, $p, is_int($p)?PDO::PARAM_INT:PDO::PARAM_STR); }
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
};
|
|
|
|
try {
|
|
$rows = $run($selectWithYearJson);
|
|
resp(['ok'=>true,'items'=>$rows]);
|
|
} catch (Throwable $e) {
|
|
$msg = $e->getMessage();
|
|
if (strpos($msg, 'Unknown column') !== false) {
|
|
// Maybe first_air_year missing — try JSON version without year
|
|
try {
|
|
$rows = $run($selectNoYearJson);
|
|
resp(['ok'=>true,'items'=>$rows]);
|
|
} catch (Throwable $e2) {
|
|
$msg2 = $e2->getMessage();
|
|
if (stripos($msg2, 'JSON_EXTRACT') !== false || stripos($msg2, 'Unknown function') !== false) {
|
|
// Fallback to non-JSON_EXTRACT selects
|
|
try {
|
|
$rows = $run($selectWithYear);
|
|
resp(['ok'=>true,'items'=>$rows]);
|
|
} catch (Throwable $e3) {
|
|
if (strpos($e3->getMessage(), 'Unknown column') !== false) {
|
|
$rows = $run($selectNoYear);
|
|
resp(['ok'=>true,'items'=>$rows]);
|
|
} else { throw $e3; }
|
|
}
|
|
} else { throw $e2; }
|
|
}
|
|
} elseif (stripos($msg, 'JSON_EXTRACT') !== false || stripos($msg, 'Unknown function') !== false) {
|
|
// JSON functions not available
|
|
try {
|
|
$rows = $run($selectWithYear);
|
|
resp(['ok'=>true,'items'=>$rows]);
|
|
} catch (Throwable $e4) {
|
|
if (strpos($e4->getMessage(), 'Unknown column') !== false) {
|
|
$rows = $run($selectNoYear);
|
|
resp(['ok'=>true,'items'=>$rows]);
|
|
} else { throw $e4; }
|
|
}
|
|
} else { throw $e; }
|
|
}
|
|
}
|
|
|
|
if ($type==='movie') {
|
|
$statusVal = null;
|
|
if ($status) {
|
|
$s = strtolower($status);
|
|
if ($s==='init') $statusVal = 0; elseif ($s==='progress') $statusVal = 1; elseif ($s==='done') $statusVal = 2;
|
|
}
|
|
$sql = "SELECT m.*, CASE m.status WHEN 1 THEN 'Progress' WHEN 2 THEN 'Done' ELSE 'Init' END AS status, m.resolution
|
|
FROM movies m WHERE 1=1";
|
|
$params=[];
|
|
if ($statusVal !== null) { $sql.=" AND m.status=?"; $params[]=$statusVal; }
|
|
if ($q){$sql.=" AND m.title LIKE ?"; $params[]='%'.$q.'%';}
|
|
$sql.=" ORDER BY m.title ASC LIMIT ?,?"; $params[]=$offset; $params[]=$limit;
|
|
$stmt=$pdo->prepare($sql); $i=1; foreach($params as $p){$stmt->bindValue($i++,$p,is_int($p)?PDO::PARAM_INT:PDO::PARAM_STR);} $stmt->execute();
|
|
resp(['ok'=>true,'items'=>$stmt->fetchAll()]);
|
|
}
|
|
fail('unsupported type');
|
|
}
|
|
|
|
case 'get_show_by_tmdb': {
|
|
$tmdbId = (int)($in['tmdb_id'] ?? 0);
|
|
if (!$tmdbId) fail('bad params');
|
|
$stmt = $pdo->prepare('SELECT id FROM shows WHERE tmdb_id = ?');
|
|
$stmt->execute([$tmdbId]);
|
|
$id = $stmt->fetchColumn();
|
|
resp(['ok' => true, 'id' => $id ? (int)$id : null]);
|
|
}
|
|
|
|
case 'ping': {
|
|
resp([
|
|
'ok' => true,
|
|
'time' => date('c'),
|
|
'origin' => $origin,
|
|
'php' => PHP_VERSION,
|
|
]);
|
|
}
|
|
|
|
default: fail('unknown action');
|
|
}
|
|
} catch (Throwable $e) {
|
|
if (MM_DEBUG) resp(['ok'=>false,'error'=>$e->getMessage()], 500);
|
|
resp(['ok'=>false,'error'=>'server error'], 500);
|
|
}
|