- 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
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 |
| 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:
httpOnlycookie (preferred for XSS safety) withSameSite=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
AuthorizationandContent-Typeheaders
# 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" }
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.