mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-11 17:27:54 +02:00
Deux comportements demandés : 1. Une promotion n'apparaît sur le site que pendant sa période de validité. Les lectures publiques passent par `VISIBLE_PROMO` (`src/lib/queries.ts`) : statut `live` **et** fenêtre `starts_on` / `ends_on`, journée calculée en Europe/Paris pour qu'un conteneur en UTC ne retire pas une offre deux heures trop tôt. Le statut n'est pas touché : hors période, le backoffice explique pourquoi l'offre n'est pas visible (`visibilityNote`) et elle revient d'elle-même si les dates changent. 2. La publication peut être programmée. `promotions.publish_at` : l'adhérent propose une date, le modérateur la garde, la déplace ou la vide dans le formulaire « Valider ». Avec une échéance future la promotion passe au statut `scheduled` — rien n'est visible, rien n'est diffusé — et `releaseDuePromotions()` fait la bascule à l'heure dite, site et réseaux ensemble. Un bouton « Publier maintenant » court-circuite l'attente. Mécanique : - `src/lib/promo-schedule.ts` (pur, testé) : conversion `datetime-local` ⇄ heure de l'association, changements d'heure compris, et refus d'une saisie illisible plutôt qu'une publication immédiate involontaire. - `src/lib/promo-publish.ts` reprend `publishPromoShares()` : deux appelants désormais, une action serveur et la boucle de libération, qui n'a pas de requête et ne peut donc pas appeler `revalidatePath()`. Idem pour `logActivity()` dans `src/lib/activity-log.ts`. - La bascule `scheduled → live` est un `UPDATE … RETURNING` filtré sur le statut : atomique, donc pas de double publication même à plusieurs instances, en plus de la garde existante sur `social_posts`. - Déclencheur : `src/instrumentation.ts`, boucle d'une minute avec rattrapage au démarrage (`PROMO_SCHEDULER=off` la désactive). Le travail vit dans `instrumentation-node.ts`, écarté du bundle edge par `next.config.mjs` : Next compile aussi l'instrumentation pour le middleware, où `pg` ne peut pas être embarqué. Migration 0016 : `promo_status += 'scheduled'`, `promotions.publish_at`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
55 lines
2.1 KiB
JavaScript
55 lines
2.1 KiB
JavaScript
/** @type {import('next').NextConfig} */
|
|
const nextConfig = {
|
|
output: "standalone",
|
|
reactStrictMode: true,
|
|
// Pas d'en-tête X-Powered-By : inutile de renseigner un attaquant sur la pile.
|
|
poweredByHeader: false,
|
|
experimental: {
|
|
// Les images d'entête/logo sont envoyées en data-URL via server action.
|
|
serverActions: { bodySizeLimit: "4mb" },
|
|
},
|
|
// `instrumentation.ts` est compilé pour les deux runtimes dès qu'un
|
|
// middleware existe. Son chargement du libérateur de publications est déjà
|
|
// conditionné à `NEXT_RUNTIME === "nodejs"`, mais webpack suit quand même
|
|
// l'import et tente d'embarquer `pg` (donc `fs`, `path`, `stream`) dans le
|
|
// bundle edge, où ces modules n'existent pas. On coupe la branche à la
|
|
// source : côté edge, le module Node n'est tout simplement pas résolu.
|
|
webpack: (config, { nextRuntime, webpack }) => {
|
|
if (nextRuntime === "edge") {
|
|
config.plugins.push(
|
|
new webpack.IgnorePlugin({ resourceRegExp: /^\.\/instrumentation-node$/ })
|
|
);
|
|
}
|
|
return config;
|
|
},
|
|
async headers() {
|
|
return [
|
|
{
|
|
source: "/:path*",
|
|
headers: [
|
|
// HSTS : une fois le site vu en HTTPS, le navigateur refuse le HTTP
|
|
// clair pendant un an. Sans effet tant que le site est servi en HTTP.
|
|
{
|
|
key: "Strict-Transport-Security",
|
|
value: "max-age=31536000; includeSubDomains",
|
|
},
|
|
// Empêche l'interprétation d'une réponse selon un type deviné.
|
|
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
// Le site ne doit pas être encadré par un tiers (clickjacking).
|
|
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
|
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
{
|
|
key: "Permissions-Policy",
|
|
value: "camera=(), microphone=(), geolocation=(), interest-cohort=()",
|
|
},
|
|
// La CSP complète est posée par le middleware : elle a besoin d'un
|
|
// nonce différent à chaque requête, ce qu'une valeur statique ne peut
|
|
// pas fournir.
|
|
],
|
|
},
|
|
];
|
|
},
|
|
};
|
|
|
|
export default nextConfig;
|