
CRM Manara
Academic team project · Delivered
What it was
CRM Manara is a registration system for a Montréal community centre that runs children's activities, from Qur'anic awakening to hip hop. Parents enrol their children in sessions, monitors record attendance, and administrators manage the catalogue, the staff and the waiting lists.
A team of three built it over a winter semester: a Spring Boot REST API on MySQL behind an Angular front end.
What I owned
The backend domain: activities, sessions and registrations, plus the users, roles and profiles underneath them. That meant entities, repositories, services, REST controllers and DTOs, with unit tests for the three core services. On the Angular side, I built the administrator session screens, the monitor screens and the services they call. I also worked on authentication and on the messaging between parents and the centre, and built the chatbot on Ollama and Llama 3.2.
The hard part
The hard part was the registration lifecycle, because the last place in a session has several claimants. Registering runs its checks in one transaction: the session must be open, the child must not already be registered, and non-cancelled registrations must be under capacity. Under capacity, the registration is confirmed; at capacity, it joins a waiting list ordered by arrival and the session flips to FULL. Cancelling reverses it: the first child waiting is promoted and notified, or the session reopens.
Confirmation emails depend on price: a free session sends one immediately, a paid one waits for the Stripe callback. Getting that wrong tells a parent their child has a place before it is paid for.
What I would do differently
The capacity check counts and then writes, with no lock or version column, so two parents claiming the last place at once can both pass and overfill the session; the condition belongs in the write itself, or the session needs optimistic locking. Every refusal also returns the same 400 with a French sentence, so callers match on wording instead of a code. And the credentials were committed in application.properties instead of read from the environment, which is the mistake I would most want back.

