# VaultDrop Backend — Spécification V1 (État Existant) > ⚠️ **Obsolète** — référentiel actuel : `docs/api-v1.md` (contrat API) et `V2.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_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