Own product
Peony
Supabase Postgres with Row Level Security · Stripe · pg_cron · Static front end, no build step · Eight tracked migrations
Problem
A solo lash, brow or nail technician has two bad options. A marketplace takes a slice of every booking and keeps the client relationship, or a salon system charges monthly for a diary built for a shop with a front door and staff rotas. Neither fits someone working out of other people’s front rooms.
This started as one page for èllash, a mobile technician covering three towns, where the awkward part was not the booking form but the travel: no salon diary understands that Tuesday is a Coventry day and Friday is Birmingham. Having built it once, the interesting question was whether the same thing could be handed to anyone else — which turns a page into a platform, and a platform has a problem a single page never had.
This started as one page for èllash, a mobile technician covering three towns, where the awkward part was not the booking form but the travel: no salon diary understands that Tuesday is a Coventry day and Friday is Birmingham. Having built it once, the interesting question was whether the same thing could be handed to anyone else — which turns a page into a platform, and a platform has a problem a single page never had.
Approach
Two people on two phones can take the same 09:00. Checking “is that slot free?” in application code does not fix it; it only narrows the window between the check and the insert. So the database refuses the second write itself — an
That is the whole design in miniature: put the rule where it cannot be talked round. A studio’s plan is written only by the Stripe webhook, through a
Each studio gets its own address and its own page, and the free tier carries a small line in the footer that Pro removes. The commercial promise is the one thing the architecture has to keep true: no commission, ever, however many clients she takes. There is no per-booking figure anywhere in the schema to take a percentage of.
EXCLUDE USING gist constraint over the studio and the appointment’s time range, so two overlapping confirmed bookings cannot both exist. The second transaction to commit raises 23P01 and rolls back. Cancelled rows sit outside the constraint, so cancelling frees the slot without any code deciding that it should.That is the whole design in miniature: put the rule where it cannot be talked round. A studio’s plan is written only by the Stripe webhook, through a
SECURITY DEFINER function with no grant to the anonymous or authenticated roles — so nothing running in a browser can promote itself to Pro. Cancelling at period end leaves her on Pro until the paid time actually runs out rather than cutting her off mid-month. And the view the booking page reads tells the client which times are gone without telling them who took them.Each studio gets its own address and its own page, and the free tier carries a small line in the footer that Pro removes. The commercial promise is the one thing the architecture has to keep true: no commission, ever, however many clients she takes. There is no per-booking figure anywhere in the schema to take a percentage of.
Outcome
It is live. The schema is eight tracked migrations deep and the app runs against a real database rather than the sample data this case study is illustrated with — those captures are the demonstration studio, which labels itself a sample on the page.
What is written: the schema and its exclusion constraint, sign-up, cancellation, photo handling, transactional email, a sender that
Pricing is published and deliberately simple — free to start, £10 a month for Pro, or £8 a month paid yearly. The reminder has to exist precisely because it is on the pricing card, which is the sort of thing worth writing down next to the code rather than trusting to memory.
Nothing here claims a customer count, because a studio signing up is their business and not a number this site gets to publish.
What is written: the schema and its exclusion constraint, sign-up, cancellation, photo handling, transactional email, a sender that
pg_cron runs every minute, the day-before reminder that is the first thing Pro actually buys, and Stripe billing. The cron file carries a warning in capitals that the repository is public and no key goes in it.Pricing is published and deliberately simple — free to start, £10 a month for Pro, or £8 a month paid yearly. The reminder has to exist precisely because it is on the pricing card, which is the sort of thing worth writing down next to the code rather than trusting to memory.
Nothing here claims a customer count, because a studio signing up is their business and not a number this site gets to publish.
The screens



If this looks like your problem
Tell me about it and I'll tell you what it takes.