feat: media – Genres, FSK-Badges, TR-Filter, Film-Modal, Favoriten-Kalender
This commit is contained in:
@@ -23,59 +23,168 @@ async function tmdb(path, params = {}) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// Genre-Cache (in-memory, wird bei erstem Aufruf befüllt)
|
||||
let genreCache = null;
|
||||
async function getGenreMap() {
|
||||
if (genreCache) return genreCache;
|
||||
const data = await tmdb('/genre/movie/list');
|
||||
genreCache = {};
|
||||
for (const g of data.genres) genreCache[g.id] = g.name;
|
||||
return genreCache;
|
||||
}
|
||||
|
||||
// FSK aus release_dates extrahieren
|
||||
function extractFSK(releaseDates) {
|
||||
const de = releaseDates?.results?.find(r => r.iso_3166_1 === 'DE');
|
||||
if (!de) return null;
|
||||
const cert = de.release_dates.find(r => r.certification)?.certification;
|
||||
return cert || null;
|
||||
}
|
||||
|
||||
// Einen Film anreichern (Genres + FSK aus append_to_response)
|
||||
function enrichMovie(movie, genreMap, releaseDates) {
|
||||
return {
|
||||
id: movie.id,
|
||||
title: movie.title || movie.original_title,
|
||||
original_title: movie.original_title,
|
||||
original_language: movie.original_language,
|
||||
poster_path: movie.poster_path,
|
||||
backdrop_path: movie.backdrop_path,
|
||||
overview: movie.overview,
|
||||
release_date: movie.release_date,
|
||||
vote_average: movie.vote_average,
|
||||
genres: (movie.genre_ids || movie.genres?.map(g=>g.id) || [])
|
||||
.map(id => genreMap[id]).filter(Boolean),
|
||||
fsk: extractFSK(releaseDates),
|
||||
};
|
||||
}
|
||||
|
||||
// GET /api/tools/media/now-playing
|
||||
router.get('/now-playing', authenticate, async (req, res) => {
|
||||
try {
|
||||
const data = await tmdb('/movie/now_playing');
|
||||
res.json(data.results.slice(0, 10));
|
||||
const [listData, genreMap] = await Promise.all([
|
||||
tmdb('/movie/now_playing'),
|
||||
getGenreMap(),
|
||||
]);
|
||||
// Türkische Produktionen rausfiltern, max 10
|
||||
const filtered = listData.results
|
||||
.filter(m => m.original_language !== 'tr')
|
||||
.slice(0, 10);
|
||||
|
||||
// FSK für jeden Film parallel holen
|
||||
const withFSK = await Promise.all(filtered.map(async (m, idx) => {
|
||||
try {
|
||||
const detail = await tmdb(`/movie/${m.id}/release_dates`);
|
||||
return { ...enrichMovie(m, genreMap, detail), rank: idx + 1 };
|
||||
} catch {
|
||||
return { ...enrichMovie(m, genreMap, null), rank: idx + 1 };
|
||||
}
|
||||
}));
|
||||
res.json(withFSK);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/tools/media/upcoming
|
||||
router.get('/upcoming', authenticate, async (req, res) => {
|
||||
try {
|
||||
const data = await tmdb('/movie/upcoming');
|
||||
res.json(data.results.slice(0, 10));
|
||||
const [listData, genreMap] = await Promise.all([
|
||||
tmdb('/movie/upcoming'),
|
||||
getGenreMap(),
|
||||
]);
|
||||
const filtered = listData.results
|
||||
.filter(m => m.original_language !== 'tr')
|
||||
.slice(0, 10);
|
||||
|
||||
const withFSK = await Promise.all(filtered.map(async (m, idx) => {
|
||||
try {
|
||||
const detail = await tmdb(`/movie/${m.id}/release_dates`);
|
||||
return { ...enrichMovie(m, genreMap, detail), rank: idx + 1 };
|
||||
} catch {
|
||||
return { ...enrichMovie(m, genreMap, null), rank: idx + 1 };
|
||||
}
|
||||
}));
|
||||
res.json(withFSK);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/tools/media/movie/:id – Details + Credits für Modal
|
||||
router.get('/movie/:id', authenticate, async (req, res) => {
|
||||
try {
|
||||
const [detail, genreMap] = await Promise.all([
|
||||
tmdb(`/movie/${req.params.id}`, { append_to_response: 'credits,release_dates' }),
|
||||
getGenreMap(),
|
||||
]);
|
||||
const cast = (detail.credits?.cast || []).slice(0, 8).map(a => a.name);
|
||||
const director = detail.credits?.crew?.find(c => c.job === 'Director')?.name || null;
|
||||
res.json({
|
||||
id: detail.id,
|
||||
title: detail.title || detail.original_title,
|
||||
original_title: detail.original_title,
|
||||
tagline: detail.tagline,
|
||||
overview: detail.overview,
|
||||
poster_path: detail.poster_path,
|
||||
backdrop_path: detail.backdrop_path,
|
||||
release_date: detail.release_date,
|
||||
runtime: detail.runtime,
|
||||
vote_average: detail.vote_average,
|
||||
vote_count: detail.vote_count,
|
||||
genres: (detail.genres || []).map(g => g.name),
|
||||
fsk: extractFSK(detail.release_dates),
|
||||
cast,
|
||||
director,
|
||||
});
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/tools/media/favorites
|
||||
router.get('/favorites', authenticate, (req, res) => {
|
||||
const rows = db.prepare(
|
||||
'SELECT * FROM movie_favorites WHERE user_id=? ORDER BY added_at DESC'
|
||||
'SELECT * FROM movie_favorites WHERE user_id=? ORDER BY release_date ASC, title ASC'
|
||||
).all(req.user.id);
|
||||
res.json(rows);
|
||||
});
|
||||
|
||||
// POST /api/tools/media/favorites
|
||||
router.post('/favorites', authenticate, (req, res) => {
|
||||
const { tmdb_id, title, poster_path, release_date_de } = req.body;
|
||||
const { tmdb_id, title, poster_path, release_date_de, release_date, genres, fsk } = req.body;
|
||||
if (!tmdb_id) return res.status(400).json({ error: 'tmdb_id fehlt' });
|
||||
try {
|
||||
db.prepare(
|
||||
'INSERT OR IGNORE INTO movie_favorites (user_id,tmdb_id,title,poster_path,release_date_de) VALUES (?,?,?,?,?)'
|
||||
).run(req.user.id, tmdb_id, title||'', poster_path||'', release_date_de||'');
|
||||
`INSERT OR IGNORE INTO movie_favorites
|
||||
(user_id,tmdb_id,title,poster_path,release_date_de,release_date,genres,fsk)
|
||||
VALUES (?,?,?,?,?,?,?,?)`
|
||||
).run(req.user.id, tmdb_id, title||'', poster_path||'', release_date_de||'',
|
||||
release_date||'', JSON.stringify(genres||[]), fsk||'');
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/tools/media/favorites/:tmdb_id
|
||||
router.delete('/favorites/:tmdb_id', authenticate, (req, res) => {
|
||||
db.prepare('DELETE FROM movie_favorites WHERE user_id=? AND tmdb_id=?')
|
||||
.run(req.user.id, Number(req.params.tmdb_id));
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// GET /api/tools/media/settings
|
||||
router.get('/settings', authenticate, requireAdmin, (req, res) => {
|
||||
const token = getToken();
|
||||
res.json({ configured: !!token });
|
||||
res.json({ configured: !!getToken() });
|
||||
});
|
||||
|
||||
// PUT /api/tools/media/settings
|
||||
router.put('/settings', authenticate, requireAdmin, (req, res) => {
|
||||
const { tmdb_token } = req.body;
|
||||
if (typeof tmdb_token !== 'string') return res.status(400).json({ error: 'tmdb_token fehlt' });
|
||||
db.prepare('INSERT OR REPLACE INTO admin_settings (key,value) VALUES (?,?)').run('tmdb_token', tmdb_token.trim());
|
||||
genreCache = null; // Cache invalidieren
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user