chore: update file structure for uploaded images, moving storage to data/uploads and enhancing README documentation
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -45,7 +45,9 @@ next-env.d.ts
|
||||
|
||||
/data/*.db
|
||||
/data/backups/*
|
||||
/data/uploads/notes/*
|
||||
!/data/uploads/notes/.gitkeep
|
||||
|
||||
# Uploaded images
|
||||
# Uploaded images (legacy - now using data/uploads)
|
||||
/public/uploads/notes/*
|
||||
!/public/uploads/notes/.gitkeep
|
||||
|
||||
@@ -9,9 +9,12 @@ data/
|
||||
├── README.md # Ce fichier
|
||||
├── prod.db # Base de données production (Docker)
|
||||
├── dev.db # Base de données développement (Docker)
|
||||
└── backups/ # Sauvegardes automatiques et manuelles
|
||||
├── towercontrol_2025-01-15T10-30-00-000Z.db.gz
|
||||
├── towercontrol_2025-01-15T11-30-00-000Z.db.gz
|
||||
├── backups/ # Sauvegardes automatiques et manuelles
|
||||
│ ├── towercontrol_2025-01-15T10-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
|
||||
└── ...
|
||||
```
|
||||
|
||||
@@ -60,6 +63,13 @@ BACKUP_STORAGE_PATH="./data/backups"
|
||||
- **Rétention** : Configurable (défaut: 5 sauvegardes)
|
||||
- **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
|
||||
|
||||
```bash
|
||||
|
||||
0
data/uploads/notes/.gitkeep
Executable file
0
data/uploads/notes/.gitkeep
Executable file
58
src/app/api/notes/images/[filename]/route.ts
Normal file
58
src/app/api/notes/images/[filename]/route.ts
Normal 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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,8 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
}
|
||||
@@ -58,7 +59,7 @@ export async function POST(request: NextRequest) {
|
||||
await writeFile(filepath, buffer);
|
||||
|
||||
// Retourner l'URL relative de l'image
|
||||
const imageUrl = `/uploads/notes/${filename}`;
|
||||
const imageUrl = `/api/notes/images/${filename}`;
|
||||
|
||||
return NextResponse.json({ url: imageUrl });
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user