docs: refresh CLAUDE.md and README with current project state, drop dead legacy pages

Both docs still described a frontend/-subfolder Vite/react-router-dom
setup that never existed in this repo's history, and were missing the
yt-dlp probe gating, SEO landing pages, sitemap, and 4-locale i18n
shipped in recent commits. Also removes src/app/page.tsx and
src/app/status/[uuid]/page.tsx, superseded by the [locale]-based
routes and unreachable since the next-intl middleware redirects all
non-API traffic into [locale]/...

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 15:02:37 +02:00
co-authored by Claude Sonnet 5
parent d2e491c762
commit 1f51ec7a2b
4 changed files with 63 additions and 61 deletions
+8 -9
View File
@@ -10,23 +10,22 @@
- Utilise Docker & Docker-Compose pour la base de données (MariaDB) - Utilise Docker & Docker-Compose pour la base de données (MariaDB)
- On développe tout en NodeJS notamment avec : NextJS & Prisma pour la partie backend et React pour la partie frontend - On développe tout en NodeJS notamment avec : NextJS & Prisma pour la partie backend et React pour la partie frontend
- Utilise Tailwind pour le CSS - Utilise Tailwind pour le CSS
- L'application est toujours multilangue (EN + FR) - Si tu dois utiliser des Workers, gère le avec Passenger (pm2)
- L'application est toujours multilangue (EN, FR, ES, IT — voir `messages/`)
## Application : Ombrora-YTDLP ## Application : Ombrora-YTDLP
- Interface web publique permettant de soumettre des URLs de videos a telecharger via yt-dlp. - Interface web publique permettant de soumettre des URLs de videos a telecharger via yt-dlp, avec pages SEO par plateforme (`/[locale]/[slug]`, ex. `/youtube-downloader`, definies dans `src/lib/downloader-platforms.ts`) et une page `supported-sites` listant tous les sites geres par yt-dlp.
- Les telechargements sont geres via une file d'attente (queue) executee par Passenger en arriere-plan avec support du multithreading. - Avant soumission, l'URL est analysee en temps reel via un probe yt-dlp (`POST /api/probe`, `src/lib/ytdlp-probe.ts`, `yt-dlp -J`) : les options presentees a l'utilisateur (qualite, sous-titres disponibles, decoupe, qualite audio MP3) sont derivees de cette analyse et non d'une liste statique ; la soumission est bloquee tant que le probe n'a pas reussi.
- Les telechargements sont geres via une file d'attente (queue) executee par un worker Node.js (`worker/`, lance via `tsx`) supervise par pm2, avec support du multithreading (concurrence configurable via `WORKER_CONCURRENCY`).
- Chaque telechargement est stocke en base de donnees (MariaDB via Prisma). **Aucune entree n'est jamais supprimee** : la DB conserve l'historique complet de tous les telechargements (statuts, erreurs, metadata). - Chaque telechargement est stocke en base de donnees (MariaDB via Prisma). **Aucune entree n'est jamais supprimee** : la DB conserve l'historique complet de tous les telechargements (statuts, erreurs, metadata).
- Le worker Passenger est responsable de dequeuer et d'executer yt-dlp en parallele selon la capacite configuree. - `yt-dlp` est un zipapp Python invoque differemment selon la plateforme (`src/lib/ytdlp.ts`) : via `python` sur Windows (le shebang n'est pas executable par `spawn()`), directement sur Linux/o2switch (shebang natif).
## Deployment (o2switch) ## Deployment (o2switch)
- o2switch is shared hosting: no compiler toolchain, no root access. Any dependency with a native/binary component must ship as a precompiled binary — it cannot be built from source on the server. - o2switch is shared hosting: no compiler toolchain, no root access. Any dependency with a native/binary component must ship as a precompiled binary — it cannot be built from source on the server. This is why `yt-dlp`/`ffmpeg`/`ffprobe` ship as precompiled binaries in `bin/` (or `BIN_DIR`) rather than as npm deps.
- Before adding any new dependency with native bindings, confirm it publishes prebuilt binaries for o2switch's platform/arch — otherwise it will fail to install or run there. - Before adding any new dependency with native bindings, confirm it publishes prebuilt binaries for o2switch's platform/arch — otherwise it will fail to install or run there.
- **The o2switch nodevenv/Passenger setup ("Setup Node.js App" in cPanel) only supports a single `package.json`/`node_modules` for the whole registered app — not one per subfolder.** Verified by directly debugging a failed `frontend/` build: `npm install --prefix frontend --include=dev` (root's own `build` script) and even a plain `npm install` run with `cd frontend` first (confirmed via `pwd` to genuinely be inside `frontend/`) both completed "successfully" (correct, unmodified `frontend/package-lock.json`, real `resolved` entries for every package) yet never created a `frontend/node_modules` directory on the server at all. Meanwhile `vite`/`@vitejs/plugin-react` (already present as root devDependencies) resolved fine during the build — only packages that exist *exclusively* in `frontend/package.json` (`react-router-dom`, `react-i18next`, `i18next`, `@phosphor-icons/react`) failed to resolve, with Vite/Rolldown erroring `Rolldown failed to resolve import "react-router-dom"`. - Single Next.js app with a single `package.json`/`node_modules` at the repo root — there is no separate frontend build or subfolder to keep in sync. Deployed via cPanel "Setup Node.js App" (Passenger) for the web app; the worker is a separate process that Passenger does not supervise, kept alive by pm2 instead (`ecosystem.config.cjs`, `npm run worker:pm2:*`). Full deployment steps (including `npm run deploy` / `scripts/deploy.sh`) are documented in `README.md`.
- **Fix (applied):** every runtime package `frontend/src/**` imports must also be listed in the **root** `package.json`'s `dependencies` (not just `frontend/package.json`'s) — `react`/`react-dom` already were; `react-router-dom`, `react-i18next`, `i18next`, `@phosphor-icons/react` were added there too. Root's single `node_modules` is an ancestor directory of `frontend/src/`, so Node/Vite's normal upward `node_modules` resolution walk finds them there even with no `frontend/node_modules` on the server.
- `frontend/package.json` still declares the same packages in its own `dependencies` — that's intentional, not stale duplication. It's what makes local dev (`npm run dev` inside `frontend/`, which gets a real, normal `frontend/node_modules` on a dev machine) work independently of this server-only constraint. When adding a new frontend runtime dependency, add it to **both** `package.json` files (frontend's own, for local dev; root's, for the o2switch build) and run `npm install` in both places to keep both lockfiles in sync.
- `frontend/package.json`'s `devDependencies` (`vite`, `@vitejs/plugin-react`, `oxlint`, `@types/react*`) do **not** need mirroring to root — only the ones already there (`vite`, `@vitejs/plugin-react`) are actually required for the production build to run at all; `oxlint`/`@types/*` are dev-only tooling never invoked during `npm run build`.
<!-- BEGIN:nextjs-agent-rules --> <!-- BEGIN:nextjs-agent-rules -->
+55 -27
View File
@@ -4,12 +4,13 @@ Interface web de téléchargement de vidéos via [yt-dlp](https://github.com/yt-
## Fonctionnement ## Fonctionnement
1. L'utilisateur soumet une URL depuis l'interface web. 1. L'utilisateur colle une URL dans l'interface web. Elle est immédiatement analysée via un probe `yt-dlp -J` (`POST /api/probe`), qui détermine les qualités, sous-titres et durée réellement disponibles pour cette vidéo.
2. La demande est enregistrée en base (statut `PENDING`). 2. Les options de téléchargement (format, qualité, sous-titres, découpe, qualité audio MP3) affichées à l'utilisateur sont dérivées de ce probe — la soumission est bloquée tant qu'il n'a pas réussi.
3. Le worker (Node.js, arrière-plan, supervisé par pm2) dépile les jobs et exécute `yt-dlp` en parallèle (concurrence configurable). 3. La demande est enregistrée en base (statut `PENDING`).
4. Le fichier téléchargé est stocké localement ; un token signé (24h) est généré. 4. Le worker (Node.js, arrière-plan, supervisé par pm2) dépile les jobs et exécute `yt-dlp` en parallèle (concurrence configurable).
5. L'utilisateur est redirigé vers une page de statut qui se rafraîchit jusqu'à ce que le fichier soit prêt. 5. Le fichier téléchargé est stocké localement ; un token signé (24h) est généré.
6. Un cron quotidien supprime les fichiers expirés et marque les entrées `FILE_DELETED` — la DB conserve l'historique complet. 6. L'utilisateur est redirigé vers une page de statut qui se rafraîchit jusqu'à ce que le fichier soit prêt.
7. Un cron quotidien supprime les fichiers expirés et marque les entrées `FILE_DELETED` — la DB conserve l'historique complet.
## Stack technique ## Stack technique
@@ -17,7 +18,10 @@ Interface web de téléchargement de vidéos via [yt-dlp](https://github.com/yt-
|--------|-------------| |--------|-------------|
| Framework | Next.js 16 (App Router) | | Framework | Next.js 16 (App Router) |
| Base de données | MariaDB 11 via Prisma 7 | | Base de données | MariaDB 11 via Prisma 7 |
| Frontend | React 19, TypeScript | | Frontend | React 19, TypeScript, Tailwind CSS v4 |
| i18n | next-intl (EN, FR, ES, IT), routing par `[locale]` |
| Thème | next-themes (clair/sombre) |
| Analytics | PostHog (cookieless) |
| Worker | Node.js (tsx), supervisé par pm2 | | Worker | Node.js (tsx), supervisé par pm2 |
| Conteneur (dev) | Docker Compose | | Conteneur (dev) | Docker Compose |
@@ -25,7 +29,7 @@ Interface web de téléchargement de vidéos via [yt-dlp](https://github.com/yt-
- Node.js 20+ - Node.js 20+
- Docker & Docker Compose (pour MariaDB en développement) - Docker & Docker Compose (pour MariaDB en développement)
- `yt-dlp` et `ffmpeg` (binaires précompilés dans `./bin/` ou dans le `PATH`) - `yt-dlp`, `ffmpeg` et `ffprobe` (binaires précompilés dans `./bin/` ou dans le `PATH`)
## Démarrage rapide ## Démarrage rapide
@@ -60,7 +64,8 @@ L'interface est accessible sur [http://localhost:3000](http://localhost:3000).
|----------|--------|---------|-------------| |----------|--------|---------|-------------|
| `DATABASE_URL` | oui | `mysql://ombrora:ombrora@localhost:3306/ombrora` | Connexion MariaDB | | `DATABASE_URL` | oui | `mysql://ombrora:ombrora@localhost:3306/ombrora` | Connexion MariaDB |
| `STORAGE_PATH` | oui | `/var/www/ombrora/storage` | Répertoire de stockage des fichiers téléchargés | | `STORAGE_PATH` | oui | `/var/www/ombrora/storage` | Répertoire de stockage des fichiers téléchargés |
| `BIN_DIR` | non | `./bin` | Répertoire contenant `yt-dlp` et `ffmpeg` (défaut : `./bin`) | | `NEXT_PUBLIC_BASE_URL` | oui | `https://example.com` | URL publique du site, utilisée pour le SEO (canonical, OpenGraph, JSON-LD, sitemap) |
| `BIN_DIR` | non | `./bin` | Répertoire contenant `yt-dlp`, `ffmpeg` et `ffprobe` (défaut : `./bin`) — absent de `.env.example`, à ajouter manuellement si le défaut ne convient pas |
## Scripts npm ## Scripts npm
@@ -87,18 +92,22 @@ L'interface est accessible sur [http://localhost:3000](http://localhost:3000).
Les paramètres applicatifs sont centralisés dans `config/app.config.ts` : Les paramètres applicatifs sont centralisés dans `config/app.config.ts` :
```typescript ```typescript
DOWNLOAD_LINK_TTL_HOURS: 24 // Durée de validité du token de téléchargement DOWNLOAD_LINK_TTL_HOURS: 24 // Durée de validité du token de téléchargement
WORKER_CONCURRENCY: 3 // Téléchargements parallèles maximum WORKER_CONCURRENCY: 3 // Téléchargements parallèles maximum
WORKER_POLL_INTERVAL_MS: 10000 // Intervalle de polling du worker (ms) WORKER_POLL_INTERVAL_MS: 10000 // Intervalle de polling du worker (ms)
RATE_LIMIT_MAX: 5 // Soumissions max par IP par fenêtre RATE_LIMIT_MAX: 5 // Soumissions max par IP par fenêtre
RATE_LIMIT_WINDOW_MS: 3600000 // Fenêtre de rate-limit (1h) RATE_LIMIT_WINDOW_MS: 3600000 // Fenêtre de rate-limit des soumissions (1h)
PROBE_RATE_LIMIT_MAX: 20 // Analyses (probe) max par IP par fenêtre
PROBE_RATE_LIMIT_WINDOW_MS: 3600000 // Fenêtre de rate-limit du probe (1h)
PROBE_TIMEOUT_MS: 20000 // Timeout du probe yt-dlp -J (ms)
``` ```
## API ## API
| Méthode | Endpoint | Description | | Méthode | Endpoint | Description |
|---------|----------|-------------| |---------|----------|-------------|
| `POST` | `/api/downloads` | Soumet une URL. Corps JSON : `{ url, format?, quality?, subtitles?, extraArgs? }`. Retourne `{ uuid }`. | | `POST` | `/api/probe` | Analyse une URL via `yt-dlp -J`. Corps JSON : `{ url }`. Retourne les qualités, sous-titres et durée réellement disponibles pour la vidéo, ou une erreur (`422` si l'analyse échoue, `429` si rate-limit dépassé). |
| `POST` | `/api/downloads` | Soumet une URL. Corps JSON : `{ url, format, quality, subtitles, subtitleLangs?, clipStart?, clipEnd?, audioQuality?, extraArgs? }`. Retourne `{ uuid }`. |
| `GET` | `/api/downloads/:uuid` | Statut d'un téléchargement. Retourne `{ status, fileName, fileSize, downloadToken?, tokenExpiresAt?, errorMsg? }`. | | `GET` | `/api/downloads/:uuid` | Statut d'un téléchargement. Retourne `{ status, fileName, fileSize, downloadToken?, tokenExpiresAt?, errorMsg? }`. |
| `GET` | `/api/download/:token` | Téléchargement du fichier (token à usage unique, 24h). | | `GET` | `/api/download/:token` | Téléchargement du fichier (token à usage unique, 24h). |
@@ -107,26 +116,45 @@ RATE_LIMIT_WINDOW_MS: 3600000 // Fenêtre de rate-limit (1h)
``` ```
src/ src/
├── app/ ├── app/
│ ├── page.tsx # Page d'accueil (formulaire de soumission) │ ├── [locale]/
│ ├── status/[uuid]/page.tsx # Page de suivi │ ├── layout.tsx # Header, thème, next-intl, JSON-LD, PostHog
│ │ ├── page.tsx # Accueil (hero, formulaire, sections SEO)
│ │ ├── [slug]/page.tsx # Pages SEO par plateforme (youtube-downloader, etc.)
│ │ ├── status/[uuid]/page.tsx # Page de suivi
│ │ └── supported-sites/page.tsx # Liste des sites supportés par yt-dlp
│ ├── sitemap.ts # Génération du sitemap.xml
│ └── api/ │ └── api/
│ ├── downloads/route.ts # POST — création │ ├── downloads/route.ts # POST — création
│ ├── downloads/[uuid]/route.ts # GET — statut │ ├── downloads/[uuid]/route.ts # GET — statut
── download/[token]/route.ts # GET — téléchargement ── download/[token]/route.ts # GET — téléchargement
│ └── probe/route.ts # POST — analyse yt-dlp -J
├── components/ ├── components/
│ ├── SubmitForm.tsx # Formulaire de soumission │ ├── SubmitForm.tsx # Formulaire de soumission (options dérivées du probe)
── StatusView.tsx # Vue de suivi (polling) ── StatusView.tsx # Vue de suivi (polling)
│ ├── Header.tsx, LanguageSwitcher.tsx, ThemeToggle.tsx
│ ├── HowItWorksSection.tsx, ReassuranceSection.tsx, SupportedPlatformsSection.tsx
│ └── FaqSection.tsx, FaqAccordion.tsx, SupportedSitesBrowser.tsx
├── i18n/
│ ├── routing.ts # Locales, préfixes d'URL
│ └── request.ts # Config next-intl côté serveur
├── proxy.ts # Middleware next-intl (détection/routage locale)
└── lib/ └── lib/
├── prisma.ts # Client Prisma (singleton) ├── prisma.ts # Client Prisma (singleton)
├── rate-limit.ts # Rate limiting par IP ├── rate-limit.ts # Factory createRateLimiter (downloads + probe)
├── token.ts # Génération/validation de tokens ├── token.ts # Génération/validation de tokens
── ytdlp.ts # Construction des arguments yt-dlp ── ytdlp.ts # Construction des arguments/commande yt-dlp
├── ytdlp-options.ts # Formats/qualités/langues proposés
├── ytdlp-probe.ts # Analyse yt-dlp -J d'une URL
├── downloader-platforms.ts # Données des pages SEO par plateforme
└── supported-sites.ts # Liste des sites supportés par yt-dlp
worker/ worker/
├── index.ts # Boucle de polling principale ├── index.ts # Boucle de polling principale
├── processor.ts # Exécution de yt-dlp, mise à jour DB ├── processor.ts # Exécution de yt-dlp, mise à jour DB
└── cron-cleanup.ts # Nettoyage des fichiers expirés └── cron-cleanup.ts # Nettoyage des fichiers expirés
messages/ # Traductions (en.json, fr.json, es.json, it.json)
ecosystem.config.cjs # Config pm2 du process "ombrora-worker" ecosystem.config.cjs # Config pm2 du process "ombrora-worker"
prisma/ prisma/
@@ -137,8 +165,8 @@ prisma/
### Contraintes spécifiques à l'hébergement mutualisé ### Contraintes spécifiques à l'hébergement mutualisé
- **Pas de compilation sur le serveur** : `yt-dlp` et `ffmpeg` doivent être des binaires précompilés pour la plateforme cible, placés dans `BIN_DIR`. - **Pas de compilation sur le serveur** : `yt-dlp`, `ffmpeg` et `ffprobe` doivent être des binaires précompilés pour la plateforme cible, placés dans `BIN_DIR`.
- **`node_modules` unique** : Passenger ne crée qu'un seul `node_modules` à la racine. Toute dépendance runtime importée par le frontend doit figurer dans le `dependencies` du `package.json` racine (en plus de `frontend/package.json`). - **`node_modules` unique** : Passenger ne crée qu'un seul `node_modules` à la racine. Le projet n'a qu'un seul `package.json` (pas de sous-dossier frontend séparé à synchroniser).
- **Worker** : Passenger ne supervise que l'app Next.js. Le worker est un process à part, maintenu vivant par **pm2** (auto-restart en cas de crash), à la manière de Passenger pour l'app web. - **Worker** : Passenger ne supervise que l'app Next.js. Le worker est un process à part, maintenu vivant par **pm2** (auto-restart en cas de crash), à la manière de Passenger pour l'app web.
### Étapes de déploiement ### Étapes de déploiement
-10
View File
@@ -1,10 +0,0 @@
import { SubmitForm } from '@/components/SubmitForm'
export default function Home() {
return (
<main style={{ padding: '2rem' }}>
<h1>Ombrora</h1>
<SubmitForm />
</main>
)
}
-15
View File
@@ -1,15 +0,0 @@
import { StatusView } from '@/components/StatusView'
export default async function StatusPage({
params,
}: {
params: Promise<{ uuid: string }>
}) {
const { uuid } = await params
return (
<main style={{ padding: '2rem' }}>
<h1>Statut du telechargement</h1>
<StatusView uuid={uuid} />
</main>
)
}