Projet 02 / 04
Web app · Sécurité · monorepo full-stack

Paper
Vault.

Un coffre-fort numérique privé pour les documents administratifs qu'on ne peut pas se permettre de perdre. Une application full-stack où la sécurité est le sujet, pas une feature qu'on coche.

Next.js 15 TypeScript PostgreSQL Drizzle ORM Clerk Cloudflare R2 Playwright
Scope Conception · Dev · Prod
Type Web app full-stack · monorepo
Architecture pnpm · Turborepo · 4 packages
Année 2026
localhost:3000/dashboard
Dashboard PaperVault — bannière mode démo, navigation par dossiers, recherche, filtres et grille des 15 documents
app privée · /dashboard · mode démo read-only Fig. 01 — vue d'ensemble, 15 documents, capture Playwright
(01)

Le problème.

Les documents qui comptent vraiment sont toujours ceux qu'on ne retrouve pas le jour où on en a besoin.

Pièces d'identité, contrats, fiches de paie, avis d'imposition : ces documents sont dispersés entre des tiroirs physiques, des dossiers email et des clouds génériques. Le jour d'un dossier de location ou d'une déclaration, on perd du temps à les chercher, sans garantie de tomber sur la bonne version. Le problème n'est pas le stockage : c'est l'organisation, la récupération, et la confiance qu'on peut placer dans un endroit où vivent des données privées.

(02)

La solution.

Un espace privé, organisé par dossiers thématiques, où chaque document appartient à une seule personne : la sienne.

PaperVault centralise tout dans cinq dossiers (Identité, Finances, Logement, Santé, Emploi) et par type de document. On uploade ses PDF et images, on les classe, on les retrouve par recherche ou par filtre, on les télécharge via des liens signés temporaires. Chaque document est lié à son propriétaire et invisible aux autres, vérifié à chaque requête.

Identité & État civil Finances & Impôts Logement Santé Emploi & Revenus
(03)

Ce que j'ai construit.

Quatre décisions d'ingénierie. Chaque couche est implémentée, pas seulement consommée.

B1 Drizzle · Neon

Un modèle relationnel à trois tables, pensé pour le multi-fichiers.

folders, documents, documentFiles : contraintes d'unicité, cascades et indexes. Un document peut porter plusieurs fichiers ; chaque ligne connaît son propriétaire. Le schéma Drizzle est la source de vérité, partagé en types end-to-end avec le reste du monorepo.

B2 Cloudflare R2 · AWS SDK

Un stockage objet qui ne fait confiance ni à l'extension, ni au nom.

Validation MIME par magic bytes (pas par l'extension), noms de fichiers sanitisés, liens signés à durée de vie courte (5 min), et nettoyage R2 avant toute suppression en base. Un fichier ne traîne jamais sans owner, ni un objet orphelin après suppression.

B3 Clerk · Zod · rate limiting

Une API RESTful où la sécurité est un réflexe, pas un afterthought.

12+ endpoints derrière le middleware Clerk, avec vérification de propriété par ligne sur chaque query, rate limiting par utilisateur et par action, validation Zod côté serveur. En profondeur : CSP, HSTS, X-Frame-Options DENY, Permissions-Policy restrictive. Aucune donnée n'existe sans session authentifiée.

B4 Playwright · CI

Un mode démo qui se prouve, et des screenshots reproductibles.

Un flag d'environnement bloque toutes les mutations, côté API (403) comme côté UI, sur 15 documents fictifs seedés. Un test Playwright rejoue le parcours utilisateur, intercepte POST/PUT/PATCH/DELETE, échoue si une mutation est tentée, et capture six images cohérentes à chaque run. Le recruteur voit des captures de l'app réelle, sans risque d'état cassé.

Filtrer, puis ouvrir.

Le parcours réel : on filtre par dossier, puis on ouvre la fiche d'un document. Captures Playwright, données de démo.
/dashboard?dossier=sante
Dashboard filtré sur le dossier Santé — Carte Vitale, ordonnance médicale, relevé de remboursements
Dashboard filtré sur le dossier Santé — Carte Vitale, ordonnance médicale, relevé de remboursements
Fig. 02 — filtre par dossier, 3 documents Santé via l'API
/documents/[id]
Page détail d'un document — métadonnées format, date, dossier et section fichiers associés avec taille et téléchargement
Page détail d'un document — métadonnées format, date, dossier et section fichiers associés avec taille et téléchargement
Fig. 03 — fiche document, métadonnées + fichiers associés
(04)

Contraintes techniques.

Les règles que je me suis imposées pour que le mode démo reste sûr.

C1 · Auth

Dashboard protégé

Toutes les routes /dashboard et /api passent par le middleware Clerk. Aucune donnée accessible sans session.

C2 · Démo

Read-only forcé

Upload, suppression et modification renvoient 403. L'UI désactive les boutons correspondants.

C3 · Données

Zéro donnée réelle

15 documents fictifs sous un user dédié. Le seed est destructif et interdit en production.

C4 · QA

Pas de mutation en capture

Playwright bloque POST/PUT/PATCH/DELETE. Le test échoue si une mutation est tentée : screenshots strictement read-only.

(05)

Le résultat.

Avant

Des documents éparpillés entre tiroirs, mails et clouds. La bonne version, jamais là au bon moment.

Après

Un coffre privé, organisé par dossier, où chaque pièce se retrouve en deux clics, avec une sécurité qui tient sans qu'on y pense.

Déployé sur Vercel, monitoré avec Sentry, testé sur chaque push via GitHub Actions (lint, types, tests, build). Le package mobile Expo/React Native partage le même monorepo et les mêmes schémas Zod. Ce que le projet démontre, ce n'est pas une feature : c'est une manière de construire, avec un projet full-stack tenu jusqu'au détail, une sécurité par défaut, et un workflow QA qui garantit qu'on voit toujours l'app telle qu'elle est vraiment.

Vercel Sentry GitHub Actions Vitest Expo / React Native
Projet suivant · 03 / 04
T'as pris ? →
Me contacter
© 2026 Nhat-Quan Ho Nguyen · Product builder full-stack Ce site : Astro · statique · zéro JS inutile. Mes apps : Next. L'outil suit le besoin.