Vellume
- Année
- 2025
- Rôle
- SaaS éducatif · IA · Fullstack
- Stack
- Next.js 16, TypeScript, MySQL, Drizzle ORM, Better-Auth, Gemini AI, FSRS, Tailwind 4, Shadcn/UI, next-intl
- Liens
- Voir le siteDépôt privé
SaaS d'études pour les apprentis CFC ICT suisses. Scan de cours en PDF ou photo, génération de flashcards par IA (Gemini), tuteur RAG ancré dans tes propres notes, révisions selon l'algorithme FSRS, suivi complet des modules et compétences du plan de formation officiel. Architecture multi-providers IA (Gemini, Claude, BYOK), i18n FR/DE/IT, dark mode, CV PDF généré dynamiquement.
Le contexte
Les apprentis CFC ICT révisent des dizaines de modules et de compétences, souvent avec des notes éparpillées. Vellume transforme leurs propres cours en fiches de révision et leur donne un tuteur qui répond à partir de leurs notes.
Mon rôle
Seul, de la conception à la production : produit, design, base de données, IA et déploiement. Le site est en ligne avec de vrais utilisateurs.
Défis techniques
- 01
Un tuteur ancré dans les cours
Le tuteur s'appuie sur la recherche augmentée (RAG) : il retrouve les passages utiles dans les documents importés avant de répondre, pour rester fidèle à ce que l'élève a réellement étudié.
- 02
Des quotas IA impossibles à contourner
Supprimer ses messages ou ses cartes remettait les compteurs à zéro. Chaque appel réserve maintenant une ligne dans un journal en ajout seul avant d'appeler le fournisseur, puis recompte : même des requêtes parallèles ne passent pas sous la limite.
- 03
Un audit de sécurité complet
Relais d'e-mails ouvert, injection HTML dans l'e-mail de vérification, pré-détournement de compte Google, SSRF sur les notifications push : une dizaine de failles corrigées, puis des sauvegardes nocturnes chiffrées.
Extrait de code
/** Insère la réservation puis recompte ; au-delà de la limite, elle est retirée. */async function reserve( userId: string, kind: Kind, units: number, over: (usage: { calls: number; units: number }) => Extract<LimitResult, { ok: false }> | null,): Promise<Reservation> { const id = crypto.randomUUID() await db.insert(aiUsage).values({ id, userId, kind, units, createdAt: new Date() }) const usage = await usageSince(userId, kind) const refused = over(usage) if (refused) { await db.delete(aiUsage).where(eq(aiUsage.id, id)) return refused } return { ok: true, id }}Résultat
En ligne sur vellume.ch : flashcards générées par IA, révisions FSRS, suivi des modules et des compétences du plan de formation, en français, allemand et italien.










