Merge remote-tracking branch 'origin/main' into Emmanuel
This commit is contained in:
@@ -3,3 +3,10 @@ PORT=5000
|
|||||||
DATABASE_URL="postgresql://postgres:password@localhost:5432/the_tip_top_db"
|
DATABASE_URL="postgresql://postgres:password@localhost:5432/the_tip_top_db"
|
||||||
|
|
||||||
JWT_SECRET="your_secret_key"
|
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>"
|
||||||
26
backend/jest.config.js
Normal file
26
backend/jest.config.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
module.exports = {
|
||||||
|
testEnvironment: "node",
|
||||||
|
|
||||||
|
// Dossier des tests
|
||||||
|
testMatch: ["**/tests/**/*.test.js"],
|
||||||
|
|
||||||
|
// Nettoyage des mocks entre les tests
|
||||||
|
clearMocks: true,
|
||||||
|
|
||||||
|
// Timeout (utile pour Prisma / DB)
|
||||||
|
testTimeout: 10000,
|
||||||
|
|
||||||
|
// Affichage des logs de test
|
||||||
|
verbose: true,
|
||||||
|
|
||||||
|
// Couverture de code (optionnel mais bon pour soutenance)
|
||||||
|
collectCoverage: true,
|
||||||
|
coverageDirectory: "coverage",
|
||||||
|
|
||||||
|
|
||||||
|
// Fichiers à ignorer
|
||||||
|
testPathIgnorePatterns: ["/node_modules/"],
|
||||||
|
|
||||||
|
// Configuration pour Jest avec Supertest
|
||||||
|
//setupFilesAfterEnv: ["<rootDir>/tests/setup.js"]
|
||||||
|
};
|
||||||
4800
backend/package-lock.json
generated
4800
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,12 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon src/app.js",
|
"dev": "nodemon server.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"start": "node server.js",
|
||||||
"generate:tickets": "node src/scripts/generateTickets.js"
|
"generate:tickets": "node src/scripts/generateTickets.js",
|
||||||
|
"test": "jest --runInBand",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:coverage": "jest --coverage"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -15,16 +18,21 @@
|
|||||||
"@prisma/client": "^6.19.3",
|
"@prisma/client": "^6.19.3",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"dotenv": "^17.4.2",
|
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
|
"nodemailer": "^9.0.1",
|
||||||
"prisma": "^6.19.3",
|
"prisma": "^6.19.3",
|
||||||
"swagger-jsdoc": "^6.3.0",
|
"swagger-jsdoc": "^6.3.0",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.14"
|
"@types/jest": "^30.0.0",
|
||||||
|
"cross-env": "^10.1.0",
|
||||||
|
"dotenv": "^17.4.2",
|
||||||
|
"jest": "^30.4.2",
|
||||||
|
"nodemon": "^3.1.14",
|
||||||
|
"supertest": "^7.2.2"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "node prisma/seed.js"
|
"seed": "node prisma/seed.js"
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "emailVerifyExpires" TIMESTAMP(3),
|
||||||
|
ADD COLUMN "emailVerifyToken" TEXT,
|
||||||
|
ADD COLUMN "isEmailVerified" BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "resetPasswordExpires" TIMESTAMP(3),
|
||||||
|
ADD COLUMN "resetPasswordToken" TEXT;
|
||||||
@@ -36,6 +36,13 @@ model User {
|
|||||||
marketingOptIn Boolean @default(false)
|
marketingOptIn Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
isEmailVerified Boolean @default(false)
|
||||||
|
emailVerifyToken String?
|
||||||
|
emailVerifyExpires DateTime?
|
||||||
|
|
||||||
|
resetPasswordToken String?
|
||||||
|
resetPasswordExpires DateTime?
|
||||||
|
|
||||||
tickets Ticket[]
|
tickets Ticket[]
|
||||||
participations Participation[]
|
participations Participation[]
|
||||||
grandPrizeWinner GrandPrizeWinner?
|
grandPrizeWinner GrandPrizeWinner?
|
||||||
|
|||||||
7
backend/server.js
Normal file
7
backend/server.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const app = require("./src/app");
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 5000;
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`Serveur lancé sur http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const cors = require("cors");
|
const cors = require("cors");
|
||||||
|
|
||||||
require("dotenv").config();
|
require("dotenv").config();
|
||||||
|
|
||||||
const authRoutes = require("./routes/auth.routes");
|
const authRoutes = require("./routes/auth.routes");
|
||||||
const gameRoutes = require("./routes/game.routes");
|
const gameRoutes = require("./routes/game.routes");
|
||||||
const employeeRoutes = require("./routes/employee.routes");
|
const employeeRoutes = require("./routes/employee.routes");
|
||||||
const adminRoutes = require("./routes/admin.routes");
|
const adminRoutes = require("./routes/admin.routes");
|
||||||
|
|
||||||
const swaggerUi = require("swagger-ui-express");
|
const swaggerUi = require("swagger-ui-express");
|
||||||
const swaggerSpec = require("./docs/swagger");
|
const swaggerSpec = require("./docs/swagger");
|
||||||
|
|
||||||
@@ -15,24 +15,18 @@ const app = express();
|
|||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
|
// routes
|
||||||
app.use("/api/auth", authRoutes);
|
app.use("/api/auth", authRoutes);
|
||||||
app.use("/api/game", gameRoutes);
|
app.use("/api/game", gameRoutes);
|
||||||
app.use("/api/employee", employeeRoutes);
|
app.use("/api/employee", employeeRoutes);
|
||||||
app.use("/api/admin", adminRoutes);
|
app.use("/api/admin", adminRoutes);
|
||||||
app.use(
|
|
||||||
"/api-docs",
|
|
||||||
swaggerUi.serve,
|
|
||||||
swaggerUi.setup(swaggerSpec)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// swagger
|
||||||
|
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
||||||
|
|
||||||
|
// test route
|
||||||
app.get("/", (req, res) => {
|
app.get("/", (req, res) => {
|
||||||
res.json({
|
res.json({ message: "API Thé Tip Top opérationnelle" });
|
||||||
message: "API Thé Tip Top opérationnelle",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const PORT = process.env.PORT || 5000;
|
module.exports = app;
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
|
||||||
console.log(`Serveur lancé sur http://localhost:${PORT}`);
|
|
||||||
});
|
|
||||||
13
backend/src/config/mailer.js
Normal file
13
backend/src/config/mailer.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const nodemailer = require("nodemailer");
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: process.env.MAIL_HOST,
|
||||||
|
port: Number(process.env.MAIL_PORT),
|
||||||
|
secure: false,
|
||||||
|
auth: {
|
||||||
|
user: process.env.MAIL_USER,
|
||||||
|
pass: process.env.MAIL_PASS,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = transporter;
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
const jwt = require("jsonwebtoken");
|
const jwt = require("jsonwebtoken");
|
||||||
|
const crypto = require("crypto");
|
||||||
const prisma = require("../config/prisma");
|
const prisma = require("../config/prisma");
|
||||||
|
const transporter = require("../config/mailer");
|
||||||
|
|
||||||
const register = async (req, res) => {
|
const register = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -32,6 +34,9 @@ const register = async (req, res) => {
|
|||||||
|
|
||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
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 user = await prisma.user.create({
|
const user = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
firstname,
|
firstname,
|
||||||
@@ -41,6 +46,9 @@ const register = async (req, res) => {
|
|||||||
birthdate: birthdate ? new Date(birthdate) : null,
|
birthdate: birthdate ? new Date(birthdate) : null,
|
||||||
gender,
|
gender,
|
||||||
marketingOptIn: marketingOptIn || false,
|
marketingOptIn: marketingOptIn || false,
|
||||||
|
isEmailVerified: false,
|
||||||
|
emailVerifyToken,
|
||||||
|
emailVerifyExpires,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -49,12 +57,28 @@ const register = async (req, res) => {
|
|||||||
email: true,
|
email: true,
|
||||||
role: true,
|
role: true,
|
||||||
marketingOptIn: true,
|
marketingOptIn: true,
|
||||||
|
isEmailVerified: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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",
|
||||||
|
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>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(201).json({
|
return res.status(201).json({
|
||||||
message: "Inscription réussie.",
|
message: "Inscription réussie. Un email de validation a été envoyé.",
|
||||||
user,
|
user,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -65,6 +89,45 @@ const register = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const verifyEmail = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { token } = req.params;
|
||||||
|
|
||||||
|
const user = await prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
emailVerifyToken: token,
|
||||||
|
emailVerifyExpires: {
|
||||||
|
gt: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Lien de validation invalide ou expiré.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { id: user.id },
|
||||||
|
data: {
|
||||||
|
isEmailVerified: true,
|
||||||
|
emailVerifyToken: null,
|
||||||
|
emailVerifyExpires: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Email validé avec succès. Vous pouvez maintenant vous connecter.",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur verifyEmail:", error);
|
||||||
|
return res.status(500).json({
|
||||||
|
message: "Erreur serveur lors de la validation de l'email.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const login = async (req, res) => {
|
const login = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { email, password } = req.body;
|
const { email, password } = req.body;
|
||||||
@@ -85,6 +148,12 @@ const login = async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!user.isEmailVerified) {
|
||||||
|
return res.status(403).json({
|
||||||
|
message: "Veuillez valider votre adresse email avant de vous connecter.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||||
|
|
||||||
if (!isPasswordValid) {
|
if (!isPasswordValid) {
|
||||||
@@ -121,6 +190,114 @@ const login = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const forgotPassword = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { email } = req.body;
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Email requis.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { email },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sécurité : ne pas dire si l'email existe ou non
|
||||||
|
if (!user) {
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Si un compte existe avec cet email, un lien de réinitialisation a été envoyé.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetPasswordToken = crypto.randomBytes(32).toString("hex");
|
||||||
|
const resetPasswordExpires = new Date(Date.now() + 1000 * 60 * 30); // 30 min
|
||||||
|
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { id: user.id },
|
||||||
|
data: {
|
||||||
|
resetPasswordToken,
|
||||||
|
resetPasswordExpires,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const resetUrl = `${process.env.APP_URL}/api/auth/reset-password/${resetPasswordToken}`;
|
||||||
|
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: process.env.MAIL_FROM,
|
||||||
|
to: user.email,
|
||||||
|
subject: "Réinitialisation de votre mot de passe Thé Tip Top",
|
||||||
|
html: `
|
||||||
|
<h2>Réinitialisation du mot de passe</h2>
|
||||||
|
<p>Bonjour ${user.firstname},</p>
|
||||||
|
<p>Vous avez demandé la réinitialisation de votre mot de passe.</p>
|
||||||
|
<p>Cliquez sur le lien ci-dessous pour définir un nouveau mot de passe :</p>
|
||||||
|
<a href="${resetUrl}">Réinitialiser mon mot de passe</a>
|
||||||
|
<p>Ce lien expire dans 30 minutes.</p>
|
||||||
|
<p>Si vous n'êtes pas à l'origine de cette demande, ignorez cet email.</p>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Si un compte existe avec cet email, un lien de réinitialisation a été envoyé.",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur forgotPassword:", error);
|
||||||
|
return res.status(500).json({
|
||||||
|
message: "Erreur serveur lors de la demande de réinitialisation.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetPassword = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { token } = req.params;
|
||||||
|
const { password } = req.body;
|
||||||
|
|
||||||
|
if (!password || password.length < 8) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Le nouveau mot de passe doit contenir au moins 8 caractères.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
resetPasswordToken: token,
|
||||||
|
resetPasswordExpires: {
|
||||||
|
gt: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Lien de réinitialisation invalide ou expiré.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { id: user.id },
|
||||||
|
data: {
|
||||||
|
password: hashedPassword,
|
||||||
|
resetPasswordToken: null,
|
||||||
|
resetPasswordExpires: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Mot de passe réinitialisé avec succès. Vous pouvez maintenant vous connecter.",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur resetPassword:", error);
|
||||||
|
return res.status(500).json({
|
||||||
|
message: "Erreur serveur lors de la réinitialisation du mot de passe.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const me = async (req, res) => {
|
const me = async (req, res) => {
|
||||||
return res.json({
|
return res.json({
|
||||||
user: req.user,
|
user: req.user,
|
||||||
@@ -131,4 +308,7 @@ module.exports = {
|
|||||||
register,
|
register,
|
||||||
login,
|
login,
|
||||||
me,
|
me,
|
||||||
|
verifyEmail,
|
||||||
|
resetPassword,
|
||||||
|
forgotPassword
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const { register, login, me } = require("../controllers/auth.controller");
|
const { register, login, verifyEmail, forgotPassword, resetPassword, me } = require("../controllers/auth.controller");
|
||||||
const authMiddleware = require("../middlewares/auth.middleware");
|
const authMiddleware = require("../middlewares/auth.middleware");
|
||||||
const validate = require("../middlewares/validate.middleware");
|
const validate = require("../middlewares/validate.middleware");
|
||||||
const { registerSchema, loginSchema } = require("../validations/auth.validation");
|
const { registerSchema, loginSchema } = require("../validations/auth.validation");
|
||||||
@@ -63,7 +63,99 @@ router.post("/login", validate(loginSchema), login);
|
|||||||
* 200:
|
* 200:
|
||||||
* description: Profil utilisateur
|
* description: Profil utilisateur
|
||||||
*/
|
*/
|
||||||
|
|
||||||
router.get("/me", authMiddleware, me);
|
router.get("/me", authMiddleware, me);
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/auth/verify-email/{token}:
|
||||||
|
* get:
|
||||||
|
* summary: Vérification de l'adresse email
|
||||||
|
* tags:
|
||||||
|
* - Auth
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: token
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Token de validation envoyé par email
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Email vérifié avec succès.
|
||||||
|
* 400:
|
||||||
|
* description: Lien de validation invalide ou expiré.
|
||||||
|
* 500:
|
||||||
|
* description: Erreur serveur.
|
||||||
|
*/
|
||||||
|
|
||||||
|
router.get("/verify-email/:token",verifyEmail);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/auth/forgot-password:
|
||||||
|
* post:
|
||||||
|
* summary: Demande de réinitialisation du mot de passe
|
||||||
|
* tags:
|
||||||
|
* - Auth
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required:
|
||||||
|
* - email
|
||||||
|
* properties:
|
||||||
|
* email:
|
||||||
|
* type: string
|
||||||
|
* format: email
|
||||||
|
* example: jean.dupont@gmail.com
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Si un compte existe avec cet email, un lien de réinitialisation a été envoyé.
|
||||||
|
* 400:
|
||||||
|
* description: Email requis.
|
||||||
|
* 500:
|
||||||
|
* description: Erreur serveur.
|
||||||
|
*/
|
||||||
|
|
||||||
|
router.post("/forgot-password", forgotPassword);
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/auth/reset-password/{token}:
|
||||||
|
* post:
|
||||||
|
* summary: Réinitialisation du mot de passe
|
||||||
|
* tags:
|
||||||
|
* - Auth
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: token
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Token de réinitialisation reçu par email
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required:
|
||||||
|
* - password
|
||||||
|
* properties:
|
||||||
|
* password:
|
||||||
|
* type: string
|
||||||
|
* format: password
|
||||||
|
* example: NouveauPassword123
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Mot de passe réinitialisé avec succès.
|
||||||
|
* 400:
|
||||||
|
* description: Lien invalide, expiré ou mot de passe invalide.
|
||||||
|
* 500:
|
||||||
|
* description: Erreur serveur.
|
||||||
|
*/
|
||||||
|
router.post("/reset-password/:token",resetPassword);
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
152
backend/tests/e2e.flow.test.js
Normal file
152
backend/tests/e2e.flow.test.js
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
const request = require("supertest");
|
||||||
|
const app = require("../src/app");
|
||||||
|
const prisma = require("../src/config/prisma");
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// Mock du mailer pour éviter envoi réel
|
||||||
|
// ------------------------
|
||||||
|
jest.mock("../src/config/mailer", () => ({
|
||||||
|
sendMail: jest.fn().mockResolvedValue(true),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// Réinitialisation DB avant tests
|
||||||
|
// ------------------------
|
||||||
|
beforeAll(async () => {
|
||||||
|
await prisma.claim.deleteMany();
|
||||||
|
await prisma.participation.deleteMany();
|
||||||
|
await prisma.ticket.deleteMany();
|
||||||
|
await prisma.user.deleteMany();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("THE TIP TOP - FULL E2E FLOW", () => {
|
||||||
|
let token;
|
||||||
|
let ticketCode = "ABC123";
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// 1. REGISTER
|
||||||
|
// ------------------------
|
||||||
|
test("Register user", async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post("/api/auth/register")
|
||||||
|
.send({
|
||||||
|
firstname: "Test",
|
||||||
|
lastname: "User",
|
||||||
|
email: "test@test.com",
|
||||||
|
password: "Password123",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect([201, 409]).toContain(res.statusCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// 2. LOGIN
|
||||||
|
// ------------------------
|
||||||
|
test("Login user", async () => {
|
||||||
|
// Validation du user pour bypasser vérification email
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { email: "test@test.com" },
|
||||||
|
data: { isEmailVerified: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.post("/api/auth/login")
|
||||||
|
.send({
|
||||||
|
email: "test@test.com",
|
||||||
|
password: "Password123",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
expect(res.body.token).toBeDefined();
|
||||||
|
token = res.body.token;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// 3. PARTICIPATE GAME
|
||||||
|
// ------------------------
|
||||||
|
test("Participate to game", async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post("/api/game/participate")
|
||||||
|
.set("Authorization", `Bearer ${token}`)
|
||||||
|
.send({ code: ticketCode });
|
||||||
|
|
||||||
|
expect([200, 201, 404, 409, 400]).toContain(res.statusCode);
|
||||||
|
console.log("Ticket in DB before participate:", ticketCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// 4. MY GAINS
|
||||||
|
// ------------------------
|
||||||
|
test("Get my gains", async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.get("/api/game/my-gains")
|
||||||
|
.set("Authorization", `Bearer ${token}`);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
expect(res.body.gains).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// 3. GENERER UN TICKET
|
||||||
|
// ------------------------
|
||||||
|
test("Create a ticket for testing", async () => {
|
||||||
|
const lot = await prisma.lot.create({
|
||||||
|
data: {
|
||||||
|
name: "Lot Test",
|
||||||
|
description: "Lot pour test E2E",
|
||||||
|
value: 10,
|
||||||
|
percentage: 100
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const ticket = await prisma.ticket.create({
|
||||||
|
data: {
|
||||||
|
code: ticketCode,
|
||||||
|
lotId: lot.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ticket).toBeDefined();
|
||||||
|
expect(ticket.code).toBe(ticketCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// 4. PARTICIPATE GAME
|
||||||
|
// ------------------------
|
||||||
|
test("Participate to game", async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post("/api/game/participate")
|
||||||
|
.set("Authorization", `Bearer ${token}`)
|
||||||
|
.send({ code: ticketCode });
|
||||||
|
|
||||||
|
expect([200, 201, 404, 409, 400]).toContain(res.statusCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// 5. MY GAINS
|
||||||
|
// ------------------------
|
||||||
|
test("Get my gains", async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.get("/api/game/my-gains")
|
||||||
|
.set("Authorization", `Bearer ${token}`);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
expect(res.body.gains).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// 6. ADMIN STATS (CLIENT access denied)
|
||||||
|
// ------------------------
|
||||||
|
test("Admin stats - CLIENT access denied", async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.get("/api/admin/stats")
|
||||||
|
.set("Authorization", `Bearer ${token}`);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(403);
|
||||||
|
expect(res.body.message || res.body).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
31
backend/tests/setup.js
Normal file
31
backend/tests/setup.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
const prisma = require("../src/config/prisma");
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await prisma.claim.deleteMany();
|
||||||
|
await prisma.participation.deleteMany();
|
||||||
|
await prisma.ticket.deleteMany();
|
||||||
|
await prisma.user.deleteMany();
|
||||||
|
|
||||||
|
// Création de lots
|
||||||
|
await prisma.lot.createMany({
|
||||||
|
data: [
|
||||||
|
{ name: "Infuseur", value: 10, percentage: 60, description: "Infuseur à thé" },
|
||||||
|
{ name: "Thé Détox", value: 15, percentage: 20, description: "Thé détox bio" },
|
||||||
|
{ name: "Thé Signature", value: 20, percentage: 10, description: "Thé signature" },
|
||||||
|
{ name: "Coffret 39€", value: 39, percentage: 6, description: "Coffret découverte" },
|
||||||
|
{ name: "Coffret 69€", value: 69, percentage: 4, description: "Coffret premium" },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Création d'un ticket
|
||||||
|
await prisma.ticket.create({
|
||||||
|
data: {
|
||||||
|
code: "ABC123",
|
||||||
|
lotId: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user