Le marché francophone de l'actualité IA est saturé de newsletters qui promettent toutes de filtrer le bruit. Carnetia tranche : chaque outil reçoit un verdict, chaque guide est daté et maintenu.
Le modèle est freemium.
Technologies utilisées
- Backend : Django 6 + Django REST Framework (Python 3.13)
- Frontend : Nuxt 4 (Vue 3, TypeScript), SSR sur les pages publiques, rendu client sur l'espace membre
- Base de données : PostgreSQL
- Authentification : django-allauth en mode headless, JWT (access court + refresh rotatif), vérification d'adresse obligatoire
- UI : Nuxt UI v4 (Tailwind v4), kit de marque en tokens CSS, thème sombre par défaut
- État : Pinia
- Markdown : Martor côté admin (édition + upload d'images), @nuxtjs/mdc côté front (rendu SSR)
- SEO : @nuxtjs/seo
- Paiement : Stripe (Checkout, Billing Portal, webhooks)
- E-mail : Brevo (SMTP transactionnel + campagnes)
- Communauté : Discord (OAuth2 + rôle attribué selon l'adhésion)
- Anti-bot : Cloudflare Turnstile
- Observabilité : Sentry (back et front)
- Tests : pytest (back), Vitest + @nuxt/test-utils (front)
- Outillage : uv, ruff, vue-tsc, GitHub Actions
- Déploiement : VPS, Nginx, Gunicorn, systemd
Architecture
- Deux dépôts indépendants (API DRF / front Nuxt), le front consomme l'API sur une origine distincte
- Rendu piloté par route : SSR là où le SEO compte (carnets, Radar, éditions, glossaire), rendu client sur l'espace membre, pages privées exclues du sitemap et de l'indexation
- Statut de publication porté par un mixin commun : le filtrage brouillon / publié se fait au queryset, en amont de la sérialisation ; un brouillon n'existe pas pour le public
- Back-office = admin Django : éditeur markdown, upload d'images réservé au staff, stockage local avec conversion WebP à l'enregistrement, aucun stockage objet
- Front : logique métier dans des composables et des stores, un client API unique ; tests sur la logique, jamais sur le rendu, typecheck + build en filet
Authentification
- allauth fabrique le JWT, DRF le vérifie : les vues métier ne dépendent d'aucun fournisseur de tokens
- Vérification d'adresse obligatoire par lien : l'inscription ne délivre aucun token tant que l'adresse n'est pas confirmée ; anti-énumération à l'inscription
- Client API unique : header Authorization, refresh puis rejeu unique de la requête sur 401
- Rate limiting sur inscription et connexion, Turnstile vérifié côté serveur
Fonctionnalités principales
- Accueil en flux : verdicts récents, carnets mis à jour, palier en cours et places restantes, compteur du cercle, teaser de la dernière édition
- Radar : outils groupés par verdict, phrase publique et date du verdict, filtre par thème
- Carnets : guides markdown avec images inline, niveau, date de dernière révision, gratuit ou premium
- Éditions : article hebdomadaire daté, intro publique + corps premium
- Glossaire : fiches A–Z publiques avec balisage JSON-LD
- Embeds médias dans le corps (YouTube avec bornes d'extrait, X), citation rendue en SSR
- Tarif : grille de paliers, palier en cours, places restantes, redirection Stripe Checkout
- Espace membre : statut, palier et prix verrouillé, portail de facturation, liaison du compte Discord, préférences e-mail (promo en opt-in, newsletter en opt-out)
- Mur d'abonnement inline pour un compte gratuit connecté
Paliers, paiement et contrôle d'accès
Le cœur du projet est la boucle adhésion → palier → accès premium, conçue pour qu'un prix promis soit tenu à vie et qu'un corps réservé ne fuite jamais.
- Grille : cohortes de membres à prix croissant, statut à venir / en cours / complet, un prix Stripe distinct par palier
- Attribution transactionnelle : à la confirmation du paiement, l'adhésion est créée sous transaction avec verrouillage des paliers ; le numéro de membre détermine le palier ; à la borne haute, le palier passe complet et le suivant s'ouvre
- Verrouillage à vie : l'abonnement Stripe reste sur le prix de son palier d'origine, indépendamment de la grille publique
- Idempotence des webhooks : rejouer un événement Stripe n'a aucun effet
- Webhooks de facturation → statut actif / impayé / résilié, fin de période, annulation programmée
- Propagation : tout changement de statut est reflété sur le contact e-mail et sur le rôle Discord
- Gating côté API : le drapeau locked est calculé à la sérialisation selon le lecteur ; le corps premium n'est jamais sérialisé pour un non-membre ; l'extrait est toujours renvoyé, jamais de 403, pour garder le teaser indexable
- Deux granularités : un carnet est premium en bloc, une édition est toujours scindée intro / corps
- Seconde barrière e-mail : envoi segmenté aux seuls membres, avec un bloc conditionnel dans le template Brevo