Work / Spotlight Furnishings, Srinagar
Spotlight Furnishings
A carpet and home furnishings shop in Srinagar, sold cash on delivery and run entirely from the owner's phone. Small enough that nobody would ever notice a bug, which is exactly why the money and the stock are the parts built hardest.
- Amounts stored as decimals
- 0
- Functions allowed to move stock
- 2
- How passwords are stored
- argon2id
- Engineer
- 1

01 / The problem
A family carpet business does not need a commerce platform. It needs a shop that loads on a slow connection, an admin panel that fits a phone, and numbers that are never wrong, because the stock on the shelf is the only thing anybody can actually check against.
Cash on delivery also takes away the safety net a card payment gives you. Nothing gets reconciled later. If the site sells the last carpet twice, somebody drives to a customer's house to apologise.
02 / What I built
Next.js serves the shop and the owner's panel from one codebase, over Supabase Postgres. Money is stored in whole paise and never as a decimal, so no total is ever a rounding artefact. Sessions live in the database instead of in a signed cookie, which is the difference between logging a device out and hoping it expires.
Every read on the shop side goes through a wrapper that returns something sensible when the query fails. If the database is down the shop still loads, with the phone number and the WhatsApp link on it. Somebody who can ring you is not a lost customer.
03 / Deep dive
Every number can be explained
Stock that can be changed from several places turns into a number nobody trusts. Here exactly two functions may move it, and both write a line in a ledger inside the same transaction as the change. The question is never whether the count is right, it is which movement made it that.
Selling the same item twice is closed off in the database rather than by a check beforehand. Checkout reduces the count only where there is enough left, and if that changes no rows, the whole order is abandoned. Two people buying the last carpet in the same second cannot both win. A constraint refuses a negative count as a second line of defence, coupons are claimed under the same kind of guard, and a status change takes a lock on the row and re-checks itself while holding it.
- Conditional decrement inside a transaction, zero rows aborts the order
- Every movement carries a reason, and nothing deletes the ledger
- Status transitions re-checked under SELECT ... FOR UPDATE
04 / Deep dive
The admin panel assumes somebody is trying
One account protects the whole business, on a public address, with nobody watching it. So every guard is written to survive being moved, forgotten, or called from somewhere nobody expected.
Passwords are hashed with argon2id at the settings OWASP recommends. A session is 32 random bytes and only its hash is kept, so a copy of the database hands over nothing usable. Every admin page checks for itself instead of trusting the layout above it, because pages and layouts can render at the same time and an inherited guard disappears the day somebody moves a file. Every server action checks on its first line too, since an action is a public address whether or not a button points at it. A wrong email and a wrong password give the same answer, and a fake check still runs for an account that does not exist, so the time it takes gives nothing away.
- Sessions in the database, so a device can actually be revoked
- Login limited per IP address and per email at the same time
- First-run owner setup serialised behind a Postgres advisory lock
05 / Outcomes
- The owner runs the catalogue, the stock, the orders and the coupons from a phone
- Selling the same last item twice is impossible, not just unlikely
- If the database goes down the shop still loads and can still be phoned
- Public database roles are revoked by a script that is re-run after every change
Stack
- Frontend
- Next.js 16, React 19, TypeScript, server components
- Data
- Supabase Postgres, Drizzle, whole paise, an inventory ledger
- Security
- argon2id, hashed sessions in the database, checked input, CSP and HSTS
- Ops
- Cloudinary, Sentry, a database hardening script, self-tests
Next case study
KOKOMATA