Une application Next.js qui agrege les annonces de concours du secteur public liees au developpement web et a l'informatique. Elle filtre les annonces par mots-cles configurables, sert les resultats sous forme de flux RSS, les affiche sur un tableau de bord web, et notifie les abonnes par email lorsque de nouveaux concours sont trouves.
- Web scraping -- Pagination des pages de liste de wadifa-info.com et scraping des pages de detail avec Cheerio
- Filtrage par mots-cles -- Correspond aux concours selon des mots-cles d'inclusion/exclusion configurables (correspondance de texte pure)
- Flux RSS -- Sert un flux RSS 2.0 valide a
/feed.xml - Tableau de bord web -- Affiche les concours correspondants avec details depliables, echeances et liens sources
- Abonnements email -- Les abonnes recoivent des emails recapitulatifs lorsque de nouveaux concours correspondants sont trouves (via Brevo)
- Historique persistant -- Les resultats scrapes sont stockes dans un fichier JSON local avec ecritures atomiques
- Rafraichissement automatique -- Planificateur
node-cronintegre (toutes les 5 heures par defaut) + endpoint API securise pour declenchement externe - Desabonnement securise -- Liens de desabonnement en un clic signes HMAC-SHA256 dans chaque email
- Rate limiting -- Protection de l'endpoint d'abonnement par limiteur a fenetre glissante
| Couche | Technologie |
|---|---|
| Framework | Next.js 16 (App Router, standalone output) |
| Langage | TypeScript 5.9 |
| UI | React 19, CSS Modules |
| Scraping | Cheerio |
| Validation | Zod 4 |
| Brevo (API SMTP transactionnelle) | |
| Stockage | Fichier JSON local (data/concours.json) avec ecritures atomiques |
| Planification | node-cron |
| Polices | Geist (woff2 auto-heberge) |
| Conteneur | Docker (multi-stage alpine, ~337 MB) |
- Bun (gestionnaire de paquets)
- Node.js 22+
cp .env.example .env # configurez vos variables d'environnement
bun install
bun run dev # demarre le serveur de dev sur http://localhost:3000bun run build
bun run startL'application est concue pour etre auto-hebergee avec Docker. Le Dockerfile utilise un build multi-stage (bun pour les deps, node pour le build, runner alpine minimal).
cp .env.example .env # configurez vos variables d'environnement
docker compose up -d --buildL'application sera disponible sur http://localhost:3000.
Le planificateur node-cron integre declenche automatiquement le scraping toutes les 5 heures. Aucun cron externe n'est requis, mais l'endpoint /api/cron/refresh reste disponible pour des declenchements manuels.
Le fichier docker-compose.yml inclut :
- Limites de ressources -- CPU et memoire plafonnes
- Securite -- Systeme de fichiers racine en lecture seule,
tmpfspour les caches temporaires, utilisateur non-root - Volume persistant -- Volume Docker nomme (
concours-data) monte sur/app/data - Healthcheck -- Verification automatique de
/healthz - Redemarrage automatique --
unless-stopped
Configurez votre fichier .env a la racine du projet. Voir la section Configuration pour les details.
Toute la configuration se fait via des variables d'environnement. Voir .env.example pour un modele.
| Variable | Description |
|---|---|
APP_BASE_URL |
URL publique de votre deploiement (ex. https://concours.example.com) |
BREVO_API_KEY |
Cle API Brevo pour les emails transactionnels et la gestion des contacts |
BREVO_LIST_ID |
ID de la liste de contacts Brevo pour les abonnes |
BREVO_SENDER_EMAIL |
Adresse email de l'expediteur pour les notifications |
CRON_SECRET |
Jeton secret pour authentifier les requetes de rafraichissement cron |
UNSUBSCRIBE_SECRET |
Secret HMAC pour signer les jetons de desabonnement |
| Variable | Defaut | Description |
|---|---|---|
BREVO_SENDER_NAME |
Concours Developpement Web |
Nom d'affichage de l'expediteur |
REFRESH_CRON |
0 */5 * * * |
Expression cron pour le planificateur integre |
DATA_DIR |
./data |
Repertoire pour le fichier JSON de stockage |
KEYWORDS |
developpement,informatique |
Mots-cles d'inclusion separes par des virgules |
EXCLUDE_KEYWORDS |
(vide) | Mots-cles d'exclusion separes par des virgules |
MAX_PAGES |
5 |
Nombre max de pages de liste a scraper |
MAX_FEED_ITEMS |
30 |
Nombre max d'elements dans le flux RSS |
CACHE_SECONDS |
3600 |
TTL du cache en memoire en secondes |
BASE_URL |
https://www.wadifa-info.com |
URL de base de la cible du scraper |
LIST_PATH |
/fr/concours-emplois-publics-maroc |
Chemin de la page de liste |
LIST_SORT_BY |
4 |
Parametre de tri pour les listes |
USER_AGENT |
Chaine UA Chrome 120 | User-Agent HTTP pour les requetes du scraper |
| Methode | Chemin | Description |
|---|---|---|
GET |
/ |
Tableau de bord web avec les concours correspondants |
GET |
/feed.xml |
Flux RSS 2.0 |
GET |
/healthz |
Verification de sante ({ ok: true }) |
GET |
/unsubscribe?token=... |
Page de confirmation de desabonnement |
POST |
/api/subscribe |
Ajouter un abonne email (rate limited) |
POST |
/api/unsubscribe |
Retirer un abonne email (verifie par jeton) |
POST |
/api/cron/refresh |
Declencher scrape + notifications email |
Le scraping est effectue automatiquement par le planificateur node-cron integre (toutes les 5 heures par defaut, configurable via REFRESH_CRON).
Pour un declenchement manuel, utilisez POST /api/cron/refresh :
curl -X POST -H "x-cron-secret: <votre-secret>" https://concours.example.com/api/cron/refresh- L'en-tete
x-cron-secretest obligatoire - Ajouter
?dryRun=1pour scraper sans envoyer d'emails - Ajouter
?force=1pour contourner le cache en memoire
src/
app/
page.tsx # Page d'accueil (composant serveur)
subscribe-card.tsx # Formulaire d'abonnement email (composant client)
layout.tsx # Layout racine
globals.css # Theme, polices, variables CSS
icon.svg # Favicon
feed.xml/route.ts # Endpoint du flux RSS
healthz/route.ts # Verification de sante
unsubscribe/ # Page de confirmation de desabonnement
api/
subscribe/route.ts # API d'abonnement (rate limited)
unsubscribe/route.ts # API de desabonnement
cron/refresh/route.ts # API de rafraichissement cron
lib/
config.ts # Configuration centrale
wadifa.ts # Scraper web + correspondance de mots-cles
wadifa-cache.ts # Cache TTL en memoire
concours-store.ts # Stockage JSON local (ecritures atomiques)
refresh.ts # Pipeline partage scrape -> merge -> notify
rate-limit.ts # Limiteur a fenetre glissante par IP
rss.ts # Constructeur de flux RSS 2.0
brevo.ts # Client API Brevo
mailer.ts # Composition + envoi d'email
date.ts # Utilitaires d'analyse de date
normalize.ts # Normalisation de texte
unsubscribe-token.ts # Signature/verification de jeton HMAC
instrumentation.ts # Hook serveur Next.js (planificateur node-cron)
data/
concours.json # Donnees persistantes (genere automatiquement)
scripts/
smoke-http.ts # Suite de tests de fumee HTTP
selftest.ts # Auto-test de bout en bout
Dockerfile # Build multi-stage (bun + node alpine)
docker-compose.yml # Config Docker avec securite et limites
# Tests de fumee contre un serveur en cours d'execution
bun run test:smoke -- --url=http://localhost:3000
# Auto-test complet (build, demarrer le serveur, executer les tests de fumee, arret)
bun run test:self