FRQN®

Work / Wheelness Technologies LLP

Wheelness

Car washing by subscription for apartment communities in India. Four apps for four kinds of person, monthly billing, daily work in the field, and a referral scheme that people tried hard to cheat. One codebase, one engineer, real customers every morning.

Sole engineerSince December 2024Visit live site ↗
Active subscribers
200+
Cleans completed
5,000+
Apps in one codebase
4
Tables the database guards
30+
Wheelness dashboards for customers, cleaners, community managers and admin
Wheelness · as shipped

01 / The problem

Cars in Indian apartment complexes get washed by cleaners who work informally. Payment is cash, the schedule is spoken, and nothing proves a wash happened. Residents complain, cleaners get blamed, and the building manager is stuck in the middle with no record of anything.

Wheelness sells the wash as a monthly subscription and runs the cleaners for the building. One system has to serve four people who want completely different things: a resident paying every month, a cleaner working off a cheap Android in a basement, a manager checking staff are who they say they are, and an owner running the business.

02 / What I built

It is one Next.js codebase with four apps inside it, one per role. The data sits in Neon Postgres, and the rules about who may read which row are enforced by the database on the connection, not by the code above it. Supabase handles sign-in. Razorpay runs the monthly billing, including renewals, the grace period, and the messages the payment provider sends back. Photos and identity documents go to Cloudflare R2 and are only ever served through links that expire.

A job that runs every night writes out the next day's cleaning list, assigns it, carries forward whatever did not get done, and sends each cleaner their summary on WhatsApp. Nobody hands out work by hand. The three sections below are the parts worth reading.

03 / Deep dive

The task board works with no signal

Cleaners work in basements and stairwells where the network drops every few minutes. A board that fails when the connection does means lost proof and an argument with a customer, so the board treats the network as a bonus.

Every change of status goes into a queue on the phone first. Proof photos go into the browser's own storage. When the signal comes back a loop replays the queue in order and the server sorts out anything sent twice. The cleaner just works. The phone catches up on its own.

  • Status flow: pending, in progress, arrived, completed or skipped
  • Photos persist in IndexedDB until upload is confirmed
  • Unresolved tasks carry over for 7 days automatically
CLEANER PHONEpatchy 4GLOCAL QUEUEstatus updatesSYNC LOOPreplays in orderSERVERdeduplicatesINDEXEDDBproof photosON RECONNECTPHOTOSOFFLINE: WORK CONTINUES, THE QUEUE GROWSONLINE: QUEUE REPLAYS, PHOTOS UPLOAD, SERVER RECONCILES
Fig 01 · Offline queue and replay

04 / Deep dive

One database, four roles, no leaks

Four kinds of user share one set of tables, which is exactly the situation where one forgotten line of code shows somebody another person's data. So the filtering was moved out of the code and into Postgres.

Each request carries the signed-in person and their role. The database wrapper reads that and sets it on the connection before any query runs, and the rules on every table check it. A query that forgets to say whose data it wants still gets nothing back, because the database refuses rather than guesses. There is no ORM in the way, just plain SQL with types on it.

  • AsyncLocalStorage scopes identity per request
  • RLS policies on 30+ tables check session variables
  • Raw SQL with TypeScript types, no ORM overhead
REQUESTJWT, role, tenantALS CONTEXTper-request scopePG CLIENTsets session varsRAW SQLtyped, no ORMPOSTGRES ROW-LEVEL SECURITYpolicies on 30+ tables decide what every query may readEVERY QUERYOWN TENANT: ROWS RETURNEDOTHER TENANT: REFUSED BY THE DATABASEA QUERY THAT FORGETS ITS WHERE CLAUSE STILL CANNOT LEAK DATA
Fig 02 · Connection-level row security

05 / Deep dive

A referral scheme nobody farmed

Credits worth real rupees attract people who will spend a weekend gaming them. So every signup is treated as suspect until it is not.

New accounts go through a run of checks: you cannot refer yourself, the device is fingerprinted and hashed, the phone number is hashed and compared against accounts deleted in the last year, there is a cap per IP address per hour, and a cap on how many people one person can bring in per day. Rewards come out of a monthly budget in the order they were earned, and the credit either lands once or not at all.

  • Device fingerprint and phone hashes catch recycled identities
  • 10 signups per IP per hour, 15 referrals per referrer per day
  • Monthly budget cap with a first in, first out reward queue
SIGNUPreferral codeFINGERPRINThashed devicePHONE HASH12-month dedupIP + VELOCITY10/hr, 15/dayBUDGET CAPmonthly, FIFOWALLET CREDITatomic, oncePASSES EVERY CHECKANY CHECK FAILS: REWARD QUEUED OR REJECTED, NEVER AUTO-PAIDCREDITS COMMIT ATOMICALLY, A REWARD CANNOT BE ISSUED TWICE
Fig 03 · Layered referral checks

06 / Outcomes

  • 200 or more residents have their cars washed every day on a subscription
  • 5,000 cleans logged, with a photo attached to each one
  • Nobody assigns a job, chases a renewal or sends a reminder by hand
  • Identity documents have never been on a public link

Stack

Frontend
Next.js 16, React 19, TypeScript, Tailwind CSS, Framer Motion
Data
Neon Postgres, row-level security, raw SQL, Cloudflare R2
Services
Supabase Auth, Razorpay, MSG91, Resend, Gemini
Ops
Nightly schedulers, payment webhooks, Sharp image pipeline

Next case study

Veynity