Skip to content

sanjith0x/Thallu-Vandi

Repository files navigation

Thalluvandi — Full-Stack App

A working full-stack build of Thalluvandi: an Express + SQLite backend (auth, product/vendor browsing, a persistent cart, order checkout with atomic inventory deduction, the Khata community-credit system, and Sandhai flash-sale reservations, plus a Socket.IO layer for live stock/order/countdown updates) serving a real, wired-up frontend (public/index.html + public/app.js) — the original visual prototype, now talking to the live API instead of mock data. One process, one port, npm start and you have the whole app.

This is a deliberately scoped-down, runnable implementation of the much larger spec discussed earlier (which called for Kafka, Debezium CDC, PostGIS, a dozen microservices, etc.). That stack needs real infrastructure to stand up — it isn't something that can be "finished" as a folder of files. This app implements the same business rules (unified inventory, per-shop Khata ledgers, atomic Sandhai reservations, real-time sync) on a single process with an embedded database, so you can actually run it today and grow it later. See "Scaling this up" at the bottom.

What's wired up in the frontend

  • Products & searchpublic/app.js fetches /api/products (with live category/search filters) instead of reading a hardcoded array.
  • Auth — real login and registration against /api/auth/*, JWT stored in localStorage, session restored on reload via /api/auth/me. The "Continue with Passkey" button still simulates WebAuthn visually but signs in with a real API call.
  • Cart — every add/remove hits /api/cart; the drawer renders whatever the server returns, so it's consistent across tabs/devices for the same account.
  • Khata — vendor tags and the cart's Khata box read real account/ application status from /api/khata. Requesting Khata creates a genuine PENDING application — nothing auto-approves itself anymore. The seeded demo customer already has an active Khata account with Muthu Vegetables & Fruits so you can see the "approved" state immediately; try requesting Khata at any other vendor to see the pending state.
  • Sandhai — the fire-badge products, ticker, and countdown are driven by /api/sandhai/live and the server's real event end-time. At checkout, the frontend best-effort converts Sandhai-flagged cart lines into confirmed reservations via /api/sandhai/:allocationId/hold before placing the order.
  • Orders — "My Orders" now fetches real order history from /api/orders.
  • Realtime — a Socket.IO client connects on load and live-updates stock counts (inventory:update), Sandhai claim percentages (sandhai:update), and the countdown clock (clock:tick).

Stack

  • Node.js + Express — REST API
  • SQLite (better-sqlite3) — embedded, file-based, zero setup, transactional
  • Socket.IO — live inventory/order/Sandhai/clock broadcasts
  • JWT + bcrypt — auth

Setup

npm install
cp .env.example .env        # edit JWT_SECRET before deploying anywhere real
npm run seed                 # creates thalluvandi.db and loads demo data
npm start                    # http://localhost:4000

Open http://localhost:4000 in a browser — that's the whole app (frontend

  • API + WebSocket), served from the one Express process.

Demo login after seeding: phone +919876543210, password password123 (pre-filled in the login modal — just tap Continue). You can also tap "Create an account" to register a brand-new customer for real.

Reseed from scratch any time with npm run seed:reset.

Project layout

src/
  db/
    schema.sql        -- full SQLite schema (users, vendors, products,
                          inventory, cart, orders, khata, sandhai)
    connection.js      -- opens the DB, applies schema on boot
    seed.js             -- demo data matching the original frontend mock data
  middleware/
    auth.js             -- JWT auth guard, role guard, vendor-ownership guard
    errorHandler.js
  routes/
    auth.routes.js
    products.routes.js
    vendors.routes.js
    cart.routes.js
    orders.routes.js     -- checkout transaction lives here
    khata.routes.js
    sandhai.routes.js
  services/
    sandhaiReservations.js -- atomic 5-minute hold/release/confirm logic
  sockets/
    index.js
  server.js

API Reference

All responses are JSON: { status: "success" | "error", data?, message? }. Authenticated routes expect Authorization: Bearer <token>.

Auth

Method Route Body Notes
POST /api/auth/register { name, phone, password, email?, role? } role defaults to customer
POST /api/auth/login { phone, password } returns accessToken
GET /api/auth/me requires auth

Products & Vendors (public)

Method Route Query/Body
GET /api/products ?category=&q=&vendorId=
GET /api/products/:id
GET /api/vendors
GET /api/vendors/:id

Cart (auth required)

Method Route Body
GET /api/cart
POST /api/cart { productId, delta } — delta can be negative
DELETE /api/cart/:productId
DELETE /api/cart clears the whole cart

Orders (auth required)

Method Route Body
POST /api/orders `{ paymentMethod: "COD"
GET /api/orders caller's order history
GET /api/orders/:id
GET /api/orders/vendor/incoming vendor role only
PATCH /api/orders/:id/status vendor role only, { status }

Checkout is a single DB transaction: validates stock for every line, deducts inventory, debits the vendor-specific Khata ledger if paymentMethod is KHATA (rejecting if it would exceed the remaining limit), confirms any Sandhai reservations passed in, writes the order, and clears the cart. If any step fails, nothing is applied.

Khata credit (auth required)

Method Route Body
POST /api/khata/apply { vendorId, requestedLimitCents }
GET /api/khata caller's accounts + pending applications
GET /api/khata/vendor/applications vendor role only
POST /api/khata/vendor/applications/:id/decision vendor role only, `{ action: "APPROVE"

Sandhai flash sales

Method Route Body
GET /api/sandhai/live public — active events with live claimed %
POST /api/sandhai/:allocationId/hold auth, { quantity } — atomic 5-minute reservation
POST /api/sandhai/reservations/:reservationId/release auth — manual release

Pass the returned reservationId in sandhaiReservationIds on POST /api/orders to convert a hold into a sale; unconfirmed holds auto-release after 5 minutes.

WebSocket events (Socket.IO, same origin/port)

  • join:vendor (emit, client→server) — vendor dashboard joins its room
  • inventory:update{ productId, stockQty }
  • order:created — pushed to vendor:<id> room
  • order:status{ orderId, status }
  • sandhai:update{ allocationId, soldQty, reservedQty, pctClaimed }
  • clock:tick — server time every 5s, for synced countdowns

Frontend source

  • public/index.html — the original visual prototype's markup and styles, unchanged, plus a couple of small additions to the login modal (a registration toggle, an inline error slot).
  • public/app.js — a full rewrite of the prototype's inline <script>: every function that used to mutate an in-memory mock array now calls the API above and re-renders from the response. Express serves this folder as static files and falls back to index.html for any non-/api route.

If you want to split the frontend into its own separately-deployed static site later, just point API_BASE in app.js at the deployed backend URL and set CORS_ORIGIN in .env to that site's origin.

Scaling this up

If you outgrow a single SQLite file:

  • Swap better-sqlite3 for pg and point schema.sql at Postgres (the schema here was written in plain, portable SQL specifically so that migration is mostly mechanical — add PostGIS only when you actually need geospatial queries).
  • Move sandhaiReservations.js's in-memory Map to Redis with SET ... EX so reservations survive process restarts and work across multiple instances.
  • Only reach for Kafka/Debezium once you have more than one service that needs to react to the same event — a single Express process doesn't need a message bus to talk to itself.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors