Skip to content

back end #44

Description

@mamadousdiouf40-sketch

{
"name": "brochette-backend",
"version": "2.0.0",
"main": "server.js",
"type": "commonjs",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"mongoose": "^8.5.1",
"socket.io": "^4.7.5"
},
"devDependencies": {
"nodemon": "^3.1.0"
}
}
PORT=5000
MONGO_URI=mongodb+srv://:@cluster0.xxxxx.mongodb.net/brochette?retryWrites=true&w=majority

Met ton vrai domaine Netlify après déploiement (ex: https://brochette-ny.netlify.app)

ALLOWED_ORIGIN=https://ton-site-netlify.netlify.app
ADMIN_KEY=change_me_admin_key_very_secretconst mongoose = require('mongoose');

const VALID_STATUSES = [
'pending','confirmed','preparing','ready','out_for_delivery','completed','cancelled'
];

const OrderSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true },
meal: { type: String, required: true },
side: { type: String, required: true },
total: { type: Number, required: true },
status: { type: String, enum: VALID_STATUSES, default: 'pending' }
}, { timestamps: true });

OrderSchema.statics.VALID_STATUSES = VALID_STATUSES;

module.exports = mongoose.model('Order', OrderSchema);const mongoose = require('mongoose');

const LoyaltySchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, unique: true, required: true },
points:{ type: Number, default: 0 }
}, { timestamps: true });

module.exports = mongoose.model('Loyalty', LoyaltySchema);const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const dotenv = require('dotenv');
const http = require('http');
const { Server: SocketIOServer } = require('socket.io');

dotenv.config();

const Order = require('./models/Order');
const Loyalty = require('./models/Loyalty');

const app = express();
const httpServer = http.createServer(app);

// Allowed origins (Netlify prod + localhost)
const allowed = new Set([
'http://localhost:3000',
'http://127.0.0.1:3000',
process.env.ALLOWED_ORIGIN
].filter(Boolean));

app.use(express.json());
app.use(cors({
origin: (origin, cb) => { if (!origin || allowed.has(origin)) return cb(null, true); cb(new Error('Origin non autorisée: '+origin)); }
}));

// Socket.IO (CORS aligné)
const io = new SocketIOServer(httpServer, {
cors: { origin: Array.from(allowed), methods: ['GET','POST','PATCH'] }
});

// --- Socket rooms: order: pour updates ciblées
io.on('connection', (socket) => {
socket.on('order:join', (orderId) => {
socket.join(order:${orderId});
});
});

// --- DB
mongoose.connect(process.env.MONGO_URI)
.then(() => console.log('✅ MongoDB connecté'))
.catch((e) => { console.error(e); process.exit(1); });

// Prix (même que frontend)
const PRICE = {
'Brochette Poulet': 12,
'Brochette Boeuf' : 14,
'Dibi Poulet' : 13,
'Dibi Mouton' : 15,
'Frites' : 5,
'Djollof Rice' : 6,
'Smashed Potatoes': 6,
'Salade' : 4
};

// --- Routes publiques
app.get('/api/health', (_, res) => res.json({ ok: true }));

app.post('/api/loyalty', async (req, res) => {
try {
const { name, email } = req.body || {};
if (!name || !email) return res.status(400).json({ success:false, error:'Champs requis' });
await Loyalty.findOneAndUpdate(
{ email },
{ $setOnInsert: { name, email }, $set: { name }, $inc: { points: 0 } },
{ upsert: true }
);
res.json({ success:true });
} catch (e) { res.status(500).json({ success:false, error:e.message }); }
});

// Créer une commande
app.post('/api/order', async (req, res) => {
try {
const { name, email, meal, side } = req.body || {};
if (!name || !email || !meal || !side) return res.status(400).json({ success:false, error:'Champs requis' });
const total = (PRICE[meal]||0) + (PRICE[side]||0);
const order = await Order.create({ name, email, meal, side, total });

// Loyalty: 1 point / $ (arrondi)
await Loyalty.findOneAndUpdate(
  { email },
  { $setOnInsert: { name, email }, $inc: { points: Math.round(total) } },
  { upsert: true }
);

// Événements temps réel
io.emit('order:created', { order }); // tableau cuisine
io.to(`order:${order._id}`).emit('order:update', { orderId: order._id, status: order.status });

res.json({ success:true, orderId: order._id, status: order.status, total });

} catch (e) { res.status(500).json({ success:false, error:e.message }); }
});

// Liste & détail
app.get('/api/orders', async (_, res) => { const orders = await Order.find().sort({ createdAt:-1 }).lean(); res.json(orders); });
app.get('/api/orders/:id', async (req, res) => {
const order = await Order.findById(req.params.id).lean();
if (!order) return res.status(404).json({ error:'Not found' });
res.json(order);
});

// Middleware Admin clé simple
function requireAdmin(req, res, next) {
if ((req.headers['x-admin-key'] || '') === process.env.ADMIN_KEY) return next();
res.status(401).json({ error:'Clé admin manquante ou invalide' });
}

// Mettre à jour statut (admin)
app.patch('/api/orders/:id/status', requireAdmin, async (req, res) => {
try {
const { status } = req.body || {};
if (!Order.schema.statics.VALID_STATUSES.includes(status)) return res.status(400).json({ error:'Statut invalide' });
const order = await Order.findByIdAndUpdate(req.params.id, { $set: { status } }, { new: true }).lean();
if (!order) return res.status(404).json({ error:'Not found' });

// Push temps réel
io.emit('order:board', { order }); // pour staff
io.to(`order:${order._id}`).emit('order:update', { orderId: order._id, status: order.status }); // pour le client

res.json({ ok:true, order });

} catch (e) { res.status(500).json({ error:e.message }); }
});

const PORT = process.env.PORT || 5000;
httpServer.listen(PORT, () => console.log(🚀 API + Socket.IO sur http://localhost:${PORT}));

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions