diff --git a/server/invites.ts b/server/invites.ts index 8b0ebaa..e5a83ff 100644 --- a/server/invites.ts +++ b/server/invites.ts @@ -49,7 +49,7 @@ export function createInvite(opts: { expiresAt: opts.expiresInHours ? new Date(Date.now() + opts.expiresInHours * 3_600_000).toISOString() : null, - maxUses : opts.maxUses ?? 1, + maxUses : opts.maxUses === undefined ? 1 : opts.maxUses, // null = illimité uses : 0, note : opts.note, } diff --git a/server/routes/import.ts b/server/routes/import.ts index a4796a7..5c88e91 100644 --- a/server/routes/import.ts +++ b/server/routes/import.ts @@ -40,7 +40,7 @@ function findOrphans(organized: Organized, catalog: any[]) { } // Dossiers série (enfants directs de la médiathèque) contenant les fichiers suivis d'une série -function serieFolders(sd: any, organized: Organized, mediaPath: string): Map { +function serieFolders(sd: any, organized: Organized, mediaPath: string, exclude = new Set()): Map { const ids = new Set() for (const season of sd.seasons ?? []) for (const ep of season.episodes ?? []) ids.add(String(ep.id)) @@ -52,12 +52,17 @@ function serieFolders(sd: any, organized: Organized, mediaPath: string): Map { + return new Set(catalog.filter(sd => sd.id !== serieId).map(sd => path.join(mediaPath, serieFolderName(sd.title ?? sd.show_title ?? '')))) +} + function listFiles(dir: string): string[] { const files: string[] = [] for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { @@ -539,10 +544,11 @@ router.get('/organized-folders', requireAdmin, async (_req, res) => { catch (err) { res.status(500).json({ error: err instanceof Error ? err.message : 'organized.json illisible' }); return } try { const result: { serie_id: number; serie_title: string; expected: string; current: string[] }[] = [] - for (const sd of await loadCatalog()) { + const catalog = await loadCatalog() + for (const sd of catalog) { const title = sd.title ?? sd.show_title ?? '' const expected = path.join(mediaPath, serieFolderName(title)) - const stale = [...serieFolders(sd, organized, mediaPath).keys()].filter(f => f !== expected && fs.existsSync(f)) + const stale = [...serieFolders(sd, organized, mediaPath, otherSerieFolders(catalog, sd.id, mediaPath)).keys()].filter(f => f !== expected && fs.existsSync(f)) if (stale.length > 0) result.push({ serie_id: sd.id, serie_title: title, expected, current: stale }) } res.json(result) @@ -561,7 +567,8 @@ router.get('/organized/:serieId/folder', requireAuth, async (req, res) => { try { organized = readOrganized() } catch (err) { res.status(500).json({ error: err instanceof Error ? err.message : 'organized.json illisible' }); return } const expected = path.join(mediaPath, serieFolderName(sd.title ?? sd.show_title ?? '')) - const current = [...serieFolders(sd, organized, mediaPath)].map(([folder, entries]) => ({ path: folder, entries, exists: fs.existsSync(folder) })) + const others = otherSerieFolders(await loadCatalog().catch(() => []), serieId, mediaPath) + const current = [...serieFolders(sd, organized, mediaPath, others)].map(([folder, entries]) => ({ path: folder, entries, exists: fs.existsSync(folder) })) res.json({ expected, current, needs_rename: current.some(f => f.exists && f.path !== expected) }) }) @@ -574,8 +581,11 @@ router.post('/organized/:serieId/folder', requireAdmin, async (req, res) => { let organized: Organized try { organized = readOrganized() } catch (err) { res.status(500).json({ error: err instanceof Error ? err.message : 'organized.json illisible' }); return } + let catalog: any[] + try { catalog = await loadCatalog() } + catch (err) { res.status(503).json({ error: `Catalogue indisponible : ${err instanceof Error ? err.message : err}` }); return } const expected = path.join(mediaPath, serieFolderName(sd.title ?? sd.show_title ?? '')) - const sources = [...serieFolders(sd, organized, mediaPath).keys()].filter(f => f !== expected && fs.existsSync(f)) + const sources = [...serieFolders(sd, organized, mediaPath, otherSerieFolders(catalog, serieId, mediaPath)).keys()].filter(f => f !== expected && fs.existsSync(f)) if (sources.length === 0) { res.json({ ok: true, moved: 0, updated: 0 }); return } // Tout le contenu (vidéos, NFO, images…) est fusionné dans le dossier attendu, sans jamais écraser diff --git a/server/routes/invites.ts b/server/routes/invites.ts index 51bb3b3..41173d2 100644 --- a/server/routes/invites.ts +++ b/server/routes/invites.ts @@ -20,7 +20,7 @@ router.post('/invites', requireAdmin, (req, res) => { const invite = createInvite({ createdBy : req.user!.id, expiresInHours: expiresInHours ?? null, - maxUses : maxUses ?? 1, + maxUses, note, }) res.json(invite) diff --git a/src/views/settings/UsersView.vue b/src/views/settings/UsersView.vue index 63f01e3..e1015c5 100644 --- a/src/views/settings/UsersView.vue +++ b/src/views/settings/UsersView.vue @@ -431,9 +431,8 @@ async function submitInviteModal() { const m = inviteModal.value m.error = null; m.loading = true - const body: Record = {} + const body: Record = { maxUses: m.maxUses || null } if (m.note) body.note = m.note - if (m.maxUses) body.maxUses = m.maxUses if (m.expiresInHours) body.expiresInHours = m.expiresInHours const res = await fetch('/api/invites', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(body) })