Skip to content

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

189 Commits

Folders and files

Repository files navigation

LocalForce

A full-stack apartment grocery platform built with Django and Next.js. A single vendor serves residents of an apartment complex — customers browse products, place orders, and pay via UPI. The vendor manages inventory, finalizes bills, and coordinates delivery. A delivery user handles fulfillment. An admin dashboard provides analytics and oversight.


Tech Stack

Layer Technology
Backend Django 5.1, Django REST Framework, PostgreSQL 15
Frontend Next.js 14 (App Router), React 18, TypeScript, Tailwind CSS
Auth JWT via djangorestframework-simplejwt (access: 30 min, refresh: 7 days)
Data Fetching SWR (stale-while-revalidate) on the frontend
Containerization Docker Compose (dev), Docker + Nginx + Gunicorn (prod)
Deployment Frontend on Vercel, Backend on DigitalOcean, DNS/SSL via Cloudflare

Architecture

Browser (port 3000)
   |
   |  /api/* and /media/* requests
   v
Next.js  ──rewrites──>  Django (port 8000)  ──>  PostgreSQL
  • Next.js acts as a proxy. The next.config.js rewrites /api/:path* to http://backend:8000/api/:path/ and /media/:path* to http://backend:8000/media/:path*. The browser never talks to Django directly.
  • Why this matters: Django's build_absolute_uri() returns http://backend:8000/... (the Docker internal hostname), which browsers can't reach. So serializers always return relative URLs like /media/products/image.jpg.
  • JWT tokens are stored in localStorage (lf_access, lf_refresh). The frontend auto-refreshes on 401 and redirects to /login if the refresh fails.

Data Models

User (extends AbstractUser)

Custom user model with four roles. Extra fields: user_type, phone, flat_number.

user_type: customer | vendor | admin | delivery

Product

Belongs to a vendor (FK to User). Fields: name, category (vegetables/fruits/dairy/staples/other), price (nullable — vendor can list without price), unit (kg/gm/piece/pack/etc.), is_available, image, description.

No stock tracking — availability is a simple boolean toggle.

Vendor

OneToOne with User. Fields: store_name, category, apartment_code, is_active.

Cart / CartItem

One cart per customer (OneToOne). CartItems link to products with a quantity. unique_together = ['cart', 'product'] prevents duplicates.

Order / OrderItem

Orders have a human-readable order_number (format: ORD-YYYYMMDD-NNN). Each order belongs to a customer and a vendor. OrderItems snapshot the product, quantity, and price at the time of ordering.

FinalBillItem

After the customer places an order, the vendor can edit the bill — adjusting quantities, prices, or adding custom items not in the original order. These are stored as FinalBillItems. The order's total is recalculated from these when the bill is sent.

VendorUpdate (Stories)

Image posts from the vendor. Auto-expire after 24 hours. Max 3 active at a time.

Relationships

User ──1:1──> Vendor
User ──1:1──> Cart ──1:N──> CartItem ──N:1──> Product
User ──1:N──> Order ──1:N──> OrderItem ──N:1──> Product
                    ──1:N──> FinalBillItem
Vendor ──1:N──> VendorUpdate
Product.vendor ──FK──> User (not Vendor)

Note: Product.vendor is a FK to User, not to Vendor. To get the store name: product.vendor.vendor.store_name (User -> Vendor -> store_name).


User Roles & Permissions

Four custom permission classes in accounts/permissions.py: IsVendor, IsAdmin, IsCustomer, IsDelivery.

Role Can Do
Customer Browse products, manage cart, place/cancel/reorder orders, view order status, pay via UPI
Vendor Manage inventory (CRUD products, toggle availability), view incoming orders, edit & send bills, update order status, post stories
Delivery View orders ready for pickup, mark orders as picked up and delivered
Admin View analytics (revenue, order counts, top products), manage users, view all orders

Order Lifecycle

placed ──> bill_sent ──> out_for_delivery ──> delivered ──> paid
  |
  └──> cancelled
Transition Who What Happens
placed -> bill_sent Vendor Vendor finalizes the bill (FinalBillItems created, total recalculated)
bill_sent -> out_for_delivery Delivery Delivery user picks up the order
out_for_delivery -> delivered Delivery Delivery user confirms delivery, delivered_at timestamp set
delivered -> paid Vendor Vendor marks payment as received
placed -> cancelled Customer Only possible while still in placed status

Constraints:

  • Max 2 active orders per customer (enforced on creation)
  • UPI payment link is generated for statuses bill_sent, out_for_delivery, and delivered
  • Order number generation retries up to 5 times on collision (race condition safety)

API Overview

All endpoints are under /api/. Auth endpoints require JWT unless marked public.

Auth (/api/auth/)

Method Endpoint Description
POST register/ Create account (public)
POST login/ Get JWT tokens (public)
POST token/refresh/ Refresh access token (public)
GET me/ Current user info
POST change-password/ Change password

Products (/api/products/)

Method Endpoint Description
GET products/ List all products (public, filterable by category, searchable)
GET products/{id}/ Single product (public)
POST products/ Create product (vendor)
PATCH products/{id}/ Update product (vendor)
DELETE products/{id}/ Delete product (vendor)
GET products/categories/ List categories with counts (public)

Cart

Method Endpoint Description
GET cart/ Get cart with items and totals
POST cart/add/ Add item (upserts if exists)
PATCH cart/update/{item_id}/ Update quantity
DELETE cart/remove/{item_id}/ Remove item
DELETE cart/clear/ Empty cart
GET cart/validate/ Check item availability

All mutating cart endpoints return the full updated Cart object.

Customer Orders

Method Endpoint Description
POST orders/create/ Create order from cart
GET orders/ List orders (paginated)
GET orders/{order_number}/ Order detail
POST orders/{order_number}/cancel/ Cancel order
POST orders/{order_number}/reorder/ Re-add items to cart

Vendor

Method Endpoint Description
GET vendor/orders/ List vendor's orders (filterable by status, date, sortable by block)
GET vendor/orders/{order_number}/ Order detail for packing
PATCH vendor/orders/{order_number}/status/ Update order status
PATCH vendor/orders/{order_number}/send-bill/ Finalize and send bill
PATCH vendor/orders/{order_number}/mark-paid/ Mark as paid
GET vendor/orders/new-count/ Count of unseen orders
POST vendor/orders/mark-seen/ Mark all orders as seen
GET/POST vendor/updates/ List/create vendor stories
DELETE vendor/updates/{id}/ Delete a story

Delivery

Method Endpoint Description
GET delivery/orders/ Orders in bill_sent + out_for_delivery
PATCH delivery/orders/{order_number}/pickup/ Mark picked up
PATCH delivery/orders/{order_number}/deliver/ Mark delivered

Admin

Method Endpoint Description
GET admin/analytics/ Dashboard stats, daily breakdown, top products
GET admin/users/ All users (searchable)
GET admin/orders/ All orders (filterable by status)

Public

Method Endpoint Description
GET updates/ Active vendor stories (no auth required)

Frontend Structure

Route Groups

src/app/
├── (customer)/        # Customer pages — maps to /, /cart, /orders, /profile, /products/[id]
├── vendor/            # Vendor pages — /vendor/orders, /vendor/inventory
├── admin/             # Admin pages — /admin/dashboard, /admin/users, /admin/orders
├── delivery/          # Delivery page — /delivery
├── login/             # /login
└── register/          # /register

Each route group has its own layout.tsx with:

  • Auth guard — redirects to /login if unauthenticated, or to the correct dashboard if wrong role
  • Navigation — mobile bottom nav + desktop top/side nav

Three Global Contexts (mounted in root layout.tsx)

AuthContext — JWT user state. Provides login(), logout(), refreshUser(), user, isAuthenticated, isLoading.

CartContext — SWR-powered cart state. Only fetches for authenticated customers. Provides addItem(), updateItem() (optimistic), removeItem() (optimistic), clearCart(), mutateCart().

ToastContext — Notification system. toast(message, type) shows a 3.5-second auto-dismissing notification.

API Client (src/lib/api.ts)

Exports: get, post, patch, del, postForm, patchForm, fetcher.

  • All JSON methods go through a shared request() function that auto-attaches the JWT and handles 401 refresh.
  • postForm/patchForm are separate implementations for FormData (file uploads) — they don't set Content-Type so the browser can set the multipart boundary.
  • fetcher is a SWR-compatible wrapper around get.

Key Frontend Patterns

  • SWR everywhere — all data fetching uses useSWR with the fetcher from api.ts
  • Optimistic updates — cart updates and removals update local state before the API call, reverting on error
  • Mobile-first — single md breakpoint (768px), bottom nav on mobile, top/side nav on desktop
  • Touch-friendly — all interactive elements have min-h-[44px]
  • Design system — emerald as the primary color, reusable UI components (Button, Card, Modal, Input, Badge, Spinner)

How to Run

With Docker (recommended)

cd localforce-backend
docker compose up        # starts db, backend (port 8000), frontend (port 3000)

Required env vars in localforce-backend/.env:

LOCALFORCE_DB_PASSWORD=<your_password>
VENDOR_UPI_ID=<upi_id>
VENDOR_NAME=<vendor_display_name>

Django management commands

docker compose exec backend python manage.py migrate
docker compose exec backend python manage.py createsuperuser
docker compose exec backend python manage.py cleanup_expired_updates

Without Docker

# Backend
cd localforce-backend
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver    # port 8000

# Frontend (separate terminal)
cd frontend
npm install
npm run dev                   # port 3000

Key Design Decisions

  1. No stock tracking — Products have a simple is_available toggle. Stock quantity was removed to keep things simple for the vendor.

  2. Billing flow — The vendor doesn't just confirm orders. They can edit quantities, adjust prices, and add custom items before sending the bill. This is the FinalBillItem model. The original OrderItem records are preserved for reference.

  3. UPI payment — Payment happens outside the app. The backend generates a upi://pay?... intent URI that opens the customer's UPI app. No payment verification or webhook — the vendor manually marks orders as paid.

  4. Relative image URLs — Because Next.js proxies to Django, and Django runs behind Docker as backend:8000, serializers return /media/products/image.jpg instead of absolute URLs.

  5. Product.vendor FK points to User, not Vendor — This means getting the store name requires product.vendor.vendor.store_name. The Vendor model is accessed via the OneToOne reverse relation.

  6. Order numbers — Human-readable format ORD-YYYYMMDD-NNN with retry logic for race conditions instead of UUIDs.

  7. No tests — Changes are verified by running the app manually. The building_documents/ folder contains manual test plans.

  8. URL ordering matters — In store/urls.py, static vendor paths (/vendor/orders/new-count/) must appear before the parametric <order_number> path to avoid routing conflicts.


Docs

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages