chore: update backup configurations and directory structure
- Modified `.gitignore` to exclude all files in the `/data/` directory. - Enhanced `BACKUP.md` with customization options for backup storage paths and updated database path configurations. - Updated `docker-compose.yml` to reflect new paths for database and backup storage. - Adjusted `Dockerfile` to create a dedicated backups directory. - Refactored `BackupService` to utilize environment variables for backup paths, improving flexibility and reliability. - Deleted `dev.db` as it is no longer needed in the repository.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -43,4 +43,4 @@ next-env.d.ts
|
|||||||
/src/generated/prisma
|
/src/generated/prisma
|
||||||
/prisma/dev.db
|
/prisma/dev.db
|
||||||
|
|
||||||
backups/
|
/data/*
|
||||||
|
|||||||
43
BACKUP.md
43
BACKUP.md
@@ -52,6 +52,19 @@ tsx scripts/backup-manager.ts config-set maxBackups=10
|
|||||||
tsx scripts/backup-manager.ts config-set compression=true
|
tsx scripts/backup-manager.ts config-set compression=true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Personnalisation du dossier de sauvegarde
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Via variable d'environnement permanente (.env)
|
||||||
|
BACKUP_STORAGE_PATH="./custom-backups"
|
||||||
|
|
||||||
|
# Via variable temporaire (une seule fois)
|
||||||
|
BACKUP_STORAGE_PATH="./my-backups" npm run backup:create
|
||||||
|
|
||||||
|
# Exemple avec un chemin absolu
|
||||||
|
BACKUP_STORAGE_PATH="/var/backups/towercontrol" npm run backup:create
|
||||||
|
```
|
||||||
|
|
||||||
## Utilisation
|
## Utilisation
|
||||||
|
|
||||||
### Interface graphique
|
### Interface graphique
|
||||||
@@ -272,8 +285,34 @@ export const prisma = globalThis.__prisma || new PrismaClient({
|
|||||||
### Variables d'environnement
|
### Variables d'environnement
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Optionnel : personnaliser le chemin de la base
|
# Configuration des chemins de base de données
|
||||||
DATABASE_URL="file:./custom/path/dev.db"
|
DATABASE_URL="file:./prisma/dev.db" # Pour Prisma
|
||||||
|
BACKUP_DATABASE_PATH="./prisma/dev.db" # Base à sauvegarder (optionnel)
|
||||||
|
BACKUP_STORAGE_PATH="./backups" # Dossier des sauvegardes (optionnel)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
En environnement Docker, tout est centralisé dans le dossier `data/` :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: "file:./data/prod.db" # Base de données Prisma
|
||||||
|
BACKUP_DATABASE_PATH: "./data/prod.db" # Base à sauvegarder
|
||||||
|
BACKUP_STORAGE_PATH: "./data/backups" # Dossier des sauvegardes
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data # Bind mount vers dossier local
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structure des dossiers :**
|
||||||
|
```
|
||||||
|
./data/ # Dossier local mappé
|
||||||
|
├── prod.db # Base de données production
|
||||||
|
├── dev.db # Base de données développement
|
||||||
|
└── backups/ # Sauvegardes (créé automatiquement)
|
||||||
|
├── towercontrol_*.db.gz
|
||||||
|
└── ...
|
||||||
```
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|||||||
201
DOCKER.md
Normal file
201
DOCKER.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# 🐳 Docker - TowerControl
|
||||||
|
|
||||||
|
Guide d'utilisation de TowerControl avec Docker.
|
||||||
|
|
||||||
|
## 🚀 Démarrage rapide
|
||||||
|
|
||||||
|
### Production
|
||||||
|
```bash
|
||||||
|
# Démarrer le service de production
|
||||||
|
docker-compose up -d towercontrol
|
||||||
|
|
||||||
|
# Accéder à l'application
|
||||||
|
open http://localhost:3006
|
||||||
|
```
|
||||||
|
|
||||||
|
### Développement
|
||||||
|
```bash
|
||||||
|
# Démarrer le service de développement avec live reload
|
||||||
|
docker-compose --profile dev up towercontrol-dev
|
||||||
|
|
||||||
|
# Accéder à l'application
|
||||||
|
open http://localhost:3005
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Services disponibles
|
||||||
|
|
||||||
|
### 🚀 `towercontrol` (Production)
|
||||||
|
- **Port** : 3006
|
||||||
|
- **Base de données** : `./data/prod.db`
|
||||||
|
- **Sauvegardes** : `./data/backups/`
|
||||||
|
- **Mode** : Optimisé, standalone
|
||||||
|
- **Restart** : Automatique
|
||||||
|
|
||||||
|
### 🛠️ `towercontrol-dev` (Développement)
|
||||||
|
- **Port** : 3005
|
||||||
|
- **Base de données** : `./data/dev.db`
|
||||||
|
- **Sauvegardes** : `./data/backups/` (partagées)
|
||||||
|
- **Mode** : Live reload, debug
|
||||||
|
- **Profile** : `dev`
|
||||||
|
|
||||||
|
## 📁 Structure des données
|
||||||
|
|
||||||
|
```
|
||||||
|
./data/ # Mappé vers /app/data dans les conteneurs
|
||||||
|
├── README.md # Documentation du dossier data
|
||||||
|
├── prod.db # Base SQLite production
|
||||||
|
├── dev.db # Base SQLite développement
|
||||||
|
└── backups/ # Sauvegardes automatiques
|
||||||
|
├── towercontrol_2025-01-15T10-30-00-000Z.db.gz
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Configuration
|
||||||
|
|
||||||
|
### Variables d'environnement
|
||||||
|
|
||||||
|
| Variable | Production | Développement | Description |
|
||||||
|
|----------|------------|---------------|-------------|
|
||||||
|
| `NODE_ENV` | `production` | `development` | Mode d'exécution |
|
||||||
|
| `DATABASE_URL` | `file:./data/prod.db` | `file:./data/dev.db` | Base Prisma |
|
||||||
|
| `BACKUP_DATABASE_PATH` | `./data/prod.db` | `./data/dev.db` | Source backup |
|
||||||
|
| `BACKUP_STORAGE_PATH` | `./data/backups` | `./data/backups` | Dossier backup |
|
||||||
|
| `TZ` | `Europe/Paris` | `Europe/Paris` | Fuseau horaire |
|
||||||
|
|
||||||
|
### Ports
|
||||||
|
|
||||||
|
- **Production** : `3006:3000`
|
||||||
|
- **Développement** : `3005:3000`
|
||||||
|
|
||||||
|
## 📚 Commandes utiles
|
||||||
|
|
||||||
|
### Gestion des conteneurs
|
||||||
|
```bash
|
||||||
|
# Voir les logs
|
||||||
|
docker-compose logs -f towercontrol
|
||||||
|
docker-compose logs -f towercontrol-dev
|
||||||
|
|
||||||
|
# Arrêter les services
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Reconstruire les images
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Nettoyer tout
|
||||||
|
docker-compose down -v --rmi all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gestion des données
|
||||||
|
```bash
|
||||||
|
# Sauvegarder les données
|
||||||
|
docker-compose exec towercontrol npm run backup:create
|
||||||
|
|
||||||
|
# Lister les sauvegardes
|
||||||
|
docker-compose exec towercontrol npm run backup:list
|
||||||
|
|
||||||
|
# Accéder au shell du conteneur
|
||||||
|
docker-compose exec towercontrol sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Base de données
|
||||||
|
```bash
|
||||||
|
# Migrations Prisma
|
||||||
|
docker-compose exec towercontrol npx prisma migrate deploy
|
||||||
|
|
||||||
|
# Reset de la base (dev uniquement)
|
||||||
|
docker-compose exec towercontrol-dev npx prisma migrate reset
|
||||||
|
|
||||||
|
# Studio Prisma (dev)
|
||||||
|
docker-compose exec towercontrol-dev npx prisma studio
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Debugging
|
||||||
|
|
||||||
|
### Vérifier la santé
|
||||||
|
```bash
|
||||||
|
# Health check
|
||||||
|
curl http://localhost:3006/api/health
|
||||||
|
curl http://localhost:3005/api/health
|
||||||
|
|
||||||
|
# Vérifier les variables d'env
|
||||||
|
docker-compose exec towercontrol env | grep -E "(DATABASE|BACKUP|NODE_ENV)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs détaillés
|
||||||
|
```bash
|
||||||
|
# Logs avec timestamps
|
||||||
|
docker-compose logs -f -t towercontrol
|
||||||
|
|
||||||
|
# Logs des 100 dernières lignes
|
||||||
|
docker-compose logs --tail=100 towercontrol
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 Dépannage
|
||||||
|
|
||||||
|
### Problèmes courants
|
||||||
|
|
||||||
|
**Port déjà utilisé**
|
||||||
|
```bash
|
||||||
|
# Trouver le processus qui utilise le port
|
||||||
|
lsof -i :3006
|
||||||
|
kill -9 <PID>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Base de données corrompue**
|
||||||
|
```bash
|
||||||
|
# Restaurer depuis une sauvegarde
|
||||||
|
docker-compose exec towercontrol npm run backup:restore filename.db.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
**Permissions**
|
||||||
|
```bash
|
||||||
|
# Corriger les permissions du dossier data
|
||||||
|
sudo chown -R $USER:$USER ./data
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Monitoring
|
||||||
|
|
||||||
|
### Espace disque
|
||||||
|
```bash
|
||||||
|
# Taille du dossier data
|
||||||
|
du -sh ./data
|
||||||
|
|
||||||
|
# Espace libre
|
||||||
|
df -h .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
```bash
|
||||||
|
# Stats des conteneurs
|
||||||
|
docker stats
|
||||||
|
|
||||||
|
# Utilisation mémoire
|
||||||
|
docker-compose exec towercontrol free -h
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 Production
|
||||||
|
|
||||||
|
### Recommandations
|
||||||
|
- Utiliser un reverse proxy (nginx, traefik)
|
||||||
|
- Configurer HTTPS
|
||||||
|
- Sauvegarder régulièrement `./data/`
|
||||||
|
- Monitorer l'espace disque
|
||||||
|
- Logs centralisés
|
||||||
|
|
||||||
|
### Exemple nginx
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name towercontrol.example.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3006;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
📚 **Voir aussi** : [BACKUP.md](./BACKUP.md) | [data/README.md](./data/README.md)
|
||||||
@@ -35,8 +35,8 @@ RUN npm run build
|
|||||||
# Production image, copy all the files and run next
|
# Production image, copy all the files and run next
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
|
|
||||||
# Set timezone to Europe/Paris
|
# Set timezone to Europe/Paris and install sqlite3 for backups
|
||||||
RUN apk add --no-cache tzdata
|
RUN apk add --no-cache tzdata sqlite
|
||||||
RUN ln -snf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
RUN ln -snf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -64,8 +64,8 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|||||||
COPY --from=builder /app/prisma ./prisma
|
COPY --from=builder /app/prisma ./prisma
|
||||||
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
||||||
|
|
||||||
# Create data directory for SQLite
|
# Create data directory for SQLite and backups
|
||||||
RUN mkdir -p /app/data && chown nextjs:nodejs /app/data
|
RUN mkdir -p /app/data/backups && chown -R nextjs:nodejs /app/data
|
||||||
|
|
||||||
# Set all ENV vars before switching user
|
# Set all ENV vars before switching user
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
|
|||||||
@@ -1,31 +1,27 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
towercontrol:
|
towercontrol:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
target: runner
|
||||||
ports:
|
ports:
|
||||||
- "3006:3000"
|
- "3006:3000"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
NODE_ENV: production
|
||||||
- DATABASE_URL=file:/app/data/prod.db
|
DATABASE_URL: "file:../data/prod.db" # Prisma
|
||||||
- TZ=Europe/Paris
|
BACKUP_DATABASE_PATH: "./data/prod.db" # Base de données à sauvegarder
|
||||||
|
BACKUP_STORAGE_PATH: "./data/backups" # Dossier des sauvegardes
|
||||||
|
TZ: Europe/Paris
|
||||||
volumes:
|
volumes:
|
||||||
# Volume persistant pour la base SQLite
|
- ./data:/app/data # Dossier local data/ vers /app/data
|
||||||
- sqlite_data:/app/data
|
|
||||||
# Monter ta DB locale (décommente pour utiliser tes données locales)
|
|
||||||
- ./prisma/dev.db:/app/data/prod.db
|
|
||||||
- ./backups:/app/backups
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health || exit 1"]
|
test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
||||||
# Service de développement (optionnel)
|
|
||||||
towercontrol-dev:
|
towercontrol-dev:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
@@ -34,20 +30,29 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "3005:3000"
|
- "3005:3000"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
NODE_ENV: development
|
||||||
- DATABASE_URL=file:/app/data/dev.db
|
DATABASE_URL: "file:../data/dev.db" # Prisma
|
||||||
|
BACKUP_DATABASE_PATH: "./data/dev.db" # Base de données à sauvegarder
|
||||||
|
BACKUP_STORAGE_PATH: "./data/backups" # Dossier des sauvegardes
|
||||||
|
TZ: Europe/Paris
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app # code en live
|
||||||
- /app/node_modules
|
- /app/node_modules # vol anonyme pour ne pas écraser ceux du conteneur
|
||||||
- /app/.next
|
- /app/.next
|
||||||
- sqlite_data_dev:/app/data
|
- ./data:/app/data # Dossier local data/ vers /app/data
|
||||||
command: sh -c "npm install && npx prisma generate && npx prisma migrate deploy && npm run dev"
|
command: >
|
||||||
|
sh -c "npm install &&
|
||||||
|
npx prisma generate &&
|
||||||
|
npx prisma migrate deploy &&
|
||||||
|
npm run dev"
|
||||||
profiles:
|
profiles:
|
||||||
- dev
|
- dev
|
||||||
|
|
||||||
volumes:
|
# 📁 Structure des données :
|
||||||
sqlite_data:
|
# ./data/ -> /app/data (bind mount)
|
||||||
driver: local
|
# ├── prod.db -> Base de données production
|
||||||
sqlite_data_dev:
|
# ├── dev.db -> Base de données développement
|
||||||
driver: local
|
# └── backups/ -> Sauvegardes automatiques
|
||||||
|
#
|
||||||
|
# 🔧 Configuration via .env.docker
|
||||||
|
# 📚 Documentation : ./data/README.md
|
||||||
10
env.example
10
env.example
@@ -1,5 +1,13 @@
|
|||||||
# Base de données (requis)
|
# Base de données (requis)
|
||||||
DATABASE_URL="file:./dev.db"
|
DATABASE_URL="file:../data/dev.db"
|
||||||
|
|
||||||
|
# Chemin de la base de données pour les backups (optionnel)
|
||||||
|
# Si non défini, utilise DATABASE_URL ou le chemin par défaut
|
||||||
|
BACKUP_DATABASE_PATH="./data/dev.db"
|
||||||
|
|
||||||
|
# Dossier de stockage des sauvegardes (optionnel)
|
||||||
|
# Par défaut: ./backups en local, ./data/backups en production
|
||||||
|
BACKUP_STORAGE_PATH="./backups"
|
||||||
|
|
||||||
# Intégration Jira (optionnel)
|
# Intégration Jira (optionnel)
|
||||||
JIRA_BASE_URL="" # https://votre-domaine.atlassian.net
|
JIRA_BASE_URL="" # https://votre-domaine.atlassian.net
|
||||||
|
|||||||
@@ -31,11 +31,23 @@ export class BackupService {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
interval: 'hourly',
|
interval: 'hourly',
|
||||||
maxBackups: 5,
|
maxBackups: 5,
|
||||||
backupPath: path.join(process.cwd(), 'backups'),
|
backupPath: this.getDefaultBackupPath(),
|
||||||
includeUploads: true,
|
includeUploads: true,
|
||||||
compression: true,
|
compression: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getDefaultBackupPath(): string {
|
||||||
|
// 1. Variable d'environnement explicite
|
||||||
|
if (process.env.BACKUP_STORAGE_PATH) {
|
||||||
|
return path.resolve(process.cwd(), process.env.BACKUP_STORAGE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Chemin par défaut selon l'environnement
|
||||||
|
return process.env.NODE_ENV === 'production'
|
||||||
|
? path.join(process.cwd(), 'data', 'backups') // Docker: /app/data/backups
|
||||||
|
: path.join(process.cwd(), 'backups'); // Local: ./backups
|
||||||
|
}
|
||||||
|
|
||||||
private config: BackupConfig;
|
private config: BackupConfig;
|
||||||
|
|
||||||
constructor(config?: Partial<BackupConfig>) {
|
constructor(config?: Partial<BackupConfig>) {
|
||||||
@@ -106,10 +118,7 @@ export class BackupService {
|
|||||||
// Créer le dossier de backup si nécessaire
|
// Créer le dossier de backup si nécessaire
|
||||||
await this.ensureBackupDirectory();
|
await this.ensureBackupDirectory();
|
||||||
|
|
||||||
// Vérifier l'état de la base de données
|
// Créer la sauvegarde SQLite (sans vérification de santé pour éviter les conflits)
|
||||||
await this.verifyDatabaseHealth();
|
|
||||||
|
|
||||||
// Créer la sauvegarde SQLite
|
|
||||||
await this.createSQLiteBackup(backupPath);
|
await this.createSQLiteBackup(backupPath);
|
||||||
|
|
||||||
// Compresser si activé
|
// Compresser si activé
|
||||||
@@ -156,7 +165,26 @@ export class BackupService {
|
|||||||
* Crée une sauvegarde SQLite en utilisant la commande .backup
|
* Crée une sauvegarde SQLite en utilisant la commande .backup
|
||||||
*/
|
*/
|
||||||
private async createSQLiteBackup(backupPath: string): Promise<void> {
|
private async createSQLiteBackup(backupPath: string): Promise<void> {
|
||||||
const dbPath = path.resolve(process.env.DATABASE_URL?.replace('file:', '') || './prisma/dev.db');
|
// Résoudre le chemin de la base de données
|
||||||
|
let dbPath: string;
|
||||||
|
if (process.env.BACKUP_DATABASE_PATH) {
|
||||||
|
// Utiliser la variable spécifique aux backups
|
||||||
|
dbPath = path.resolve(process.cwd(), process.env.BACKUP_DATABASE_PATH);
|
||||||
|
} else if (process.env.DATABASE_URL) {
|
||||||
|
// Fallback sur DATABASE_URL si BACKUP_DATABASE_PATH n'est pas défini
|
||||||
|
dbPath = path.resolve(process.env.DATABASE_URL.replace('file:', ''));
|
||||||
|
} else {
|
||||||
|
// Chemin par défaut vers prisma/dev.db
|
||||||
|
dbPath = path.resolve(process.cwd(), 'prisma', 'dev.db');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que le fichier source existe
|
||||||
|
try {
|
||||||
|
await fs.stat(dbPath);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Source database not found: ${dbPath}`, error);
|
||||||
|
throw new Error(`Source database not found: ${dbPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Méthode 1: Utiliser sqlite3 CLI (plus fiable)
|
// Méthode 1: Utiliser sqlite3 CLI (plus fiable)
|
||||||
try {
|
try {
|
||||||
@@ -199,7 +227,19 @@ export class BackupService {
|
|||||||
*/
|
*/
|
||||||
async restoreBackup(filename: string): Promise<void> {
|
async restoreBackup(filename: string): Promise<void> {
|
||||||
const backupPath = path.join(this.config.backupPath, filename);
|
const backupPath = path.join(this.config.backupPath, filename);
|
||||||
const dbPath = path.resolve(process.env.DATABASE_URL?.replace('file:', '') || './prisma/dev.db');
|
|
||||||
|
// Résoudre le chemin de la base de données
|
||||||
|
let dbPath: string;
|
||||||
|
if (process.env.BACKUP_DATABASE_PATH) {
|
||||||
|
// Utiliser la variable spécifique aux backups
|
||||||
|
dbPath = path.resolve(process.cwd(), process.env.BACKUP_DATABASE_PATH);
|
||||||
|
} else if (process.env.DATABASE_URL) {
|
||||||
|
// Fallback sur DATABASE_URL si BACKUP_DATABASE_PATH n'est pas défini
|
||||||
|
dbPath = path.resolve(process.env.DATABASE_URL.replace('file:', ''));
|
||||||
|
} else {
|
||||||
|
// Chemin par défaut vers prisma/dev.db
|
||||||
|
dbPath = path.resolve(process.cwd(), 'prisma', 'dev.db');
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`🔄 Restore paths - backup: ${backupPath}, target: ${dbPath}`);
|
console.log(`🔄 Restore paths - backup: ${backupPath}, target: ${dbPath}`);
|
||||||
|
|
||||||
|
|||||||
94
src/app/api/backups/[filename]/route.ts
Normal file
94
src/app/api/backups/[filename]/route.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { backupService } from '@/services/backup';
|
||||||
|
|
||||||
|
interface RouteParams {
|
||||||
|
params: Promise<{
|
||||||
|
filename: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(
|
||||||
|
request: NextRequest,
|
||||||
|
{ params }: RouteParams
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { filename } = await params;
|
||||||
|
|
||||||
|
// Vérification de sécurité - s'assurer que c'est bien un fichier de backup
|
||||||
|
if (!filename.startsWith('towercontrol_') ||
|
||||||
|
(!filename.endsWith('.db') && !filename.endsWith('.db.gz'))) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: 'Invalid backup filename' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await backupService.deleteBackup(filename);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: `Backup ${filename} deleted successfully`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting backup:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to delete backup'
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(
|
||||||
|
request: NextRequest,
|
||||||
|
{ params }: RouteParams
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { filename } = await params;
|
||||||
|
const body = await request.json();
|
||||||
|
const { action } = body;
|
||||||
|
|
||||||
|
if (action === 'restore') {
|
||||||
|
// Vérification de sécurité
|
||||||
|
if (!filename.startsWith('towercontrol_') ||
|
||||||
|
(!filename.endsWith('.db') && !filename.endsWith('.db.gz'))) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: 'Invalid backup filename' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protection environnement de production
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: 'Restore not allowed in production via API' },
|
||||||
|
{ status: 403 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await backupService.restoreBackup(filename);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: `Database restored from ${filename}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: 'Invalid action' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in backup operation:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Operation failed'
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
103
src/app/api/backups/route.ts
Normal file
103
src/app/api/backups/route.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { backupService } from '@/services/backup';
|
||||||
|
import { backupScheduler } from '@/services/backup-scheduler';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
console.log('🔄 API GET /api/backups called');
|
||||||
|
|
||||||
|
// Test de la configuration d'abord
|
||||||
|
const config = backupService.getConfig();
|
||||||
|
console.log('✅ Config loaded:', config);
|
||||||
|
|
||||||
|
// Test du scheduler
|
||||||
|
const schedulerStatus = backupScheduler.getStatus();
|
||||||
|
console.log('✅ Scheduler status:', schedulerStatus);
|
||||||
|
|
||||||
|
// Test de la liste des backups
|
||||||
|
const backups = await backupService.listBackups();
|
||||||
|
console.log('✅ Backups loaded:', backups.length);
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
backups,
|
||||||
|
scheduler: schedulerStatus,
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('✅ API response ready');
|
||||||
|
return NextResponse.json(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error fetching backups:', error);
|
||||||
|
console.error('Error stack:', error instanceof Error ? error.stack : 'Unknown');
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to fetch backups',
|
||||||
|
details: error instanceof Error ? error.stack : undefined
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const { action, ...params } = body;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'create':
|
||||||
|
const backup = await backupService.createBackup('manual');
|
||||||
|
return NextResponse.json({ success: true, data: backup });
|
||||||
|
|
||||||
|
case 'verify':
|
||||||
|
await backupService.verifyDatabaseHealth();
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Database health check passed'
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'config':
|
||||||
|
await backupService.updateConfig(params.config);
|
||||||
|
// Redémarrer le scheduler si la config a changé
|
||||||
|
if (params.config.enabled !== undefined || params.config.interval !== undefined) {
|
||||||
|
backupScheduler.restart();
|
||||||
|
}
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Configuration updated',
|
||||||
|
data: backupService.getConfig()
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'scheduler':
|
||||||
|
if (params.enabled) {
|
||||||
|
backupScheduler.start();
|
||||||
|
} else {
|
||||||
|
backupScheduler.stop();
|
||||||
|
}
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: backupScheduler.getStatus()
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: 'Invalid action' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in backup operation:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user