Coller un lien de playlist (youtube.com/playlist?list=...) dans le champ URL affiche toutes ses videos dans la meme zone de resultats que la recherche de chaine ; chaque ligne a son bouton "Ajouter", et un bouton "Tout ajouter a la file" ajoute le reste d'un coup. Serveur : - GET /api/playlist (PlaylistDiscoveryService) : yt-dlp --flat-playlist -J. -J plutot que -j : le titre de la playlist n'existe qu'a la racine, et une playlist vide reste un resultat valide. - playlistUrl.ts : n'accepte que les liens youtube.com/playlist?list=<id> et reconstruit l'URL canonique depuis le seul identifiant valide (rien d'autre de l'entree n'atteint yt-dlp). Un lien watch?v=...&list=... reste une video (--no-playlist etait deja passe au telechargement). - videoSummary.ts : conversion d'une entree --flat-playlist en resume de video, extraite de ChannelDiscoveryService pour etre partagee (chaine et playlist parsent desormais identiquement). ChannelVideoSummary devient VideoSummary (et ChannelVideoDto -> VideoDto cote client). Client : - Le champ URL accepte video ou playlist (isPlaylistUrl aiguille) ; zone de resultats partagee (createResultsView). - "Tout ajouter" : POST /api/queue sequentiels (ordre conserve, pas de doublon avec les lignes deja ajoutees). Un echec n'interrompt pas le lot : nombre d'echecs + premier message affiches, seules les lignes en echec redeviennent ajoutables. Le mode est lu une seule fois pour tout le lot. - Miniatures de resultats en loading="lazy" (une playlist peut compter des centaines de videos). Verifie contre le vrai yt-dlp (2026.07.04) : forme reelle de -J relevee (fixtures de test calquees dessus), playlist de 1607 videos listee en ~11 s (delai fixe a 120 s). Teste dans le navigateur reel : playlist de 13 videos, ajout unitaire, "tout ajouter" (13 elements, aucun doublon), echec partiel simule + reprise, playlist inexistante, lien video avec list=, recherche de chaine (non-regression). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
85 lines
2.5 KiB
TypeScript
85 lines
2.5 KiB
TypeScript
import fastifyStatic from '@fastify/static';
|
|
import Fastify, { type FastifyInstance } from 'fastify';
|
|
import { type ServerConfig, loadConfig } from './config.js';
|
|
import { channelRoutes } from './routes/channel.js';
|
|
import { healthRoutes } from './routes/health.js';
|
|
import { playlistRoutes } from './routes/playlist.js';
|
|
import { createQueueRoutes } from './routes/queue.js';
|
|
|
|
/**
|
|
* Construit et configure l'instance Fastify de l'application : fichiers
|
|
* statiques du client, et routes applicatives.
|
|
*
|
|
* @param config - Configuration runtime résolue depuis l'environnement.
|
|
* @returns L'instance Fastify prête à être démarrée.
|
|
*/
|
|
async function buildServer(config: ServerConfig): Promise<FastifyInstance> {
|
|
const app = Fastify({ logger: true });
|
|
|
|
try {
|
|
await app.register(fastifyStatic, {
|
|
root: config.clientDistPath,
|
|
index: ['index.html'],
|
|
});
|
|
await app.register(healthRoutes);
|
|
await app.register(channelRoutes);
|
|
await app.register(playlistRoutes);
|
|
await app.register(createQueueRoutes(config));
|
|
} catch (error) {
|
|
app.log.error({ err: error }, "Échec de l'enregistrement des plugins Fastify");
|
|
throw error;
|
|
}
|
|
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* Installe les gestionnaires d'arrêt propre du serveur (SIGINT/SIGTERM).
|
|
*
|
|
* @param app - Instance Fastify à fermer proprement à l'arrêt.
|
|
*/
|
|
function installShutdownHandlers(app: FastifyInstance): void {
|
|
const shutdown = async (signal: string): Promise<void> => {
|
|
app.log.info(`Signal ${signal} reçu, arrêt du serveur...`);
|
|
try {
|
|
await app.close();
|
|
process.exit(0);
|
|
} catch (error) {
|
|
app.log.error({ err: error }, "Échec de l'arrêt propre du serveur");
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
|
process.on(signal, () => {
|
|
shutdown(signal).catch((error: unknown) => {
|
|
console.error(`Échec du gestionnaire ${signal}`, error);
|
|
process.exit(1);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Point d'entrée du serveur : construit l'application, la démarre, et
|
|
* installe les gestionnaires d'arrêt propre.
|
|
*/
|
|
async function main(): Promise<void> {
|
|
const config = loadConfig();
|
|
const app = await buildServer(config);
|
|
|
|
try {
|
|
await app.listen({ port: config.port, host: config.host });
|
|
app.log.info(`Serveur démarré sur http://${config.host}:${config.port}`);
|
|
} catch (error) {
|
|
app.log.error({ err: error }, 'Échec du démarrage du serveur');
|
|
process.exit(1);
|
|
}
|
|
|
|
installShutdownHandlers(app);
|
|
}
|
|
|
|
main().catch((error: unknown) => {
|
|
console.error('Erreur fatale au démarrage', error);
|
|
process.exit(1);
|
|
});
|