merge: integrate Alexis latest changes
This commit is contained in:
@@ -4,9 +4,10 @@ DATABASE_URL="postgresql://postgres:password@localhost:5432/the_tip_top_db"
|
||||
|
||||
JWT_SECRET="your_secret_key"
|
||||
|
||||
APP_URL=http://localhost:5000
|
||||
MAIL_HOST=smtp.gmail.com
|
||||
MAIL_PORT=587
|
||||
MAIL_USER=yourmail_adress@gmail.com
|
||||
MAIL_PASS=_______________________
|
||||
MAIL_FROM="Thé Tip Top <yourmail_adress@gmail.com>"
|
||||
APP_URL="http://localhost:5000"
|
||||
MAIL_HOST="smtp.gmail.com"
|
||||
MAIL_PORT="587"
|
||||
MAIL_USER="engongomo.emma27@gmail.com"
|
||||
MAIL_PASS="rczo zqps hsnm msdm"
|
||||
MAIL_FROM="Thé Tip Top <engongomo.emma27@gmail.com>"
|
||||
|
||||
|
||||
39
backend/package-lock.json
generated
39
backend/package-lock.json
generated
@@ -14,7 +14,7 @@
|
||||
"cors": "^2.8.6",
|
||||
"express": "^5.2.1",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"nodemailer": "^9.0.1",
|
||||
"nodemailer": "^9.0.3",
|
||||
"prisma": "^6.19.3",
|
||||
"swagger-jsdoc": "^6.3.0",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
@@ -109,6 +109,7 @@
|
||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
@@ -604,31 +605,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
|
||||
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
|
||||
@@ -636,7 +612,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
@@ -1988,6 +1963,7 @@
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -2306,6 +2282,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.10.38",
|
||||
"caniuse-lite": "^1.0.30001799",
|
||||
@@ -3172,6 +3149,7 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.1",
|
||||
@@ -5487,9 +5465,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.1.tgz",
|
||||
"integrity": "sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh5ME+uesJUDRbR3Ye8Bw==",
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.3.tgz",
|
||||
"integrity": "sha512-n+YP+NKwR5zRWa60k3GiQ6Q3B4KXCoAw40dAKeCtYn020iNN74aWK2liXIC3ZEATeGql7we3tE3t8QwhY0eskw==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
@@ -5924,6 +5902,7 @@
|
||||
"integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/config": "6.19.3",
|
||||
"@prisma/engines": "6.19.3"
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"cors": "^2.8.6",
|
||||
"express": "^5.2.1",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"nodemailer": "^9.0.1",
|
||||
"nodemailer": "^9.0.3",
|
||||
"prisma": "^6.19.3",
|
||||
"swagger-jsdoc": "^6.3.0",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
|
||||
@@ -34,8 +34,8 @@ const register = async (req, res) => {
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const emailVerifyToken = crypto.randomBytes(32).toString("hex");
|
||||
const emailVerifyExpires = new Date(Date.now() + 1000 * 60 * 60); // 1h
|
||||
const emailVerifyToken = String(Math.floor(1000 + Math.random() * 9000));
|
||||
const emailVerifyExpires = new Date(Date.now() + 1000 * 60 * 15); // 15 min
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
@@ -62,23 +62,25 @@ const register = async (req, res) => {
|
||||
},
|
||||
});
|
||||
|
||||
const verifyUrl = `${process.env.APP_URL}/api/auth/verify-email/${emailVerifyToken}`;
|
||||
|
||||
await transporter.sendMail({
|
||||
from: process.env.MAIL_FROM,
|
||||
to: email,
|
||||
subject: "Validation de votre compte Thé Tip Top",
|
||||
subject: "Votre code de validation Thé Tip Top",
|
||||
html: `
|
||||
<h2>Bienvenue ${firstname}</h2>
|
||||
<p>Merci pour votre inscription.</p>
|
||||
<p>Veuillez cliquer sur le lien ci-dessous pour valider votre compte :</p>
|
||||
<a href="${verifyUrl}">Valider mon compte</a>
|
||||
<p>Ce lien expire dans 1 heure.</p>
|
||||
<div style="font-family:sans-serif;max-width:480px;margin:auto">
|
||||
<h2 style="color:#006B6B">Bienvenue ${firstname} !</h2>
|
||||
<p>Merci pour votre inscription sur Thé Tip Top.</p>
|
||||
<p>Voici votre code de validation à 4 chiffres :</p>
|
||||
<div style="font-size:36px;font-weight:bold;letter-spacing:12px;color:#006B6B;text-align:center;padding:20px 0">
|
||||
${emailVerifyToken}
|
||||
</div>
|
||||
<p style="color:#888;font-size:13px">Ce code expire dans <strong>15 minutes</strong>.</p>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
return res.status(201).json({
|
||||
message: "Inscription réussie. Un email de validation a été envoyé.",
|
||||
message: "Inscription réussie. Un code de validation a été envoyé par email.",
|
||||
user,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -148,7 +150,7 @@ const login = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (!user.isEmailVerified) {
|
||||
if (!user.isEmailVerified && user.role === "CLIENT") {
|
||||
return res.status(403).json({
|
||||
message: "Veuillez valider votre adresse email avant de vous connecter.",
|
||||
});
|
||||
@@ -298,17 +300,144 @@ const resetPassword = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
const verifyCode = async (req, res) => {
|
||||
try {
|
||||
const { email, code } = req.body;
|
||||
|
||||
if (!email || !code) {
|
||||
return res.status(400).json({ message: "Email et code requis." });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { email } });
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "Compte introuvable." });
|
||||
}
|
||||
|
||||
if (user.isEmailVerified) {
|
||||
return res.status(400).json({ message: "Ce compte est déjà vérifié." });
|
||||
}
|
||||
|
||||
if (!user.emailVerifyToken || user.emailVerifyToken !== String(code)) {
|
||||
return res.status(400).json({ message: "Code invalide." });
|
||||
}
|
||||
|
||||
if (new Date() > user.emailVerifyExpires) {
|
||||
return res.status(400).json({ message: "Code expiré. Veuillez vous réinscrire." });
|
||||
}
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { isEmailVerified: true, emailVerifyToken: null, emailVerifyExpires: null },
|
||||
});
|
||||
|
||||
const token = jwt.sign(
|
||||
{ id: user.id, role: user.role },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: "24h" }
|
||||
);
|
||||
|
||||
return res.json({
|
||||
message: "Compte vérifié avec succès.",
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
firstname: user.firstname,
|
||||
lastname: user.lastname,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erreur verifyCode:", error);
|
||||
return res.status(500).json({ message: "Erreur serveur lors de la vérification." });
|
||||
}
|
||||
};
|
||||
|
||||
const me = async (req, res) => {
|
||||
return res.json({
|
||||
user: req.user,
|
||||
});
|
||||
};
|
||||
|
||||
const deleteAccount = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// 1. Supprimer le grand gagnant si c'est lui
|
||||
await tx.grandPrizeWinner.deleteMany({ where: { userId } });
|
||||
|
||||
// 2. Récupérer les participations pour supprimer leurs claims
|
||||
const participations = await tx.participation.findMany({
|
||||
where: { userId },
|
||||
select: { id: true },
|
||||
});
|
||||
const participationIds = participations.map((p) => p.id);
|
||||
|
||||
// 3. Supprimer les claims
|
||||
if (participationIds.length > 0) {
|
||||
await tx.claim.deleteMany({ where: { participationId: { in: participationIds } } });
|
||||
}
|
||||
|
||||
// 4. Supprimer les participations
|
||||
await tx.participation.deleteMany({ where: { userId } });
|
||||
|
||||
// 5. Libérer les tickets utilisés par ce user
|
||||
await tx.ticket.updateMany({
|
||||
where: { userId },
|
||||
data: { userId: null, isUsed: false, usedAt: null },
|
||||
});
|
||||
|
||||
// 6. Supprimer l'utilisateur
|
||||
await tx.user.delete({ where: { id: userId } });
|
||||
});
|
||||
|
||||
return res.json({ message: "Compte supprimé avec succès." });
|
||||
} catch (error) {
|
||||
console.error("Erreur deleteAccount:", error);
|
||||
return res.status(500).json({ message: "Erreur serveur lors de la suppression du compte." });
|
||||
}
|
||||
};
|
||||
|
||||
const changePassword = async (req, res) => {
|
||||
try {
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const userId = req.user.id;
|
||||
|
||||
if (!currentPassword || !newPassword) {
|
||||
return res.status(400).json({ message: "Les deux mots de passe sont requis." });
|
||||
}
|
||||
|
||||
if (newPassword.length < 8) {
|
||||
return res.status(400).json({ message: "Le nouveau mot de passe doit faire au moins 8 caractères." });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
|
||||
const isValid = await bcrypt.compare(currentPassword, user.password);
|
||||
if (!isValid) {
|
||||
return res.status(401).json({ message: "Mot de passe actuel incorrect." });
|
||||
}
|
||||
|
||||
const hashed = await bcrypt.hash(newPassword, 10);
|
||||
await prisma.user.update({ where: { id: userId }, data: { password: hashed } });
|
||||
|
||||
return res.json({ message: "Mot de passe modifié avec succès." });
|
||||
} catch (error) {
|
||||
console.error("Erreur changePassword:", error);
|
||||
return res.status(500).json({ message: "Erreur serveur lors du changement de mot de passe." });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
register,
|
||||
login,
|
||||
me,
|
||||
verifyEmail,
|
||||
verifyCode,
|
||||
resetPassword,
|
||||
forgotPassword
|
||||
forgotPassword,
|
||||
changePassword,
|
||||
deleteAccount,
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
const express = require("express");
|
||||
const { register, login, verifyEmail, forgotPassword, resetPassword, me } = require("../controllers/auth.controller");
|
||||
const { register, login, verifyEmail, verifyCode, forgotPassword, resetPassword, me, changePassword, deleteAccount } = require("../controllers/auth.controller");
|
||||
const authMiddleware = require("../middlewares/auth.middleware");
|
||||
const validate = require("../middlewares/validate.middleware");
|
||||
const { registerSchema, loginSchema } = require("../validations/auth.validation");
|
||||
@@ -155,7 +155,10 @@ router.post("/forgot-password", forgotPassword);
|
||||
* 500:
|
||||
* description: Erreur serveur.
|
||||
*/
|
||||
router.post("/reset-password/:token",resetPassword);
|
||||
router.post("/reset-password/:token", resetPassword);
|
||||
|
||||
router.post("/verify-code", verifyCode);
|
||||
router.patch("/change-password", authMiddleware, changePassword);
|
||||
router.delete("/account", authMiddleware, deleteAccount);
|
||||
|
||||
module.exports = router;
|
||||
39
backend/src/scripts/createAdmin.js
Normal file
39
backend/src/scripts/createAdmin.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const bcrypt = require('bcrypt')
|
||||
const prisma = require('../config/prisma')
|
||||
|
||||
async function createAdmin() {
|
||||
const email = 'admin@thetiptop.fr'
|
||||
const password = 'Admin@TipTop2026!'
|
||||
|
||||
const existing = await prisma.user.findUnique({ where: { email } })
|
||||
|
||||
if (existing) {
|
||||
await prisma.user.update({
|
||||
where: { email },
|
||||
data: { isEmailVerified: true, role: 'ADMIN' },
|
||||
})
|
||||
console.log('Admin mis à jour :', email)
|
||||
return
|
||||
}
|
||||
|
||||
const hashed = await bcrypt.hash(password, 10)
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
firstname: 'Admin',
|
||||
lastname: 'TipTop',
|
||||
email,
|
||||
password: hashed,
|
||||
role: 'ADMIN',
|
||||
isEmailVerified: true,
|
||||
},
|
||||
})
|
||||
|
||||
console.log('Admin créé avec succès')
|
||||
console.log(' Email :', email)
|
||||
console.log(' Mot de passe:', password)
|
||||
}
|
||||
|
||||
createAdmin()
|
||||
.catch((e) => { console.error('Erreur :', e.message); process.exit(1) })
|
||||
.finally(() => prisma.$disconnect())
|
||||
Reference in New Issue
Block a user