Film / Serie Update
This commit is contained in:
@@ -161,8 +161,9 @@ class BackendApi {
|
||||
return (map['items'] as List).cast<Map<String, dynamic>>();
|
||||
}
|
||||
|
||||
Future<void> upsertShow(Map<String, dynamic> tmdbJson) async {
|
||||
await _post({'action': 'upsert_show', 'tmdb': tmdbJson});
|
||||
Future<int> upsertShow(Map<String, dynamic> tmdbJson) async {
|
||||
final map = await _post({'action': 'upsert_show', 'tmdb': tmdbJson});
|
||||
return (map['id'] as num).toInt();
|
||||
}
|
||||
|
||||
Future<int> upsertSeason(int showId, Map<String, dynamic> seasonJson) async {
|
||||
@@ -187,6 +188,17 @@ class BackendApi {
|
||||
return v == null ? null : (v as num).toInt();
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> listShows() async {
|
||||
final map = await _post({'action': 'list_shows'});
|
||||
return (map['items'] as List).cast<Map<String, dynamic>>();
|
||||
}
|
||||
|
||||
Future<int?> getTmdbIdByShowId(int showId) async {
|
||||
final map = await _post({'action': 'get_tmdb_by_show_id', 'show_id': showId});
|
||||
final v = map['tmdb_id'];
|
||||
return v == null ? null : (v as num).toInt();
|
||||
}
|
||||
|
||||
Future<void> setShowMeta({
|
||||
required int showId,
|
||||
String? resolution,
|
||||
|
||||
@@ -90,6 +90,35 @@ class _MovieDetailScreenState extends ConsumerState<MovieDetailScreen> {
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
title: Text(m.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: 'TMDB aktualisieren',
|
||||
icon: const Icon(Icons.sync),
|
||||
onPressed: () async {
|
||||
try {
|
||||
final tmdb = ref.read(tmdbApiProvider);
|
||||
final json = await tmdb.getMovie(m.tmdbId);
|
||||
await ref.read(backendApiProvider).upsertMovie(json);
|
||||
// Update local TMDB data for immediate UI refresh
|
||||
if (mounted) setState(() => _tmdb = json);
|
||||
// Refresh movie list so the list reflects new metadata
|
||||
// ignore: unused_result
|
||||
ref.invalidate(moviesProvider);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('TMDB Daten aktualisiert')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('TMDB Update fehlgeschlagen: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
floatingActionButton: _dirty
|
||||
? FloatingActionButton.extended(
|
||||
|
||||
@@ -5,6 +5,7 @@ import '../../../core/status.dart';
|
||||
import '../data/movie_repository.dart';
|
||||
import 'widgets/status_chip.dart';
|
||||
import 'movie_detail_screen.dart';
|
||||
import '../../shared/providers.dart';
|
||||
|
||||
class MovieListScreen extends ConsumerWidget {
|
||||
const MovieListScreen({super.key});
|
||||
@@ -23,6 +24,71 @@ class MovieListScreen extends ConsumerWidget {
|
||||
selected: filter,
|
||||
onChanged: (f) => ref.read(movieFilterProvider.notifier).state = f,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.sync),
|
||||
label: const Text('TMDB: alle Filme updaten'),
|
||||
onPressed: () async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
String current = '';
|
||||
int idx = 0;
|
||||
int total = 0;
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) {
|
||||
return StatefulBuilder(builder: (ctx, setState) {
|
||||
Future<void> run() async {
|
||||
try {
|
||||
final tmdb = ref.read(tmdbApiProvider);
|
||||
final backend = ref.read(backendApiProvider);
|
||||
final items = await ref.read(moviesProvider.future);
|
||||
total = items.length;
|
||||
int ok = 0;
|
||||
for (final m in items) {
|
||||
idx++;
|
||||
current = m.title;
|
||||
setState(() {});
|
||||
try {
|
||||
final json = await tmdb.getMovie(m.tmdbId);
|
||||
await backend.upsertMovie(json);
|
||||
ok++;
|
||||
} catch (_) {}
|
||||
}
|
||||
// Refresh list after update
|
||||
// ignore: unused_result
|
||||
ref.invalidate(moviesProvider);
|
||||
messenger.showSnackBar(SnackBar(content: Text('TMDB Update fertig: $ok/$total Filme')));
|
||||
} finally {
|
||||
if (ctx.mounted) Navigator.of(ctx).pop();
|
||||
}
|
||||
}
|
||||
if (idx == 0 && total == 0) {
|
||||
// ignore: discarded_futures
|
||||
run();
|
||||
}
|
||||
return AlertDialog(
|
||||
title: const Text('TMDB Update'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Aktualisiere: $idx/$total'),
|
||||
const SizedBox(height: 8),
|
||||
Text(current, maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
const SizedBox(height: 12),
|
||||
LinearProgressIndicator(value: total > 0 ? idx / total : null),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Expanded(
|
||||
child: moviesAsync.when(
|
||||
@@ -169,4 +235,3 @@ class MovieListScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
|
||||
String? _overview;
|
||||
List<Map<String, dynamic>> _cast = const [];
|
||||
List<Map<String, dynamic>> _crew = const [];
|
||||
int? _tmdbId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -76,10 +77,11 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
|
||||
if (_downloadCtrl.text != (_downloadPath ?? '')) {
|
||||
_downloadCtrl.text = _downloadPath ?? '';
|
||||
}
|
||||
// parse overview/cast/crew from show_json
|
||||
// parse overview/cast/crew (and tmdb id) from show_json
|
||||
if (first.showJson != null && first.showJson!.isNotEmpty) {
|
||||
try {
|
||||
final m = jsonDecode(first.showJson!) as Map<String, dynamic>;
|
||||
_tmdbId = (m['id'] as num?)?.toInt() ?? _tmdbId;
|
||||
_overview = (m['overview'] as String?) ?? _overview;
|
||||
final credits = m['credits'] as Map<String, dynamic>?;
|
||||
if (credits != null) {
|
||||
@@ -237,6 +239,76 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
|
||||
elevation: 0,
|
||||
title: Text(widget.showName),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: 'TMDB aktualisieren',
|
||||
icon: const Icon(Icons.sync),
|
||||
onPressed: _tmdbId == null
|
||||
? null
|
||||
: () async {
|
||||
try {
|
||||
final tmdb = ref.read(tmdbApiProvider);
|
||||
final backend = ref.read(backendApiProvider);
|
||||
int? tid = _tmdbId;
|
||||
if (tid == null && _showId != null) {
|
||||
tid = await backend.getTmdbIdByShowId(_showId!);
|
||||
}
|
||||
if (tid == null) {
|
||||
throw 'tmdb_id für diese Serie konnte nicht ermittelt werden';
|
||||
}
|
||||
final showJson = await tmdb.getShow(tid);
|
||||
// Update header meta immediately (overview/cast/crew)
|
||||
try {
|
||||
setState(() {
|
||||
_overview = (showJson['overview'] as String?) ?? _overview;
|
||||
final credits = showJson['credits'] as Map<String, dynamic>?;
|
||||
if (credits != null) {
|
||||
final c = (credits['cast'] as List? ?? const [])
|
||||
.cast()
|
||||
.map((e) => Map<String, dynamic>.from(e as Map))
|
||||
.toList();
|
||||
final cr = (credits['crew'] as List? ?? const [])
|
||||
.cast()
|
||||
.map((e) => Map<String, dynamic>.from(e as Map))
|
||||
.toList();
|
||||
_cast = c.take(12).toList();
|
||||
_crew = cr.take(12).toList();
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
final dbShowId = await backend.upsertShow(showJson);
|
||||
// seasons
|
||||
final seasons = (showJson['seasons'] as List? ?? const [])
|
||||
.where((s) => (s['season_number'] ?? -1) is num)
|
||||
.map((s) => (s as Map<String, dynamic>)['season_number'] as int)
|
||||
.toList();
|
||||
for (final sNo in seasons) {
|
||||
if (sNo < 0) continue;
|
||||
final seasonJson = await tmdb.getSeason(tid, sNo);
|
||||
final seasonId = await backend.upsertSeason(dbShowId, seasonJson);
|
||||
final eps = (seasonJson['episodes'] as List? ?? const [])
|
||||
.cast<Map<String, dynamic>>();
|
||||
for (final e in eps) {
|
||||
await backend.upsertEpisode(seasonId, e);
|
||||
}
|
||||
}
|
||||
// Refresh list + reload local grouped data
|
||||
// ignore: unused_result
|
||||
ref.invalidate(seriesGroupedProvider);
|
||||
await _load();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('TMDB Daten aktualisiert')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('TMDB Update fehlgeschlagen: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
if (dirty)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
|
||||
@@ -7,6 +7,7 @@ import '../../movies/presentation/widgets/status_chip.dart';
|
||||
import '../data/series_repository.dart';
|
||||
import 'widgets/episode_status_strip.dart';
|
||||
import 'series_detail_screen.dart';
|
||||
import '../../shared/providers.dart';
|
||||
|
||||
class SeriesListScreen extends ConsumerWidget {
|
||||
const SeriesListScreen({super.key});
|
||||
@@ -27,6 +28,211 @@ class SeriesListScreen extends ConsumerWidget {
|
||||
onChanged: (f) =>
|
||||
ref.read(episodeFilterProvider.notifier).state = f,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.sync),
|
||||
label: const Text('TMDB: alle Serien updaten'),
|
||||
onPressed: () async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
String current = '';
|
||||
int idx = 0;
|
||||
int total = 0;
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) {
|
||||
// Stateful progress dialog
|
||||
return StatefulBuilder(builder: (ctx, setState) {
|
||||
Future<void> run() async {
|
||||
try {
|
||||
final tmdb = ref.read(tmdbApiProvider);
|
||||
final backend = ref.read(backendApiProvider);
|
||||
List<Map<String, dynamic>> showRows = [];
|
||||
try {
|
||||
showRows = await backend.listShows();
|
||||
} catch (_) {
|
||||
// Fallback: aus der aktuellen grouped-Liste tmdbId parsen
|
||||
final grouped = await ref.read(seriesGroupedProvider.future);
|
||||
for (final entry in grouped.data.entries) {
|
||||
final flat = entry.value.values.expand((e) => e);
|
||||
if (flat.isEmpty) continue;
|
||||
final ep = flat.first;
|
||||
if (ep.showJson == null) continue;
|
||||
try {
|
||||
final m = jsonDecode(ep.showJson!);
|
||||
final t = (m['id'] as num?)?.toInt();
|
||||
if (t != null) {
|
||||
showRows.add({'tmdb_id': t, 'name': entry.key});
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
total = showRows.length;
|
||||
int ok = 0;
|
||||
for (final row in showRows) {
|
||||
idx++;
|
||||
final tmdbId = (row['tmdb_id'] as num?)?.toInt();
|
||||
current = (row['name'] as String?) ?? 'tmdb:$tmdbId';
|
||||
setState(() {});
|
||||
if (tmdbId == null) continue;
|
||||
try {
|
||||
final showJson = await tmdb.getShow(tmdbId);
|
||||
final dbShowId = await backend.upsertShow(showJson);
|
||||
final seasons = (showJson['seasons'] as List? ?? const [])
|
||||
.where((s) => (s['season_number'] ?? -1) is num)
|
||||
.map((s) => (s as Map<String, dynamic>)['season_number'] as int)
|
||||
.toList();
|
||||
for (final sNo in seasons) {
|
||||
if (sNo < 0) continue;
|
||||
final seasonJson = await tmdb.getSeason(tmdbId, sNo);
|
||||
final seasonId = await backend.upsertSeason(dbShowId, seasonJson);
|
||||
final eps = (seasonJson['episodes'] as List? ?? const [])
|
||||
.cast<Map<String, dynamic>>();
|
||||
for (final e in eps) {
|
||||
await backend.upsertEpisode(seasonId, e);
|
||||
}
|
||||
}
|
||||
ok++;
|
||||
} catch (_) {}
|
||||
}
|
||||
// ignore: unused_result
|
||||
ref.invalidate(seriesGroupedProvider);
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text('TMDB Update fertig: $ok/$total Serien')),
|
||||
);
|
||||
} finally {
|
||||
if (ctx.mounted) Navigator.of(ctx).pop();
|
||||
}
|
||||
}
|
||||
// kick off once when dialog builds first time
|
||||
if (idx == 0 && total == 0) {
|
||||
// ignore: discarded_futures
|
||||
run();
|
||||
}
|
||||
return AlertDialog(
|
||||
title: const Text('TMDB Update'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Aktualisiere: $idx/$total'),
|
||||
const SizedBox(height: 8),
|
||||
Text(current, maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
const SizedBox(height: 12),
|
||||
LinearProgressIndicator(value: total > 0 ? idx / total : null),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.build),
|
||||
label: const Text('TMDB: Serie reparieren (IDs)'),
|
||||
onPressed: () async {
|
||||
final ctrl = TextEditingController();
|
||||
final ids = await showDialog<List<int>>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('TMDB IDs (Komma/Leerzeichen getrennt)'),
|
||||
content: TextField(
|
||||
controller: ctrl,
|
||||
decoration: const InputDecoration(hintText: 'z.B. 1396, 1399'),
|
||||
autofocus: true,
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Abbrechen')),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
final parts = ctrl.text.split(RegExp(r'[\s,;]+'));
|
||||
final list = <int>[];
|
||||
for (final p in parts) {
|
||||
final v = int.tryParse(p.trim());
|
||||
if (v != null) list.add(v);
|
||||
}
|
||||
Navigator.of(ctx).pop(list);
|
||||
},
|
||||
child: const Text('Start'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (ids == null || ids.isEmpty) return;
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) {
|
||||
String current = '';
|
||||
int idx = 0;
|
||||
final tmdb = ref.read(tmdbApiProvider);
|
||||
final backend = ref.read(backendApiProvider);
|
||||
return StatefulBuilder(builder: (ctx, setState) {
|
||||
Future<void> run() async {
|
||||
try {
|
||||
int ok = 0;
|
||||
for (final tmdbId in ids) {
|
||||
idx++;
|
||||
current = 'tmdb:$tmdbId';
|
||||
setState(() {});
|
||||
try {
|
||||
final showJson = await tmdb.getShow(tmdbId);
|
||||
await backend.upsertShow(showJson);
|
||||
final seasons = (showJson['seasons'] as List? ?? const [])
|
||||
.where((s) => (s['season_number'] ?? -1) is num)
|
||||
.map((s) => (s as Map<String, dynamic>)['season_number'] as int)
|
||||
.toList();
|
||||
final dbShowId = await backend.getShowDbIdByTmdbId(tmdbId);
|
||||
if (dbShowId == null) continue;
|
||||
for (final sNo in seasons) {
|
||||
final seasonJson = await tmdb.getSeason(tmdbId, sNo);
|
||||
final seasonId = await backend.upsertSeason(dbShowId, seasonJson);
|
||||
final eps = (seasonJson['episodes'] as List? ?? const [])
|
||||
.cast<Map<String, dynamic>>();
|
||||
for (final e in eps) {
|
||||
await backend.upsertEpisode(seasonId, e);
|
||||
}
|
||||
}
|
||||
ok++;
|
||||
} catch (_) {}
|
||||
}
|
||||
// ignore: unused_result
|
||||
ref.invalidate(seriesGroupedProvider);
|
||||
} finally {
|
||||
if (ctx.mounted) Navigator.of(ctx).pop();
|
||||
}
|
||||
}
|
||||
if (idx == 0) {
|
||||
// ignore: discarded_futures
|
||||
run();
|
||||
}
|
||||
return AlertDialog(
|
||||
title: const Text('TMDB Reparatur'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Aktualisiere: $idx/${ids.length}'),
|
||||
const SizedBox(height: 8),
|
||||
Text(current, maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
const SizedBox(height: 12),
|
||||
LinearProgressIndicator(value: idx / ids.length),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Expanded(
|
||||
child: groupedAsync.when(
|
||||
|
||||
+68
-6
@@ -170,8 +170,24 @@ try {
|
||||
}
|
||||
$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(); }
|
||||
if ($tmdbId) {
|
||||
try {
|
||||
$q=$pdo->prepare('SELECT id FROM episodes WHERE tmdb_id=?');
|
||||
$q->execute([$tmdbId]);
|
||||
$id=$q->fetchColumn();
|
||||
} catch (Throwable $e) {
|
||||
if (strpos($e->getMessage(), 'Unknown column') !== false || ($e instanceof PDOException && $e->getCode()==='42S22')) {
|
||||
$q=$pdo->prepare('SELECT id FROM episodes WHERE season_id=? AND episode_number=?');
|
||||
$q->execute([$seasonId,$epNo]);
|
||||
$id=$q->fetchColumn();
|
||||
} else { throw $e; }
|
||||
}
|
||||
}
|
||||
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]);
|
||||
}
|
||||
@@ -359,10 +375,56 @@ try {
|
||||
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]);
|
||||
try {
|
||||
$stmt = $pdo->prepare('SELECT id FROM shows WHERE tmdb_id = ?');
|
||||
$stmt->execute([$tmdbId]);
|
||||
$id = $stmt->fetchColumn();
|
||||
resp(['ok' => true, 'id' => $id ? (int)$id : null]);
|
||||
} catch (Throwable $e) {
|
||||
// Fallback if column tmdb_id does not exist (older schema): scan JSON
|
||||
if (strpos($e->getMessage(), 'Unknown column') !== false || ($e instanceof PDOException && $e->getCode()==='42S22')) {
|
||||
$stmt = $pdo->query('SELECT id, json FROM shows');
|
||||
$rows = $stmt->fetchAll();
|
||||
$found = null;
|
||||
foreach ($rows as $r) {
|
||||
$j = json_decode($r['json'] ?? 'null', true);
|
||||
if (is_array($j) && isset($j['id']) && (int)$j['id'] === $tmdbId) {
|
||||
$found = (int)$r['id'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
resp(['ok' => true, 'id' => $found]);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'get_tmdb_by_show_id': {
|
||||
$showId = (int)($in['show_id'] ?? 0);
|
||||
if (!$showId) fail('bad params');
|
||||
try {
|
||||
$stmt = $pdo->prepare('SELECT tmdb_id FROM shows WHERE id = ?');
|
||||
$stmt->execute([$showId]);
|
||||
$tm = $stmt->fetchColumn();
|
||||
resp(['ok' => true, 'tmdb_id' => $tm !== false ? (int)$tm : null]);
|
||||
} catch (Throwable $e) {
|
||||
// Fallback if column tmdb_id does not exist: load from JSON
|
||||
if (strpos($e->getMessage(), 'Unknown column') !== false || ($e instanceof PDOException && $e->getCode()==='42S22')) {
|
||||
$stmt = $pdo->prepare('SELECT json FROM shows WHERE id = ?');
|
||||
$stmt->execute([$showId]);
|
||||
$json = $stmt->fetchColumn();
|
||||
$m = json_decode($json ?: 'null', true);
|
||||
$tid = is_array($m) && isset($m['id']) ? (int)$m['id'] : null;
|
||||
resp(['ok' => true, 'tmdb_id' => $tid]);
|
||||
} else { throw $e; }
|
||||
}
|
||||
}
|
||||
|
||||
case 'list_shows': {
|
||||
$stmt = $pdo->query('SELECT id, tmdb_id, name, json FROM shows');
|
||||
$rows = $stmt->fetchAll();
|
||||
resp(['ok' => true, 'items' => $rows]);
|
||||
}
|
||||
|
||||
case 'set_show_meta': {
|
||||
|
||||
Reference in New Issue
Block a user