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/*.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
|
||||||
|
|||||||
@@ -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
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
|
// 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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user