Projet de troisième année · Dublin City University · 2023—2024
Bynle
Une plateforme de billetterie pour les clubs et associations étudiantes — événements, paiements, entrée par QR code et couche sociale — conçue pour qu’un bureau vende son billet en direct et qu’un étudiant puisse le céder à un ami en toute sécurité.
- Django
- React
- PostgreSQL
- Stripe Connect
- QR ticketing
- AWS
- Période
- 2023 — 2024
- Cadre
- Projet de troisième année, licence en informatique
- Établissement
- Dublin City University
- Déploiement
- Railway, AWS Amplify et S3
- 134
- Tests unitaires
- 10
- Entités du domaine
- 3
- Cibles de déploiement
- 2
- Types de compte
Sur les modèles et les vues du backend, sans qu’aucune modification ne soit fusionnée sans relecture.
Utilisateurs, profils, clubs, événements, billets, comptes Stripe, abonnements, amitiés et demandes de transfert.
Django et PostgreSQL sur Railway, React sur AWS Amplify, fichiers téléversés dans un bucket S3.
Utilisateurs ordinaires et scanneurs de billets, séparés dans le jeton et dans l’arbre de routes.
01L’idée
De la revente de billets à la gestion de tout l’événement
Le projet a commencé ailleurs. L’idée de départ était une couche de transfert sécurisé posée sur les plateformes de billetterie existantes : l’un lance la cession, l’autre paie dans l’application, et le billet ne change de mains qu’une fois le paiement validé — un remède aux arnaques de revente qui suivent chaque événement complet.
Nous l’avons abandonnée. En travaillant contre les API de ces plateformes, ni la sécurité offerte ni le niveau de vérification réellement possible ne nous convenaient. Si la vérification devait être fiable, il fallait que le billet soit le nôtre.
Nous avons donc construit le système de billetterie lui-même, visant le terrain que nous connaissions le mieux : les clubs et associations étudiantes. Un club crée un événement, vend les billets directement et les scanne à l’entrée. Et comme le billet nous appartenait désormais de bout en bout, le transfert sécurisé à l’origine du projet est devenu quelque chose que nous pouvions vraiment garantir.
Le dernier virage a été social. Dès lors que les étudiants découvraient des événements sur la plateforme, il était peu logique que cette découverte se réduise à une liste. Suivre des clubs, se connecter à des amis et voir où vont les gens a transformé un outil de billetterie en quelque chose de plus proche d’un tableau d’affichage de campus.
02Le produit
Trois types de personnes, une plateforme
Trois types de personnes utilisent Bynle, et l’interface change de forme pour chacun.
- Étudiants
- Parcourent les événements, suivent des clubs, envoient et acceptent des demandes d’amitié, achètent des billets et cèdent à un ami celui qu’ils n’utiliseront pas.
- Responsables de club
- Créent et gèrent les événements, fixent les tarifs, connectent un compte Stripe, éditent la page du club, nomment d’autres responsables, créent des comptes scanneur et consultent les statistiques du club.
- Scanneurs de billets
- Un type de compte distinct, créé par un responsable de club, avec une seule fonction : se connecter sur un téléphone à l’entrée et scanner des QR codes.
- Événements. Les clubs publient des événements avec date, heure, lieu, capacité, type et image de couverture, gratuits ou payants.
- Billets. Chaque billet porte un code unique et un QR code généré, liés à un événement et à un propriétaire.
- Transferts. Un billet peut être cédé à un ami, le paiement étant traité dans l’application lorsque le billet n’était pas gratuit.
- Graphe social. Demandes d’amitié avec état en attente, abonnements aux clubs, et les relations que deux utilisateurs ont en commun.
- Statistiques de club. Un responsable voit quelles filières et quelles années suivent le club, agrégées côté backend et rendues sous forme de graphiques.
- Médias. Logos, couvertures de club et affiches d’événement téléversés par les responsables et servis depuis un stockage cloud plutôt que depuis le serveur applicatif.


03Architecture
Cinq couches, trois hébergeurs
Le système se divise en un client React, une couche applicative Django, une couche de données PostgreSQL, une couche d’intégration pour les paiements et les médias, et une couche de déploiement qui place chaque pièce où il faut.
Le backend est une API REST. Les vues Django sont regroupées en gestionnaires par domaine — authentification, clubs, statistiques, événements, amitiés, scanneurs, paiements, billets, transferts et données utilisateur — avec des sérialiseurs qui valident tout ce qui entre et convertissent les instances de modèles en JSON à la sortie.
Le tout tourne sur trois services. L’application Django et un conteneur PostgreSQL cohabitent sur Railway, qui récupère la dernière version d’une branche fixe à chaque push et exécute un script de démarrage qui migre, collecte les fichiers statiques et lance Gunicorn. Le frontend React est hébergé sur AWS Amplify. Les fichiers téléversés — logos, couvertures et affiches — vivent dans un bucket S3, pas sur le serveur applicatif.
- Client
- React, avec React Router protégeant les routes privées et un arbre de routes distinct pour les comptes scanneur.
- Application
- Vues REST Django regroupées en dix gestionnaires de domaine, avec des sérialiseurs pour la validation et la conversion JSON.
- Données
- PostgreSQL pour les données structurées, exécuté depuis une image Docker ; un bucket S3 pour les fichiers téléversés.
- Intégration
- Stripe pour les paiements et l’inscription à Connect, avec une vérification d’état décidant si un club ou un utilisateur peut encaisser.
- Déploiement
- Railway pour le backend et la base, AWS Amplify pour le frontend, gestion de versions Git pour les deux.


04Billets
Vendre un billet est simple ; le déplacer en sécurité ne l’est pas
Un billet ne vaut quelque chose que si exactement une personne peut l’utiliser, exactement une fois. Cette contrainte façonne les trois parties les plus délicates du système : comment l’argent parvient au club, comment un billet change de mains, et ce qui se passe à l’entrée.
Le transfert est ce que nous avons raté en premier. Au début, un billet en cours de cession restait un billet valide : il pouvait donc être scanné pendant que le transfert était en vol, et quelqu’un pouvait entrer avec un billet qu’il était en train de donner. La solution a été de doter les billets d’un état de transfert propre.
- Stripe Connect. Les clubs encaissent via leur propre compte Stripe Connect, pas via le nôtre. Le système vérifie si ce compte est complet et interdit au club de faire payer un événement tant qu’il ne l’est pas.
- État de transfert. Un billet en cours de cession est maintenu dans un état de transfert. Il est scanné comme invalide et n’appartient à personne tant que le destinataire n’a pas accepté ou l’expéditeur annulé.
- Changement de propriétaire. À l’acceptation, l’ancien billet est supprimé et un nouveau est créé pour le destinataire : un billet cédé est donc un nouvel enregistrement, pas un enregistrement modifié.
- Cessions payantes. Lorsque le billet était payant, le destinataire règle dans l’application et le transfert ne se finalise qu’une fois le paiement traité.
- QR à l’entrée. Chaque billet porte un QR code généré pointant vers un point d’accès de validation que seuls les comptes de type scanneur ont le droit d’atteindre.
- Comptes scanneur. Les responsables créent des comptes scanneur pour un événement. Ils ont leurs propres identifiants, leur propre arbre de routes, et ne peuvent rien faire d’autre que scanner.


05Qualité
Ce que nous avons testé, et ce qui a cassé
Les tests ont suivi trois pistes : relecture de code sur chaque merge request, tests unitaires du backend, et sessions de test avec de vrais étudiants.
Le backend a terminé avec 134 tests unitaires sur les modèles et les vues : intégrité des données et règles de validation d’un côté, traitement des requêtes et logique métier de l’autre, y compris des contrôles de permission comme vérifier que seul un compte scanneur peut atteindre le point de validation des billets. Aucune modification n’a rejoint la branche principale sans relecture par l’autre.
Les tests utilisateurs ont changé le produit. Deux demandes sont revenues assez nettement pour que nous les construisions :
- Une cloche de notification. Les utilisateurs voulaient atteindre les demandes d’amitié et les transferts entrants depuis n’importe où, plutôt qu’en naviguant jusqu’à la page qui les contenait. Une cloche est apparue dans la barre de navigation, avec un compteur.
- Supprimer un transfert envoyé. Les participants voulaient retirer un transfert envoyé par erreur ou après un changement d’avis. Les transferts envoyés sont devenus supprimables.

Ce qui a cassé
01
Les responsables de club modélisés en classe dédiée
Problème
Les responsables de club ont d’abord été une classe de modèle à part entière. C’était la mauvaise forme : la relation entre utilisateurs et clubs est de plusieurs à plusieurs, et la forcer dans une classe dédiée rendait l’administration pénible.
Correctif
Refonte en relation plusieurs-à-plusieurs, ce qui a permis à plusieurs utilisateurs d’administrer plusieurs clubs sans cérémonie et a simplifié tout le parcours d’administration.
02
Deux personnes, des migrations supprimées, une base cassée
Problème
Au début, nous travaillions chacun sur une branche et générions tous les deux des migrations en modifiant les modèles. Aucun de nous ne maîtrisait assez les migrations Django, et nous en avions tous les deux supprimé. Au rebase pour fusionner dans main, la base a cassé : elle ne correspondait plus aux modèles, et il nous a fallu longtemps pour comprendre pourquoi.
Correctif
Nous avons convenu de ne plus jamais supprimer une migration, sauf base sauvegardée et remise à zéro réellement justifiée. Un script qui repeuplait la base depuis zéro nous a permis de reconstruire un jeu de données fonctionnel pendant que nous démêlions cela.
03
Les billets restaient valides pendant le transfert
Problème
Tant qu’un transfert était en cours, le billet restait actif, avec un risque d’entrée non autorisée sur un billet déjà en train d’être cédé.
Correctif
Les billets conservent un état de transfert jusqu’à l’acceptation du destinataire ou l’annulation par l’expéditeur. Dans cet état, le billet ne peut pas être scanné et compte comme invalide à l’entrée.
04
Déployer trois pièces mobiles
Problème
Déployer ensemble le backend Django, le frontend React et la base a demandé des jours de documentation et une longue série de tentatives ratées sur le serveur backend.
Correctif
Railway, qui s’intègre au dépôt et récupère la dernière version d’une branche fixe, a mis le backend et la base en ligne dans une instance partagée ; le frontend est parti sur AWS Amplify.
Technologies
Construit avec
Frontend
- React
- React Router
- JavaScript
- Axios
Backend
- Django
- Python
- Sérialiseurs
- JWT
- Gunicorn
Données
- PostgreSQL
- Docker
- AWS S3
Plateforme
- Railway
- AWS Amplify
- Stripe Connect
- Pexels API
- GitLab CI
Crédits
Équipe et documentation
Réalisé à deux comme projet de troisième année pour la licence en informatique de Dublin City University — le même binôme que le pare-feu SMS 5G un an plus tard.
- Avec
- Jack Keenan
- Établissement
- Dublin City University, 2023—2024
- Paiements
- Stripe Connect
- Documentation
- Manuel technique et manuel utilisateur, publiés avec le code source.