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

434 lines
13 KiB
Markdown

# 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):**
```json
{
"data": { ... },
"meta": null
}
```
**Success (paginated list):**
```json
{
"data": [ ... ],
"meta": {
"total": 142,
"page": 1,
"per_page": 25,
"pages": 6
}
}
```
**Error:**
```json
{
"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.
```python
# 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:
```json
"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:
```python
# 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:
```python
@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
```python
# 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`):
```dotenv
# 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`:
```python
# 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
```bash
# 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
```bash
# 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" }
```
---
## 10. Module Build Order (Recommended)
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.*