mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-23 15:18:04 +02:00
La case « Envoyer aussi par e-mail » du bouton Publier met une ligne par adhérent dans la file : jamais de copie partagée, aucune adresse visible des autres. `informations.email_sent_at` fait office de garde anti-double-diffusion, comme `publishPromoShares` pour les réseaux — une information dépubliée puis republiée ne repart pas. La diffusion est réservée à `manageEmails` : un modérateur publie, il n'écrit pas à tous les adhérents. Sans adresse publique enregistrée, rien ne part et le bandeau le dit, plutôt que d'expédier un message dont le logo et tous les liens seraient cassés. L'image de couverture passe par `/api/informations/<id>/image` et non en data-URI : Gmail et Outlook suppriment les `<img src="data:">`, et l'incorporer gonflerait chaque ligne de la file à plusieurs méga-octets. La route ne sert que les informations publiées. Deux défauts corrigés en chemin, trouvés en jouant le parcours complet : - `db.execute` rend les colonnes brutes, sans les rebaptiser comme le fait le constructeur de requêtes : `to_address` n'arrivait pas dans `toAddress`, et nodemailer refusait tous les messages faute de destinataire. - `PROMO_SCHEDULER=off` empêchait le chargement du module de fond, donc coupait aussi l'envoi des e-mails. Chaque boucle a maintenant son propre interrupteur. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QsLjRAnuLwivqxCbM4WeP
47 lines
1.6 KiB
TypeScript
47 lines
1.6 KiB
TypeScript
/**
|
|
* Boucles de fond : libération des publications programmées et vidage de la
|
|
* file d'envoi. Fichier séparé de `instrumentation.ts` : il touche la base et
|
|
* `nodemailer`, et ne doit donc jamais entrer dans le bundle edge du
|
|
* middleware — c'est cet import-là que l'`IgnorePlugin` de `next.config.mjs`
|
|
* coupe.
|
|
*/
|
|
import { releaseDuePromotions } from "@/lib/promo-publish";
|
|
import { processOutbox } from "@/lib/mail-outbox";
|
|
|
|
const PROMO_INTERVAL_MS = 60_000;
|
|
// Plus court que la minute des promotions : un mot de passe ou une invitation
|
|
// mis en file ne doit pas attendre une minute pleine.
|
|
const MAIL_INTERVAL_MS = 20_000;
|
|
|
|
async function promoTick() {
|
|
try {
|
|
const n = await releaseDuePromotions();
|
|
if (n > 0) console.log(`[promotions] ${n} publication(s) programmée(s) mise(s) en ligne`);
|
|
} catch (error) {
|
|
// `releaseDuePromotions` avale déjà ses erreurs ; ce filet couvre le cas
|
|
// d'une base injoignable au démarrage, qui ne doit pas tuer le serveur.
|
|
console.error("[promotions] libérateur :", error);
|
|
}
|
|
}
|
|
|
|
async function mailTick() {
|
|
try {
|
|
const { sent, failed } = await processOutbox();
|
|
if (sent || failed) console.log(`[mail] ${sent} envoyé(s), ${failed} en échec`);
|
|
} catch (error) {
|
|
console.error("[mail] file d'attente :", error);
|
|
}
|
|
}
|
|
|
|
// Un premier passage au démarrage rattrape les échéances tombées pendant un
|
|
// redéploiement ou une coupure.
|
|
if (process.env.PROMO_SCHEDULER !== "off") {
|
|
void promoTick();
|
|
setInterval(promoTick, PROMO_INTERVAL_MS).unref();
|
|
}
|
|
|
|
if (process.env.MAIL_WORKER !== "off") {
|
|
void mailTick();
|
|
setInterval(mailTick, MAIL_INTERVAL_MS).unref();
|
|
}
|