feat: gestion des téléchargements qBittorrent avec import automatique
- Nouvelle table `torrent_downloads` pour suivre les téléchargements gérés - API : endpoint POST /torrent-downloads/notify (webhook optionnel) et GET /torrent-downloads - Poller background toutes les 30s qui interroge qBittorrent pour détecter les torrents terminés — aucune config "run external program" nécessaire - Import automatique : déplacement des fichiers vers la série cible, renommage selon le pattern existant (détection de la largeur des digits), support packs multi-volumes, scan job déclenché après import - Page /downloads dans le backoffice : filtres, auto-refresh, carte par download - Toggle auto-import intégré dans la card qBittorrent des settings - Erreurs de détection download affichées dans le détail des jobs - Volume /downloads monté dans docker-compose Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1282,8 +1282,27 @@ export type ProwlarrTestResponse = {
|
||||
export type QBittorrentAddResponse = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
torrent_download_id?: string | null;
|
||||
};
|
||||
|
||||
export type TorrentDownloadDto = {
|
||||
id: string;
|
||||
library_id: string;
|
||||
series_name: string;
|
||||
expected_volumes: number[];
|
||||
qb_hash: string | null;
|
||||
content_path: string | null;
|
||||
status: "downloading" | "completed" | "importing" | "imported" | "error";
|
||||
imported_files: Array<{ volume: number; source: string; destination: string }> | null;
|
||||
error_message: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export async function fetchTorrentDownloads(): Promise<TorrentDownloadDto[]> {
|
||||
return apiFetch<TorrentDownloadDto[]>("/torrent-downloads");
|
||||
}
|
||||
|
||||
export type QBittorrentTestResponse = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
|
||||
@@ -6,6 +6,7 @@ const en: Record<TranslationKey, string> = {
|
||||
"nav.books": "Books",
|
||||
"nav.series": "Series",
|
||||
"nav.libraries": "Libraries",
|
||||
"nav.downloads": "Downloads",
|
||||
"nav.jobs": "Jobs",
|
||||
"nav.tokens": "Tokens",
|
||||
"nav.users": "Users",
|
||||
@@ -401,6 +402,8 @@ const en: Record<TranslationKey, string> = {
|
||||
"jobDetail.downloadAvailableReleases": "Available releases",
|
||||
"jobDetail.downloadAvailableReleasesDesc": "{{count}} series with at least one release found",
|
||||
"jobDetail.downloadMissingCount": "{{count}} missing",
|
||||
"jobDetail.downloadErrors": "Detection errors",
|
||||
"jobDetail.downloadErrorsDesc": "{{count}} series with errors during detection",
|
||||
|
||||
// Job types
|
||||
"jobType.rebuild": "Indexing",
|
||||
@@ -882,6 +885,25 @@ const en: Record<TranslationKey, string> = {
|
||||
"series.metadataAll": "All",
|
||||
"series.metadataLinked": "Linked",
|
||||
"series.metadataUnlinked": "Not linked",
|
||||
|
||||
// Downloads page
|
||||
"downloads.title": "Downloads",
|
||||
"downloads.refresh": "Refresh",
|
||||
"downloads.filterActive": "In progress",
|
||||
"downloads.empty": "No downloads",
|
||||
"downloads.volumes": "Volumes",
|
||||
"downloads.filesImported": "files imported",
|
||||
"downloads.status.downloading": "Downloading",
|
||||
"downloads.status.completed": "Completed",
|
||||
"downloads.status.importing": "Importing",
|
||||
"downloads.status.imported": "Imported",
|
||||
"downloads.status.error": "Error",
|
||||
|
||||
// Settings - Torrent Import
|
||||
"settings.torrentImport": "Auto import",
|
||||
"settings.torrentImportDesc": "When enabled, torrents added via the backoffice are tracked and files are automatically imported into the library when the download completes.",
|
||||
"settings.torrentImportEnabled": "Enable auto import",
|
||||
"settings.torrentImportPollingInfo": "The API polls qBittorrent every 30 seconds to detect completed downloads. No additional configuration in qBittorrent is required.",
|
||||
};
|
||||
|
||||
export default en;
|
||||
|
||||
@@ -4,6 +4,7 @@ const fr = {
|
||||
"nav.books": "Livres",
|
||||
"nav.series": "Séries",
|
||||
"nav.libraries": "Bibliothèques",
|
||||
"nav.downloads": "Téléchargements",
|
||||
"nav.jobs": "Tâches",
|
||||
"nav.tokens": "Jetons",
|
||||
"nav.users": "Utilisateurs",
|
||||
@@ -399,6 +400,8 @@ const fr = {
|
||||
"jobDetail.downloadAvailableReleases": "Releases disponibles",
|
||||
"jobDetail.downloadAvailableReleasesDesc": "{{count}} série(s) avec au moins une release trouvée",
|
||||
"jobDetail.downloadMissingCount": "{{count}} manquant(s)",
|
||||
"jobDetail.downloadErrors": "Erreurs de détection",
|
||||
"jobDetail.downloadErrorsDesc": "{{count}} série(s) en erreur lors de la détection",
|
||||
|
||||
// Job types
|
||||
"jobType.rebuild": "Indexation",
|
||||
@@ -880,6 +883,25 @@ const fr = {
|
||||
"series.metadataAll": "Toutes",
|
||||
"series.metadataLinked": "Associée",
|
||||
"series.metadataUnlinked": "Non associée",
|
||||
|
||||
// Downloads page
|
||||
"downloads.title": "Téléchargements",
|
||||
"downloads.refresh": "Actualiser",
|
||||
"downloads.filterActive": "En cours",
|
||||
"downloads.empty": "Aucun téléchargement",
|
||||
"downloads.volumes": "Volumes",
|
||||
"downloads.filesImported": "fichiers importés",
|
||||
"downloads.status.downloading": "Téléchargement",
|
||||
"downloads.status.completed": "Terminé",
|
||||
"downloads.status.importing": "Import en cours",
|
||||
"downloads.status.imported": "Importé",
|
||||
"downloads.status.error": "Erreur",
|
||||
|
||||
// Settings - Torrent Import
|
||||
"settings.torrentImport": "Import automatique",
|
||||
"settings.torrentImportDesc": "Lorsqu'activé, les torrents ajoutés via le backoffice sont suivis et les fichiers sont automatiquement importés dans la bibliothèque à la fin du téléchargement.",
|
||||
"settings.torrentImportEnabled": "Activer l'import automatique",
|
||||
"settings.torrentImportPollingInfo": "L'API interroge qBittorrent toutes les 30 secondes pour détecter les téléchargements terminés. Aucune configuration supplémentaire dans qBittorrent n'est nécessaire.",
|
||||
} as const;
|
||||
|
||||
export type TranslationKey = keyof typeof fr;
|
||||
|
||||
Reference in New Issue
Block a user