+
Récapitulatif
- Place:
+ Place :
-
- Date:
+ Date :
-
- Heure:
+ Heure :
-
- Durée:
+ Durée :
-
- Total:
+ Total :
-
-
-
Scannez ce QR code pour payer
-
-
Ou utilisez le code: -
+
+
+
🏷️
+
Merci pour votre réservation !
+
Votre place est bien réservée.
+ Vous recevrez votre badge d'accès dans les 24 heures.
-
-
-
-
+
+
diff --git a/server/db/database.js b/server/db/database.js
index c6ba7dd..11b1f42 100644
--- a/server/db/database.js
+++ b/server/db/database.js
@@ -1,125 +1,135 @@
-const mysql = require('mysql2/promise');
+/**
+ * ============================================
+ * DATABASE.JS - Gestion MariaDB
+ * Smart Parking - BTS CIEL IR
+ * MODIFIÉ : ajout de getReservationById
+ * ============================================
+ */
+
+const mysql = require('mysql2/promise');
const bcrypt = require('bcryptjs');
require('dotenv').config();
const pool = mysql.createPool({
- host: process.env.DB_HOST || 'localhost',
- port: process.env.DB_PORT || 3306,
- user: process.env.DB_USER || 'smartparking_user',
- password: process.env.DB_PASSWORD || 'smartparking_pass',
- database: process.env.DB_NAME || 'smartparking',
- waitForConnections: true,
- connectionLimit: 10,
- queueLimit: 0
+ host: process.env.DB_HOST || 'localhost',
+ port: process.env.DB_PORT || 3306,
+ user: process.env.DB_USER || 'smartparking_user',
+ password: process.env.DB_PASSWORD || 'smartparking_pass',
+ database: process.env.DB_NAME || 'smartparking',
+ waitForConnections: true,
+ connectionLimit: 10,
+ queueLimit: 0
});
async function initDatabase() {
- try {
- await pool.query(`
- CREATE TABLE IF NOT EXISTS users (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(255) NOT NULL,
- email VARCHAR(255) UNIQUE NOT NULL,
- phone VARCHAR(50),
- password VARCHAR(255) NOT NULL,
- role ENUM('admin','client') DEFAULT 'client',
- status ENUM('active','inactive') DEFAULT 'active',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )
- `);
+ try {
+ await pool.query(`
+ CREATE TABLE IF NOT EXISTS users (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ email VARCHAR(255) UNIQUE NOT NULL,
+ phone VARCHAR(50),
+ password VARCHAR(255) NOT NULL,
+ role ENUM('admin','client') DEFAULT 'client',
+ status ENUM('active','inactive') DEFAULT 'active',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ `);
- await pool.query(`
- CREATE TABLE IF NOT EXISTS spots (
- id INT AUTO_INCREMENT PRIMARY KEY,
- number INT UNIQUE NOT NULL,
- status ENUM('free','occupied','reserved') DEFAULT 'free',
- sensor_id VARCHAR(100),
- last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- )
- `);
+ await pool.query(`
+ CREATE TABLE IF NOT EXISTS spots (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ number INT UNIQUE NOT NULL,
+ status ENUM('free','occupied','reserved') DEFAULT 'free',
+ sensor_id VARCHAR(100),
+ last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ )
+ `);
- await pool.query(`
- CREATE TABLE IF NOT EXISTS reservations (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- spot_id INT NOT NULL,
- date DATE NOT NULL,
- start_time TIME NOT NULL,
- end_time TIME NOT NULL,
- duration INT NOT NULL,
- vehicle VARCHAR(20),
- price DECIMAL(10,2) NOT NULL,
- status ENUM('pending','active','completed','cancelled') DEFAULT 'pending',
- payment_code VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (spot_id) REFERENCES spots(id) ON DELETE CASCADE
- )
- `);
+ await pool.query(`
+ CREATE TABLE IF NOT EXISTS reservations (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ user_id INT NOT NULL,
+ spot_id INT NOT NULL,
+ date DATE NOT NULL,
+ start_time TIME NOT NULL,
+ end_time TIME NOT NULL,
+ duration INT NOT NULL,
+ vehicle VARCHAR(20),
+ price DECIMAL(10,2) NOT NULL,
+ status ENUM('pending','active','completed','cancelled') DEFAULT 'pending',
+ payment_code VARCHAR(50),
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY (spot_id) REFERENCES spots(id) ON DELETE CASCADE
+ )
+ `);
- await pool.query(`
- CREATE TABLE IF NOT EXISTS history (
- id INT AUTO_INCREMENT PRIMARY KEY,
- action VARCHAR(255) NOT NULL,
- details TEXT,
- user_id INT,
- timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
- )
- `);
+ await pool.query(`
+ CREATE TABLE IF NOT EXISTS history (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ action VARCHAR(255) NOT NULL,
+ details TEXT,
+ user_id INT,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
+ )
+ `);
- await pool.query(`
- CREATE TABLE IF NOT EXISTS stats (
- id INT AUTO_INCREMENT PRIMARY KEY,
- total_spots INT,
- free_spots INT,
- occupied_spots INT,
- reserved_spots INT,
- occupancy_rate DECIMAL(5,2),
- recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )
- `);
+ await pool.query(`
+ CREATE TABLE IF NOT EXISTS stats (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ total_spots INT,
+ free_spots INT,
+ occupied_spots INT,
+ reserved_spots INT,
+ occupancy_rate DECIMAL(5,2),
+ recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ `);
- await pool.query(`
- CREATE TABLE IF NOT EXISTS mqtt_events (
- id INT AUTO_INCREMENT PRIMARY KEY,
- topic VARCHAR(255) NOT NULL,
- message TEXT,
- received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )
- `);
+ await pool.query(`
+ CREATE TABLE IF NOT EXISTS mqtt_events (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ topic VARCHAR(255) NOT NULL,
+ message TEXT,
+ received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ `);
- console.log('✅ Tables vérifiées/créées avec succès');
+ console.log('✅ Tables vérifiées/créées avec succès');
- const [rows] = await pool.query('SELECT * FROM users WHERE email = ?', ['admin@smartparking.fr']);
- if (rows.length === 0) {
- const hashedPassword = await bcrypt.hash('admin123', 10);
- await pool.query(
- 'INSERT INTO users (name, email, phone, password, role) VALUES (?, ?, ?, ?, ?)',
- ['Administrateur', 'admin@smartparking.fr', '01 23 45 67 89', hashedPassword, 'admin']
- );
- console.log('✅ Administrateur par défaut créé');
+ // Compte admin par défaut
+ const [rows] = await pool.query('SELECT id FROM users WHERE email = ?', ['admin@smartparking.fr']);
+ if (rows.length === 0) {
+ const hashed = await bcrypt.hash('admin123', 10);
+ await pool.query(
+ 'INSERT INTO users (name, email, phone, password, role) VALUES (?, ?, ?, ?, ?)',
+ ['Administrateur', 'admin@smartparking.fr', '01 23 45 67 89', hashed, 'admin']
+ );
+ console.log('✅ Administrateur par défaut créé');
+ }
+
+ // Places par défaut
+ const [spots] = await pool.query('SELECT COUNT(*) AS count FROM spots');
+ if (spots[0].count === 0) {
+ for (let i = 1; i <= 10; i++) {
+ let status = 'free';
+ const rand = Math.random();
+ if (rand > 0.85) status = 'reserved';
+ else if (rand > 0.60) status = 'occupied';
+ await pool.query(
+ 'INSERT INTO spots (number, sensor_id, status) VALUES (?, ?, ?)',
+ [i, `SENSOR_${String(i).padStart(3, '0')}`, status]
+ );
+ }
+ console.log('✅ 10 places par défaut créées');
+ }
+
+ } catch (err) {
+ console.error("❌ Erreur lors de l'initialisation de la base :", err.message);
+ throw err;
}
-
- const [spots] = await pool.query('SELECT COUNT(*) as count FROM spots');
- if (spots[0].count === 0) {
- for (let i = 1; i <= 10; i++) {
- let status = 'free';
- const rand = Math.random();
- if (rand > 0.85) status = 'reserved';
- else if (rand > 0.60) status = 'occupied';
- await pool.query(
- 'INSERT INTO spots (number, sensor_id, status) VALUES (?, ?, ?)',
- [i, `SENSOR_${String(i).padStart(3, '0')}`, status]
- );
- }
- console.log('✅ 10 places par défaut créées');
- }
-
- } catch (err) {
- console.error('❌ Erreur lors de l\'initialisation de la base :', err.message);
- throw err;
- }
}
// ============================================
@@ -127,43 +137,43 @@ async function initDatabase() {
// ============================================
async function createUser(name, email, phone, hashedPassword, role = 'client') {
- const [result] = await pool.query(
- 'INSERT INTO users (name, email, phone, password, role) VALUES (?, ?, ?, ?, ?)',
- [name, email, phone, hashedPassword, role]
- );
- return { id: result.insertId };
+ const [result] = await pool.query(
+ 'INSERT INTO users (name, email, phone, password, role) VALUES (?, ?, ?, ?, ?)',
+ [name, email, phone, hashedPassword, role]
+ );
+ return { id: result.insertId };
}
async function getUserByEmail(email) {
- const [rows] = await pool.query('SELECT * FROM users WHERE email = ?', [email]);
- return rows[0];
+ const [rows] = await pool.query('SELECT * FROM users WHERE email = ?', [email]);
+ return rows[0];
}
async function getUserById(id) {
- const [rows] = await pool.query(
- 'SELECT id, name, email, phone, role, status, created_at FROM users WHERE id = ?',
- [id]
- );
- return rows[0];
+ const [rows] = await pool.query(
+ 'SELECT id, name, email, phone, role, status, created_at FROM users WHERE id = ?',
+ [id]
+ );
+ return rows[0];
}
async function getAllUsers() {
- const [rows] = await pool.query(
- 'SELECT id, name, email, phone, role, status, created_at FROM users ORDER BY name'
- );
- return rows;
+ const [rows] = await pool.query(
+ 'SELECT id, name, email, phone, role, status, created_at FROM users ORDER BY name'
+ );
+ return rows;
}
async function updateUser(id, updates) {
- const fields = Object.keys(updates).map(k => `${k} = ?`).join(', ');
- const values = Object.values(updates);
- const [result] = await pool.query(`UPDATE users SET ${fields} WHERE id = ?`, [...values, id]);
- return { changed: result.affectedRows };
+ const fields = Object.keys(updates).map(k => `${k} = ?`).join(', ');
+ const values = Object.values(updates);
+ const [result] = await pool.query(`UPDATE users SET ${fields} WHERE id = ?`, [...values, id]);
+ return { changed: result.affectedRows };
}
async function deleteUser(id) {
- const [result] = await pool.query('DELETE FROM users WHERE id = ?', [id]);
- return { deleted: result.affectedRows };
+ const [result] = await pool.query('DELETE FROM users WHERE id = ?', [id]);
+ return { deleted: result.affectedRows };
}
// ============================================
@@ -171,34 +181,34 @@ async function deleteUser(id) {
// ============================================
async function createSpot(number, sensorId, status = 'free') {
- const [result] = await pool.query(
- 'INSERT INTO spots (number, sensor_id, status) VALUES (?, ?, ?)',
- [number, sensorId, status]
- );
- return { id: result.insertId };
+ const [result] = await pool.query(
+ 'INSERT INTO spots (number, sensor_id, status) VALUES (?, ?, ?)',
+ [number, sensorId, status]
+ );
+ return { id: result.insertId };
}
async function getAllSpots() {
- const [rows] = await pool.query('SELECT * FROM spots ORDER BY number');
- return rows;
+ const [rows] = await pool.query('SELECT * FROM spots ORDER BY number');
+ return rows;
}
async function getSpotById(id) {
- const [rows] = await pool.query('SELECT * FROM spots WHERE id = ?', [id]);
- return rows[0];
+ const [rows] = await pool.query('SELECT * FROM spots WHERE id = ?', [id]);
+ return rows[0];
}
async function updateSpotStatus(id, status) {
- const [result] = await pool.query(
- 'UPDATE spots SET status = ?, last_update = CURRENT_TIMESTAMP WHERE id = ?',
- [status, id]
- );
- return { changed: result.affectedRows };
+ const [result] = await pool.query(
+ 'UPDATE spots SET status = ?, last_update = CURRENT_TIMESTAMP WHERE id = ?',
+ [status, id]
+ );
+ return { changed: result.affectedRows };
}
async function deleteAllSpots() {
- const [result] = await pool.query('DELETE FROM spots');
- return { deleted: result.affectedRows };
+ const [result] = await pool.query('DELETE FROM spots');
+ return { deleted: result.affectedRows };
}
// ============================================
@@ -206,44 +216,59 @@ async function deleteAllSpots() {
// ============================================
async function createReservation(userId, spotId, date, startTime, endTime, duration, vehicle, price, paymentCode) {
- const [result] = await pool.query(
- `INSERT INTO reservations
- (user_id, spot_id, date, start_time, end_time, duration, vehicle, price, payment_code, status)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active')`,
- [userId, spotId, date, startTime, endTime, duration, vehicle, price, paymentCode]
- );
- return { id: result.insertId };
+ const [result] = await pool.query(
+ `INSERT INTO reservations
+ (user_id, spot_id, date, start_time, end_time, duration, vehicle, price, payment_code, status)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active')`,
+ [userId, spotId, date, startTime, endTime, duration, vehicle, price, paymentCode]
+ );
+ return { id: result.insertId };
+}
+
+/**
+ * Récupère une réservation par son ID
+ * AJOUTÉ : nécessaire pour l'annulation/complétion (libération de la place)
+ */
+async function getReservationById(id) {
+ const [rows] = await pool.query(
+ `SELECT r.*, s.number AS spot_number
+ FROM reservations r
+ JOIN spots s ON r.spot_id = s.id
+ WHERE r.id = ?`,
+ [id]
+ );
+ return rows[0];
}
async function getReservationsByUser(userId) {
- const [rows] = await pool.query(
- `SELECT r.*, s.number as spot_number
- FROM reservations r
- JOIN spots s ON r.spot_id = s.id
- WHERE r.user_id = ?
- ORDER BY r.created_at DESC`,
- [userId]
- );
- return rows;
+ const [rows] = await pool.query(
+ `SELECT r.*, s.number AS spot_number
+ FROM reservations r
+ JOIN spots s ON r.spot_id = s.id
+ WHERE r.user_id = ?
+ ORDER BY r.created_at DESC`,
+ [userId]
+ );
+ return rows;
}
async function getAllReservations() {
- const [rows] = await pool.query(
- `SELECT r.*, s.number as spot_number, u.name as user_name
- FROM reservations r
- JOIN spots s ON r.spot_id = s.id
- JOIN users u ON r.user_id = u.id
- ORDER BY r.created_at DESC`
- );
- return rows;
+ const [rows] = await pool.query(
+ `SELECT r.*, s.number AS spot_number, u.name AS user_name
+ FROM reservations r
+ JOIN spots s ON r.spot_id = s.id
+ JOIN users u ON r.user_id = u.id
+ ORDER BY r.created_at DESC`
+ );
+ return rows;
}
async function updateReservationStatus(id, status) {
- const [result] = await pool.query(
- 'UPDATE reservations SET status = ? WHERE id = ?',
- [status, id]
- );
- return { changed: result.affectedRows };
+ const [result] = await pool.query(
+ 'UPDATE reservations SET status = ? WHERE id = ?',
+ [status, id]
+ );
+ return { changed: result.affectedRows };
}
// ============================================
@@ -251,23 +276,23 @@ async function updateReservationStatus(id, status) {
// ============================================
async function addHistory(action, details, userId = null) {
- const [result] = await pool.query(
- 'INSERT INTO history (action, details, user_id) VALUES (?, ?, ?)',
- [action, details, userId]
- );
- return { id: result.insertId };
+ const [result] = await pool.query(
+ 'INSERT INTO history (action, details, user_id) VALUES (?, ?, ?)',
+ [action, details, userId]
+ );
+ return { id: result.insertId };
}
async function getHistory(limit = 50) {
- const [rows] = await pool.query(
- `SELECT h.*, u.name as user_name
- FROM history h
- LEFT JOIN users u ON h.user_id = u.id
- ORDER BY h.timestamp DESC
- LIMIT ?`,
- [limit]
- );
- return rows;
+ const [rows] = await pool.query(
+ `SELECT h.*, u.name AS user_name
+ FROM history h
+ LEFT JOIN users u ON h.user_id = u.id
+ ORDER BY h.timestamp DESC
+ LIMIT ?`,
+ [limit]
+ );
+ return rows;
}
// ============================================
@@ -275,22 +300,22 @@ async function getHistory(limit = 50) {
// ============================================
async function recordStats(total, free, occupied, reserved) {
- const rate = total > 0 ? Math.round(((occupied + reserved) / total) * 100) : 0;
- const [result] = await pool.query(
- 'INSERT INTO stats (total_spots, free_spots, occupied_spots, reserved_spots, occupancy_rate) VALUES (?, ?, ?, ?, ?)',
- [total, free, occupied, reserved, rate]
- );
- return { id: result.insertId };
+ const rate = total > 0 ? Math.round(((occupied + reserved) / total) * 100) : 0;
+ const [result] = await pool.query(
+ 'INSERT INTO stats (total_spots, free_spots, occupied_spots, reserved_spots, occupancy_rate) VALUES (?, ?, ?, ?, ?)',
+ [total, free, occupied, reserved, rate]
+ );
+ return { id: result.insertId };
}
async function getStats(days = 7) {
- const [rows] = await pool.query(
- `SELECT * FROM stats
- WHERE recorded_at > DATE_SUB(NOW(), INTERVAL ? DAY)
- ORDER BY recorded_at DESC`,
- [days]
- );
- return rows;
+ const [rows] = await pool.query(
+ `SELECT * FROM stats
+ WHERE recorded_at > DATE_SUB(NOW(), INTERVAL ? DAY)
+ ORDER BY recorded_at DESC`,
+ [days]
+ );
+ return rows;
}
// ============================================
@@ -298,43 +323,37 @@ async function getStats(days = 7) {
// ============================================
async function recordMqttEvent(topic, message) {
- const [result] = await pool.query(
- 'INSERT INTO mqtt_events (topic, message) VALUES (?, ?)',
- [topic, message]
- );
- return { id: result.insertId };
+ const [result] = await pool.query(
+ 'INSERT INTO mqtt_events (topic, message) VALUES (?, ?)',
+ [topic, message]
+ );
+ return { id: result.insertId };
}
// ============================================
-// FERMETURE DU POOL
+// FERMETURE
// ============================================
async function closeDatabase() {
- await pool.end();
- console.log('🔌 Connexions à la base fermées');
+ await pool.end();
+ console.log('🔌 Connexions à la base fermées');
}
module.exports = {
- initDatabase,
- closeDatabase,
- createUser,
- getUserByEmail,
- getUserById,
- getAllUsers,
- updateUser,
- deleteUser,
- createSpot,
- getAllSpots,
- getSpotById,
- updateSpotStatus,
- deleteAllSpots,
- createReservation,
- getReservationsByUser,
- getAllReservations,
- updateReservationStatus,
- addHistory,
- getHistory,
- recordStats,
- getStats,
- recordMqttEvent
+ pool, // exposé pour les requêtes directes si besoin
+ initDatabase,
+ closeDatabase,
+ // Utilisateurs
+ createUser, getUserByEmail, getUserById, getAllUsers, updateUser, deleteUser,
+ // Places
+ createSpot, getAllSpots, getSpotById, updateSpotStatus, deleteAllSpots,
+ // Réservations
+ createReservation, getReservationById, getReservationsByUser,
+ getAllReservations, updateReservationStatus,
+ // Historique
+ addHistory, getHistory,
+ // Stats
+ recordStats, getStats,
+ // MQTT
+ recordMqttEvent
};
\ No newline at end of file
diff --git a/server/routes/api.js b/server/routes/api.js
index 127dd32..d0ed2e5 100644
--- a/server/routes/api.js
+++ b/server/routes/api.js
@@ -2,129 +2,58 @@
* ============================================
* API ROUTES - Routes de l'API REST
* Smart Parking - BTS CIEL IR
+ * CORRIGÉ : annulation libère bien la place
+ * ajout route /complete pour l'admin
* ============================================
*/
const express = require('express');
-const router = express.Router();
-const bcrypt = require('bcryptjs');
-const { v4: uuidv4 } = require('uuid');
-const db = require('../db/database');
+const router = express.Router();
+const bcrypt = require('bcryptjs');
+const db = require('../db/database');
const { generateToken, authenticateToken, requireAdmin } = require('../middleware/auth');
// ============================================
// AUTHENTIFICATION
// ============================================
-/**
- * POST /api/register
- * Inscription d'un nouvel utilisateur
- */
router.post('/register', async (req, res) => {
try {
const { name, email, phone, password } = req.body;
-
- if (!name || !email || !password) {
- return res.status(400).json({
- success: false,
- message: 'Tous les champs sont requis'
- });
- }
-
- // Vérifier si l'email existe déjà
- const existingUser = await db.getUserByEmail(email);
- if (existingUser) {
- return res.status(409).json({
- success: false,
- message: 'Cet email est déjà utilisé'
- });
- }
-
- // Hasher le mot de passe
+ if (!name || !email || !password)
+ return res.status(400).json({ success: false, message: 'Tous les champs sont requis' });
+ if (await db.getUserByEmail(email))
+ return res.status(409).json({ success: false, message: 'Cet email est déjà utilisé' });
const hashedPassword = await bcrypt.hash(password, 10);
-
- // Créer l'utilisateur
const result = await db.createUser(name, email, phone, hashedPassword, 'client');
-
- // Générer le token
- const user = await db.getUserById(result.id);
- const token = generateToken(user);
-
+ const user = await db.getUserById(result.id);
+ const token = generateToken(user);
res.status(201).json({
- success: true,
- message: 'Compte créé avec succès',
- token,
- user: {
- id: user.id,
- name: user.name,
- email: user.email,
- phone: user.phone,
- role: user.role
- }
+ success: true, message: 'Compte créé avec succès', token,
+ user: { id: user.id, name: user.name, email: user.email, phone: user.phone, role: user.role }
});
} catch (err) {
console.error('❌ Erreur register:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
-/**
- * POST /api/login
- * Connexion
- */
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
-
- if (!email || !password) {
- return res.status(400).json({
- success: false,
- message: 'Email et mot de passe requis'
- });
- }
-
- // Récupérer l'utilisateur
+ if (!email || !password)
+ return res.status(400).json({ success: false, message: 'Email et mot de passe requis' });
const user = await db.getUserByEmail(email);
- if (!user) {
- return res.status(401).json({
- success: false,
- message: 'Email ou mot de passe incorrect'
- });
- }
-
- // Vérifier le mot de passe
- const validPassword = await bcrypt.compare(password, user.password);
- if (!validPassword) {
- return res.status(401).json({
- success: false,
- message: 'Email ou mot de passe incorrect'
- });
- }
-
- // Générer le token
+ if (!user || !(await bcrypt.compare(password, user.password)))
+ return res.status(401).json({ success: false, message: 'Email ou mot de passe incorrect' });
const token = generateToken(user);
-
res.json({
- success: true,
- message: 'Connexion réussie',
- token,
- user: {
- id: user.id,
- name: user.name,
- email: user.email,
- phone: user.phone,
- role: user.role
- }
+ success: true, message: 'Connexion réussie', token,
+ user: { id: user.id, name: user.name, email: user.email, phone: user.phone, role: user.role }
});
} catch (err) {
console.error('❌ Erreur login:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
@@ -132,44 +61,21 @@ router.post('/login', async (req, res) => {
// UTILISATEURS
// ============================================
-/**
- * GET /api/users
- * Liste tous les utilisateurs (admin uniquement)
- */
router.get('/users', authenticateToken, requireAdmin, async (req, res) => {
try {
const users = await db.getAllUsers();
- res.json({
- success: true,
- count: users.length,
- data: users
- });
+ res.json({ success: true, count: users.length, data: users });
} catch (err) {
- console.error('❌ Erreur get users:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
-/**
- * DELETE /api/users/:id
- * Supprime un utilisateur (admin uniquement)
- */
router.delete('/users/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
await db.deleteUser(req.params.id);
- res.json({
- success: true,
- message: 'Utilisateur supprimé'
- });
+ res.json({ success: true, message: 'Utilisateur supprimé' });
} catch (err) {
- console.error('❌ Erreur delete user:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
@@ -177,95 +83,43 @@ router.delete('/users/:id', authenticateToken, requireAdmin, async (req, res) =>
// PLACES
// ============================================
-/**
- * GET /api/spots
- * Liste toutes les places
- */
router.get('/spots', authenticateToken, async (req, res) => {
try {
const spots = await db.getAllSpots();
- res.json({
- success: true,
- count: spots.length,
- data: spots
- });
+ res.json({ success: true, count: spots.length, data: spots });
} catch (err) {
- console.error('❌ Erreur get spots:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
-/**
- * PUT /api/spots/:id/status
- * Met à jour le statut d'une place
- */
router.put('/spots/:id/status', authenticateToken, async (req, res) => {
try {
const { status } = req.body;
- const validStatuses = ['free', 'occupied', 'reserved'];
-
- if (!status || !validStatuses.includes(status)) {
- return res.status(400).json({
- success: false,
- message: 'Statut invalide'
- });
- }
-
+ if (!['free', 'occupied', 'reserved'].includes(status))
+ return res.status(400).json({ success: false, message: 'Statut invalide' });
await db.updateSpotStatus(req.params.id, status);
-
- // Ajouter à l'historique
- await db.addHistory('Mise à jour place', `Place ${req.params.id} - ${status}`, req.user.id);
-
- res.json({
- success: true,
- message: 'Statut mis à jour'
- });
+ await db.addHistory('Mise à jour place', `Place ${req.params.id} -> ${status}`, req.user.id);
+ res.json({ success: true, message: 'Statut mis à jour' });
} catch (err) {
- console.error('❌ Erreur update spot:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
-/**
- * POST /api/spots/init
- * Réinitialise les places (admin uniquement)
- */
router.post('/spots/init', authenticateToken, requireAdmin, async (req, res) => {
try {
- const { count } = req.body;
- const spotCount = count || 10;
-
- // Supprimer les places existantes
+ const spotCount = Math.min(Math.max(parseInt(req.body.count) || 10, 5), 50);
await db.deleteAllSpots();
-
- // Créer les nouvelles places
for (let i = 1; i <= spotCount; i++) {
let status = 'free';
const rand = Math.random();
if (rand > 0.85) status = 'reserved';
else if (rand > 0.60) status = 'occupied';
-
await db.createSpot(i, `SENSOR_${String(i).padStart(3, '0')}`, status);
}
-
await db.addHistory('Réinitialisation places', `${spotCount} places créées`, req.user.id);
-
- res.json({
- success: true,
- message: `${spotCount} places créées`
- });
+ res.json({ success: true, message: `${spotCount} places créées` });
} catch (err) {
- console.error('❌ Erreur init spots:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
@@ -273,131 +127,91 @@ router.post('/spots/init', authenticateToken, requireAdmin, async (req, res) =>
// RÉSERVATIONS
// ============================================
-/**
- * GET /api/reservations
- * Liste les réservations de l'utilisateur connecté
- */
router.get('/reservations', authenticateToken, async (req, res) => {
try {
const reservations = await db.getReservationsByUser(req.user.id);
- res.json({
- success: true,
- count: reservations.length,
- data: reservations
- });
+ res.json({ success: true, count: reservations.length, data: reservations });
} catch (err) {
- console.error('❌ Erreur get reservations:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
-/**
- * GET /api/reservations/all
- * Liste toutes les réservations (admin uniquement)
- */
router.get('/reservations/all', authenticateToken, requireAdmin, async (req, res) => {
try {
const reservations = await db.getAllReservations();
- res.json({
- success: true,
- count: reservations.length,
- data: reservations
- });
+ res.json({ success: true, count: reservations.length, data: reservations });
} catch (err) {
- console.error('❌ Erreur get all reservations:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
-/**
- * POST /api/reservations
- * Crée une nouvelle réservation
- */
router.post('/reservations', authenticateToken, async (req, res) => {
try {
const { spotId, date, startTime, endTime, duration, vehicle, price } = req.body;
-
- if (!spotId || !date || !startTime || !endTime || !duration || !price) {
- return res.status(400).json({
- success: false,
- message: 'Tous les champs sont requis'
- });
- }
-
- // Vérifier que la place est libre
+ if (!spotId || !date || !startTime || !endTime || !duration || !price)
+ return res.status(400).json({ success: false, message: 'Tous les champs sont requis' });
const spot = await db.getSpotById(spotId);
- if (!spot || spot.status !== 'free') {
- return res.status(409).json({
- success: false,
- message: 'Cette place n\'est plus disponible'
- });
- }
-
- // Générer un code de paiement
+ if (!spot || spot.status !== 'free')
+ return res.status(409).json({ success: false, message: "Cette place n'est plus disponible" });
const paymentCode = 'PARK' + Date.now().toString().slice(-8);
-
- // Créer la réservation
const result = await db.createReservation(
- req.user.id,
- spotId,
- date,
- startTime,
- endTime,
- duration,
- vehicle,
- price,
- paymentCode
+ req.user.id, spotId, date, startTime, endTime, duration, vehicle, price, paymentCode
);
-
- // Mettre à jour le statut de la place
await db.updateSpotStatus(spotId, 'reserved');
-
- // Ajouter à l'historique
- await db.addHistory('Nouvelle réservation', `Place ${spot.number} - ${price}€`, req.user.id);
-
- res.status(201).json({
- success: true,
- message: 'Réservation créée',
- data: {
- id: result.id,
- paymentCode
- }
- });
+ await db.addHistory('Nouvelle réservation', `Place ${spot.number} - ${price}EUR`, req.user.id);
+ res.status(201).json({ success: true, message: 'Réservation créée', data: { id: result.id, paymentCode } });
} catch (err) {
console.error('❌ Erreur create reservation:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
/**
* PUT /api/reservations/:id/cancel
- * Annule une réservation
+ * CORRIGÉ : libère désormais la place associée
*/
router.put('/reservations/:id/cancel', authenticateToken, async (req, res) => {
try {
+ const reservation = await db.getReservationById(req.params.id);
+ if (!reservation)
+ return res.status(404).json({ success: false, message: 'Réservation introuvable' });
+ if (reservation.user_id !== req.user.id && req.user.role !== 'admin')
+ return res.status(403).json({ success: false, message: 'Accès refusé' });
+
await db.updateReservationStatus(req.params.id, 'cancelled');
-
- // TODO: Libérer la place associée
-
- res.json({
- success: true,
- message: 'Réservation annulée'
- });
+ await db.updateSpotStatus(reservation.spot_id, 'free'); // ← BUG CORRIGÉ ICI
+ await db.addHistory(
+ 'Annulation réservation',
+ `Reservation #${req.params.id} annulee - place ${reservation.spot_id} liberee`,
+ req.user.id
+ );
+ res.json({ success: true, message: 'Réservation annulée' });
} catch (err) {
console.error('❌ Erreur cancel reservation:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
+ }
+});
+
+/**
+ * PUT /api/reservations/:id/complete (admin uniquement)
+ */
+router.put('/reservations/:id/complete', authenticateToken, requireAdmin, async (req, res) => {
+ try {
+ const reservation = await db.getReservationById(req.params.id);
+ if (!reservation)
+ return res.status(404).json({ success: false, message: 'Réservation introuvable' });
+
+ await db.updateReservationStatus(req.params.id, 'completed');
+ await db.updateSpotStatus(reservation.spot_id, 'free');
+ await db.addHistory(
+ 'Réservation terminée',
+ `Reservation #${req.params.id} terminee - place ${reservation.spot_id} liberee`,
+ req.user.id
+ );
+ res.json({ success: true, message: 'Réservation terminée' });
+ } catch (err) {
+ console.error('❌ Erreur complete reservation:', err.message);
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
@@ -405,79 +219,32 @@ router.put('/reservations/:id/cancel', authenticateToken, async (req, res) => {
// STATISTIQUES
// ============================================
-/**
- * GET /api/stats
- * Récupère les statistiques
- */
router.get('/stats', authenticateToken, async (req, res) => {
try {
const spots = await db.getAllSpots();
const total = spots.length;
- const free = spots.filter(s => s.status === 'free').length;
+ const free = spots.filter(s => s.status === 'free').length;
const occupied = spots.filter(s => s.status === 'occupied').length;
const reserved = spots.filter(s => s.status === 'reserved').length;
const occupancyRate = total > 0 ? Math.round(((occupied + reserved) / total) * 100) : 0;
-
- res.json({
- success: true,
- data: {
- total,
- free,
- occupied,
- reserved,
- occupancyRate
- }
- });
+ res.json({ success: true, data: { total, free, occupied, reserved, occupancyRate } });
} catch (err) {
- console.error('❌ Erreur get stats:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
-// ============================================
-// HISTORIQUE
-// ============================================
-
-/**
- * GET /api/history
- * Récupère l'historique
- */
router.get('/history', authenticateToken, requireAdmin, async (req, res) => {
try {
- const limit = parseInt(req.query.limit) || 50;
+ const limit = parseInt(req.query.limit) || 50;
const history = await db.getHistory(limit);
- res.json({
- success: true,
- count: history.length,
- data: history
- });
+ res.json({ success: true, count: history.length, data: history });
} catch (err) {
- console.error('❌ Erreur get history:', err.message);
- res.status(500).json({
- success: false,
- message: 'Erreur serveur'
- });
+ res.status(500).json({ success: false, message: 'Erreur serveur' });
}
});
-// ============================================
-// STATUS
-// ============================================
-
-/**
- * GET /api/status
- * Vérifie le statut du serveur
- */
-router.get('/status', (req, res) => {
- res.json({
- success: true,
- message: 'Smart Parking API opérationnelle',
- version: '1.0.0',
- timestamp: new Date().toISOString()
- });
+router.get('/status', (_req, res) => {
+ res.json({ success: true, message: 'Smart Parking API operationnelle', version: '1.0.0', timestamp: new Date().toISOString() });
});
-module.exports = router;
+module.exports = router;
\ No newline at end of file