Carnetia
Projet

Carnetia

Plateforme éditoriale sur l’IA développée from scratch pour le marché francophone : Radar de verdicts (ADOPTE / TESTE / SURVEILLE / IGNORE), carnets vivants datés, éditions hebdomadaires scindées intro publique / corps premium, adhésion annuelle par paliers à prix verrouillé à vie via Stripe et communauté Discord.

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