Aller au contenu
Retour aux projets

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

Sur les modèles et les vues du backend, sans qu’aucune modification ne soit fusionnée sans relecture.

10
Entités du domaine

Utilisateurs, profils, clubs, événements, billets, comptes Stripe, abonnements, amitiés et demandes de transfert.

3
Cibles de déploiement

Django et PostgreSQL sur Railway, React sur AWS Amplify, fichiers téléversés dans un bucket S3.

2
Types de compte

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.
Parcours des événements. La barre latérale s’adapte au compte : un responsable de club voit aussi, sous sa propre navigation, les clubs qu’il administre.
Parcours des événements. La barre latérale s’adapte au compte : un responsable de club voit aussi, sous sa propre navigation, les clubs qu’il administre.
Cession d’un billet à un ami, d’après le manuel utilisateur. Un billet payant ne peut être envoyé tant que l’expéditeur n’a pas un compte Stripe capable de recevoir le paiement.
Cession d’un billet à un ami, d’après le manuel utilisateur. Un billet payant ne peut être envoyé tant que l’expéditeur n’a pas un compte Stripe capable de recevoir le paiement.

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.
Le modèle opérationnel du manuel technique : où s’exécute chaque composant, et qui parle à qui.
Le modèle opérationnel du manuel technique : où s’exécute chaque composant, et qui parle à qui.
Le modèle de données. Dix entités, les tables de jointure — Follow, Friend et TransferRequest — portant leur propre état plutôt que d’être de simples liens.
Le modèle de données. Dix entités, les tables de jointure — Follow, Friend et TransferRequest — portant leur propre état plutôt que d’être de simples liens.

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.
Cession d’un billet payant, d’après le manuel technique. La branche paiement ne s’exécute que si le prix est supérieur à zéro, et la propriété change après confirmation du paiement.
Cession d’un billet payant, d’après le manuel technique. La branche paiement ne s’exécute que si le prix est supérieur à zéro, et la propriété change après confirmation du paiement.
Un billet scanné à l’entrée. Le point d’accès renvoie le nom et le numéro d’étudiant du participant, et enregistre le moment du scan.
Un billet scanné à l’entrée. Le point d’accès renvoie le nom et le numéro d’étudiant du participant, et enregistre le moment du scan.

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.
La suite backend : 134 tests, tous au vert.
La suite backend : 134 tests, tous au vert.

Ce qui a cassé

  1. 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.

  2. 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.

  3. 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.

  4. 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.
Retour aux projets