~/workfitflixcase study 02 / 03

FitFlix

Fund factsheet · Hamdan Range · 02

Objective

Class-booking marketplace live in Vilnius. Built and operated solo: five apps behind one monorepo, cross-app single sign-on, Revolut payments, and studios onboarded by hand.

Key information

StatusLive, fitflix.lt
RoleFounder, sole engineer
Apps in production5
DB migrations58

Top holdings

Rough share of the work; read it as a sketch.

NestJS / Prisma40%
Next.js apps30%
Redis / BullMQ15%
Payments / email15%
5
apps
1,798
commits
154
spec files
1,471
line schema
EN/LT
i18n

Problem

Booking a class in Vilnius was five different apps

A gym-goer in Vilnius who wants variety needs an account with every studio they use. Studios on the other side of that want their timetable full, and a customer who only ever books one class is not worth building a booking system for.

FitFlix is the marketplace in between. One account, many studios, credits that work everywhere. I built it, I run it, and I onboarded the studios on it myself: Mindloom, Oro Yoga Loftas, Pilateson, Unity Fitness.

Constraints

One person, four audiences

  • Four kinds of user with genuinely different needs: people booking, studios managing their timetable, admins, and me.
  • Real money moves. A booking takes payment from a customer and owes a payout to a studio, and those two numbers are not the same.
  • Lithuania, so everything ships in English and Lithuanian from day one.
  • No team. Every hour spent on infrastructure is an hour not spent on the product, so the architecture had to be boring on purpose.

Decision 01

Five apps, one monorepo, one login

The product is five Next.js applications (marketing, booking, studio, admin, auth) sharing six packages, against a single NestJS API. Separate apps because a studio owner and a customer want different things on screen and I did not want one codebase full of role checks. Shared packages because I only want to write the design system, the API client and the types once.

Sign-in happens in one place. Moving between apps uses a single-use handoff token: the auth app mints it, the destination app redeems it exactly once and drops it. Short lived, not replayable, and it keeps session state in one service instead of four.

cross-app ssomoneycustomerauth.fitflix.ltone loginPOST /auth/handoffsingle-use code, 2 minstudio.fitflix.ltapp.fitflix.ltadmin.fitflix.ltcustomerbuys creditsRevolutamountCentswebhookorder completedledgerinteger credits, idempotency keybookingconsumes classType.creditCoststudio accruesat ratePerCreditCentspayouttotalAmountCents?handoff=<code>, exchanged once, then a session

cross-app ssoFive separately deployed Next.js apps, one login. The handoff token is single-use: replaying the URL gets you nothing.

moneyTwo prices on purpose: what a class costs a member in credits, and what a credit pays the studio in cents, are separate numbers.

Decision 02

Money is integers, and there are two of them

Every amount is stored in cents, as an integer. No floats anywhere near a price. That decision is standard for a reason: it stops you finding a rounding error in a payout six months later.

Each booking carries two prices: what the customer paid and what the studio is owed. They differ by the commission, and keeping them as separate stored values (rather than deriving the payout at reporting time) means a change to commission never rewrites history. Revolut handles the payment, Resend handles the receipt.

Decision 03

The ops team is a Telegram bot

Running this alone means the interesting events have to come to me. A grammY bot posts bookings, cancellations and payment failures into a channel, and takes a few commands back. It replaced the admin dashboard I would otherwise have had to build, and it means I find out about a failed payment on my phone rather than the next time I look at a database.

The schema is 1,471 lines across 58 migrations, all of them applied to a live database with real data in it. That makes you write the migration that can run while people are using the thing.

Results

What is live

  • Live at fitflix.lt with studios onboarded. Public booking has not opened yet.
  • Five applications in production plus the API, 797 backend and 1,001 web commits, 154 spec files.
  • Full English and Lithuanian translation, including transactional email.
  • A public demo repository at github.com/Rorrschach/fitflix-v1, with a SECURITY.md that explains what was removed from it and why.

Honestly

What I would do differently

Fifty-eight migrations against a live database is the honest record of a schema I did not think hard enough about before launch. Most are small. The ones that hurt are the ones that moved live data, and they clustered around money, which is exactly where you least want to be reshaping tables in production. The worst single one renamed the money columns to integer cents on a database that was already running.

If I started again I would spend a week on the payment and payout model before writing a single endpoint. Everything else in the product tolerated being wrong for a while and getting fixed. That part did not.

If this is the kind of work your team needs, the contact section is one click away. I answer every real email.