Files
LynkedUpPro_CRM/docs/diagrams/lld_architecture.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

389 lines
14 KiB
Markdown

# LLD — Architecture (B0)
**Scope:** FastAPI backend — internal structure, request lifecycle, auth flows, module dependencies
**Companion doc:** `docs/backend/00_project_overview.md`
---
## Diagram 1 — FastAPI Application Layer Structure
How the files inside `app/` relate to each other. Arrows show import/dependency direction.
```mermaid
graph TD
subgraph Entry["Entry Point"]
MAIN["main.py\nFastAPI() instance\nMiddleware registration\nRouter registration"]
end
subgraph Config["Configuration"]
CFG["config.py\nSettings (pydantic-settings)\nReads .env"]
DB_MOD["database.py\nasync engine\nAsyncSession factory\nget_db()"]
end
subgraph Deps["Shared Dependencies"]
DEPMOD["dependencies.py\nget_db()\nget_current_user()\nrequire_role(*roles)"]
end
subgraph Routers["Routers — /api/v1/"]
R_AUTH["auth.py\n/auth/login\n/auth/refresh\n/auth/logout"]
R_USERS["users.py\n/users/me\n/users/{id}"]
R_PROP["properties.py\n/properties\n/properties/{id}"]
R_MEET["meetings.py\n/meetings\n/meetings/{id}"]
R_VEND["vendors.py\n/vendors\n/vendors/{id}"]
R_PROJ["projects.py\n/projects\n/projects/{id}/tasks"]
R_INV["invoices.py\n/invoices\n/invoices/{id}/approve"]
R_DOC["documents.py\n/documents\n/documents/{id}"]
R_CHAT["chatbot.py\n/chatbot/message"]
end
subgraph Services["Service Layer (Business Logic)"]
S_AUTH["auth_service.py\nverify_password()\ncreate_tokens()\nrotate_refresh_token()"]
S_PROP["property_service.py\nget_properties()\nassign_agent()\nmark_contacted()"]
S_MEET["meeting_service.py\nschedule()\nchange_status()\nrequest_change()"]
S_VEND["vendor_service.py\ncheck_compliance()\nupdate_coi()"]
S_NOTIF["notification_service.py\nsend_email()\ncreate_notification()"]
S_CHAT["chatbot_service.py\nbuild_context(role)\nproxy_to_groq()"]
end
subgraph Models["ORM Models (SQLAlchemy)"]
M_ALL["user.py · property.py\nmeeting.py · vendor.py\nproject.py · invoice.py\ndocument.py · audit_log.py"]
end
subgraph Schemas["Pydantic Schemas (Request/Response)"]
SCH_ALL["user.py · property.py\nmeeting.py · vendor.py\nauth.py · invoice.py"]
end
subgraph Utils["Utilities"]
U_SEC["security.py\njwt_encode / jwt_decode\nbcrypt helpers"]
U_PERM["permissions.py\nrole_can_access()"]
U_MASK["mask.py\nmask_ssn() · mask_bank()"]
end
MAIN --> CFG
MAIN --> DB_MOD
MAIN --> DEPMOD
MAIN --> R_AUTH & R_USERS & R_PROP & R_MEET & R_VEND & R_PROJ & R_INV & R_DOC & R_CHAT
R_AUTH --> DEPMOD --> S_AUTH
R_PROP --> DEPMOD --> S_PROP
R_MEET --> DEPMOD --> S_MEET
R_VEND --> DEPMOD --> S_VEND
R_CHAT --> DEPMOD --> S_CHAT
S_NOTIF --> S_PROP & S_MEET & S_VEND
S_AUTH & S_PROP & S_MEET & S_VEND & S_PROJ --> M_ALL
R_AUTH & R_PROP & R_MEET --> SCH_ALL
S_AUTH --> U_SEC
DEPMOD --> U_PERM
R_USERS --> U_MASK
```
---
## Diagram 2 — HTTP Request Lifecycle
Every request follows this exact path through the backend. Each step is a named file/function.
```mermaid
sequenceDiagram
participant C as Client (React)
participant CORS as CORS Middleware
participant RL as Rate Limiter
participant LOG as Request Logger
participant RT as Router (e.g. properties.py)
participant DEP as Dependencies
participant SVC as Service Layer
participant ORM as SQLAlchemy ORM
participant DB as PostgreSQL
C->>CORS: HTTP Request
Note over CORS: Check Origin header<br/>against ALLOWED_ORIGINS
CORS-->>C: 403 if origin not allowed
CORS->>RL: Pass through
Note over RL: Check rate limit<br/>by IP / user ID
RL-->>C: 429 if exceeded
RL->>LOG: Pass through
Note over LOG: Log: method, path,<br/>request_id, timestamp
LOG->>RT: Route matched
Note over RT,DEP: FastAPI resolves Depends()
RT->>DEP: get_db() → yields AsyncSession
RT->>DEP: get_current_user(token)
Note over DEP: Decode JWT<br/>Validate expiry<br/>Load user from DB
DEP-->>RT: 401 if invalid token
RT->>DEP: require_role("ADMIN","OWNER")
DEP-->>RT: 403 if role mismatch
DEP-->>RT: current_user injected
RT->>SVC: call service function(db, current_user, params)
Note over SVC: Business logic<br/>Permission refinement<br/>Data transformation
SVC->>ORM: db.execute(select(Property)...)
ORM->>DB: SQL query
DB-->>ORM: Rows
ORM-->>SVC: Model instances
SVC-->>RT: Result dict / model
Note over RT: Pydantic response schema<br/>serializes result
RT-->>C: 200 { data: {...}, meta: {...} }
Note over LOG: Log: status_code,<br/>duration_ms, response_size
```
---
## Diagram 3 — Authentication Flow
### 3a — Login
```mermaid
sequenceDiagram
actor U as User
participant FE as React AuthContext
participant API as POST /auth/login
participant SVC as auth_service.py
participant DB as users table
participant SEC as security.py
U->>FE: login(identifier, password, type)
FE->>API: { identifier, password, type }
API->>SVC: authenticate(identifier, password, type)
alt type == "customer"
SVC->>DB: SELECT WHERE username = identifier
else type == "employee" (FIELD_AGENT / ADMIN)
SVC->>DB: SELECT WHERE emp_id = identifier
else type == "employee" (OWNER / CONTRACTOR / VENDOR / SUBCONTRACTOR)
SVC->>DB: SELECT WHERE username = identifier
end
DB-->>SVC: User row (or None)
alt User not found
SVC-->>API: None
API-->>FE: 401 { detail: "Invalid credentials" }
FE-->>U: Toast "Invalid credentials"
else User found
SVC->>SEC: verify_password(plain, hash)
alt Password wrong
SEC-->>SVC: False
SVC-->>API: None
API-->>FE: 401 { detail: "Invalid credentials" }
else Password correct
SEC-->>SVC: True
SVC->>SEC: create_access_token(user.id, user.role)
SVC->>SEC: create_refresh_token(user.id)
SVC->>DB: UPDATE users SET refresh_token_hash = ?, last_login_at = NOW()
SVC-->>API: { access_token, refresh_token, user }
API-->>FE: Set httpOnly cookie (access + refresh)\n200 { user: { id, name, role, ... } }
FE-->>U: Redirect to role portal
end
end
```
### 3b — Authenticated Request
```mermaid
sequenceDiagram
participant FE as React
participant API as Any Protected Endpoint
participant DEP as get_current_user()
participant SEC as security.py
participant DB as users table
FE->>API: GET /properties\nCookie: access_token=<jwt>
API->>DEP: Depends(get_current_user)
DEP->>DEP: Extract token from cookie / Authorization header
alt No token present
DEP-->>API: HTTPException 401
API-->>FE: 401 UNAUTHORIZED
end
DEP->>SEC: jwt_decode(token, SECRET_KEY)
alt Token expired
SEC-->>DEP: JWTError (ExpiredSignatureError)
DEP-->>API: HTTPException 401 "Token expired"
API-->>FE: 401 → FE triggers refresh flow
else Token invalid (tampered)
SEC-->>DEP: JWTError
DEP-->>API: HTTPException 401 "Invalid token"
else Token valid
SEC-->>DEP: { sub: user_id, role: "FIELD_AGENT", exp: ... }
DEP->>DB: SELECT WHERE id = user_id AND deleted_at IS NULL
DB-->>DEP: User row
DEP-->>API: current_user injected
API->>API: require_role check
API-->>FE: 200 with data
end
```
### 3c — Token Refresh
```mermaid
sequenceDiagram
participant FE as React (axios interceptor)
participant API as POST /auth/refresh
participant SVC as auth_service.py
participant DB as users table
participant SEC as security.py
Note over FE: Receives 401 "Token expired"
FE->>API: POST /auth/refresh\nCookie: refresh_token=<jwt>
API->>SVC: refresh_access_token(refresh_token)
SVC->>SEC: jwt_decode(refresh_token)
alt Refresh token invalid/expired
SEC-->>SVC: JWTError
SVC-->>API: Raise 401
API-->>FE: 401 → FE calls logout(), redirect to /login
else Valid
SEC-->>SVC: { sub: user_id }
SVC->>DB: SELECT refresh_token_hash WHERE id = user_id
SVC->>SEC: verify(incoming_token, stored_hash)
alt Hash mismatch (token reuse attack)
SEC-->>SVC: False
SVC->>DB: UPDATE users SET refresh_token_hash = NULL (revoke all)
SVC-->>API: 401 "Refresh token reuse detected"
API-->>FE: 401 → Force logout
else Hash matches
SVC->>SEC: create_access_token(user_id, role)
SVC->>SEC: create_refresh_token(user_id)
SVC->>DB: UPDATE users SET refresh_token_hash = new_hash
SVC-->>API: { new_access_token, new_refresh_token }
API-->>FE: Set new cookies\n200 OK
FE->>FE: Retry original request with new token
end
end
```
---
## Diagram 4 — RBAC Middleware Decision Tree
```mermaid
flowchart TD
REQ["Incoming Request"] --> HAS_TOKEN{Token present\nin cookie\nor header?}
HAS_TOKEN -->|No| R401_A["401 UNAUTHORIZED\nNo authentication provided"]
HAS_TOKEN -->|Yes| DECODE{Decode JWT\nvalid signature?}
DECODE -->|No / tampered| R401_B["401 UNAUTHORIZED\nInvalid token"]
DECODE -->|Yes| EXPIRED{Token\nexpired?}
EXPIRED -->|Yes| R401_C["401 UNAUTHORIZED\nToken expired\n→ client should refresh"]
EXPIRED -->|No| LOAD_USER{User exists\nin DB and\nis_active?}
LOAD_USER -->|No| R401_D["401 UNAUTHORIZED\nUser not found or deactivated"]
LOAD_USER -->|Yes| HAS_ROLE_CHECK{Route has\nrequire_role()?}
HAS_ROLE_CHECK -->|No - public route| PASS["✅ Proceed to handler\ncurrent_user = None"]
HAS_ROLE_CHECK -->|Yes| ROLE_MATCH{user.role\nin allowed_roles?}
ROLE_MATCH -->|No| R403["403 FORBIDDEN\nInsufficient permissions\nfor this resource"]
ROLE_MATCH -->|Yes| HANDLER["✅ Proceed to handler\ncurrent_user = User object"]
HANDLER --> SERVICE["Service layer\nfurther scoping\n(e.g. agent only sees own meetings)"]
```
---
## Diagram 5 — Module Build Dependency Graph
The recommended order to build backend modules. Each module depends on the ones above it.
```mermaid
graph TD
B0["B0\nProject Overview\n& Conventions"]
B2["B2\nDatabase Schema\n(All ORM models)"]
B1["B1\nAuthentication\n(JWT, RBAC, all 6 roles)"]
B3["B3\nProperties Module\n(CRUD, geospatial, leads)"]
B4["B4\nUsers & People\n(profiles, masking)"]
B5["B5\nMeetings & Scheduling\n(status machine, change requests)"]
B6["B6\nVendors & Compliance\n(COI, W9, expiry alerts)"]
B7["B7\nFinancials\n(invoices, AR/AP, payouts)"]
B8["B8\nChatbot & AI\n(RBAC context, Groq proxy)"]
B9["B9\nNotifications\n(email, in-app, alerts)"]
B0 --> B2
B2 --> B1
B1 --> B3
B1 --> B4
B3 --> B5
B4 --> B5
B5 --> B6
B3 --> B7
B6 --> B7
B3 & B4 & B5 & B6 & B7 --> B8
B5 & B6 & B7 --> B9
style B0 fill:#1e3a5f,color:#fff
style B2 fill:#1e3a5f,color:#fff
style B1 fill:#1e5f3a,color:#fff
style B3 fill:#3a1e5f,color:#fff
style B4 fill:#3a1e5f,color:#fff
style B5 fill:#5f3a1e,color:#fff
style B6 fill:#5f3a1e,color:#fff
style B7 fill:#5f1e3a,color:#fff
style B8 fill:#1e5f5f,color:#fff
style B9 fill:#5f5f1e,color:#fff
```
---
## Diagram 6 — Chatbot RBAC Context Builder
How the chatbot service decides what data to inject into the LLM prompt based on the requesting user's role.
```mermaid
flowchart TD
REQ["POST /chatbot/message\n{ message, role }"] --> AUTH["Validate JWT\nExtract role"]
AUTH --> ROLE{user.role?}
ROLE -->|FIELD_AGENT| FA_CTX["Build Agent Context\n• Today's meetings\n• Assigned properties\n• Personal XP / streak\n• Top 5 hot leads in territory"]
ROLE -->|ADMIN| ADM_CTX["Build Admin Context\n• Unassigned leads count\n• Today's schedule (all agents)\n• Pending signatures\n• Revenue MTD"]
ROLE -->|OWNER| OWN_CTX["Build Owner Context\n• Project health scores\n• Overdue invoices\n• Compliance alerts\n• Revenue vs budget"]
ROLE -->|CONTRACTOR| CON_CTX["Build Contractor Context\n• Assigned projects\n• Pending tasks\n• Open change orders"]
ROLE -->|VENDOR| VEN_CTX["Build Vendor Context\n• Open orders\n• Compliance doc expiry\n• Payment status"]
ROLE -->|CUSTOMER| CUS_CTX["Build Customer Context\n• Own property details\n• Upcoming meetings\n• Service history"]
FA_CTX & ADM_CTX & OWN_CTX & CON_CTX & VEN_CTX & CUS_CTX --> PROMPT["Assemble system prompt\nRole context + user message"]
PROMPT --> GROQ["POST Groq API\nqwen-32b\nstream=true"]
GROQ --> AUDIT["Write audit_log\nactor_id, action=chatbot.query\nredacted message"]
AUDIT --> STREAM["Stream response\nback to client"]
```
---
## Diagram 7 — Environment Configuration Flow
How environment variables flow from `.env` into every part of the app.
```mermaid
graph LR
ENV_FILE[".env file\n(never committed)"]
ENV_EXAMPLE[".env.example\n(committed template)"]
ENV_FILE -->|"pydantic-settings\nBaseSettings"| SETTINGS["config.py\nSettings singleton\nsettings.database_url\nsettings.secret_key\nsettings.groq_api_key\n..."]
SETTINGS -->|"settings.database_url"| DB_MOD["database.py\ncreate_async_engine(url)"]
SETTINGS -->|"settings.secret_key"| SEC["security.py\njwt_encode/decode"]
SETTINGS -->|"settings.groq_api_key"| CHAT_SVC["chatbot_service.py\nhttpx.post(headers={api_key})"]
SETTINGS -->|"settings.backend_cors_origins"| CORS_MW["main.py\nCORSMiddleware(allow_origins=...)"]
SETTINGS -->|"settings.aws_*"| S3_SVC["document upload\nboto3.client(s3)"]
SETTINGS -->|"settings.sendgrid_api_key"| NOTIF_SVC["notification_service.py\nSendGrid client"]
ENV_EXAMPLE -.->|"documents expected vars\n(no real values)"| ENV_FILE
```