14 KiB
14 KiB
VaultDrop Backend — Spécification V1 (État Existant)
⚠️ Obsolète — référentiel actuel :
docs/api-v1.md(contrat API) etV2.md(modèle cible). L'OCR est décidé en Tesseract (appel système), pas de microservice Python.
1. Vue d'ensemble
Application de gestion de documents : upload, scan OCR, tagging, recherche, organisation en dossiers. Backend Go avec API REST, base PostgreSQL, microservice OCR Python séparé.
┌──────────────────┐ ┌──────────────────────┐
│ Mobile App │ HTTPS │ Go Backend (Gin) │
│ (React Native) │────────>│ REST API │
│ │ │ - Upload / Tags │
│ │ │ - Auth (PASETO) │
│ │ │ - Thumbnails │
└──────────────────┘ │ - Conversion PDF │
└────────┬─────────────┘
│ HTTP
┌────────▼─────────────┐
│ OCR Server (Python) │
│ PaddleOCR + FastAPI │
└──────────────────────┘
2. Choix Techniques
| Composant | Technologie |
|---|---|
| Framework HTTP | Gin v1.12 |
| Base de données | PostgreSQL (lib/pq) |
| ORM/Query | sqlc v1.31 (généré) |
| Migrations | golang-migrate v4 |
| Auth | PASETO v4 (go-paseto) + Argon2id |
| OCR | PaddleOCR (microservice Python séparé) |
| Conversion PDF | LibreOffice + pdftoppm (appels système) |
| Checksum | SHA-256 |
| Stockage fichiers | Disque local (./uploads/) |
| URLs signées | HMAC-SHA256 avec expiry |
3. Structure du Code
backend/
├── cmd/server/main.go # Point d'entrée
├── internal/
│ ├── auth/ # Auth (3 fichiers)
│ │ ├── service.go # Register, Login, Refresh, Logout, ValidateAccessToken
│ │ ├── handler.go # HTTP handlers auth
│ │ └── middleware.go # RequireAuth() middleware
│ ├── config/config.go # Config struct (11 vars d'env)
│ ├── db/
│ │ ├── connect.go # Connexion PostgreSQL
│ │ ├── migrate.go # golang-migrate
│ │ ├── db.go # sqlc Queries interface
│ │ ├── models.go # sqlc models (généré)
│ │ ├── files.sql.go # sqlc queries fichiers (généré)
│ │ ├── tags.sql.go # sqlc queries tags (généré)
│ │ ├── thumbnails.sql.go # sqlc queries thumbnails (généré)
│ │ ├── health.sql.go # sqlc health check
│ │ ├── auth.sql.go # sqlc queries auth (généré)
│ │ ├── queries/ # SQL sources (5 fichiers .sql)
│ │ └── migrations/ # 11 migrations (001→011)
│ ├── handler/
│ │ ├── handler.go # Handler struct + constructor
│ │ ├── router.go # SetupRoutes()
│ │ ├── files.go # 14 handlers fichiers/thumbnails
│ │ ├── ocr.go # OCR stubs (501/404)
│ │ └── health.go # Health check
│ ├── model/
│ │ ├── file.go # File, Tag, UploadResult structs
│ │ ├── ocrjob.go # OcrJob struct
│ │ ├── tag.go # (vide)
│ │ └── thumbnail.go # Thumbnail struct
│ ├── ocr/
│ │ ├── client.go # Client HTTP PaddleOCR + détection doc type
│ │ └── types.go # OCR request/response types
│ └── service/
│ ├── file.go # FileService: upload, list, get, delete, tags, folders, thumbnails, dedup
│ ├── ocr.go # OCRService: worker async, enqueue, process
│ ├── conversion.go # ConversionService: PDF→images, thumbnails multi-résolution
│ ├── url.go # URLService: signed URLs HMAC
│ └── checksum.go # SHA-256 hash helpers
├── pkg/api/response.go # Helpers: Success, Created, Paginated, Error
├── ocr-server/
│ ├── server.py # FastAPI + PaddleOCR
│ └── Dockerfile
├── uploads/ # Stockage fichiers + thumbnails
├── sqlc.yaml
├── Dockerfile
├── go.mod
└── .env
4. Base de Données (Schéma Final)
Tables
files
| Colonne | Type | Contraintes |
|---|---|---|
| id | TEXT | PK, DEFAULT gen_random_uuid() |
| name | TEXT | NOT NULL |
| mime_type | TEXT | NOT NULL DEFAULT '' |
| size | BIGINT | NOT NULL DEFAULT 0 |
| storage_key | TEXT | NOT NULL DEFAULT '' |
| checksum | TEXT | NOT NULL DEFAULT '' |
| ocr_text | TEXT | NOT NULL DEFAULT '' |
| is_folder | BOOLEAN | NOT NULL DEFAULT false |
| parent_file_id | TEXT | FK → files(id) |
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP |
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP |
tags
| Colonne | Type | Contraintes |
|---|---|---|
| id | TEXT | PK, DEFAULT gen_random_uuid() |
| parent_tag_id | TEXT | FK → tags(id) |
| tag_name | TEXT | NOT NULL |
| tag_type | TEXT | NOT NULL DEFAULT 'none' |
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP |
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP |
file_tags
| Colonne | Type | Contraintes |
|---|---|---|
| id | TEXT | PK, DEFAULT gen_random_uuid() |
| tag_id | TEXT | FK → tags(id) |
| file_id | TEXT | FK → files(id) |
users
| Colonne | Type | Contraintes |
|---|---|---|
| id | TEXT | PK, DEFAULT gen_random_uuid() |
| username | TEXT | NOT NULL, UNIQUE |
| password_hash | TEXT | NOT NULL |
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP |
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP |
refresh_tokens
| Colonne | Type | Contraintes |
|---|---|---|
| id | TEXT | PK, DEFAULT gen_random_uuid() |
| user_id | TEXT | FK → users(id), ON DELETE CASCADE |
| token_hash | TEXT | NOT NULL |
| expires_at | TIMESTAMP | NOT NULL |
| revoked | BOOLEAN | DEFAULT FALSE |
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP |
thumbnails
| Colonne | Type | Contraintes |
|---|---|---|
| id | TEXT | PK, DEFAULT gen_random_uuid() |
| file_id | TEXT | FK → files(id), ON DELETE CASCADE |
| page_number | INTEGER | NOT NULL |
| resolution_label | TEXT | NOT NULL |
| width | INTEGER | NOT NULL |
| height | INTEGER | NOT NULL |
| storage_key | TEXT | NOT NULL |
| mime_type | TEXT | DEFAULT 'image/jpeg' |
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP |
Index
idx_refresh_tokens_user_idON refresh_tokens(user_id)idx_refresh_tokens_token_hashON refresh_tokens(token_hash)idx_thumbnails_file_idON thumbnails(file_id)idx_thumbnails_uniqueUNIQUE ON thumbnails(file_id, page_number, resolution_label)
5. API Endpoints
Public
| Méthode | Path | Handler | Description |
|---|---|---|---|
| GET | /api/v1/health |
HealthHandler.Check | Retourne {status: "healthy"} |
| POST | /api/v1/auth/register |
AuthHandler.Register | Créer un compte (username + password) |
| POST | /api/v1/auth/login |
AuthHandler.Login | Login → access_token + refresh_token |
| POST | /api/v1/auth/refresh |
AuthHandler.Refresh | Renouveler les tokens |
| POST | /api/v1/auth/logout |
AuthHandler.Logout | Révoquer le refresh token |
| GET | /api/v1/files/download/:id |
FileHandler.Download | Télécharger un fichier (URL signée) |
| GET | /api/v1/thumbnails/:id |
FileHandler.ServeThumbnail | Télécharger une thumbnail (URL signée) |
Protégées (Bearer token)
| Méthode | Path | Handler | Description |
|---|---|---|---|
| GET | /api/v1/files |
FileHandler.List | Lister tous les fichiers (racine) |
| POST | /api/v1/files/upload |
FileHandler.Upload | Upload multi-fichiers (multipart) |
| POST | /api/v1/files/move |
FileHandler.MoveFiles | Déplacer des fichiers vers un dossier |
| POST | /api/v1/files/folders |
FileHandler.CreateFolder | Créer un dossier |
| GET | /api/v1/files/folders |
FileHandler.ListFolders | Lister les dossiers |
| GET | /api/v1/files/folders/:id/files |
FileHandler.ListFilesByParent | Lister les fichiers d'un dossier |
| DELETE | /api/v1/files/:id |
FileHandler.Delete | Supprimer un fichier (+ thumbnails + fichier disque) |
| GET | /api/v1/files/:id |
FileHandler.Get | Détail d'un fichier (+ tags + thumbnails) |
| POST | /api/v1/files/:id/tags |
FileHandler.AddTags | Ajouter des tags à un fichier |
| GET | /api/v1/files/:id/tags |
FileHandler.GetTags | Récupérer les tags d'un fichier |
| POST | /api/v1/files/:id/thumbnails |
FileHandler.GetThumbnails | Lister les thumbnails d'un fichier |
| POST | /api/v1/files/dedup-check |
FileHandler.CheckDuplicates | Vérifier les doublons (nom + taille) |
| POST | /api/v1/ocr/jobs |
OCRHandler.CreateJob | STUB — retourne 501 |
| GET | /api/v1/ocr/jobs/:id |
OCRHandler.GetJobStatus | STUB — retourne 404 |
Format de réponse
Succès : { "data": ... } ou { "data": ..., "meta": { "page": N, "total": N } }
Erreur : { "error": { "code": "...", "message": "..." } }
6. Services Métier
FileService (service/file.go — 349 lignes)
Upload(file)— Upload multipart, checksum SHA-256, détection doublons par checksum, stockage disqueList()— Tous les fichiers racine (parent_file_id IS NULL), avec tagsGet(id)— Fichier par ID avec tagsDelete(id)— Suppression DB (handler gère suppression disque + thumbnails)GetStoragePath(id)— Chemin disque du fichierUpdateOCRText(id, text)— Mise à jour du champ ocr_textAddTags(fileID, tags, tagType)— Création auto des tags + liaisonGetTagsByFileID(fileID)— Tags d'un fichierMoveFiles(fileIDs, parentFileID)— Déplacer vers un dossier (ou racine si nil)CreateFolder(name)— Créer un dossierListFolders()— Lister tous les dossiersListFilesByParentID(parentID)— Fichiers d'un dossierGetThumbnailsByFileID(fileID)— Thumbnails d'un fichierGetThumbnailStoragePath(id)— Chemin disque thumbnailGetBestThumbnail(fileID, label)— Meilleure thumbnail (page 1, label préféré ou fallback)FindDuplicatesByNameSize(name, size)— Recherche doublons par nom+taille
OCRService (service/ocr.go — 95 lignes)
Start()— Lance le worker goroutineStop()— Ferme le channel jobsEnqueue(fileID, filePath)— Ajoute un job OCR (channel buffer 100)process(job)— Lecture fichier → Client OCR → Flatten text → UpdateOCRTextRecognizeFromBytes(data)— OCR direct (pas utilisé par les handlers)HealthCheck()— Vérifie l'OCR server
ConversionService (service/conversion.go — 253 lignes)
Start()/Stop()— Worker goroutineEnqueue(fileID, filePath, mimeType)— Conversion async (channel buffer 100)process(job):- Si document Office → conversion vers PDF via LibreOffice headless
- Si PDF → direct
- PDF → images via pdftoppm à 2 résolutions : "thumbnail" (21 DPI) et "full" (200 DPI)
- Création des enregistrements thumbnails en DB
- Supporte : PDF, .docx, .xlsx, .pptx, formats OpenDocument
URLService (service/url.go — 67 lignes)
GenerateDownloadURL(fileUUID)— URL signée HMAC avec expiry (défaut 60 min)GenerateThumbnailURL(thumbUUID)— Idem pour thumbnailsValidate(fileID, sig, expires)— Vérifie signature HMAC + expiry
Auth (auth/ — 250+135+41 lignes)
- Register : validation username (3-30 chars) + password (8+ chars), Argon2id hash, PASETO v4 tokens
- Login : vérification credentials, émission token pair
- Refresh : révoque l'ancien refresh token, émet un nouveau pair
- Logout : révoque le refresh token
- Middleware : Bearer token extraction → ValidateAccessToken → set userID in context
- Access token TTL : 30 min | Refresh token TTL : 7 jours
7. OCR Server (Python)
ocr-server/server.py — FastAPI + PaddleOCR
GET /health— health checkPOST /ocr— OCR via JSON base64POST /ocr/upload— OCR via upload fichier- Supporte images (JPEG/PNG) et PDF (conversion vers images via pypdfium2)
- Langue par défaut : français (
OCR_LANG=fr) - Timeout client Go : 120 secondes
8. Détection de Doublons
Deux mécanismes :
- Par checksum (SHA-256) : lors de l'upload, si un fichier avec le même checksum existe déjà, le fichier uploadé est supprimé et l'ID existant retourné
- Par nom+taille : endpoint
POST /files/dedup-checkpour vérifier avant upload
9. Variables d'Environnement
| Variable | Défaut | Description |
|---|---|---|
| PORT | 8080 |
Port du serveur |
| DATABASE_URL | postgres://localhost:5432/vaultdrop?sslmode=disable |
URL PostgreSQL |
| OCR_ENDPOINT | http://localhost:9090 |
URL du microservice OCR |
| UPLOAD_DIR | ./uploads |
Répertoire de stockage |
| HMAC_SECRET | thisismyrandomstring |
Secret pour URLs signées |
| SERVER_HOST | http://192.168.1.17:8080 |
Host public du serveur |
| PASETO_KEY | (clé hex 64 car.) | Clé symétrique PASETO v4 |
| LIBREOFFICE_PATH | /usr/bin/libreoffice |
Chemin LibreOffice |
| PDFTOPPM_PATH | /usr/bin/pdftoppm |
Chemin pdftoppm |
| THUMBNAIL_DIR | ./uploads/thumbnails |
Répertoire thumbnails |
| URL_EXPIRY_MINUTES | 60 |
Durée de vie des URLs signées |
10. Docker
Backend : Multi-stage build (golang:1.24-alpine → alpine:3.21 avec libreoffice-core + poppler-utils)
OCR Server : Dockerfile séparé dans ocr-server/
11. Ce qui manque / Stubs
OCRHandler.CreateJob→ retourne 501 NOT_IMPLEMENTEDOCRHandler.GetJobStatus→ retourne 404 JOB_NOT_FOUND- Pas de recherche full-text (pas de FTS5/PostgreSQL tsvector)
- Pas de pagination côté DB (tout est chargé en mémoire)
- Pas de gestion d'erreurs robuste dans le worker OCR (logs seulement)
- Les fichiers n'ont pas de lien avec les utilisateurs (pas de user_id sur files)
- Le health check ne vérifie pas la DB ni l'OCR server