# Workflow (sequence) diagrams Core flows, traced against the delivered source. Mermaid sequence diagrams — editable and diffable in version control. Endpoints are relative to the API prefix `/api/v1`. --- ## 1. Registration & email verification ```mermaid sequenceDiagram participant C as Client participant API as AuthController participant S as AuthService participant DB as PostgreSQL participant EV as EventEmitter participant EL as EmailListener participant M as Email gateway C->>API: POST /auth/register API->>S: register(dto) S->>DB: find user by email alt email already exists S-->>C: 409 Conflict (states existing role) else new user S->>S: argon2.hash(password) S->>DB: create User (+ profile) S->>DB: create verification token S->>EV: emit "user.registered" S-->>C: 201 Created end EV-->>EL: user.registered EL->>M: POST EMAIL_API_URL (verification email) C->>API: POST /auth/verify-email { token } API->>DB: mark emailVerified API->>EV: emit "user.email-verified" EV-->>EL: user.email-verified EL->>M: POST EMAIL_API_URL (welcome email, role-aware) ``` Email delivery is asynchronous and best-effort: a failure is logged by `EmailService` and does **not** fail the registration request. --- ## 2. Login (with 2FA branch) ```mermaid sequenceDiagram participant C as Client participant API as AuthController participant S as AuthService participant T as TwoFactorService participant DB as PostgreSQL C->>API: POST /auth/login { email, password } API->>DB: load user API->>S: argon2.verify alt invalid credentials S-->>C: 401 Unauthorized else 2FA enabled S->>T: generateTempToken(userId) S-->>C: 200 { requiresTwoFactor: true, tempToken } C->>API: POST /auth/2fa/verify { tempToken, code } API->>T: verifyTempToken(tempToken) T->>DB: read twoFactorSecret (encrypted) T->>T: AES-256-GCM decrypt, speakeasy.verify(code) alt code invalid T-->>C: 401 Unauthorized else code valid T->>DB: create Session (refresh token) T-->>C: 200 { accessToken, refreshToken } end else 2FA disabled S->>DB: create Session (refresh token) S-->>C: 200 { accessToken, refreshToken } end ``` Backup codes follow the same path via `POST /auth/2fa/verify-backup`. ### Token refresh ```mermaid sequenceDiagram participant C as Client participant API as AuthController participant DB as PostgreSQL C->>API: POST /auth/refresh { refreshToken } API->>DB: look up Session alt session missing / expired / revoked API-->>C: 401 — client must log in again else valid API->>DB: rotate Session API-->>C: { accessToken, refreshToken } end ``` `JWT_REFRESH_EXPIRATION` must be longer than `JWT_ACCESS_EXPIRATION`; if it is shorter, refresh fails before the access token it is meant to renew expires and users are logged out. Boot validation rejects this configuration. --- ## 3. Google sign-in ```mermaid sequenceDiagram participant B as Browser participant NA as NextAuth (frontend) participant G as Google OAuth participant API as AuthController participant DB as PostgreSQL B->>NA: click "Continue with Google" NA->>G: OAuth authorisation code flow G-->>NA: id_token + profile NA->>NA: verify token, extract profile NA->>API: POST /auth/social { provider, email, name, providerId } API->>DB: find or create User (authProvider = GOOGLE) API->>DB: create Session API-->>NA: { accessToken, refreshToken } NA-->>B: session established ``` The OAuth client ID/secret live in the **frontend** environment. The `GOOGLE_*` variables in the backend environment are not consumed. Facebook and Twitter are not enabled. --- ## 4. Connection requests ```mermaid sequenceDiagram participant U as User participant API as ConnectionRequestsController participant DB as PostgreSQL participant EV as EventEmitter participant N as NotificationsService participant A as Agent U->>API: POST /connection-requests { agentProfileId, message } API->>DB: create ConnectionRequest (status PENDING) API->>EV: emit "notification.connection_request" EV-->>N: handle N->>DB: create Notification N->>A: FCM push + email (per user preferences) A->>API: PATCH /connection-requests/:id/respond { status } API->>DB: update status (ACCEPTED / REJECTED) API->>EV: emit "notification.connection_response" EV-->>N: handle N->>U: notification + push note over DB: on ACCEPTED a Conversation becomes available ``` Supporting reads: `GET /connection-requests/my-requests`, `/received`, `/received/counts`, `/status/:agentProfileId`. --- ## 5. Real-time messaging ```mermaid sequenceDiagram participant C1 as Sender participant GW as MessagesGateway participant SVC as MessagesService participant DB as PostgreSQL participant RD as Redis participant C2 as Recipient C1->>GW: connect (handshake.auth.token = JWT) GW->>GW: jwtService.verifyAsync alt invalid token GW-->>C1: disconnect else valid GW->>RD: mark user online (presence) GW->>GW: join room user:{userId} GW-->>C1: connected end C1->>GW: join_conversation { conversationId } GW->>GW: join room conversation:{id} C1->>GW: send_message { conversationId, content } GW->>SVC: create message SVC->>DB: insert Message SVC->>GW: emit "notification.message" (offline delivery) GW->>RD: publish via Socket.IO Redis adapter RD-->>GW: fan-out to other API instances GW-->>C2: new_message C2->>GW: mark_read { conversationId } GW->>DB: update read state GW-->>C1: messages_read ``` Other events: `typing_start` / `typing_stop`, `message_received`, `user_status_change` (presence broadcast), and the parallel support-chat set (`support_join`, `support_send_message`, `support_new_message`, …). Ping interval and timeout are both 5s, so a dropped connection is detected in roughly 10s rather than the Socket.IO default of ~45s. CORS on this gateway is driven by `CORS_ORIGINS`, read at module-import time — see `src/load-env.ts`. --- ## 6. Subscription, payment & Stripe webhooks ```mermaid sequenceDiagram participant A as Agent participant API as StripeController participant ST as Stripe participant WH as StripeWebhookController participant DB as PostgreSQL A->>API: GET /stripe/plans API-->>A: SubscriptionPlan list A->>API: POST /stripe/create-checkout-session { planId } API->>ST: checkout.sessions.create ST-->>A: redirect to Stripe Checkout A->>ST: completes payment ST->>WH: POST /stripe/webhook (signed) WH->>WH: constructEvent(rawBody, sig, STRIPE_WEBHOOK_SECRET) alt signature invalid WH-->>ST: 400 else valid alt checkout.session.completed WH->>DB: create/activate AgentSubscription else invoice.paid WH->>DB: record Payment, extend period else invoice.payment_failed WH->>DB: mark PAST_DUE else customer.subscription.updated WH->>DB: sync status / plan else customer.subscription.deleted WH->>DB: mark CANCELED end WH-->>ST: 200 end ``` The webhook route needs the **raw** request body for signature verification (`rawBody: true` is set on the Nest application). Any proxy that rewrites the body will break signature checks. Self-service billing management goes through `POST /stripe/create-portal-session`; cancellation through `POST /stripe/cancel-subscription`. --- ## 7. Agent verification ```mermaid sequenceDiagram participant AG as Agent participant API as UsersController participant DB as PostgreSQL participant AD as Admin participant EV as EventEmitter participant N as NotificationsService AG->>API: POST /users/me/verification/submit API->>DB: AgentProfile.verificationStatus = PENDING_REVIEW AD->>API: GET /users?verificationStatus=PENDING_REVIEW AD->>API: PATCH /users/:id/verification { status } API->>DB: update status + insert VerificationHistory (admin, reason) API->>EV: emit "notification.verification" EV-->>N: handle N->>AG: notification + push + email ``` `VerificationHistory` is the audit trail: who changed the status, when, and why. Note that approval is **not** gated on an active subscription in the backend — see architecture.md §4. --- ## 8. File upload ```mermaid sequenceDiagram participant C as Client participant API as UploadController participant S3 as Object storage participant DB as PostgreSQL C->>API: POST /upload/presigned-url { filename, contentType } API->>API: validate type/size, build key with S3_FOLDER_PREFIX API->>S3: sign PUT URL API-->>C: { uploadUrl, key } C->>S3: PUT file (direct, bypasses the API) C->>API: PATCH resource with { key } API->>DB: store the object KEY (never a full URL) note over C,S3: reading back C->>API: GET /upload/presigned-download-url?key=... API-->>C: time-limited URL ``` Variants: `message-presigned-url`, `avatar-presigned-url`, `user-avatar-presigned-url`; deletion via `DELETE /upload/:key`. Because only object **keys** are stored, any storage migration must preserve the key layout exactly or every existing reference breaks. --- ## 9. Transactional email — trigger events All email is sent by POSTing JSON to `EMAIL_API_URL`. There is no SMTP path in the code; `MAIL_*` / `SMTP_*` variables are ignored. | Event | Listener | Email sent | |---|---|---| | `user.registered` | `EmailListener` | Verification email | | `user.email-verified` | `EmailListener` | Welcome email (USER vs Professional/AGENT variant) | | `password.reset-requested` | `EmailListener` | Password reset link | | `password.reset-completed` | `EmailListener` | Confirmation | | `user.email-change-requested` | `EmailListener` | Verification for the new address | | notification fan-out | `NotificationsService` | Generic notification email (connection requests/responses, new messages, verification outcome) subject to user preferences | Templates live in `backend/src/email/templates`. --- ## 10. Data-flow diagram Where data lives and who it crosses. ```mermaid flowchart LR subgraph Clients W[Web] AD[Admin] MO[Mobile] end W -- "credentials, profile, messages" --> API AD -- "moderation actions" --> API MO -- "credentials, profile, messages" --> API API[Backend API] API -- "users, profiles, messages,
subscriptions, audit log" --> PG[(PostgreSQL)] API -- "presence, socket rooms" --> RD[(Redis)] API -- "object keys only" --> PG W -- "file bytes (presigned PUT)" --> S3[(Object storage)] MO -- "file bytes (presigned PUT)" --> S3 API -- "sign / delete" --> S3 API -- "email address, name, links" --> MAIL[Email gateway] API -- "device token, title, body" --> FCM[Firebase FCM] API -- "customer id, price id, amount" --> STRIPE[Stripe] STRIPE -- "subscription + invoice events" --> API W -- "OAuth profile" --> GOOGLE[Google OAuth] classDef pii fill:#fde,stroke:#b47 class MAIL,STRIPE,GOOGLE,FCM pii ``` Personal data leaving the platform boundary (highlighted): email address and display name to the email gateway; email and billing identifiers to Stripe; device tokens and notification content to Firebase; email and profile to Google during sign-in. Message bodies and uploaded files never leave PostgreSQL and object storage.