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
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
# 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.*
|
||||
Reference in New Issue
Block a user