Skip to main content

Platform Demo


Overview

SPN uses a PostgreSQL-first hybrid approach. PostgreSQL is the single source of truth for all structured data. Sanity CMS is used exclusively for rich media content (logos, hero images, galleries).
┌──────────────────────────────────────────────────────────┐
│                      SPN Platform                         │
│                                                           │
│   Browser / Mobile App                                    │
│         │                                                 │
│         ▼                                                 │
│   ┌─────────────┐        ┌─────────────┐                 │
│   │  React 19   │◄──────►│  Sanity CMS │                 │
│   │  Frontend   │        │  (media)    │                 │
│   └──────┬──────┘        └─────────────┘                 │
│          │                                                │
│          ▼                                                │
│   ┌─────────────┐                                        │
│   │   FastAPI   │◄──── PostgreSQL (Source of Truth)      │
│   │   Backend   │                                        │
│   └──────┬──────┘                                        │
│          │                                                │
│    ┌─────┼──────────┬──────────┐                        │
│    ▼     ▼          ▼          ▼                         │
│  AWS S3  Gemini  WhatsApp   Zoho CRM                     │
│ (Files)  (AI)   (Catalog)   (Leads)                      │
└──────────────────────────────────────────────────────────┘

Why PostgreSQL-First?

No Document Limits

Sanity free tier caps at 10K documents. PostgreSQL handles millions of records with no limits.

Full-Text Search

PostgreSQL tsvector delivers sub-50ms search across 100K+ records — no external search service needed.

Relational Integrity

UUID foreign keys with CASCADE ensure referential integrity across companies, users, products, and contacts.

Simpler Integrations

One-way pushes to Zoho and WhatsApp — no complex bi-directional syncs to maintain.
AspectOld ApproachNew Approach
Company dataSanity CMS (10K limit)PostgreSQL (unlimited)
CatalogSanity + DB splitPostgreSQL only
SearchSanity APIPostgreSQL tsvector (<50ms)
WhatsAppDirect APIXML bulk feed
CRM syncBi-directionalOne-way push

Tech Stack

  • Python 3.11 / FastAPI 0.115
  • PostgreSQL 15 + SQLAlchemy 2.0 (async)
  • Alembic — database migrations
  • Pydantic 2.x — request/response validation
  • slowapi — rate limiting
  • Docker — containerised deployment
  • React 19 / TypeScript 5.9
  • Vite 7 — build tool
  • Tailwind CSS 4 — styling
  • React Router DOM 7 — routing
  • Axios — HTTP client with JWT interceptor
  • Sanity CMS v3 — rich media content only
  • AWS S3 — file storage (product images, profile pictures)
  • Google Gemini AI — virtual try-on image generation
  • TalkingShops — WhatsApp Business API
  • Zoho CRM — one-way lead sync
  • Caddy — reverse proxy + automatic HTTPS

Database Schema

Core Tables

TablePurpose
usersUser accounts (linked to a company)
companiesSupplier/buyer company profiles
catalog_productsProduct listings
contactsCompany contact persons
enquiriesBuyer-to-supplier leads
refresh_tokensJWT refresh token store
tryon_jobsAI try-on generation jobs
All tables use UUID primary keys and include created_at / updated_at timestamps.
-- Weighted search vector (updated on insert/update)
search_vector :=
  setweight(to_tsvector('english', name), 'A') ||
  setweight(to_tsvector('english', category), 'B') ||
  setweight(to_tsvector('english', address_city), 'C');

-- Query
SELECT * FROM companies
WHERE search_vector @@ plainto_tsquery('english', 'textile mumbai')
ORDER BY ts_rank(search_vector, plainto_tsquery('english', 'textile mumbai')) DESC;

Integration Patterns

WhatsApp Catalog

CatalogProduct updated
  → FastAPI generates XML feed
  → Meta Commerce Manager polls feed hourly
  → Products appear in WhatsApp Business catalog

Zoho CRM

Enquiry created
  → Background task fires
  → POST to Zoho Leads API
  → zoho_lead_id stored on enquiry record

Sanity CMS (Media Only)

Company logo/hero uploaded via frontend
  → Stored in AWS S3
  → S3 key pushed to Sanity
  → Frontend fetches rich content from Sanity
  → Structured data always from PostgreSQL

Rate Limiting

All endpoints use slowapi. Limits are configurable via environment variables.
TierDefaultApplied To
AUTH20/minLogin, register, password reset
GENERAL120/minAuthenticated CRUD endpoints
UPLOAD10/minFile uploads
PUBLIC60/minPublic unauthenticated endpoints
Set RATE_LIMIT_ENABLED=false to disable all limits (useful for testing).

Project Structure

spn/
├── backend/
│   ├── app/
│   │   ├── api/v1/endpoints/   # Route handlers (auth, users, companies…)
│   │   ├── models/domain.py    # All SQLAlchemy models
│   │   ├── schemas/            # Pydantic request/response schemas
│   │   ├── services/           # Business logic
│   │   └── core/               # Config, security, rate limiting
│   ├── alembic/                # Database migrations
│   └── tests/                  # pytest test suite
├── frontend/
│   └── src/
│       ├── pages/              # Page components
│       ├── components/         # Reusable UI components
│       ├── contexts/           # AuthContext, EnquiryContext
│       └── utils/              # Axios instance, Sanity client
├── sanity-studio/              # Sanity CMS Studio
├── docs/                       # This documentation
└── docker-compose.yml

Next Steps

Quickstart

Run SPN locally in under 5 minutes

API Reference

Explore all REST endpoints