Files
Kazier/V1.md
T
2026-07-28 18:12:56 +02:00

316 lines
14 KiB
Markdown

# VaultDrop Backend — Spécification V1 (État Existant)
## 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_id` ON refresh_tokens(user_id)
- `idx_refresh_tokens_token_hash` ON refresh_tokens(token_hash)
- `idx_thumbnails_file_id` ON thumbnails(file_id)
- `idx_thumbnails_unique` UNIQUE 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 disque
- `List()` — Tous les fichiers racine (parent_file_id IS NULL), avec tags
- `Get(id)` — Fichier par ID avec tags
- `Delete(id)` — Suppression DB (handler gère suppression disque + thumbnails)
- `GetStoragePath(id)` — Chemin disque du fichier
- `UpdateOCRText(id, text)` — Mise à jour du champ ocr_text
- `AddTags(fileID, tags, tagType)` — Création auto des tags + liaison
- `GetTagsByFileID(fileID)` — Tags d'un fichier
- `MoveFiles(fileIDs, parentFileID)` — Déplacer vers un dossier (ou racine si nil)
- `CreateFolder(name)` — Créer un dossier
- `ListFolders()` — Lister tous les dossiers
- `ListFilesByParentID(parentID)` — Fichiers d'un dossier
- `GetThumbnailsByFileID(fileID)` — Thumbnails d'un fichier
- `GetThumbnailStoragePath(id)` — Chemin disque thumbnail
- `GetBestThumbnail(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 goroutine
- `Stop()` — Ferme le channel jobs
- `Enqueue(fileID, filePath)` — Ajoute un job OCR (channel buffer 100)
- `process(job)` — Lecture fichier → Client OCR → Flatten text → UpdateOCRText
- `RecognizeFromBytes(data)` — OCR direct (pas utilisé par les handlers)
- `HealthCheck()` — Vérifie l'OCR server
### ConversionService (service/conversion.go — 253 lignes)
- `Start()` / `Stop()` — Worker goroutine
- `Enqueue(fileID, filePath, mimeType)` — Conversion async (channel buffer 100)
- `process(job)` :
1. Si document Office → conversion vers PDF via LibreOffice headless
2. Si PDF → direct
3. PDF → images via pdftoppm à 2 résolutions : "thumbnail" (21 DPI) et "full" (200 DPI)
4. 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 thumbnails
- `Validate(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 check
- `POST /ocr` — OCR via JSON base64
- `POST /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 :
1. **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é
2. **Par nom+taille** : endpoint `POST /files/dedup-check` pour 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_IMPLEMENTED
- `OCRHandler.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