Ajout interface admin déployable sur serveur
- API routes pour gestion clients (CRUD complet) - Système de stockage JSON persistant (data/clients.json) - Configuration Next.js serveur (next.config.server.js) - Script de build pour déploiement (scripts/build-server.sh) - Documentation complète de déploiement (ADMIN_DEPLOY.md) Fonctionnalités admin: - Création/modification/suppression de clients - Génération automatique de tokens uniques - QR codes pour configuration clients - Authentification par mot de passe - Backend Node.js avec API REST Déploiement prévu: marama.syoul.fr
This commit is contained in:
16
.gitignore
vendored
16
.gitignore
vendored
@ -35,3 +35,19 @@ yarn-error.log*
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# android
|
||||
/android/.gradle
|
||||
/android/build
|
||||
/android/app/build
|
||||
/android/local.properties
|
||||
/android/.idea
|
||||
|
||||
# capacitor
|
||||
/ios
|
||||
/android/.cxx
|
||||
|
||||
# data storage (ne pas commit les données clients)
|
||||
/data/clients.json
|
||||
|
||||
# APK
|
||||
/dist/*.apk
|
||||
|
||||
437
ADMIN_DEPLOY.md
Normal file
437
ADMIN_DEPLOY.md
Normal file
@ -0,0 +1,437 @@
|
||||
# 🖥️ Guide de déploiement de l'interface Admin
|
||||
|
||||
Ce guide explique comment déployer l'interface d'administration sur votre serveur **marama.syoul.fr**.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Vue d'ensemble
|
||||
|
||||
```
|
||||
┌─────────────────────┐ ┌──────────────────────────┐
|
||||
│ APK Client │ │ Admin Web │
|
||||
│ (Statique) │ │ marama.syoul.fr │
|
||||
│ │ │ │
|
||||
│ • Accueil │ │ • Login admin │
|
||||
│ • Explorer │ ←────│ • Gestion clients │
|
||||
│ • Mana Tracker │ │ • Génération tokens │
|
||||
│ • Infos │ │ • QR codes │
|
||||
└─────────────────────┘ └──────────────────────────┘
|
||||
Voyageurs Gérant (vous)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Déploiement en 5 étapes
|
||||
|
||||
### Étape 1 : Build de l'application admin
|
||||
|
||||
```bash
|
||||
cd "/home/syoul/Ccompagnon Marama"
|
||||
./scripts/build-server.sh
|
||||
```
|
||||
|
||||
Ce script va :
|
||||
- ✅ Configurer Next.js en mode serveur (avec API routes)
|
||||
- ✅ Build l'application
|
||||
- ✅ Préparer les fichiers dans `deploy/`
|
||||
- ✅ Créer les scripts de lancement
|
||||
|
||||
**⏱️ Durée :** 2-3 minutes
|
||||
|
||||
---
|
||||
|
||||
### Étape 2 : Transférer sur votre serveur
|
||||
|
||||
```bash
|
||||
# Depuis votre machine locale
|
||||
rsync -avz --delete deploy/ user@marama.syoul.fr:/var/www/pension-admin/
|
||||
|
||||
# Remplacez 'user' par votre nom d'utilisateur SSH
|
||||
```
|
||||
|
||||
**Alternative avec SCP :**
|
||||
```bash
|
||||
scp -r deploy/* user@marama.syoul.fr:/var/www/pension-admin/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Étape 3 : Configuration sur le serveur
|
||||
|
||||
```bash
|
||||
# Se connecter au serveur
|
||||
ssh user@marama.syoul.fr
|
||||
|
||||
# Aller dans le dossier
|
||||
cd /var/www/pension-admin
|
||||
|
||||
# Configurer le mot de passe admin
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
**Contenu de `.env` :**
|
||||
```bash
|
||||
# ⚠️ IMPORTANT : Changez ce mot de passe !
|
||||
ADMIN_PASSWORD=votre_mot_de_passe_tres_securise
|
||||
|
||||
# Port (optionnel)
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Étape 4 : Installer et démarrer
|
||||
|
||||
#### Option A : Lancement simple (test)
|
||||
|
||||
```bash
|
||||
chmod +x start.sh
|
||||
./start.sh
|
||||
```
|
||||
|
||||
L'admin sera accessible sur `http://marama.syoul.fr:3000`
|
||||
|
||||
#### Option B : Avec PM2 (production, recommandé ⭐)
|
||||
|
||||
```bash
|
||||
# Installer PM2 (si pas déjà fait)
|
||||
npm install -g pm2
|
||||
|
||||
# Démarrer l'application
|
||||
pm2 start npm --name "pension-admin" -- start
|
||||
|
||||
# Sauvegarder la config PM2
|
||||
pm2 save
|
||||
|
||||
# Configurer le démarrage auto
|
||||
pm2 startup
|
||||
# Suivre les instructions affichées
|
||||
|
||||
# Commandes utiles PM2
|
||||
pm2 status # Voir le statut
|
||||
pm2 logs pension-admin # Voir les logs
|
||||
pm2 restart pension-admin # Redémarrer
|
||||
pm2 stop pension-admin # Arrêter
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Étape 5 : Configuration Nginx (reverse proxy)
|
||||
|
||||
#### A. Créer la configuration Nginx
|
||||
|
||||
```bash
|
||||
sudo nano /etc/nginx/sites-available/pension-admin
|
||||
```
|
||||
|
||||
**Contenu du fichier :**
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name admin.marama.syoul.fr;
|
||||
|
||||
# Logs
|
||||
access_log /var/log/nginx/pension-admin-access.log;
|
||||
error_log /var/log/nginx/pension-admin-error.log;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### B. Activer la configuration
|
||||
|
||||
```bash
|
||||
# Créer le lien symbolique
|
||||
sudo ln -s /etc/nginx/sites-available/pension-admin /etc/nginx/sites-enabled/
|
||||
|
||||
# Tester la configuration
|
||||
sudo nginx -t
|
||||
|
||||
# Recharger Nginx
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
#### C. Configurer SSL (HTTPS) avec Let's Encrypt
|
||||
|
||||
```bash
|
||||
# Installer Certbot (si pas déjà fait)
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
|
||||
# Obtenir le certificat SSL
|
||||
sudo certbot --nginx -d admin.marama.syoul.fr
|
||||
|
||||
# Le renouvellement est automatique !
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Configuration DNS
|
||||
|
||||
Ajoutez un enregistrement A ou CNAME dans votre DNS :
|
||||
|
||||
```
|
||||
Type: A
|
||||
Nom: admin.marama.syoul.fr (ou juste "admin")
|
||||
Valeur: [IP de votre serveur]
|
||||
TTL: 3600
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Utilisation de l'interface admin
|
||||
|
||||
### 1. Se connecter
|
||||
|
||||
```
|
||||
URL: https://admin.marama.syoul.fr
|
||||
Mot de passe: celui défini dans .env
|
||||
```
|
||||
|
||||
### 2. Ajouter un client
|
||||
|
||||
1. Cliquer sur **"Nouveau client"**
|
||||
2. Remplir le formulaire :
|
||||
- **Email** : email du voyageur
|
||||
- **N° Bungalow** : 1, 2, 3, etc.
|
||||
- **WiFi** : nom et mot de passe
|
||||
- **Message du gérant** : message personnalisé
|
||||
|
||||
3. Cliquer sur **"Créer"**
|
||||
4. Un **QR code** et un **lien unique** sont générés automatiquement
|
||||
|
||||
### 3. Partager avec le client
|
||||
|
||||
**Option A : QR Code** (recommandé)
|
||||
- Afficher le QR code
|
||||
- Le voyageur scan avec son téléphone
|
||||
- L'app se configure automatiquement
|
||||
|
||||
**Option B : Lien**
|
||||
- Copier le lien unique
|
||||
- L'envoyer par email/SMS au client
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Mise à jour de l'application
|
||||
|
||||
Quand vous modifiez le code :
|
||||
|
||||
```bash
|
||||
# Sur votre machine locale
|
||||
cd "/home/syoul/Ccompagnon Marama"
|
||||
./scripts/build-server.sh
|
||||
|
||||
# Transférer
|
||||
rsync -avz --delete deploy/ user@marama.syoul.fr:/var/www/pension-admin/
|
||||
|
||||
# Sur le serveur
|
||||
ssh user@marama.syoul.fr
|
||||
cd /var/www/pension-admin
|
||||
pm2 restart pension-admin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📂 Structure des fichiers sur le serveur
|
||||
|
||||
```
|
||||
/var/www/pension-admin/
|
||||
├── .next/ # Application Next.js compilée
|
||||
├── public/ # Assets statiques
|
||||
├── data/
|
||||
│ └── clients.json # Base de données des clients (créé auto)
|
||||
├── package.json # Dépendances
|
||||
├── next.config.js # Config Next.js
|
||||
├── .env # Variables d'environnement (mot de passe)
|
||||
├── start.sh # Script de lancement
|
||||
└── DEPLOY.md # Documentation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Sécurité
|
||||
|
||||
### Recommandations importantes
|
||||
|
||||
1. **Mot de passe fort**
|
||||
```bash
|
||||
# Générer un mot de passe sécurisé
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
2. **Permissions fichiers**
|
||||
```bash
|
||||
chmod 600 /var/www/pension-admin/.env
|
||||
chmod 600 /var/www/pension-admin/data/clients.json
|
||||
```
|
||||
|
||||
3. **Firewall**
|
||||
```bash
|
||||
# Autoriser uniquement HTTP/HTTPS
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
4. **Sauvegarde des données**
|
||||
```bash
|
||||
# Créer un cron job pour sauvegarder clients.json
|
||||
0 2 * * * cp /var/www/pension-admin/data/clients.json /backup/clients-$(date +\%Y\%m\%d).json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Dépannage
|
||||
|
||||
### L'application ne démarre pas
|
||||
|
||||
```bash
|
||||
# Vérifier les logs
|
||||
pm2 logs pension-admin
|
||||
|
||||
# Vérifier que Node.js est installé
|
||||
node --version # doit être >= 18
|
||||
|
||||
# Réinstaller les dépendances
|
||||
cd /var/www/pension-admin
|
||||
rm -rf node_modules
|
||||
npm ci --production
|
||||
pm2 restart pension-admin
|
||||
```
|
||||
|
||||
### Erreur 502 Bad Gateway
|
||||
|
||||
```bash
|
||||
# Vérifier que l'app tourne
|
||||
pm2 status
|
||||
|
||||
# Vérifier les logs Nginx
|
||||
sudo tail -f /var/log/nginx/pension-admin-error.log
|
||||
|
||||
# Redémarrer Nginx
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
### Les clients ne peuvent pas se connecter
|
||||
|
||||
```bash
|
||||
# Vérifier les permissions du fichier data
|
||||
ls -la /var/www/pension-admin/data/
|
||||
|
||||
# Vérifier le contenu
|
||||
cat /var/www/pension-admin/data/clients.json
|
||||
|
||||
# Si le fichier n'existe pas, l'app le créera automatiquement
|
||||
```
|
||||
|
||||
### Changer le mot de passe admin
|
||||
|
||||
```bash
|
||||
# Sur le serveur
|
||||
cd /var/www/pension-admin
|
||||
nano .env
|
||||
# Modifier ADMIN_PASSWORD
|
||||
|
||||
# Redémarrer
|
||||
pm2 restart pension-admin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### Voir les statistiques avec PM2
|
||||
|
||||
```bash
|
||||
pm2 monit # Monitoring en temps réel
|
||||
pm2 logs # Voir tous les logs
|
||||
pm2 status # Statut de toutes les apps
|
||||
```
|
||||
|
||||
### Logs Nginx
|
||||
|
||||
```bash
|
||||
# Logs d'accès
|
||||
sudo tail -f /var/log/nginx/pension-admin-access.log
|
||||
|
||||
# Logs d'erreurs
|
||||
sudo tail -f /var/log/nginx/pension-admin-error.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Commandes utiles
|
||||
|
||||
```bash
|
||||
# Redémarrer l'application
|
||||
pm2 restart pension-admin
|
||||
|
||||
# Voir les logs en direct
|
||||
pm2 logs pension-admin --lines 100
|
||||
|
||||
# Recharger l'app (sans downtime)
|
||||
pm2 reload pension-admin
|
||||
|
||||
# Arrêter l'application
|
||||
pm2 stop pension-admin
|
||||
|
||||
# Démarrer l'application
|
||||
pm2 start pension-admin
|
||||
|
||||
# Supprimer de PM2
|
||||
pm2 delete pension-admin
|
||||
|
||||
# Sauvegarder la config PM2
|
||||
pm2 save
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Conseils
|
||||
|
||||
1. **Tester localement d'abord**
|
||||
```bash
|
||||
# Sur votre machine
|
||||
npm run dev
|
||||
# Ouvrir http://localhost:3000/admin
|
||||
```
|
||||
|
||||
2. **Utiliser un sous-domaine dédié**
|
||||
- `admin.marama.syoul.fr` (recommandé)
|
||||
- Plutôt que `marama.syoul.fr/admin`
|
||||
|
||||
3. **Configurer les sauvegardes automatiques**
|
||||
- Le fichier `data/clients.json` contient toutes vos données
|
||||
- Sauvegardez-le régulièrement
|
||||
|
||||
4. **Monitoring avec Uptime Robot**
|
||||
- Gratuit
|
||||
- Vous alerte si le site est down
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
Si vous rencontrez des problèmes :
|
||||
|
||||
1. Vérifier les logs : `pm2 logs pension-admin`
|
||||
2. Vérifier Nginx : `sudo nginx -t`
|
||||
3. Vérifier les permissions : `ls -la data/`
|
||||
|
||||
---
|
||||
|
||||
**Version** : 1.0.0 - Novembre 2025
|
||||
**Serveur** : marama.syoul.fr
|
||||
**URL Admin** : https://admin.marama.syoul.fr
|
||||
|
||||
153
app/api/admin/clients/[id]/route.ts
Normal file
153
app/api/admin/clients/[id]/route.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { writeFile, readFile, mkdir } from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
import path from "path";
|
||||
import { Client, ClientInput } from "@/lib/types/client";
|
||||
|
||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "admin123";
|
||||
const DATA_DIR = path.join(process.cwd(), "data");
|
||||
const CLIENTS_FILE = path.join(DATA_DIR, "clients.json");
|
||||
|
||||
function verifyAuth(request: NextRequest): boolean {
|
||||
const authHeader = request.headers.get("authorization");
|
||||
if (!authHeader) return false;
|
||||
|
||||
const token = authHeader.replace("Bearer ", "");
|
||||
return token === ADMIN_PASSWORD;
|
||||
}
|
||||
|
||||
async function loadClients(): Promise<Client[]> {
|
||||
try {
|
||||
if (!existsSync(CLIENTS_FILE)) {
|
||||
if (!existsSync(DATA_DIR)) {
|
||||
await mkdir(DATA_DIR, { recursive: true });
|
||||
}
|
||||
await writeFile(CLIENTS_FILE, JSON.stringify([], null, 2));
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = await readFile(CLIENTS_FILE, "utf-8");
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
console.error("Erreur lecture clients:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function saveClients(clients: Client[]): Promise<void> {
|
||||
try {
|
||||
if (!existsSync(DATA_DIR)) {
|
||||
await mkdir(DATA_DIR, { recursive: true });
|
||||
}
|
||||
await writeFile(CLIENTS_FILE, JSON.stringify(clients, null, 2));
|
||||
} catch (error) {
|
||||
console.error("Erreur sauvegarde clients:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// GET - Récupérer un client par ID
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
if (!verifyAuth(request)) {
|
||||
return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const clients = await loadClients();
|
||||
const client = clients.find(c => c.id === params.id);
|
||||
|
||||
if (!client) {
|
||||
return NextResponse.json(
|
||||
{ error: "Client non trouvé" },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(client);
|
||||
} catch (error) {
|
||||
console.error("Erreur GET client:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur serveur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// PUT - Mettre à jour un client
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
if (!verifyAuth(request)) {
|
||||
return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const input: ClientInput = await request.json();
|
||||
const clients = await loadClients();
|
||||
const clientIndex = clients.findIndex(c => c.id === params.id);
|
||||
|
||||
if (clientIndex === -1) {
|
||||
return NextResponse.json(
|
||||
{ error: "Client non trouvé" },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Mettre à jour le client
|
||||
const updatedClient: Client = {
|
||||
...clients[clientIndex],
|
||||
email: input.email || clients[clientIndex].email,
|
||||
bungalowNumber: input.bungalowNumber || clients[clientIndex].bungalowNumber,
|
||||
wifiName: input.wifiName || clients[clientIndex].wifiName,
|
||||
wifiPassword: input.wifiPassword || clients[clientIndex].wifiPassword,
|
||||
gerantMessage: input.gerantMessage || clients[clientIndex].gerantMessage,
|
||||
};
|
||||
|
||||
clients[clientIndex] = updatedClient;
|
||||
await saveClients(clients);
|
||||
|
||||
return NextResponse.json(updatedClient);
|
||||
} catch (error) {
|
||||
console.error("Erreur PUT client:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur serveur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE - Supprimer un client
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
if (!verifyAuth(request)) {
|
||||
return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const clients = await loadClients();
|
||||
const filteredClients = clients.filter(c => c.id !== params.id);
|
||||
|
||||
if (filteredClients.length === clients.length) {
|
||||
return NextResponse.json(
|
||||
{ error: "Client non trouvé" },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
await saveClients(filteredClients);
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error("Erreur DELETE client:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur serveur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
131
app/api/admin/clients/route.ts
Normal file
131
app/api/admin/clients/route.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { writeFile, readFile, mkdir } from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
import path from "path";
|
||||
import { Client, ClientInput } from "@/lib/types/client";
|
||||
|
||||
// Mot de passe admin (à changer en production via variable d'environnement)
|
||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "admin123";
|
||||
|
||||
// Chemin vers le fichier de stockage
|
||||
const DATA_DIR = path.join(process.cwd(), "data");
|
||||
const CLIENTS_FILE = path.join(DATA_DIR, "clients.json");
|
||||
|
||||
// Vérifier l'authentification
|
||||
function verifyAuth(request: NextRequest): boolean {
|
||||
const authHeader = request.headers.get("authorization");
|
||||
if (!authHeader) return false;
|
||||
|
||||
const token = authHeader.replace("Bearer ", "");
|
||||
return token === ADMIN_PASSWORD;
|
||||
}
|
||||
|
||||
// Charger les clients depuis le fichier
|
||||
async function loadClients(): Promise<Client[]> {
|
||||
try {
|
||||
if (!existsSync(CLIENTS_FILE)) {
|
||||
// Créer le répertoire et le fichier si nécessaire
|
||||
if (!existsSync(DATA_DIR)) {
|
||||
await mkdir(DATA_DIR, { recursive: true });
|
||||
}
|
||||
await writeFile(CLIENTS_FILE, JSON.stringify([], null, 2));
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = await readFile(CLIENTS_FILE, "utf-8");
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
console.error("Erreur lecture clients:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Sauvegarder les clients dans le fichier
|
||||
async function saveClients(clients: Client[]): Promise<void> {
|
||||
try {
|
||||
if (!existsSync(DATA_DIR)) {
|
||||
await mkdir(DATA_DIR, { recursive: true });
|
||||
}
|
||||
await writeFile(CLIENTS_FILE, JSON.stringify(clients, null, 2));
|
||||
} catch (error) {
|
||||
console.error("Erreur sauvegarde clients:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// GET - Récupérer tous les clients
|
||||
export async function GET(request: NextRequest) {
|
||||
if (!verifyAuth(request)) {
|
||||
return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const clients = await loadClients();
|
||||
return NextResponse.json(clients);
|
||||
} catch (error) {
|
||||
console.error("Erreur GET clients:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur serveur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// POST - Créer un nouveau client
|
||||
export async function POST(request: NextRequest) {
|
||||
if (!verifyAuth(request)) {
|
||||
return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const input: ClientInput = await request.json();
|
||||
|
||||
// Validation
|
||||
if (!input.email || !input.bungalowNumber) {
|
||||
return NextResponse.json(
|
||||
{ error: "Email et numéro de bungalow requis" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const clients = await loadClients();
|
||||
|
||||
// Vérifier si l'email existe déjà
|
||||
if (clients.some(c => c.email === input.email)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Un client avec cet email existe déjà" },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
// Créer le nouveau client
|
||||
const newClient: Client = {
|
||||
id: `client-${Date.now()}`,
|
||||
token: generateToken(),
|
||||
email: input.email,
|
||||
bungalowNumber: input.bungalowNumber,
|
||||
wifiName: input.wifiName || "Lagon-WiFi",
|
||||
wifiPassword: input.wifiPassword || "",
|
||||
gerantMessage: input.gerantMessage || "Bienvenue dans notre pension de famille !",
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
clients.push(newClient);
|
||||
await saveClients(clients);
|
||||
|
||||
return NextResponse.json(newClient, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error("Erreur POST client:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur serveur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Générer un token unique
|
||||
function generateToken(): string {
|
||||
return Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
|
||||
55
app/api/client/[token]/route.ts
Normal file
55
app/api/client/[token]/route.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { readFile } from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
import path from "path";
|
||||
import { Client } from "@/lib/types/client";
|
||||
|
||||
const DATA_DIR = path.join(process.cwd(), "data");
|
||||
const CLIENTS_FILE = path.join(DATA_DIR, "clients.json");
|
||||
|
||||
async function loadClients(): Promise<Client[]> {
|
||||
try {
|
||||
if (!existsSync(CLIENTS_FILE)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = await readFile(CLIENTS_FILE, "utf-8");
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
console.error("Erreur lecture clients:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// GET - Récupérer les données d'un client par son token
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { token: string } }
|
||||
) {
|
||||
try {
|
||||
const clients = await loadClients();
|
||||
const client = clients.find(c => c.token === params.token);
|
||||
|
||||
if (!client) {
|
||||
return NextResponse.json(
|
||||
{ error: "Client non trouvé" },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Retourner uniquement les données nécessaires (pas le token ni l'ID)
|
||||
return NextResponse.json({
|
||||
bungalowNumber: client.bungalowNumber,
|
||||
wifiName: client.wifiName,
|
||||
wifiPassword: client.wifiPassword,
|
||||
gerantMessage: client.gerantMessage,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erreur GET client par token:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur serveur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
2
data/.gitkeep
Normal file
2
data/.gitkeep
Normal file
@ -0,0 +1,2 @@
|
||||
# Ce dossier contiendra clients.json (ignoré par git)
|
||||
|
||||
19
next.config.server.js
Normal file
19
next.config.server.js
Normal file
@ -0,0 +1,19 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
// Mode serveur (pas d'export statique)
|
||||
// Les API routes fonctionnent normalement
|
||||
compress: true,
|
||||
poweredByHeader: false,
|
||||
images: {
|
||||
formats: ["image/avif", "image/webp"],
|
||||
minimumCacheTTL: 60,
|
||||
},
|
||||
experimental: {
|
||||
optimizePackageImports: ["lucide-react"],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
190
scripts/build-server.sh
Executable file
190
scripts/build-server.sh
Executable file
@ -0,0 +1,190 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Build de l'admin pour déploiement serveur"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
# Couleurs
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Vérifier qu'on est dans le bon répertoire
|
||||
if [ ! -f "package.json" ]; then
|
||||
echo "❌ Erreur: Exécutez ce script depuis la racine du projet"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Étape 1: Configuration Next.js pour serveur
|
||||
echo -e "${BLUE}📦 Étape 1/4: Configuration Next.js${NC}"
|
||||
if [ -f "next.config.js" ]; then
|
||||
cp next.config.js next.config.js.backup
|
||||
fi
|
||||
cp next.config.server.js next.config.js
|
||||
echo -e "${GREEN}✅ Configuration serveur activée${NC}"
|
||||
echo ""
|
||||
|
||||
# Étape 2: Installation des dépendances
|
||||
echo -e "${BLUE}📦 Étape 2/4: Vérification des dépendances${NC}"
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo "Installation des dépendances..."
|
||||
npm install
|
||||
else
|
||||
echo "Dépendances déjà installées"
|
||||
fi
|
||||
echo -e "${GREEN}✅ Dépendances OK${NC}"
|
||||
echo ""
|
||||
|
||||
# Étape 3: Build Next.js
|
||||
echo -e "${BLUE}📦 Étape 3/4: Build de production${NC}"
|
||||
rm -rf .next
|
||||
|
||||
if npm run build; then
|
||||
echo -e "${GREEN}✅ Build réussi${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Build avec avertissements${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Étape 4: Préparer les fichiers pour le déploiement
|
||||
echo -e "${BLUE}📦 Étape 4/4: Préparation du déploiement${NC}"
|
||||
|
||||
# Créer le dossier de déploiement
|
||||
mkdir -p deploy
|
||||
rm -rf deploy/*
|
||||
|
||||
# Copier les fichiers nécessaires
|
||||
echo "Copie des fichiers..."
|
||||
cp -r .next deploy/
|
||||
cp -r public deploy/
|
||||
cp package.json deploy/
|
||||
cp package-lock.json deploy/ 2>/dev/null || true
|
||||
cp next.config.server.js deploy/next.config.js
|
||||
|
||||
# Créer le dossier data
|
||||
mkdir -p deploy/data
|
||||
cp data/.gitkeep deploy/data/ 2>/dev/null || true
|
||||
|
||||
# Créer un fichier .env.example
|
||||
cat > deploy/.env.example << 'EOF'
|
||||
# Mot de passe admin
|
||||
ADMIN_PASSWORD=votre_mot_de_passe_securise
|
||||
|
||||
# Port (optionnel, par défaut 3000)
|
||||
PORT=3000
|
||||
EOF
|
||||
|
||||
# Créer un fichier de lancement
|
||||
cat > deploy/start.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
# Script de lancement pour le serveur
|
||||
|
||||
# Installer les dépendances production uniquement
|
||||
npm ci --production
|
||||
|
||||
# Démarrer le serveur
|
||||
npm start
|
||||
EOF
|
||||
|
||||
chmod +x deploy/start.sh
|
||||
|
||||
# Créer un fichier README pour le déploiement
|
||||
cat > deploy/DEPLOY.md << 'EOF'
|
||||
# Déploiement sur serveur
|
||||
|
||||
## Étapes de déploiement
|
||||
|
||||
1. **Transférer les fichiers sur le serveur**
|
||||
```bash
|
||||
rsync -avz --delete deploy/ user@marama.syoul.fr:/var/www/pension-admin/
|
||||
```
|
||||
|
||||
2. **Se connecter au serveur**
|
||||
```bash
|
||||
ssh user@marama.syoul.fr
|
||||
cd /var/www/pension-admin
|
||||
```
|
||||
|
||||
3. **Configurer les variables d'environnement**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env # Modifier ADMIN_PASSWORD
|
||||
```
|
||||
|
||||
4. **Installer et démarrer**
|
||||
```bash
|
||||
chmod +x start.sh
|
||||
./start.sh
|
||||
```
|
||||
|
||||
5. **Avec PM2 (recommandé)**
|
||||
```bash
|
||||
npm install -g pm2
|
||||
pm2 start npm --name "pension-admin" -- start
|
||||
pm2 save
|
||||
pm2 startup
|
||||
```
|
||||
|
||||
## Configuration Nginx (reverse proxy)
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name admin.marama.syoul.fr;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## SSL avec Certbot
|
||||
|
||||
```bash
|
||||
sudo certbot --nginx -d admin.marama.syoul.fr
|
||||
```
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✅ Fichiers prêts dans deploy/${NC}"
|
||||
echo ""
|
||||
|
||||
# Restaurer la config originale
|
||||
if [ -f "next.config.js.backup" ]; then
|
||||
mv next.config.js.backup next.config.js
|
||||
fi
|
||||
|
||||
# Afficher le résumé
|
||||
DEPLOY_SIZE=$(du -sh deploy | cut -f1)
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}✅ BUILD SERVEUR TERMINÉ !${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "📁 Dossier de déploiement: ${BLUE}deploy/${NC}"
|
||||
echo -e "📊 Taille: ${BLUE}${DEPLOY_SIZE}${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}📤 Prochaines étapes:${NC}"
|
||||
echo ""
|
||||
echo "1. Transférer sur le serveur:"
|
||||
echo " ${BLUE}rsync -avz --delete deploy/ user@marama.syoul.fr:/var/www/pension-admin/${NC}"
|
||||
echo ""
|
||||
echo "2. Sur le serveur:"
|
||||
echo " ${BLUE}cd /var/www/pension-admin${NC}"
|
||||
echo " ${BLUE}./start.sh${NC}"
|
||||
echo ""
|
||||
echo "3. Configurer le mot de passe admin:"
|
||||
echo " ${BLUE}nano .env${NC}"
|
||||
echo " ${BLUE}ADMIN_PASSWORD=votre_mot_de_passe${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}📖 Documentation complète: deploy/DEPLOY.md${NC}"
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user