add go bench client
This commit is contained in:
359
docs/FEATURE_FILE_ORGANIZATION.md
Normal file
359
docs/FEATURE_FILE_ORGANIZATION.md
Normal file
@@ -0,0 +1,359 @@
|
||||
# 📁 Organisation des fichiers par hostname
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Le système d'upload a été amélioré pour organiser automatiquement les fichiers et images par hostname de device dans des sous-dossiers structurés.
|
||||
|
||||
### Structure précédente
|
||||
```
|
||||
uploads/
|
||||
├── 3562b30f85326e79_3.jpg
|
||||
├── 7660e368d0cb566e_4.png
|
||||
├── 8b5371f003d8616f_3.png
|
||||
├── ec199bc98be16a37_3.pdf
|
||||
└── peripherals/
|
||||
```
|
||||
|
||||
### Nouvelle structure
|
||||
```
|
||||
uploads/
|
||||
├── srv-proxmox/
|
||||
│ ├── images/
|
||||
│ │ ├── 3562b30f85326e79_3.jpg
|
||||
│ │ └── 7660e368d0cb566e_4.png
|
||||
│ └── files/
|
||||
│ └── ec199bc98be16a37_3.pdf
|
||||
├── rpi4-cluster-01/
|
||||
│ ├── images/
|
||||
│ │ └── a1b2c3d4e5f67890_1.jpg
|
||||
│ └── files/
|
||||
│ └── datasheet_5.pdf
|
||||
└── peripherals/
|
||||
└── (unchanged)
|
||||
```
|
||||
|
||||
## Avantages
|
||||
|
||||
1. **Organisation claire** : Les fichiers sont regroupés par device
|
||||
2. **Séparation images/fichiers** : Facilite la gestion et les sauvegardes
|
||||
3. **Scalabilité** : Fonctionne avec des milliers de devices
|
||||
4. **Navigation facile** : Accès direct aux fichiers d'un device
|
||||
5. **Nettoyage simplifié** : Suppression d'un device = suppression d'un dossier
|
||||
|
||||
## Fonctionnement
|
||||
|
||||
### Détection automatique
|
||||
|
||||
Le système détecte automatiquement si un fichier est une image :
|
||||
|
||||
**Extensions d'images** :
|
||||
- `.jpg`, `.jpeg`
|
||||
- `.png`
|
||||
- `.gif`
|
||||
- `.webp`
|
||||
- `.bmp`
|
||||
- `.svg`
|
||||
|
||||
**Type MIME** :
|
||||
- Tout MIME type commençant par `image/`
|
||||
|
||||
### Sanitisation des noms
|
||||
|
||||
Les hostnames sont nettoyés pour être utilisables comme noms de dossiers :
|
||||
|
||||
```python
|
||||
# Exemples de sanitisation
|
||||
"srv-proxmox.local" → "srv-proxmox.local"
|
||||
"my server (old)" → "my_server_old"
|
||||
"test@2024" → "test_2024"
|
||||
"___test___" → "test"
|
||||
```
|
||||
|
||||
**Règles** :
|
||||
- Caractères interdits remplacés par `_`
|
||||
- Points et tirets conservés
|
||||
- Underscores multiples condensés
|
||||
- Longueur limitée à 100 caractères
|
||||
- Fallback sur "unknown" si vide
|
||||
|
||||
## Migration des fichiers existants
|
||||
|
||||
Un script de migration est fourni pour réorganiser les fichiers existants.
|
||||
|
||||
### Dry-run (simulation)
|
||||
|
||||
Pour voir ce qui serait fait sans modifier les fichiers :
|
||||
|
||||
```bash
|
||||
python backend/migrate_file_organization.py
|
||||
```
|
||||
|
||||
Sortie exemple :
|
||||
```
|
||||
================================================================================
|
||||
File Organization Migration
|
||||
================================================================================
|
||||
|
||||
Found 15 documents to migrate
|
||||
Mode: DRY RUN
|
||||
--------------------------------------------------------------------------------
|
||||
📄 Document 1 (image):
|
||||
Device: srv-proxmox (ID: 3)
|
||||
From: ./uploads/3562b30f85326e79_3.jpg
|
||||
To: ./uploads/srv-proxmox/images/3562b30f85326e79_3.jpg
|
||||
[DRY RUN - would migrate]
|
||||
|
||||
📄 Document 5 (file):
|
||||
Device: srv-proxmox (ID: 3)
|
||||
From: ./uploads/ec199bc98be16a37_3.pdf
|
||||
To: ./uploads/srv-proxmox/files/ec199bc98be16a37_3.pdf
|
||||
[DRY RUN - would migrate]
|
||||
|
||||
...
|
||||
|
||||
Summary:
|
||||
Migrated: 12
|
||||
Skipped: 2
|
||||
Errors: 1
|
||||
Total: 15
|
||||
|
||||
This was a DRY RUN. To actually migrate files, run:
|
||||
python backend/migrate_file_organization.py --execute
|
||||
```
|
||||
|
||||
### Migration réelle
|
||||
|
||||
Pour effectuer réellement la migration :
|
||||
|
||||
```bash
|
||||
python backend/migrate_file_organization.py --execute
|
||||
```
|
||||
|
||||
### Avec nettoyage
|
||||
|
||||
Pour migrer ET supprimer les dossiers vides :
|
||||
|
||||
```bash
|
||||
python backend/migrate_file_organization.py --execute --cleanup
|
||||
```
|
||||
|
||||
## Utilisation de l'API
|
||||
|
||||
### Upload d'un document
|
||||
|
||||
L'API détecte automatiquement le type et place le fichier au bon endroit :
|
||||
|
||||
```bash
|
||||
# Upload d'une image
|
||||
curl -X POST "http://localhost:8007/api/devices/3/docs" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-F "file=@photo.jpg" \
|
||||
-F "doc_type=photo"
|
||||
|
||||
# Sera stocké dans: uploads/srv-proxmox/images/hash_3.jpg
|
||||
```
|
||||
|
||||
```bash
|
||||
# Upload d'un PDF
|
||||
curl -X POST "http://localhost:8007/api/devices/3/docs" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-F "file=@manual.pdf" \
|
||||
-F "doc_type=manual"
|
||||
|
||||
# Sera stocké dans: uploads/srv-proxmox/files/hash_3.pdf
|
||||
```
|
||||
|
||||
### Téléchargement
|
||||
|
||||
Le téléchargement utilise toujours le même endpoint :
|
||||
|
||||
```bash
|
||||
curl "http://localhost:8007/api/docs/123/download" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-o document.pdf
|
||||
```
|
||||
|
||||
Le système lit le `stored_path` en base de données qui contient le chemin complet.
|
||||
|
||||
## Architecture technique
|
||||
|
||||
### Module file_organizer.py
|
||||
|
||||
```python
|
||||
from app.utils.file_organizer import (
|
||||
sanitize_hostname,
|
||||
get_device_upload_paths,
|
||||
ensure_device_directories,
|
||||
get_upload_path,
|
||||
is_image_file
|
||||
)
|
||||
```
|
||||
|
||||
**Fonctions principales** :
|
||||
|
||||
#### `sanitize_hostname(hostname: str) -> str`
|
||||
Nettoie un hostname pour utilisation comme nom de dossier.
|
||||
|
||||
#### `get_device_upload_paths(base_dir: str, hostname: str) -> Tuple[str, str]`
|
||||
Retourne les chemins (images, files) pour un device.
|
||||
|
||||
#### `ensure_device_directories(base_dir: str, hostname: str) -> Tuple[str, str]`
|
||||
Crée les dossiers s'ils n'existent pas et retourne les chemins.
|
||||
|
||||
#### `get_upload_path(base_dir: str, hostname: str, is_image: bool, filename: str) -> str`
|
||||
Retourne le chemin complet où stocker un fichier.
|
||||
|
||||
#### `is_image_file(filename: str, mime_type: str = None) -> bool`
|
||||
Détermine si un fichier est une image.
|
||||
|
||||
### Modification de docs.py
|
||||
|
||||
Avant :
|
||||
```python
|
||||
stored_path = os.path.join(settings.UPLOAD_DIR, stored_filename)
|
||||
os.makedirs(settings.UPLOAD_DIR, exist_ok=True)
|
||||
```
|
||||
|
||||
Après :
|
||||
```python
|
||||
is_image = is_image_file(file.filename, file.content_type)
|
||||
stored_path = get_upload_path(
|
||||
settings.UPLOAD_DIR,
|
||||
device.hostname,
|
||||
is_image,
|
||||
stored_filename
|
||||
)
|
||||
```
|
||||
|
||||
## Compatibilité
|
||||
|
||||
### Anciens fichiers
|
||||
|
||||
Les fichiers existants continuent de fonctionner grâce au `stored_path` en base de données :
|
||||
- Les anciens chemins (`uploads/hash_id.ext`) restent valides
|
||||
- Les nouveaux uploads utilisent la nouvelle structure
|
||||
- La migration est **optionnelle** mais recommandée
|
||||
|
||||
### Téléchargement
|
||||
|
||||
L'API de téléchargement utilise le `stored_path` de la base de données, donc :
|
||||
- ✅ Anciens fichiers : fonctionnent
|
||||
- ✅ Nouveaux fichiers : fonctionnent
|
||||
- ✅ Fichiers migrés : fonctionnent
|
||||
|
||||
## Cas d'usage
|
||||
|
||||
### Sauvegarde sélective
|
||||
|
||||
```bash
|
||||
# Sauvegarder seulement les images d'un device
|
||||
rsync -av uploads/srv-proxmox/images/ backup/srv-proxmox-images/
|
||||
|
||||
# Sauvegarder tous les PDF
|
||||
find uploads/*/files -name "*.pdf" -exec cp {} backup/pdfs/ \;
|
||||
```
|
||||
|
||||
### Nettoyage par device
|
||||
|
||||
```bash
|
||||
# Supprimer tous les fichiers d'un device désinstallé
|
||||
rm -rf uploads/old-server/
|
||||
```
|
||||
|
||||
### Audit de l'espace
|
||||
|
||||
```bash
|
||||
# Voir l'espace utilisé par device
|
||||
du -sh uploads/*/
|
||||
|
||||
# Sortie :
|
||||
# 45M uploads/srv-proxmox/
|
||||
# 120M uploads/rpi4-cluster-01/
|
||||
# 2.3M uploads/laptop-dev/
|
||||
```
|
||||
|
||||
## Migration progressive
|
||||
|
||||
Vous pouvez migrer progressivement :
|
||||
|
||||
1. **Phase 1** : Déployer le nouveau code
|
||||
- Nouveaux uploads utilisent la nouvelle structure
|
||||
- Anciens fichiers restent en place
|
||||
|
||||
2. **Phase 2** : Tester la migration
|
||||
- Faire un dry-run
|
||||
- Vérifier les chemins générés
|
||||
|
||||
3. **Phase 3** : Migrer en production
|
||||
- Exécuter la migration réelle
|
||||
- Vérifier que les téléchargements fonctionnent
|
||||
|
||||
4. **Phase 4** : Nettoyage
|
||||
- Nettoyer les dossiers vides
|
||||
- Archiver les anciens fichiers si nécessaire
|
||||
|
||||
## Sécurité
|
||||
|
||||
### Validation
|
||||
|
||||
- Les noms de fichiers sont hashés (pas de conflit de noms)
|
||||
- Les hostnames sont sanitisés (pas d'injection de chemin)
|
||||
- Les tailles de fichiers sont vérifiées
|
||||
- Les extensions sont validées
|
||||
|
||||
### Isolation
|
||||
|
||||
- Chaque device a son propre dossier
|
||||
- Pas de risque de collision entre devices
|
||||
- Permissions préservées
|
||||
|
||||
## Performance
|
||||
|
||||
### Impact
|
||||
|
||||
- ✅ Création de dossiers : négligeable (mkdir -p)
|
||||
- ✅ Upload : identique à avant
|
||||
- ✅ Download : identique à avant
|
||||
- ✅ Migration : proportionnel au nombre de fichiers
|
||||
|
||||
### Optimisations
|
||||
|
||||
- Les dossiers sont créés une seule fois
|
||||
- Pas de scans récursifs
|
||||
- Utilise les fonctions OS natives
|
||||
|
||||
## Limitations
|
||||
|
||||
1. **Hostname changeant** : Si un hostname change, les fichiers restent dans l'ancien dossier
|
||||
- Solution : Script de remapping si nécessaire
|
||||
|
||||
2. **Caractères spéciaux** : Certains caractères sont remplacés par `_`
|
||||
- C'est intentionnel pour la compatibilité filesystem
|
||||
|
||||
3. **Périphériques** : Le dossier `peripherals/` garde sa propre structure
|
||||
- Pour éviter de casser le code existant
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Que se passe-t-il si je ne migre pas les anciens fichiers ?**
|
||||
R: Ils continuent de fonctionner normalement. Seuls les nouveaux uploads utilisent la nouvelle structure.
|
||||
|
||||
**Q: Puis-je revenir en arrière ?**
|
||||
R: Oui, en modifiant les `stored_path` en base de données et en déplaçant les fichiers.
|
||||
|
||||
**Q: La migration supprime-t-elle les fichiers originaux ?**
|
||||
R: Non, elle les **déplace** (move, pas copy). Les fichiers ne sont pas dupliqués.
|
||||
|
||||
**Q: Que faire si un device a le même hostname qu'un autre ?**
|
||||
R: Les fichiers iront dans le même dossier, mais les noms de fichiers incluent le device_id donc pas de collision.
|
||||
|
||||
---
|
||||
|
||||
**Fichiers créés** :
|
||||
- `backend/app/utils/file_organizer.py` - Module utilitaire
|
||||
- `backend/migrate_file_organization.py` - Script de migration
|
||||
|
||||
**Fichiers modifiés** :
|
||||
- `backend/app/api/docs.py` - Utilise la nouvelle organisation
|
||||
|
||||
**Créé le** : 2026-01-11
|
||||
Reference in New Issue
Block a user