Initial commit: CSV Vie Locale

This commit is contained in:
2026-08-23 09:06:32 +00:00
commit 563e7b7d9a
39 changed files with 2159 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
FROM node:22-alpine
WORKDIR /app
COPY package.json .
RUN npm install --production
COPY . .
EXPOSE 3002
CMD ["node", "src/index.js"]
+18
View File
@@ -0,0 +1,18 @@
{
"name": "csv-backend",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "node --watch src/index.js",
"start": "node src/index.js"
},
"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.11.0"
}
}
+14
View File
@@ -0,0 +1,14 @@
import mysql from 'mysql2/promise';
const pool = mysql.createPool({
host: process.env.DB_HOST || '127.0.0.1',
port: parseInt(process.env.DB_PORT || '3306'),
user: process.env.DB_USER || 'csvusr',
password: process.env.DB_PASSWORD || 'Fdp1989Dor@',
database: process.env.DB_NAME || 'csv',
waitForConnections: true,
connectionLimit: 10,
charset: 'utf8mb4'
});
export default pool;
+154
View File
@@ -0,0 +1,154 @@
import express from 'express';
import multer from 'multer';
import cors from 'cors';
import dotenv from 'dotenv';
import pool from './config/db.js';
import authRoutes from './routes/auth.js';
import articleRoutes from './routes/articles.js';
import eventRoutes from './routes/events.js';
import listingRoutes from './routes/listings.js';
import helpRoutes from './routes/helpRequests.js';
import businessRoutes from './routes/businesses.js';
dotenv.config();
const app = express();
const PORT = process.env.PORT || 3002;
app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use('/uploads', express.static('uploads'));
app.use('/api/uploads', express.static('uploads'));
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, 'uploads/'),
filename: (req, file, cb) => cb(null, Date.now() + '-' + file.originalname)
});
const upload = multer({ storage });
app.use('/api/uploads', express.static('uploads'));
const initDB = async () => {
const conn = await pool.getConnection();
await conn.query(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
phone VARCHAR(20),
address VARCHAR(255),
role ENUM('user','admin') DEFAULT 'user',
avatar VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
await conn.query(`
CREATE TABLE IF NOT EXISTS articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
image VARCHAR(255),
author_id INT,
published TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL
)
`);
await conn.query(`
CREATE TABLE IF NOT EXISTS events (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
date DATE NOT NULL,
time TIME,
location VARCHAR(255),
image VARCHAR(255),
category VARCHAR(50) DEFAULT 'autre',
author_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL
)
`);
await conn.query(`
CREATE TABLE IF NOT EXISTS categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
type ENUM('listing','event') DEFAULT 'listing'
)
`);
await conn.query(`
CREATE TABLE IF NOT EXISTS listings (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10,2) DEFAULT 0,
type ENUM('don','vente','troc') DEFAULT 'don',
category_id INT,
images TEXT,
status ENUM('disponible','archive','vendu') DEFAULT 'disponible',
approved TINYINT(1) DEFAULT 1,
author_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
)
`);
await conn.query(`
CREATE TABLE IF NOT EXISTS help_requests (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
category ENUM('course','bricolage','jardinage','transport','numerique','autre') DEFAULT 'autre',
status ENUM('ouvert','en_cours','resolu') DEFAULT 'ouvert',
approved TINYINT(1) DEFAULT 1,
urgent TINYINT(1) DEFAULT 0,
author_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE
)
`);
await conn.query(`
CREATE TABLE IF NOT EXISTS businesses (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
address VARCHAR(255),
phone VARCHAR(20),
website VARCHAR(255),
email VARCHAR(255),
image VARCHAR(255),
owner_id INT,
approved TINYINT(1) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE
)
`);
await conn.query(`
INSERT IGNORE INTO categories (name, type) VALUES
('Meuble','listing'), ('Electromenager','listing'), ('Vetement','listing'),
('Livres','listing'), ('Jouets','listing'), ('Bricolage','listing'),
('Jardinage','listing'), ('Animaux','listing'), ('Autre','listing')
`);
conn.release();
console.log('DB initialized');
};
initDB().catch(console.error);
app.use('/api/auth', authRoutes);
app.use('/api/articles', articleRoutes);
app.use('/api/events', eventRoutes);
app.use('/api/listings', listingRoutes);
app.use('/api/help', helpRoutes);
app.use('/api/businesses', businessRoutes);
app.get('/api/health', (_, res) => res.json({ status: 'ok' }));
app.post('/api/upload', upload.single('file'), (req, res) => {
if (!req.file) return res.status(400).json({ error: 'Aucun fichier' });
res.json({ url: '/api/uploads/' + req.file.filename });
});
app.listen(PORT, '0.0.0.0', () => {
console.log('CSV API running on port', PORT);
});
+31
View File
@@ -0,0 +1,31 @@
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET || 'csv-secret-key-change-in-production';
export const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Authentification requise' });
try {
req.user = jwt.verify(token, JWT_SECRET);
next();
} catch {
res.status(401).json({ error: 'Token invalide' });
}
};
export const optionalAuth = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (token) {
try { req.user = jwt.verify(token, JWT_SECRET); } catch {}
}
next();
};
export const requireAdmin = (req, res, next) => {
if (!req.user || req.user.role !== 'admin') {
return res.status(403).json({ error: 'Accès réservé aux administrateurs' });
}
next();
};
export { JWT_SECRET };
+58
View File
@@ -0,0 +1,58 @@
import { Router } from 'express';
import pool from '../config/db.js';
import { authenticate, requireAdmin, optionalAuth } from '../middleware/auth.js';
const router = Router();
router.get('/', optionalAuth, async (req, res) => {
try {
const [rows] = await pool.execute(
'SELECT a.*, u.name as author_name FROM articles a LEFT JOIN users u ON a.author_id = u.id WHERE a.published = 1 OR (a.author_id = ? AND a.published = 0) ORDER BY a.created_at DESC',
[req.user?.id || 0]
);
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get('/:id', optionalAuth, async (req, res) => {
try {
const [rows] = await pool.execute(
'SELECT a.*, u.name as author_name FROM articles a LEFT JOIN users u ON a.author_id = u.id WHERE a.id = ? AND (a.published = 1 OR a.author_id = ?)',
[req.params.id, req.user?.id || 0]
);
if (!rows.length) return res.status(404).json({ error: 'Article introuvable' });
res.json(rows[0]);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.post('/', authenticate, requireAdmin, async (req, res) => {
try {
const { title, content, image } = req.body;
if (!title || !content) return res.status(400).json({ error: 'Titre et contenu requis' });
const [result] = await pool.execute(
'INSERT INTO articles (title, content, image, author_id) VALUES (?, ?, ?, ?)',
[title, content, image || null, req.user.id]
);
res.status(201).json({ id: result.insertId, message: 'Article créé' });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.put('/:id', authenticate, requireAdmin, async (req, res) => {
try {
const { title, content, image, published } = req.body;
await pool.execute(
'UPDATE articles SET title = ?, content = ?, image = ?, published = ? WHERE id = ?',
[title, content, image || null, published ?? 1, req.params.id]
);
res.json({ message: 'Article mis à jour' });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.delete('/:id', authenticate, requireAdmin, async (req, res) => {
try {
await pool.execute('DELETE FROM articles WHERE id = ?', [req.params.id]);
res.json({ message: 'Article supprimé' });
} catch (e) { res.status(500).json({ error: e.message }); }
});
export default router;
+83
View File
@@ -0,0 +1,83 @@
import { Router } from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import pool from '../config/db.js';
import { authenticate, JWT_SECRET } from '../middleware/auth.js';
const router = Router();
router.post('/register', async (req, res) => {
try {
const { name, email, password, phone, address } = req.body;
if (!name || !email || !password) {
return res.status(400).json({ error: 'Nom, email et mot de passe requis' });
}
if (password.length < 6) {
return res.status(400).json({ error: 'Mot de passe trop court (6 car. min)' });
}
const [existing] = await pool.execute('SELECT id FROM users WHERE email = ?', [email]);
if (existing.length) {
return res.status(409).json({ error: 'Cet email est déjà utilisé' });
}
const hash = await bcrypt.hash(password, 10);
const [result] = await pool.execute(
'INSERT INTO users (name, email, password, phone, address) VALUES (?, ?, ?, ?, ?)',
[name, email, hash, phone || null, address || null]
);
const token = jwt.sign({ id: result.insertId, name, email, role: 'user' }, JWT_SECRET, { expiresIn: '30d' });
res.status(201).json({ token, user: { id: result.insertId, name, email, role: 'user' } });
} catch (e) {
res.status(500).json({ error: e.message });
}
});
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email et mot de passe requis' });
}
const [users] = await pool.execute('SELECT * FROM users WHERE email = ?', [email]);
if (!users.length) {
return res.status(401).json({ error: 'Email ou mot de passe incorrect' });
}
const user = users[0];
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
return res.status(401).json({ error: 'Email ou mot de passe incorrect' });
}
const token = jwt.sign(
{ id: user.id, name: user.name, email: user.email, role: user.role },
JWT_SECRET,
{ expiresIn: '30d' }
);
res.json({
token,
user: { id: user.id, name: user.name, email: user.email, role: user.role, phone: user.phone, address: user.address }
});
} catch (e) {
res.status(500).json({ error: e.message });
}
});
router.get('/me', authenticate, async (req, res) => {
try {
const [users] = await pool.execute('SELECT id, name, email, role, phone, address, avatar, created_at FROM users WHERE id = ?', [req.user.id]);
if (!users.length) return res.status(404).json({ error: 'Utilisateur introuvable' });
res.json(users[0]);
} catch (e) {
res.status(500).json({ error: e.message });
}
});
router.put('/profile', authenticate, async (req, res) => {
try {
const { name, phone, address } = req.body;
await pool.execute('UPDATE users SET name = ?, phone = ?, address = ? WHERE id = ?', [name, phone, address, req.user.id]);
res.json({ message: 'Profil mis à jour' });
} catch (e) {
res.status(500).json({ error: e.message });
}
});
export default router;
+85
View File
@@ -0,0 +1,85 @@
import { Router } from "express";
import pool from "../config/db.js";
import { authenticate, requireAdmin, optionalAuth } from "../middleware/auth.js";
const router = Router();
router.get("/", async (req, res) => {
try {
const [rows] = await pool.execute(
"SELECT * FROM businesses WHERE approved = 1 ORDER BY name"
);
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get("/pending", authenticate, requireAdmin, async (req, res) => {
try {
const [rows] = await pool.execute("SELECT * FROM businesses WHERE approved = 0 ORDER BY created_at DESC");
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get("/mine", authenticate, async (req, res) => {
try {
const [rows] = await pool.execute("SELECT * FROM businesses WHERE owner_id = ? ORDER BY created_at DESC", [req.user.id]);
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get("/:id", async (req, res) => {
try {
const [rows] = await pool.execute("SELECT * FROM businesses WHERE id = ?", [req.params.id]);
if (!rows.length) return res.status(404).json({ error: "Commerce introuvable" });
res.json(rows[0]);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.post("/", authenticate, async (req, res) => {
try {
const { name, description, category, address, phone, website, email, image } = req.body;
if (!name) return res.status(400).json({ error: "Nom requis" });
const [result] = await pool.execute(
"INSERT INTO businesses (name, description, category, address, phone, website, email, image, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
[name, description || null, category || null, address || null, phone || null, website || null, email || null, image || null, req.user.id]
);
res.status(201).json({ id: result.insertId, message: "Commerce soumis, en attente d'approbation" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.put("/:id", authenticate, async (req, res) => {
try {
const [businesses] = await pool.execute("SELECT * FROM businesses WHERE id = ?", [req.params.id]);
if (!businesses.length) return res.status(404).json({ error: "Commerce introuvable" });
if (businesses[0].owner_id !== req.user.id && req.user.role !== "admin") {
return res.status(403).json({ error: "Action non autorisee" });
}
const { name, description, category, address, phone, website, email, image } = req.body;
await pool.execute(
"UPDATE businesses SET name=?, description=?, category=?, address=?, phone=?, website=?, email=?, image=? WHERE id=?",
[name, description, category, address, phone, website, email, image, req.params.id]
);
res.json({ message: "Commerce mis a jour" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.put("/:id/approve", authenticate, requireAdmin, async (req, res) => {
try {
await pool.execute("UPDATE businesses SET approved = 1 WHERE id = ?", [req.params.id]);
res.json({ message: "Commerce approuve" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.delete("/:id", authenticate, async (req, res) => {
try {
const [businesses] = await pool.execute("SELECT * FROM businesses WHERE id = ?", [req.params.id]);
if (!businesses.length) return res.status(404).json({ error: "Commerce introuvable" });
if (businesses[0].owner_id !== req.user.id && req.user.role !== "admin") {
return res.status(403).json({ error: "Action non autorisee" });
}
await pool.execute("DELETE FROM businesses WHERE id = ?", [req.params.id]);
res.json({ message: "Commerce supprime" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
export default router;
+57
View File
@@ -0,0 +1,57 @@
import { Router } from "express";
import pool from "../config/db.js";
import { authenticate, requireAdmin } from "../middleware/auth.js";
const router = Router();
router.get("/", async (req, res) => {
try {
const [rows] = await pool.execute(
"SELECT e.*, u.name as author_name FROM events e LEFT JOIN users u ON e.author_id = u.id ORDER BY e.date DESC"
);
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get("/:id", async (req, res) => {
try {
const [rows] = await pool.execute(
"SELECT e.*, u.name as author_name FROM events e LEFT JOIN users u ON e.author_id = u.id WHERE e.id = ?",
[req.params.id]
);
if (!rows.length) return res.status(404).json({ error: "Evenement introuvable" });
res.json(rows[0]);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.post("/", authenticate, requireAdmin, async (req, res) => {
try {
const { title, description, date, time, location, image, category } = req.body;
if (!title || !date) return res.status(400).json({ error: "Titre et date requis" });
const [result] = await pool.execute(
"INSERT INTO events (title, description, date, time, location, image, category, author_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
[title, description || null, date, time || null, location || null, image || null, category || "autre", req.user.id]
);
res.status(201).json({ id: result.insertId, message: "Evenement cree" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.put("/:id", authenticate, requireAdmin, async (req, res) => {
try {
const { title, description, date, time, location, image, category } = req.body;
await pool.execute(
"UPDATE events SET title=?, description=?, date=?, time=?, location=?, image=?, category=? WHERE id=?",
[title, description, date, time, location, image, category, req.params.id]
);
res.json({ message: "Evenement mis a jour" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.delete("/:id", authenticate, requireAdmin, async (req, res) => {
try {
await pool.execute("DELETE FROM events WHERE id = ?", [req.params.id]);
res.json({ message: "Evenement supprime" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
export default router;
+83
View File
@@ -0,0 +1,83 @@
import { Router } from "express";
import pool from "../config/db.js";
import { authenticate, requireAdmin } from "../middleware/auth.js";
const router = Router();
router.get("/", async (req, res) => {
try {
const [rows] = await pool.execute(
"SELECT h.*, u.name as author_name, u.phone as author_phone FROM help_requests h LEFT JOIN users u ON h.author_id = u.id WHERE h.status != 'resolu' AND h.approved = 1 ORDER BY h.urgent DESC, h.created_at DESC"
);
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get("/pending", authenticate, requireAdmin, async (req, res) => {
try {
const [rows] = await pool.execute(
"SELECT h.*, u.name as author_name FROM help_requests h LEFT JOIN users u ON h.author_id = u.id WHERE h.approved = 0 ORDER BY h.created_at DESC"
);
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get("/:id", async (req, res) => {
try {
const [rows] = await pool.execute(
"SELECT h.*, u.name as author_name, u.phone as author_phone FROM help_requests h LEFT JOIN users u ON h.author_id = u.id WHERE h.id = ?",
[req.params.id]
);
if (!rows.length) return res.status(404).json({ error: "Demande introuvable" });
res.json(rows[0]);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.post("/", authenticate, async (req, res) => {
try {
const { title, description, category, urgent, image } = req.body;
if (!title || !description) return res.status(400).json({ error: "Titre et description requis" });
const [result] = await pool.execute(
"INSERT INTO help_requests (title, description, category, urgent, image, author_id) VALUES (?, ?, ?, ?, ?, ?)",
[title, description, category || "autre", urgent ? 1 : 0, image || null, req.user.id]
);
res.status(201).json({ id: result.insertId, message: "Demande soumise, en attente d'approbation" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.put("/:id", authenticate, async (req, res) => {
try {
const [requests] = await pool.execute("SELECT * FROM help_requests WHERE id = ?", [req.params.id]);
if (!requests.length) return res.status(404).json({ error: "Demande introuvable" });
if (requests[0].author_id !== req.user.id && req.user.role !== "admin") {
return res.status(403).json({ error: "Action non autorisee" });
}
const { title, description, category, urgent, status, image } = req.body;
await pool.execute(
"UPDATE help_requests SET title=?, description=?, category=?, urgent=?, status=?, image=? WHERE id=?",
[title, description, category, urgent ? 1 : 0, status || "ouvert", image || null, req.params.id]
);
res.json({ message: "Demande mise a jour" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.put("/:id/approve", authenticate, requireAdmin, async (req, res) => {
try {
await pool.execute("UPDATE help_requests SET approved = 1 WHERE id = ?", [req.params.id]);
res.json({ message: "Demande approuvee" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.delete("/:id", authenticate, async (req, res) => {
try {
const [requests] = await pool.execute("SELECT * FROM help_requests WHERE id = ?", [req.params.id]);
if (!requests.length) return res.status(404).json({ error: "Demande introuvable" });
if (requests[0].author_id !== req.user.id && req.user.role !== "admin") {
return res.status(403).json({ error: "Action non autorisee" });
}
await pool.execute("DELETE FROM help_requests WHERE id = ?", [req.params.id]);
res.json({ message: "Demande supprimee" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
export default router;
+105
View File
@@ -0,0 +1,105 @@
import { Router } from "express";
import pool from "../config/db.js";
import { authenticate, requireAdmin, optionalAuth } from "../middleware/auth.js";
const router = Router();
router.get("/", optionalAuth, async (req, res) => {
try {
const { type, category, search } = req.query;
let sql = "SELECT l.*, u.name as author_name, u.phone as author_phone, c.name as category_name FROM listings l LEFT JOIN users u ON l.author_id = u.id LEFT JOIN categories c ON l.category_id = c.id WHERE l.status = 'disponible' AND l.approved = 1";
const params = [];
if (type && type !== "all") { sql += " AND l.type = ?"; params.push(type); }
if (category) { sql += " AND l.category_id = ?"; params.push(category); }
if (search) { sql += " AND (l.title LIKE ? OR l.description LIKE ?)"; params.push("%" + search + "%", "%" + search + "%"); }
sql += " ORDER BY l.created_at DESC";
const [rows] = await pool.execute(sql, params);
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get("/mine", authenticate, async (req, res) => {
try {
const [rows] = await pool.execute(
"SELECT l.*, c.name as category_name FROM listings l LEFT JOIN categories c ON l.category_id = c.id WHERE l.author_id = ? ORDER BY l.created_at DESC",
[req.user.id]
);
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get("/pending", authenticate, requireAdmin, async (req, res) => {
try {
const [rows] = await pool.execute(
"SELECT l.*, u.name as author_name FROM listings l LEFT JOIN users u ON l.author_id = u.id WHERE l.approved = 0 ORDER BY l.created_at DESC"
);
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get("/:id", async (req, res) => {
try {
const [rows] = await pool.execute(
"SELECT l.*, u.name as author_name, u.phone as author_phone, c.name as category_name FROM listings l LEFT JOIN users u ON l.author_id = u.id LEFT JOIN categories c ON l.category_id = c.id WHERE l.id = ?",
[req.params.id]
);
if (!rows.length) return res.status(404).json({ error: "Annonce introuvable" });
res.json(rows[0]);
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.post("/", authenticate, async (req, res) => {
try {
const { title, description, price, type, category_id, image } = req.body;
if (!title) return res.status(400).json({ error: "Titre requis" });
const [result] = await pool.execute(
"INSERT INTO listings (title, description, price, type, category_id, image, author_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
[title, description || null, price || 0, type || "don", category_id || null, image || null, req.user.id]
);
res.status(201).json({ id: result.insertId, message: "Annonce soumise, en attente d'approbation" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.put("/:id", authenticate, async (req, res) => {
try {
const [listings] = await pool.execute("SELECT * FROM listings WHERE id = ?", [req.params.id]);
if (!listings.length) return res.status(404).json({ error: "Annonce introuvable" });
if (listings[0].author_id !== req.user.id && req.user.role !== "admin") {
return res.status(403).json({ error: "Action non autorisee" });
}
const { title, description, price, type, category_id, image, status } = req.body;
await pool.execute(
"UPDATE listings SET title=?, description=?, price=?, type=?, category_id=?, image=?, status=? WHERE id=?",
[title, description, price, type, category_id, image, status || "disponible", req.params.id]
);
res.json({ message: "Annonce mise a jour" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.put("/:id/approve", authenticate, requireAdmin, async (req, res) => {
try {
await pool.execute("UPDATE listings SET approved = 1 WHERE id = ?", [req.params.id]);
res.json({ message: "Annonce approuvee" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.delete("/:id", authenticate, async (req, res) => {
try {
const [listings] = await pool.execute("SELECT * FROM listings WHERE id = ?", [req.params.id]);
if (!listings.length) return res.status(404).json({ error: "Annonce introuvable" });
if (listings[0].author_id !== req.user.id && req.user.role !== "admin") {
return res.status(403).json({ error: "Action non autorisee" });
}
await pool.execute("DELETE FROM listings WHERE id = ?", [req.params.id]);
res.json({ message: "Annonce supprimee" });
} catch (e) { res.status(500).json({ error: e.message }); }
});
router.get("/categories/all", async (req, res) => {
try {
const [rows] = await pool.execute("SELECT * FROM categories WHERE type = 'listing' ORDER BY name");
res.json(rows);
} catch (e) { res.status(500).json({ error: e.message }); }
});
export default router;