changement
This commit is contained in:
@@ -7,7 +7,6 @@ JWT_SECRET="your_secret_key"
|
|||||||
APP_URL="http://localhost:5000"
|
APP_URL="http://localhost:5000"
|
||||||
MAIL_HOST="smtp.gmail.com"
|
MAIL_HOST="smtp.gmail.com"
|
||||||
MAIL_PORT="587"
|
MAIL_PORT="587"
|
||||||
MAIL_USER="engongomo.emma27@gmail.com"
|
MAIL_USER="your_email@gmail.com"
|
||||||
MAIL_PASS="rczo zqps hsnm msdm"
|
MAIL_PASS="your_app_password"
|
||||||
MAIL_FROM="Thé Tip Top <engongomo.emma27@gmail.com>"
|
MAIL_FROM="Thé Tip Top <your_email@gmail.com>"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "GameSettings" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"startDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "GameSettings_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
@@ -116,3 +116,12 @@ model GrandPrizeWinner {
|
|||||||
prize String @default("Un an de thé d'une valeur de 360€")
|
prize String @default("Un an de thé d'une valeur de 360€")
|
||||||
drawnAt DateTime @default(now())
|
drawnAt DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ligne unique de configuration : date de début du jeu-concours.
|
||||||
|
// Participation et réclamation des lots sont autorisées pendant 60 jours
|
||||||
|
// (30 jours de jeu + 30 jours supplémentaires) à compter de startDate.
|
||||||
|
model GameSettings {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
startDate DateTime @default(now())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ async function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log("Lots créés avec succès");
|
console.log("Lots créés avec succès");
|
||||||
|
|
||||||
|
const existingSettings = await prisma.gameSettings.findFirst();
|
||||||
|
if (!existingSettings) {
|
||||||
|
await prisma.gameSettings.create({ data: {} });
|
||||||
|
console.log("Date de début du jeu-concours initialisée à aujourd'hui.");
|
||||||
|
} else {
|
||||||
|
console.log("Date de début du jeu-concours déjà définie, inchangée.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,8 +1,41 @@
|
|||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
const prisma = require("../config/prisma");
|
const prisma = require("../config/prisma");
|
||||||
|
const { getGameWindow, expireOverdueClaims } = require("../utils/gameWindow");
|
||||||
|
|
||||||
|
const AGE_BRACKETS = ["-18", "18-24", "25-34", "35-44", "45-54", "55-64", "65+"];
|
||||||
|
|
||||||
|
function getAgeBracket(birthdate) {
|
||||||
|
if (!birthdate) return "unknown";
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
let age = now.getFullYear() - birthdate.getFullYear();
|
||||||
|
const hasHadBirthdayThisYear =
|
||||||
|
now.getMonth() > birthdate.getMonth() ||
|
||||||
|
(now.getMonth() === birthdate.getMonth() && now.getDate() >= birthdate.getDate());
|
||||||
|
if (!hasHadBirthdayThisYear) age -= 1;
|
||||||
|
|
||||||
|
if (age < 18) return "-18";
|
||||||
|
if (age <= 24) return "18-24";
|
||||||
|
if (age <= 34) return "25-34";
|
||||||
|
if (age <= 44) return "35-44";
|
||||||
|
if (age <= 54) return "45-54";
|
||||||
|
if (age <= 64) return "55-64";
|
||||||
|
return "65+";
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeAgeStats(users) {
|
||||||
|
const counts = Object.fromEntries([...AGE_BRACKETS, "unknown"].map((b) => [b, 0]));
|
||||||
|
for (const user of users) {
|
||||||
|
counts[getAgeBracket(user.birthdate)] += 1;
|
||||||
|
}
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
|
||||||
const getStats = async (req, res) => {
|
const getStats = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
await expireOverdueClaims();
|
||||||
|
const gameWindow = await getGameWindow();
|
||||||
|
|
||||||
const [
|
const [
|
||||||
ticketsTotal,
|
ticketsTotal,
|
||||||
ticketsUsed,
|
ticketsUsed,
|
||||||
@@ -13,6 +46,7 @@ const getStats = async (req, res) => {
|
|||||||
users,
|
users,
|
||||||
men,
|
men,
|
||||||
women,
|
women,
|
||||||
|
clientsWithBirthdate,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
prisma.ticket.count(),
|
prisma.ticket.count(),
|
||||||
prisma.ticket.count({ where: { isUsed: true } }),
|
prisma.ticket.count({ where: { isUsed: true } }),
|
||||||
@@ -23,6 +57,7 @@ const getStats = async (req, res) => {
|
|||||||
prisma.user.count({ where: { role: "CLIENT" } }),
|
prisma.user.count({ where: { role: "CLIENT" } }),
|
||||||
prisma.user.count({ where: { role: "CLIENT", gender: "Homme" } }),
|
prisma.user.count({ where: { role: "CLIENT", gender: "Homme" } }),
|
||||||
prisma.user.count({ where: { role: "CLIENT", gender: "Femme" } }),
|
prisma.user.count({ where: { role: "CLIENT", gender: "Femme" } }),
|
||||||
|
prisma.user.findMany({ where: { role: "CLIENT" }, select: { birthdate: true } }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const lotsStats = await prisma.lot.findMany({
|
const lotsStats = await prisma.lot.findMany({
|
||||||
@@ -48,6 +83,8 @@ const getStats = async (req, res) => {
|
|||||||
claimsDelivered,
|
claimsDelivered,
|
||||||
claimsCancelled,
|
claimsCancelled,
|
||||||
users,
|
users,
|
||||||
|
gameWindow,
|
||||||
|
ageStats: computeAgeStats(clientsWithBirthdate),
|
||||||
genderStats: {
|
genderStats: {
|
||||||
men,
|
men,
|
||||||
women,
|
women,
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
const prisma = require("../config/prisma");
|
const prisma = require("../config/prisma");
|
||||||
|
const { expireOverdueClaims } = require("../utils/gameWindow");
|
||||||
|
|
||||||
const getGainByTicketCode = async (req, res) => {
|
const getGainByTicketCode = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { code } = req.params;
|
const { code } = req.params;
|
||||||
|
|
||||||
|
await expireOverdueClaims();
|
||||||
|
|
||||||
const ticket = await prisma.ticket.findUnique({
|
const ticket = await prisma.ticket.findUnique({
|
||||||
where: { code: code.toUpperCase() },
|
where: { code: code.toUpperCase() },
|
||||||
include: {
|
include: {
|
||||||
@@ -66,6 +69,8 @@ const deliverClaim = async (req, res) => {
|
|||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const employeeId = req.user.id;
|
const employeeId = req.user.id;
|
||||||
|
|
||||||
|
await expireOverdueClaims();
|
||||||
|
|
||||||
const claim = await prisma.claim.findUnique({
|
const claim = await prisma.claim.findUnique({
|
||||||
where: { id: Number(id) },
|
where: { id: Number(id) },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
const prisma = require("../config/prisma");
|
const prisma = require("../config/prisma");
|
||||||
|
const { getGameWindow, expireOverdueClaims } = require("../utils/gameWindow");
|
||||||
|
|
||||||
const participate = async (req, res) => {
|
const participate = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { code } = req.body;
|
const { code } = req.body;
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
|
|
||||||
|
const gameWindow = await getGameWindow();
|
||||||
|
if (!gameWindow.isOpen) {
|
||||||
|
return res.status(410).json({
|
||||||
|
message: "Le jeu-concours est terminé. Vous ne pouvez plus tester de code ni réclamer de lot.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const ticket = await prisma.ticket.findUnique({
|
const ticket = await prisma.ticket.findUnique({
|
||||||
where: { code },
|
where: { code },
|
||||||
include: {
|
include: {
|
||||||
@@ -83,6 +91,8 @@ const myGains = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
|
|
||||||
|
await expireOverdueClaims();
|
||||||
|
|
||||||
const participations = await prisma.participation.findMany({
|
const participations = await prisma.participation.findMany({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
include: {
|
include: {
|
||||||
|
|||||||
51
backend/src/utils/gameWindow.js
Normal file
51
backend/src/utils/gameWindow.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const prisma = require("../config/prisma");
|
||||||
|
|
||||||
|
const GAME_DURATION_DAYS = 30;
|
||||||
|
const CLAIM_EXTENSION_DAYS = 30;
|
||||||
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// Fenêtre pendant laquelle un joueur peut à la fois tester son code et
|
||||||
|
// réclamer son lot : 30 jours de jeu + 30 jours supplémentaires à compter
|
||||||
|
// de startDate. Tant qu'aucune GameSettings n'existe, le jeu est considéré
|
||||||
|
// comme non démarré et donc ouvert (comportement de dev par défaut).
|
||||||
|
async function getGameWindow() {
|
||||||
|
const settings = await prisma.gameSettings.findFirst({ orderBy: { id: "asc" } });
|
||||||
|
|
||||||
|
if (!settings) {
|
||||||
|
return {
|
||||||
|
configured: false,
|
||||||
|
startDate: null,
|
||||||
|
participationDeadline: null,
|
||||||
|
claimDeadline: null,
|
||||||
|
isOpen: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const participationDeadline = new Date(settings.startDate.getTime() + GAME_DURATION_DAYS * DAY_MS);
|
||||||
|
const claimDeadline = new Date(
|
||||||
|
settings.startDate.getTime() + (GAME_DURATION_DAYS + CLAIM_EXTENSION_DAYS) * DAY_MS
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
configured: true,
|
||||||
|
startDate: settings.startDate,
|
||||||
|
participationDeadline,
|
||||||
|
claimDeadline,
|
||||||
|
isOpen: new Date() <= claimDeadline,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bascule en CANCELLED les gains PENDING non réclamés une fois la fenêtre
|
||||||
|
// fermée. Appelé au fil de l'eau (pas de cron) avant toute lecture/écriture
|
||||||
|
// de claims, ce qui suffit pour ce projet.
|
||||||
|
async function expireOverdueClaims() {
|
||||||
|
const window = await getGameWindow();
|
||||||
|
if (window.isOpen) return;
|
||||||
|
|
||||||
|
await prisma.claim.updateMany({
|
||||||
|
where: { status: "PENDING" },
|
||||||
|
data: { status: "CANCELLED" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getGameWindow, expireOverdueClaims };
|
||||||
Reference in New Issue
Block a user