Files
LynkedUpPro_CRM/docs/backend/00_project_overview.md
T
Satyam 7694788387 feat(ProCanvas): retro game overhaul with log actions, challenges & achievements modals
- Complete ProCanvas redesign with retro sports game aesthetic
- Card-game style photo frame with tilt, shimmer, corner star accents
- Daily Missions card with weekly challenge + individual quest progress bars
- Log Action card (Door Knocked, Lead Gained, Appointment Set, Client Meeting)
- Each log action opens themed modal with relevant input fields
- Challenges modal with 6 active challenges and progress tracking
- Achievements modal with all badges, unlock status, descriptions
- Nav bar tabs (Leaderboard, Challenges, Achievements) wired to modals
- Rewards & Checkpoints with named stages (Daily Grind to Legend Run)
- Smoother Hot Streak pulse animation (2.5s float + 3s pulse rings)
- Hit The Map button with smooth pulsing glow animation
- Grid pattern overlay in Leaderboard card
- Light mode support via dark: Tailwind variants throughout
- Top 3 badges displayed on profile card
- Fixed dropdown option visibility in dark mode
2026-02-26 01:36:36 +05:30

13 KiB

B0 — Backend Project Overview & API Conventions

Module: B0 Depends on: Nothing (foundation document) Read before: All other backend modules


1. What We're Building

The LynkedUpPro frontend is a fully-functional React 19 SPA that currently runs entirely on a client-side mock data layer (src/data/mockStore.jsx). There is no real server, no database, and no persistent state.

This backend will replace that mock layer with a production-grade API server. The frontend does not need to be rewritten — the Integration Team will replace mock calls with real API calls, component by component.

Topology

┌─────────────────────────────┐        ┌────────────────────────────────┐
│     Vercel (Frontend)        │        │     Backend Host (Railway etc.) │
│                              │        │                                │
│  React 19 SPA                │ HTTPS  │  FastAPI (Python)              │
│  └─ AuthContext.jsx          │◄──────►│  └─ /api/v1/...               │
│  └─ mockStore.jsx  ──────────┼──────► │  └─ PostgreSQL (via SQLAlchemy)│
│     (will be replaced)       │        │  └─ Alembic (migrations)       │
└─────────────────────────────┘        └────────────────────────────────┘
                                                      │
                                          ┌───────────┴───────────┐
                                          │    External Services   │
                                          │  - Groq API (chatbot)  │
                                          │  - SMTP (email alerts) │
                                          │  - AWS S3 (file upload)│
                                          └───────────────────────┘

2. Technology Stack

Layer Technology Version Reason
Language Python 3.12+ Type hints, async support, ecosystem
Framework FastAPI 0.115+ Async, auto OpenAPI docs, Pydantic v2
ORM SQLAlchemy 2.0 (async) Native async, type-safe, PostgreSQL support
Migrations Alembic 1.13+ Paired with SQLAlchemy, revision history
Database PostgreSQL 16+ JSONB, full-text search, PostGIS optional
Auth python-jose + passlib latest JWT tokens + bcrypt hashing
Validation Pydantic v2 2.x Request/response schemas, coercion
HTTP Client httpx 0.27+ Async requests to Groq API
File Storage boto3 (AWS S3) latest COI/W9/document uploads
Email smtplib / sendgrid-python latest Compliance alerts, meeting reminders
Testing pytest + pytest-asyncio latest Async test support
Linting ruff + mypy latest Fast linting + static type checking

3. Backend Project Structure

lynkeduppro-api/
├── alembic/
│   ├── env.py
│   ├── script.py.mako
│   └── versions/              # One file per migration
├── app/
│   ├── __init__.py
│   ├── main.py                # FastAPI app instantiation, middleware, CORS
│   ├── config.py              # Settings via pydantic-settings (reads .env)
│   ├── database.py            # SQLAlchemy async engine + session factory
│   ├── dependencies.py        # Shared FastAPI Depends() (get_db, get_current_user, require_role)
│   │
│   ├── models/                # SQLAlchemy ORM models (one file per entity)
│   │   ├── __init__.py
│   │   ├── user.py
│   │   ├── property.py
│   │   ├── meeting.py
│   │   ├── vendor.py
│   │   ├── document.py
│   │   ├── invoice.py
│   │   ├── project.py
│   │   ├── compliance.py
│   │   └── audit_log.py
│   │
│   ├── schemas/               # Pydantic request/response schemas
│   │   ├── __init__.py
│   │   ├── user.py
│   │   ├── property.py
│   │   ├── meeting.py
│   │   ├── vendor.py
│   │   ├── document.py
│   │   ├── invoice.py
│   │   ├── project.py
│   │   └── auth.py
│   │
│   ├── routers/               # FastAPI routers (one file per module)
│   │   ├── __init__.py
│   │   ├── auth.py            # /api/v1/auth/*
│   │   ├── users.py           # /api/v1/users/*
│   │   ├── properties.py      # /api/v1/properties/*
│   │   ├── meetings.py        # /api/v1/meetings/*
│   │   ├── vendors.py         # /api/v1/vendors/*
│   │   ├── documents.py       # /api/v1/documents/*
│   │   ├── invoices.py        # /api/v1/invoices/*
│   │   ├── projects.py        # /api/v1/projects/*
│   │   └── chatbot.py         # /api/v1/chatbot/*
│   │
│   ├── services/              # Business logic (no direct DB calls, no HTTP)
│   │   ├── auth_service.py
│   │   ├── property_service.py
│   │   ├── meeting_service.py
│   │   ├── vendor_service.py
│   │   ├── notification_service.py
│   │   └── chatbot_service.py
│   │
│   └── utils/
│       ├── security.py        # JWT encode/decode, bcrypt helpers
│       ├── permissions.py     # Role-based permission checks
│       └── mask.py            # Sensitive data masking (SSN, bank, EIN)
│
├── tests/
│   ├── conftest.py            # Test DB setup, auth fixtures
│   ├── test_auth.py
│   ├── test_properties.py
│   ├── test_meetings.py
│   └── ...
│
├── .env                       # Local secrets (never committed)
├── .env.example               # Committed template (no real values)
├── alembic.ini
├── pyproject.toml             # Project metadata + dependencies (uv or poetry)
└── README.md

4. API Conventions

Base URL

Environment Base URL
Development http://localhost:8000/api/v1
Production https://api.lynkeduppro.com/api/v1

URL Pattern

/api/v1/{resource}/{id?}/{sub-resource?}

Examples:
GET    /api/v1/properties              ← list
GET    /api/v1/properties/42           ← single item
POST   /api/v1/properties              ← create
PATCH  /api/v1/properties/42          ← partial update
DELETE /api/v1/properties/42          ← delete
GET    /api/v1/properties/42/meetings ← nested resource

HTTP Methods

Method Use Case
GET Read (no side effects)
POST Create new resource
PATCH Partial update (preferred over PUT)
DELETE Soft delete (sets deleted_at, never hard deletes)

Response Shape

Success (single object):

{
  "data": { ... },
  "meta": null
}

Success (paginated list):

{
  "data": [ ... ],
  "meta": {
    "total": 142,
    "page": 1,
    "per_page": 25,
    "pages": 6
  }
}

Error:

{
  "detail": "You do not have permission to view this resource.",
  "code": "FORBIDDEN"
}

Error codes used across the API:

Code HTTP Status Meaning
UNAUTHORIZED 401 No valid token
FORBIDDEN 403 Valid token, wrong role
NOT_FOUND 404 Resource doesn't exist
VALIDATION_ERROR 422 Request body fails Pydantic validation
CONFLICT 409 Duplicate (e.g., email already registered)
RATE_LIMITED 429 Too many requests
INTERNAL_ERROR 500 Unexpected server error

Pagination

All list endpoints support:

GET /api/v1/properties?page=1&per_page=25&sort=created_at&order=desc
Param Default Max Description
page 1 Page number (1-indexed)
per_page 25 100 Results per page
sort created_at Field to sort by
order desc asc or desc

Filtering

Filters are passed as query params with double-underscore notation:

GET /api/v1/properties?status__eq=Hot+Lead&year_built__lt=2000
GET /api/v1/meetings?agent_id__eq=e1&date__gte=2026-02-01

Money Convention

All monetary values travel as integers in cents between backend and frontend.

# Backend stores and returns: 450000  (cents)
# Frontend displays:          $4,500.00

The frontend divides by 100 for display. This avoids all floating-point precision issues.

Date/Time Convention

All timestamps are ISO 8601 UTC strings:

"created_at": "2026-02-24T14:30:00Z"
"date_of_loss": "2025-11-15"

Date-only fields (no time component) use YYYY-MM-DD.


5. Authentication Overview

(Full detail in 01_authentication_module.md)

  • Mechanism: JWT Bearer tokens
  • Access token lifetime: 15 minutes
  • Refresh token lifetime: 7 days
  • Storage: httpOnly cookie (preferred for XSS safety) with SameSite=Strict
  • Header (alternative): Authorization: Bearer <token>

Every protected endpoint uses the get_current_user dependency:

# app/dependencies.py
async def get_current_user(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)) -> User:
    ...

async def require_role(*roles: str):
    async def checker(current_user: User = Depends(get_current_user)):
        if current_user.role not in roles:
            raise HTTPException(status_code=403, detail="Forbidden", headers={"WWW-Authenticate": "Bearer"})
        return current_user
    return checker

Usage in a router:

@router.get("/admin/stats")
async def get_admin_stats(
    current_user: User = Depends(require_role("ADMIN", "OWNER"))
):
    ...

6. CORS Configuration

The frontend is hosted on https://lynkeduppro-crm.vercel.app. CORS must allow:

  • Credentials (for cookie-based auth)
  • All standard methods
  • Authorization and Content-Type headers
# app/main.py
from fastapi.middleware.cors import CORSMiddleware

ALLOWED_ORIGINS = [
    "http://localhost:5173",          # Vite dev server
    "https://lynkeduppro-crm.vercel.app",  # Production frontend
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=ALLOWED_ORIGINS,
    allow_credentials=True,           # Required for httpOnly cookies
    allow_methods=["*"],
    allow_headers=["Authorization", "Content-Type", "X-Request-ID"],
    expose_headers=["X-Total-Count"],
)

Important: allow_credentials=True cannot be combined with allow_origins=["*"]. The origins list must be explicit.


7. Environment Variables

.env.example (commit this; never commit .env):

# Database
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/lynkeduppro

# JWT
SECRET_KEY=CHANGE_ME_generate_with_openssl_rand_hex_32
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=15
REFRESH_TOKEN_EXPIRE_DAYS=7

# Groq AI
GROQ_API_KEY=gsk_...

# AWS S3 (document storage)
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_S3_BUCKET=lynkeduppro-docs
AWS_REGION=us-east-1

# Email (SendGrid)
SENDGRID_API_KEY=SG...
EMAIL_FROM=noreply@lynkeduppro.com

# App
ENVIRONMENT=development    # development | staging | production
DEBUG=true
BACKEND_CORS_ORIGINS=http://localhost:5173,https://lynkeduppro-crm.vercel.app

Loaded in FastAPI via pydantic-settings:

# app/config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    secret_key: str
    algorithm: str = "HS256"
    access_token_expire_minutes: int = 15
    refresh_token_expire_days: int = 7
    groq_api_key: str
    environment: str = "development"
    debug: bool = False
    backend_cors_origins: list[str] = []

    class Config:
        env_file = ".env"

settings = Settings()

8. Database Setup

# Install dependencies
pip install -r requirements.txt   # or: uv sync

# Create database
createdb lynkeduppro

# Run migrations
alembic upgrade head

# Seed development data
python -m app.scripts.seed_dev

The seed_dev script creates:

  • All 17 mock users from the frontend with real bcrypt-hashed passwords (password: password)
  • A representative set of properties, meetings, vendors, and projects
  • Matches the IDs used in the frontend (c1, e1, own_001, etc.) for a smooth integration transition

9. Running Locally

# Start the API server
uvicorn app.main:app --reload --port 8000

# OpenAPI docs (auto-generated)
http://localhost:8000/docs          # Swagger UI
http://localhost:8000/redoc         # ReDoc

# Health check
GET http://localhost:8000/health
→ { "status": "ok", "version": "1.0.0", "environment": "development" }

Build modules in this sequence to avoid dependency blockers:

B2 (Schema) → B1 (Auth) → B3 (Properties) → B4 (Users)
    → B5 (Meetings) → B6 (Vendors/Compliance)
    → B7 (Financials) → B8 (Chatbot) → B9 (Notifications)

Why schema first: Every other module's router depends on the ORM models being defined. Define models and run alembic revision --autogenerate before writing a single router.


Next: Read 02_database_schema.md before writing any ORM models.