Initial commit: CSV Vie Locale
This commit is contained in:
@@ -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"]
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user