Skip to content

Commit d82a976

Browse files
Guigui64260claude
andcommitted
Merge PR #27 — notifications métier push/email (issue #16)
Système de notifications proactives par email (cron interne node-cron + nodemailer existant) et push navigateur (service worker + Web Push/VAPID), avec réglage par type dans /parametres. Résolution de conflits (la PR était calée sur origin/main, main local avait avancé de deux commits) : - src/instrumentation.ts : register() installait déjà le journal global des erreurs serveur (f73b0be). Les deux briques cohabitent désormais dans le même hook, chacune isolée en try/catch, et onRequestError est conservé. - prisma/schema.prisma : relation pushSubscriptions ajoutée à côté de accounts. Correctifs appliqués à la relecture : - itp-semaine : `semaineCourante` numérotait la semaine avec `getWeek` (firstWeekContainsDate: 1) alors que `annee` venait de `getISOWeekYear` et que tout le reste de Gleba lit/écrit en semaine ISO. C'est exactement la divergence corrigée par QA cmswxqnaz (assistant-helpers.ts) : dès que le 1er janvier tombe un vendredi, samedi ou dimanche (2027, 2028, 2032…), le couple (annee, semaine) désignait une autre semaine que la fenêtre calculée par `calculerDateDepuisSemaine`, faussant la clé anti-redondance `tache-itp-semaine:AAAA-Sww` et le « Sww » affiché. Passage à `getISOWeek`, et les deux tests qui figeaient l'ancienne convention sont corrigés. - config/scheduler : NOTIF_METEO_INTERVAL_MIN est borné à 1440 mais la valeur partait dans le champ « minutes » d'une expression cron (`*/120 * * * *`), invalide au-delà de 59. node-cron levait, et comme le scan est planifié AVANT le résumé quotidien, le résumé n'était plus planifié du tout. Nouvelle `getMeteoCronExpression()` qui bascule sur le champ « heures » dès 60 min, couverte par src/lib/notifications/config.test.ts. - sender/templates : ces emails sont récurrents et non transactionnels ; ils partent désormais avec les en-têtes List-Unsubscribe (RFC 8058) et un lien de désabonnement 1 clic en pied de page, via src/lib/unsubscribe.ts déjà utilisé par les campagnes. `emailOptOut` était déjà respecté côté destinataires. Vérifié sur le résultat du merge : tsc sans nouvelle erreur, 920 tests (157 fichiers) au vert, gate lint 519 < 640. Reste à faire, hors périmètre de cette PR (dépend de travaux non commités) : les requêtes d'irrigation des notifications ne filtrent pas `perimee: false` (colonne absente de main), à ajouter quand la péremption d'irrigation sera commitée, sinon les passages abandonnés seront rappelés. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2 parents 3cbdc79 + 1f85608 commit d82a976

44 files changed

Lines changed: 5284 additions & 16 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ SMTP_USER=
3030
SMTP_PASS=
3131
SMTP_FROM=
3232

33+
# Notifications métier par email (résumé quotidien + alertes temps réel)
34+
# Les alertes sont activées par défaut dès que SMTP_HOST/SMTP_USER sont définis.
35+
# NOTIF_ENABLED=false coupe tout (météo temps réel, urgent, résumé).
36+
NOTIF_ENABLED=true
37+
# Heure du résumé quotidien « Quoi faire ce matin ? » (heure locale conteneur, TZ).
38+
NOTIF_RESUME_HEURE=07:00
39+
# Fréquence (minutes) du scan météo temps réel + alertes urgentes (borné 5-1440).
40+
NOTIF_METEO_INTERVAL_MIN=30
41+
# Notifications push navigateur — générer les clés : npx web-push generate-vapid-keys
42+
VAPID_PUBLIC_KEY=
43+
VAPID_PRIVATE_KEY=
44+
VAPID_SUBJECT=mailto:contact@example.com
45+
# Test des scripts scripts/test-notif-*.ts depuis l'hôte (optionnel)
46+
TEST_DATABASE_URL=
47+
3348
# Ollama Cloud (assistant chat — optionnel, désactivé par défaut côté public)
3449
OLLAMA_API_KEY=
3550
OLLAMA_MODEL=nemotron-3-nano:30b

docker-compose.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ services:
5353
SMTP_PASS: ${SMTP_PASS:-}
5454
SMTP_FROM: ${SMTP_FROM:-}
5555
FEEDBACK_EMAIL: ${FEEDBACK_EMAIL:-}
56+
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-}
57+
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-}
58+
VAPID_SUBJECT: ${VAPID_SUBJECT:-}
59+
# Notifications métier par email (cron interne : résumé + alertes temps réel)
60+
NOTIF_ENABLED: ${NOTIF_ENABLED:-true}
61+
NOTIF_RESUME_HEURE: ${NOTIF_RESUME_HEURE:-07:00}
62+
NOTIF_METEO_INTERVAL_MIN: ${NOTIF_METEO_INTERVAL_MIN:-30}
5663
# Audit compta 2026-06 : les bornes d'exercice comptable sont calculées
5764
# en heure locale du serveur — en UTC, une vente saisie le 1er janvier
5865
# 00h30 (heure de Paris) tombait dans l'exercice précédent.

package-lock.json

Lines changed: 141 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"mathjs": "^15.2.0",
6565
"next": "^16.2.10",
6666
"next-auth": "^5.0.0-beta.31",
67+
"node-cron": "^4.6.0",
6768
"nodemailer": "^9.0.3",
6869
"ollama": "^0.6.3",
6970
"pdfkit": "^0.18.0",
@@ -79,6 +80,7 @@
7980
"tailwind-merge": "^3.4.0",
8081
"tailwindcss-animate": "^1.0.7",
8182
"three": "^0.169.0",
83+
"web-push": "^3.6.7",
8284
"zod": "^4.3.6"
8385
},
8486
"devDependencies": {
@@ -90,13 +92,14 @@
9092
"@types/react": "^19.2.17",
9193
"@types/react-dom": "^19.2.3",
9294
"@types/three": "^0.169.0",
95+
"@types/web-push": "^3.6.4",
9396
"better-sqlite3": "^12.6.2",
9497
"eslint": "^8",
9598
"eslint-config-next": "14.2.18",
9699
"postcss": "^8",
97100
"tailwindcss": "^3.4.1",
98101
"tsx": "^4.21.0",
99-
"typescript": "5.9.3",
102+
"typescript": "^5.9.3",
100103
"vitest": "^2.1.5"
101104
}
102105
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Seuils minimums pour les alertes de stocks utilisateur.
2+
ALTER TABLE "user_stock_varietes"
3+
ADD COLUMN "stock_min_graines" DOUBLE PRECISION,
4+
ADD COLUMN "stock_min_plants" INTEGER;
5+
6+
ALTER TABLE "user_stock_fertilisants"
7+
ADD COLUMN "stock_min" DOUBLE PRECISION;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-- Abonnements push navigateur par utilisateur.
2+
CREATE TABLE "push_subscriptions" (
3+
"id" SERIAL NOT NULL,
4+
"user_id" TEXT NOT NULL,
5+
"endpoint" TEXT NOT NULL,
6+
"p256dh" TEXT NOT NULL,
7+
"auth" TEXT NOT NULL,
8+
"userAgent" TEXT,
9+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
10+
11+
CONSTRAINT "push_subscriptions_pkey" PRIMARY KEY ("id")
12+
);
13+
14+
CREATE UNIQUE INDEX "push_subscriptions_endpoint_key" ON "push_subscriptions"("endpoint");
15+
CREATE INDEX "push_subscriptions_user_id_idx" ON "push_subscriptions"("user_id");
16+
17+
ALTER TABLE "push_subscriptions"
18+
ADD CONSTRAINT "push_subscriptions_user_id_fkey"
19+
FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Please do not edit this file manually
2+
# It should be added in your version-control system (i.e. Git)
3+
provider = "postgresql"

0 commit comments

Comments
 (0)