chore: update file structure for uploaded images, moving storage to data/uploads and enhancing README documentation

This commit is contained in:
Julien Froidefond
2026-01-28 11:33:40 +01:00
parent b969ab2bd8
commit 040efcdf29
5 changed files with 78 additions and 7 deletions

4
.gitignore vendored
View File

@@ -45,7 +45,9 @@ next-env.d.ts
/data/*.db /data/*.db
/data/backups/* /data/backups/*
/data/uploads/notes/*
!/data/uploads/notes/.gitkeep
# Uploaded images # Uploaded images (legacy - now using data/uploads)
/public/uploads/notes/* /public/uploads/notes/*
!/public/uploads/notes/.gitkeep !/public/uploads/notes/.gitkeep

View File

@@ -9,10 +9,13 @@ data/
├── README.md # Ce fichier ├── README.md # Ce fichier
├── prod.db # Base de données production (Docker) ├── prod.db # Base de données production (Docker)
├── dev.db # Base de données développement (Docker) ├── dev.db # Base de données développement (Docker)
── backups/ # Sauvegardes automatiques et manuelles ── backups/ # Sauvegardes automatiques et manuelles
├── towercontrol_2025-01-15T10-30-00-000Z.db.gz ├── towercontrol_2025-01-15T10-30-00-000Z.db.gz
├── towercontrol_2025-01-15T11-30-00-000Z.db.gz ├── towercontrol_2025-01-15T11-30-00-000Z.db.gz
└── ... └── ...
└── uploads/ # Images et fichiers uploadés
└── notes/ # Images des notes markdown
└── ...
``` ```
## 🎯 Utilisation ## 🎯 Utilisation
@@ -60,6 +63,13 @@ BACKUP_STORAGE_PATH="./data/backups"
- **Rétention** : Configurable (défaut: 5 sauvegardes) - **Rétention** : Configurable (défaut: 5 sauvegardes)
- **Fréquence** : Configurable (défaut: horaire) - **Fréquence** : Configurable (défaut: horaire)
### Images uploadées
- **Dossier** : `data/uploads/notes/`
- **Format** : Images collées dans les notes markdown
- **Accès** : Via `/api/notes/images/[filename]`
- **Persistance** : Monté en volume Docker pour persistance
## 🚀 Commandes utiles ## 🚀 Commandes utiles
```bash ```bash

0
data/uploads/notes/.gitkeep Executable file
View File

View File

@@ -0,0 +1,58 @@
import { NextRequest, NextResponse } from 'next/server';
import { readFile } from 'fs/promises';
import { join } from 'path';
import { existsSync } from 'fs';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ filename: string }> }
) {
try {
const { filename } = await params;
// Sécurité : empêcher les path traversal attacks
if (filename.includes('..') || filename.includes('/')) {
return NextResponse.json(
{ error: 'Nom de fichier invalide' },
{ status: 400 }
);
}
// Chemin vers l'image dans data/uploads/notes
const imagePath = join(process.cwd(), 'data', 'uploads', 'notes', filename);
// Vérifier que le fichier existe
if (!existsSync(imagePath)) {
return NextResponse.json({ error: 'Image non trouvée' }, { status: 404 });
}
// Lire le fichier
const imageBuffer = await readFile(imagePath);
// Déterminer le type MIME basé sur l'extension
const extension = filename.split('.').pop()?.toLowerCase();
const mimeTypes: Record<string, string> = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
webp: 'image/webp',
svg: 'image/svg+xml',
};
const contentType = mimeTypes[extension || ''] || 'image/jpeg';
// Retourner l'image avec les bons headers
return new NextResponse(new Uint8Array(imageBuffer), {
headers: {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=31536000, immutable',
},
});
} catch (error) {
console.error('Error serving image:', error);
return NextResponse.json(
{ error: "Erreur lors de la récupération de l'image" },
{ status: 500 }
);
}
}

View File

@@ -40,7 +40,8 @@ export async function POST(request: NextRequest) {
} }
// Créer le dossier de stockage s'il n'existe pas // Créer le dossier de stockage s'il n'existe pas
const uploadsDir = join(process.cwd(), 'public', 'uploads', 'notes'); // Utiliser data/ qui est monté en volume dans Docker
const uploadsDir = join(process.cwd(), 'data', 'uploads', 'notes');
if (!existsSync(uploadsDir)) { if (!existsSync(uploadsDir)) {
await mkdir(uploadsDir, { recursive: true }); await mkdir(uploadsDir, { recursive: true });
} }
@@ -58,7 +59,7 @@ export async function POST(request: NextRequest) {
await writeFile(filepath, buffer); await writeFile(filepath, buffer);
// Retourner l'URL relative de l'image // Retourner l'URL relative de l'image
const imageUrl = `/uploads/notes/${filename}`; const imageUrl = `/api/notes/images/${filename}`;
return NextResponse.json({ url: imageUrl }); return NextResponse.json({ url: imageUrl });
} catch (error) { } catch (error) {