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

14 KiB

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.

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.

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

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

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

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

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.

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.

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.

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