Commit graph

33 commits

Author SHA1 Message Date
8d115622ce Merge pull request 'feat(ui): une zone de dépôt par cible, pause globale' (#15) from feat/upload-ui-v2 into main
All checks were successful
Publier l'image Docker / test (push) Successful in 25s
Publier l'image Docker / publish (push) Successful in 26s
2026-10-14 07:43:14 +02:00
3ef7b09f84 Merge pull request 'fix(session): session glissante prolongée par l'activité' (#14) from fix/sliding-session into main
Some checks failed
Publier l'image Docker / publish (push) Blocked by required conditions
Publier l'image Docker / test (push) Has been cancelled
2026-10-14 07:43:10 +02:00
58588b9dbd feat(ui): une zone de dépôt par cible, pause globale (#10, #11, #13)
- une zone de dépôt par cible à la place du menu déroulant, avec pastille
  de catégorie sur chaque fichier quand il y a plusieurs cibles (#11)
- un seul sélecteur : le doublon « Parcourir un dossier » disparaît, les
  dossiers passent par glisser-déposer (#10)
- bouton « Tout mettre en pause / Tout reprendre » à côté de « Tout annuler » (#13)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 07:39:02 +02:00
8a3fe1407f fix(session): session glissante prolongée par l'activité (#12)
Chaque requête authentifiée repousse l'expiration (au plus une fois par
minute), côté jeton et côté cookie.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 07:36:16 +02:00
f8dd45a6f0 Merge pull request 'feat(ui): pause, réessayer, titres tronqués et pleine largeur' (#9) from feat/upload-ui-controls into main
All checks were successful
Publier l'image Docker / test (push) Successful in 21s
Publier l'image Docker / publish (push) Successful in 44s
2026-10-13 20:32:59 +02:00
5ef89e87a4 feat(ui): pause, réessayer, titres tronqués et pleine largeur
- bouton Pause/Reprendre par fichier (#6)
- bouton Réessayer sur les échecs (#7)
- noms de fichiers tronqués avec ellipse + titre au survol (#5)
- contenu pleine largeur (#4)
- cible figée à l'ajout, nouvelle tentative d'init sur 429 (#1, #3)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 20:32:41 +02:00
f4bd82f9ad Merge pull request 'fix(server): destination des uploads fragmentés et gestion des erreurs' (#8) from fix/server-upload-errors into main
Some checks failed
Publier l'image Docker / publish (push) Blocked by required conditions
Publier l'image Docker / test (push) Has been cancelled
2026-10-13 20:32:34 +02:00
246b078fec fix(server): destination des uploads fragmentés et gestion des erreurs
- init fragmenté : lit target/path dans la query (corps en repli) (#1)
- erreurs client interrompu / disque plein / fichier partiel absent mappées
  en 400/507/404 sans webhook d'échec pour les interruptions (#2)
- nettoyage des sessions expirées et des .part orphelins sécurisé,
  log des unhandledRejection (#2)
- plafond de sessions porté à 256 pour l'usage multi-fenêtres (#3)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 20:32:17 +02:00
806270e57b fix
All checks were successful
Publier l'image Docker / test (push) Successful in 17s
Publier l'image Docker / publish (push) Successful in 10s
2026-09-19 15:48:12 +02:00
6b6b67a31c revert
Some checks failed
Publier l'image Docker / test (push) Failing after 13s
Publier l'image Docker / publish (push) Has been skipped
2026-09-19 15:46:09 +02:00
5d2cf9a2f3 log console
All checks were successful
Publier l'image Docker / test (push) Successful in 17s
Publier l'image Docker / publish (push) Successful in 11s
2026-09-19 15:34:32 +02:00
f05f240d4d fix
All checks were successful
Publier l'image Docker / test (push) Successful in 18s
Publier l'image Docker / publish (push) Successful in 42s
2026-09-19 15:22:50 +02:00
4f2bed5932 fix tests
Some checks failed
Publier l'image Docker / test (push) Failing after 17s
Publier l'image Docker / publish (push) Has been skipped
2026-09-19 15:18:07 +02:00
490b9ac61f fix tests
Some checks failed
Publier l'image Docker / test (push) Failing after 17s
Publier l'image Docker / publish (push) Has been skipped
2026-09-19 15:04:28 +02:00
4f8f06d9da fix test
Some checks failed
Publier l'image Docker / test (push) Failing after 15s
Publier l'image Docker / publish (push) Has been skipped
2026-09-19 15:01:40 +02:00
52ace91580 change config
Some checks failed
Publier l'image Docker / test (push) Failing after 21s
Publier l'image Docker / publish (push) Has been skipped
2026-09-19 14:59:16 +02:00
9758fe75f9 update config 2026-09-19 14:49:47 +02:00
0962c2cc1f Page de connexion distincte avant la page de dépôt
All checks were successful
Publier l'image Docker / test (push) Successful in 22s
Publier l'image Docker / publish (push) Successful in 12s
`GET /` décide côté serveur, à chaque requête, laquelle des deux pages
servir : `public/login.html` (aucune session valide — juste le bouton
« Se connecter », aucune zone de dépôt dans le HTML) ou
`public/upload.html` (session valide — nom d'utilisateur injecté et
échappé, bouton « Se déconnecter »). Nouveau `src/routes/pages.ts`.

Simplifications côté page de dépôt, permises par cette garantie
serveur : plus de fetch `/auth/me` ni de bascule `authenticated` en
JS, plus de retrait/réinsertion de la zone de dépôt dans le DOM — elle
est toujours présente puisque la page n'est servie qu'aux sessions
valides. Une session qui expire en cours d'usage (401 sur /targets ou
/upload*) renvoie vers `/?login=expired`, qui affiche alors la vraie
page de connexion avec le message adapté.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:49:21 +02:00
615d0fd45a Types MIME configurables par cible d'upload
All checks were successful
Publier l'image Docker / test (push) Successful in 24s
Publier l'image Docker / publish (push) Successful in 13s
`targets[].allowedMimeTypes` : chaque cible peut définir sa propre
politique (mêmes formes : "all", types exacts, jokers "image/*"…).
Absente => la cible hérite du `allowedMimeTypes` global, qui devient
la valeur par défaut. Résolu au parsing : chaque `DropTarget` porte
toujours une politique.

- `POST /upload` et `POST /upload/init` valident contre
  `target.allowedMimeTypes` (au lieu de la valeur globale).
- `GET /targets` renvoie `allowedMimeTypes` ; la page en déduit
  l'attribut `accept` du sélecteur de fichiers (le serveur reste
  l'autorité — le glisser-déposer n'est pas filtré).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 22:22:30 +02:00
60d7ba1c39 Corrige la déconnexion : accepter le POST de formulaire (urlencoded)
All checks were successful
Publier l'image Docker / test (push) Successful in 17s
Publier l'image Docker / publish (push) Successful in 9s
Le <form> de déconnexion envoie `application/x-www-form-urlencoded` ;
Fastify n'ayant aucun parseur pour ce type répondait 415
(FST_ERR_CTP_INVALID_MEDIA_TYPE) avant même d'atteindre le handler.

Ajout d'un parseur `application/x-www-form-urlencoded` (URLSearchParams
-> objet, sans dépendance). Le test de déconnexion envoie désormais
l'en-tête du navigateur pour couvrir le cas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 19:48:15 +02:00
8d8cfb6fad UI : liste scrollable, alignement auth, zones de dépôt retirées du DOM hors session
All checks were successful
Publier l'image Docker / test (push) Successful in 19s
Publier l'image Docker / publish (push) Successful in 8s
- `#list` plafonné à 55vh avec défilement interne (au lieu d'allonger la page).
- En-tête d'auth : suppression du <span> qui cassait l'alignement ; « Connecté : … »
  et le bouton « Se déconnecter » sont alignés (flex, space-between).
- Les zones de dépôt (`#dropzone` + `#dirRow`, regroupées dans `#dropArea`) sont
  retirées du DOM tant que l'on n'est pas connecté, réinsérées à la connexion.
- Ajout de `[hidden] { display: none !important }` : des règles `display` d'auteur
  (`.btn`, `#queueRow`) neutralisaient l'attribut `hidden` — bouton de déconnexion
  et barre « Tout annuler » restaient visibles hors contexte.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 19:40:49 +02:00
8eb0470474 Notifications par webhook (upload finalisé / échec)
All checks were successful
Publier l'image Docker / test (push) Successful in 22s
Publier l'image Docker / publish (push) Successful in 10s
Nouvelle clé `webhooks` : liste de points de terminaison POST notifiés à
chaque upload finalisé (`upload`, direct comme fragmenté) et à chaque
requête `/upload*` terminée en 4xx/5xx (`error`).

Chaque entrée : `url` (http/https), `events` (défaut : les deux),
`headers` (auth du récepteur), `secret` (signature HMAC-SHA256 dans
`X-MediaDrop-Signature`), `timeoutMs` (défaut 5 s, max 30 s).

Corps JSON structuré + champs `text`/`content` portant un résumé lisible,
pour fonctionner tel quel avec Slack / Discord / ntfy ou un récepteur
custom. Livraison en parallèle, sans bloquer la réponse d'upload ; une
nouvelle tentative sur erreur réseau / 429 / 5xx ; échecs journalisés en
`warn` ; l'arrêt du serveur attend les envois en cours. Sans webhook
configuré, le notifieur est une non-opération.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 19:29:44 +02:00
64a38b1e41 Upload fragmenté : pool de connexions partagé et configurable
All checks were successful
Publier l'image Docker / test (push) Successful in 17s
Publier l'image Docker / publish (push) Successful in 11s
Remplace les 3 workers par fichier par un pool global de morceaux en vol,
réparti équitablement entre les fichiers fragmentés actifs : 1 fichier
utilise tout le pool, N fichiers en reçoivent une part chacun
(ceil(taille / N)), la capacité libérée par un fichier terminé est
redistribuée. Un fichier en cours d'init compte dans la part pour ne pas
être affamé ; les morceaux déjà en vol ne sont jamais interrompus.

La taille du pool est réglable via `chunkPoolSize` (config.json, défaut
30, borné à 256) et transmise au navigateur par un nouveau `GET /config`.

Corps des morceaux repassé en streaming (writeChunkAt sur un flux + garde
`maxBytes`) : mémoire ~constante même à pool élevé, plus de bufferisation
de 16 Mo par requête.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 19:12:04 +02:00
3a555c0bc9 CI : login registre via un secret REGISTRY_TOKEN
All checks were successful
Publier l'image Docker / test (push) Successful in 16s
Publier l'image Docker / publish (push) Successful in 19s
Le jeton automatique de Forgejo Actions n'a pas le droit paquets sur cette
instance (unauthorized: reqPackageAccess). Le job publish utilise désormais
secrets.REGISTRY_TOKEN (jeton d'accès write:package) et échoue tôt avec un
message clair s'il est absent. Nom d'utilisateur = propriétaire du dépôt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 18:27:43 +02:00
1b429049c2 Upload fragmenté et reprenable pour les gros fichiers
Some checks failed
Publier l'image Docker / test (push) Successful in 19s
Publier l'image Docker / publish (push) Failing after 9s
Serveur :
- POST /upload/init -> session { id, chunkSize (16 Mo), chunkCount }
- PUT /upload/:id/:index -> écrit le morceau à son offset (octet-stream brut,
  bufferisé, parser dédié) ; renvoie { received, total }
- POST /upload/:id/complete -> vérifie tous les morceaux + la taille, renomme
  le .mediadrop-part vers le chemin final ; 201 + métadonnées
- DELETE /upload/:id -> abandon, supprime le fichier partiel
- src/lib/upload-sessions.ts : registre en mémoire, purge après 30 min
  d'inactivité, 100 sessions max
- src/lib/targets.ts : resolveTarget / resolvePlacement extraits et partagés
- writeChunkAt / createEmptyFile / finalizeFile / removeFile dans storage.ts
- limites, allowDirectories, jokers MIME et confinement dans la cible
  s'appliquent aussi au chemin fragmenté

Page :
- bascule automatique en fragmenté au-delà de 32 Mo : 3 morceaux en parallèle,
  3 réessais par morceau, progression et annulation agrégées
- makeReporter partagé (débit + ETA) ; job.cancel unifié (simple / fragmenté /
  en file d'attente)

Tests : +tests/upload-chunked.test.ts (9), +writeChunkAt (storage) ; 73 tests.
Vérifié en live : 40 Mio -> 3 morceaux // -> octets exacts, DELETE nettoie.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 18:25:27 +02:00
5a1219f00d Page : progression détaillée, annulation, file d'attente
Some checks failed
Publier l'image Docker / test (push) Failing after 0s
Publier l'image Docker / publish (push) Has been skipped
- Progression en temps réel par fichier : transféré / total, %, débit
  (moyenne glissante), ETA.
- Annulation : bouton « Annuler » par fichier (en attente ou en cours) +
  « Tout annuler ». xhr.abort() -> le serveur nettoie déjà le fichier partiel.
- File d'attente : au plus 3 transferts simultanés, le reste patiente
  (évite de saturer le lien sur un dépôt de dossier). Bandeau récap.

Changements purement page ; aucune modif serveur. 62 tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 17:59:18 +02:00
0f5bdb7080 allowedMimeTypes : jokers de préfixe
Some checks failed
Publier l'image Docker / test (push) Successful in 18s
Publier l'image Docker / publish (push) Failing after 11s
isAllowedMedia gère désormais "video/*", "*/*" et "*" en plus des types
exacts (comparaison insensible à la casse). parseConfig valide la forme des
entrées et lève ConfigError sur un joker malformé ("video/**", "vi*deo/mp4",
"video/") au lieu de laisser passer une liste qui ne matche rien.

Schéma + doc mis à jour. 62 tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 09:18:52 +02:00
5bff28fefe CI : checkout Git direct dans le job publish
Some checks failed
Publier l'image Docker / test (push) Successful in 24s
Publier l'image Docker / publish (push) Failing after 7s
Le conteneur docker:27-cli (Alpine) n'a pas Node -> actions/checkout (action
JS) échoue avec « exec: "node": not found ». Remplacé par un clone Git du
dépôt public (git init + fetch --depth 1 + checkout FETCH_HEAD).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 08:58:10 +02:00
7be173e08e CI Forgejo : test + publication de l'image Docker
Some checks failed
Publier l'image Docker / test (push) Successful in 1m32s
Publier l'image Docker / publish (push) Failing after 19s
.forgejo/workflows/publish-docker.yml
- job `test` : npm ci / lint / typecheck / test (conteneur node:22-bookworm)
- job `publish` (needs: test) : docker build + push vers
  git.dev.kyuno.fr/kyuno/mediadrop (conteneur docker:27-cli)
- push main -> latest + <sha court> ; tag vX.Y.Z -> X.Y.Z, X.Y, latest ;
  workflow_dispatch pour lancement manuel
- auth registre via secrets.GITHUB_TOKEN (permissions: packages: write),
  repli documenté sur un secret REGISTRY_TOKEN
- prérequis (runner, socket Docker, accès réseau) détaillés en tête du fichier

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 08:48:23 +02:00
b85607b096 Dépôt de dossiers + très gros fichiers
Dépôt de dossiers :
- flag `allowDirectories` (défaut false) par cible dans config.json + schéma.
- POST /upload accepte `?path=<chemin relatif>` : l'arborescence est recréée
  sous `dir`, nom d'origine conservé, fichier écrasé si présent.
- src/lib/paths.ts : assainissement segment par segment (pas de `..`, pas de
  séparateur) + vérification de confinement dans storage.ts -> aucune sortie
  possible de la cible. Refus 400 si la cible n'autorise pas les dossiers.
- GET /targets expose `allowDirectories`.
- Page : glisser-déposer d'un dossier (parcours récursif webkitGetAsEntry) +
  bouton « parcourir un dossier », visibles selon la cible.

Très gros fichiers (~10 Go) :
- `requestTimeout: 0` / `connectionTimeout: 0` : aucun timeout serveur imposé.
- écriture disque avec un tampon de 1 Mio.
- doc : `maxFileSizeMb` à l'échelle Go, réglages reverse proxy.

Tests : +paths.test.ts, +placement (storage), +dossier & gros fichier (upload) ;
60 tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 00:19:20 +02:00
f9f17bd169 Ajout d'un compose.yaml
- Service unique : build local (ou image publiée en commentaire), non-root,
  restart: unless-stopped.
- config.json monté en lecture seule ; un montage ./data:/data pour les
  dossiers de dépôt (les `dir` des targets pointent sous /data).
- Commentaires sur l'uid 1000, le remap de port, l'override `user:`.
- .gitignore / .dockerignore : ajout de data/ ; README : section Docker
  réécrite autour de Compose.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 23:25:11 +02:00
f32a04c56f Ajout d'un Dockerfile multi-stage + .dockerignore
- Build en 3 étapes (deps prod / compilation TS / runtime), image
  node:22-bookworm-slim exécutée en non-root (utilisateur `node`).
- Debian slim et non Alpine : @fastify/secure-session dépend de
  sodium-native, dont les prebuilds linux-x64/arm64 ciblent la glibc.
- config.json et dossiers de dépôt montés en volumes, jamais dans l'image
  (.dockerignore exclut .env, config.json, uploads, node_modules, dist...).
- HEALTHCHECK sur GET /health ; section Docker ajoutée au README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 23:12:21 +02:00
fe8bd26bb7 Initial commit: mediaDrop — serveur de dépôt de fichiers médias
Serveur TypeScript (Fastify 5) permettant de déposer des fichiers médias
dans des dossiers nommés, via une page web de glisser-déposer.

- Authentification OpenID Connect gérée côté serveur (Authorization Code +
  PKCE, client confidentiel, openid-client) ; session via cookie chiffré
  (@fastify/secure-session).
- Configuration dans un fichier JSON (schéma fourni) ; l'environnement ne
  porte que le pointeur MEDIADROP_CONFIG.
- Cibles de dépôt nommées : liste { name, label, dir }, sélection par
  ?target=, première cible par défaut.
- Upload multipart en streaming : nom de fichier dérivé côté serveur (pas
  de path traversal), liste blanche de types MIME, plafond de taille.
- TypeScript strict, ESLint + Prettier sans warning, 46 tests Vitest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 23:06:53 +02:00