101 lines
3.6 KiB
JavaScript
101 lines
3.6 KiB
JavaScript
const prisma = require("../config/prisma");
|
|
|
|
// Le concours traverse cinq états, et non deux : confondre « pas encore
|
|
// ouvert » avec « terminé » produit des messages faux côté joueur et laisse
|
|
// passer le tirage au sort avant le début du jeu.
|
|
const PHASES = {
|
|
NOT_CONFIGURED: "NOT_CONFIGURED",
|
|
SCHEDULED: "SCHEDULED",
|
|
OPEN: "OPEN",
|
|
CLAIM_ONLY: "CLAIM_ONLY",
|
|
CLOSED: "CLOSED",
|
|
};
|
|
|
|
const GAME_DURATION_DAYS = 30;
|
|
const CLAIM_EXTENSION_DAYS = 30;
|
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
|
// Deux échéances distinctes à partir de startDate :
|
|
// - participationDeadline (30 j) : dernier moment pour tester un code ;
|
|
// - claimDeadline (60 j) : dernier moment pour retirer un lot déjà gagné.
|
|
// Les 30 jours supplémentaires servent uniquement au retrait, pas au jeu.
|
|
// Tant qu'aucune GameSettings n'existe, le jeu est considéré comme non
|
|
// configuré et donc ouvert (comportement de dev par défaut).
|
|
async function getGameWindow(now = new Date()) {
|
|
const settings = await prisma.gameSettings.findFirst({ orderBy: { id: "asc" } });
|
|
|
|
// Tant que l'administrateur n'a pas fixé de date, le concours n'existe pas :
|
|
// rien n'est jouable et aucun tirage n'est possible. Ouvrir par défaut
|
|
// reviendrait à faire gagner des lots dans un jeu qui n'a jamais été lancé.
|
|
if (!settings) {
|
|
return {
|
|
configured: false,
|
|
phase: PHASES.NOT_CONFIGURED,
|
|
startDate: null,
|
|
participationDeadline: null,
|
|
claimDeadline: null,
|
|
isParticipationOpen: false,
|
|
isOpen: false,
|
|
};
|
|
}
|
|
|
|
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
|
|
);
|
|
|
|
const hasStarted = now >= settings.startDate;
|
|
const isParticipationOpen = hasStarted && now <= participationDeadline;
|
|
const isOpen = hasStarted && now <= claimDeadline;
|
|
|
|
let phase;
|
|
if (!hasStarted) phase = PHASES.SCHEDULED;
|
|
else if (isParticipationOpen) phase = PHASES.OPEN;
|
|
else if (isOpen) phase = PHASES.CLAIM_ONLY;
|
|
else phase = PHASES.CLOSED;
|
|
|
|
return {
|
|
configured: true,
|
|
phase,
|
|
startDate: settings.startDate,
|
|
participationDeadline,
|
|
claimDeadline,
|
|
hasStarted,
|
|
isParticipationOpen,
|
|
isOpen,
|
|
};
|
|
}
|
|
|
|
// L'expiration d'un gain est *dérivée* de la fenêtre de jeu, jamais écrite en
|
|
// base : un PENDING dont le délai de retrait est passé est présenté comme
|
|
// CANCELLED, mais la ligne reste intacte. Corriger une startDate erronée
|
|
// suffit donc à rétablir les gains, et aucune lecture ne modifie la base.
|
|
function effectiveClaimStatus(status, gameWindow) {
|
|
if (status === "PENDING" && !gameWindow.isOpen) return "CANCELLED";
|
|
return status;
|
|
}
|
|
|
|
// Message destiné au joueur qui tente de jouer hors de la fenêtre : chaque
|
|
// phase a sa propre explication, avec la date utile quand elle existe.
|
|
function participationClosedMessage(gameWindow) {
|
|
switch (gameWindow.phase) {
|
|
case PHASES.NOT_CONFIGURED:
|
|
return "Le jeu-concours n'est pas encore ouvert. Revenez prochainement.";
|
|
case PHASES.SCHEDULED:
|
|
return `Le jeu-concours ouvrira le ${gameWindow.startDate.toLocaleDateString("fr-FR")}.`;
|
|
case PHASES.CLAIM_ONLY:
|
|
return "La période de participation est terminée. Vous pouvez encore retirer les lots déjà gagnés.";
|
|
default:
|
|
return "Le jeu-concours est terminé. Vous ne pouvez plus tester de code ni réclamer de lot.";
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getGameWindow,
|
|
effectiveClaimStatus,
|
|
participationClosedMessage,
|
|
PHASES,
|
|
GAME_DURATION_DAYS,
|
|
CLAIM_EXTENSION_DAYS,
|
|
};
|