@@ -27,6 +27,193 @@ Support critical system events like forcing a user to logout when viewing any pa
2727
2828---
2929
30+ ## Architecture Diagram
31+
32+ ### Overall System Architecture
33+
34+ ``` mermaid
35+ graph TB
36+ subgraph Frontend["Frontend (Browser)"]
37+ Tab1["Browser Tab 1<br/>EventSource<br/>(Coach)"]
38+ Tab2["Browser Tab 2<br/>EventSource<br/>(Coachee)"]
39+ end
40+
41+ subgraph Nginx["Nginx Reverse Proxy"]
42+ SSERoute["/api/sse<br/>proxy_buffering off<br/>proxy_read_timeout 24h"]
43+ end
44+
45+ subgraph Backend["Backend (Single Instance)"]
46+ Handler["SSE Handler<br/>(handler.rs)<br/>• Extract AuthenticatedUser<br/>• Create channel<br/>• Register connection"]
47+
48+ Manager["SSE Manager<br/>(manager.rs)<br/>• DashMap connections<br/>• Filter by scope<br/>• Route messages"]
49+
50+ Controller["Action Controller<br/>(action_controller.rs)<br/>• Create resource in DB<br/>• Determine recipient<br/>• Send SSE message"]
51+
52+ DB[(PostgreSQL)]
53+ end
54+
55+ Tab1 -->|"GET /api/sse<br/>(session cookie)"| SSERoute
56+ Tab2 -->|"GET /api/sse<br/>(session cookie)"| SSERoute
57+
58+ SSERoute -->|"Long-lived connection"| Handler
59+
60+ Handler -->|"register_connection(metadata)"| Manager
61+
62+ Controller -->|"send_message(SseMessage)"| Manager
63+ Controller -->|"Save resource"| DB
64+
65+ Manager -.->|"Event stream"| Handler
66+ Handler -.->|"SSE events"| SSERoute
67+ SSERoute -.->|"Server-Sent Events"| Tab1
68+ SSERoute -.->|"Server-Sent Events"| Tab2
69+
70+ style Manager fill:#b3e5fc,stroke:#01579b,stroke-width:2px,color:#000
71+ style Handler fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
72+ style Controller fill:#f8bbd0,stroke:#880e4f,stroke-width:2px,color:#000
73+ style SSERoute fill:#c8e6c9,stroke:#1b5e20,stroke-width:2px,color:#000
74+ ```
75+
76+ ### Message Flow Sequence
77+
78+ ``` mermaid
79+ sequenceDiagram
80+ participant Coach as Coach Browser
81+ participant Coachee as Coachee Browser
82+ participant Nginx as Nginx
83+ participant Handler as SSE Handler
84+ participant Manager as SSE Manager
85+ participant Controller as Action Controller
86+ participant DB as Database
87+
88+ Note over Coach,Coachee: Connection Establishment
89+ Coach->>+Nginx: GET /api/sse (session cookie)
90+ Nginx->>+Handler: Forward request
91+ Handler->>Handler: Extract user from<br/>AuthenticatedUser
92+ Handler->>Manager: register_connection(coach_metadata)
93+ Handler-->>Coach: SSE connection established
94+
95+ Coachee->>+Nginx: GET /api/sse (session cookie)
96+ Nginx->>+Handler: Forward request
97+ Handler->>Handler: Extract user from<br/>AuthenticatedUser
98+ Handler->>Manager: register_connection(coachee_metadata)
99+ Handler-->>Coachee: SSE connection established
100+
101+ Note over Coach,DB: Resource Creation Flow
102+ Coach->>Controller: POST /actions<br/>{action data}
103+ Controller->>DB: Insert action
104+ DB-->>Controller: Action saved
105+ Controller->>Controller: Determine recipient<br/>(Coachee)
106+ Controller->>Manager: send_message(SseMessage)<br/>scope: User{coachee_id}
107+ Manager->>Manager: Filter connections<br/>by user_id
108+ Manager-->>Handler: Send to Coachee's channel
109+ Handler-->>Nginx: SSE event
110+ Nginx-->>Coachee: event: action_created<br/>data: {action}
111+ Controller-->>Coach: HTTP 201 Created<br/>{action}
112+
113+ Note over Coachee: Coachee sees action immediately
114+ ```
115+
116+ ### SSE Manager Internal Structure
117+
118+ ``` mermaid
119+ graph LR
120+ subgraph "SseManager (In-Memory)"
121+ DashMap["DashMap<ConnectionId, Metadata>"]
122+
123+ subgraph Connections["Active Connections"]
124+ C1["conn_uuid_1<br/>• user_id: coach_id<br/>• sender: Channel"]
125+ C2["conn_uuid_2<br/>• user_id: coachee_id<br/>• sender: Channel"]
126+ C3["conn_uuid_3<br/>• user_id: coach_id<br/>• sender: Channel"]
127+ end
128+ end
129+
130+ subgraph "Message Routing"
131+ Msg["SseMessage<br/>• event: ActionCreated<br/>• scope: User{coachee_id}"]
132+ Filter{"Filter by<br/>scope"}
133+ end
134+
135+ Msg --> Filter
136+ Filter -->|"user_id == coachee_id"| C2
137+ Filter -.->|"Skip"| C1
138+ Filter -.->|"Skip"| C3
139+
140+ DashMap --- Connections
141+
142+ style C2 fill:#81c784,stroke:#2e7d32,stroke-width:2px,color:#000
143+ style C1 fill:#ef9a9a,stroke:#c62828,stroke-width:2px,color:#000
144+ style C3 fill:#ef9a9a,stroke:#c62828,stroke-width:2px,color:#000
145+ style Filter fill:#ffb74d,stroke:#e65100,stroke-width:2px,color:#000
146+ ```
147+
148+ ### Event Types and Scopes
149+
150+ ``` mermaid
151+ graph TD
152+ subgraph "SseEvent Types"
153+ Session["Session-Scoped<br/>• ActionCreated<br/>• ActionUpdated<br/>• ActionDeleted<br/>• NoteCreated<br/>• NoteUpdated<br/>• NoteDeleted"]
154+
155+ Relationship["Relationship-Scoped<br/>• AgreementCreated<br/>• AgreementUpdated<br/>• AgreementDeleted<br/>• GoalCreated<br/>• GoalUpdated<br/>• GoalDeleted"]
156+
157+ System["System Events<br/>• ForceLogout"]
158+ end
159+
160+ subgraph "MessageScope"
161+ User["User Scope<br/>Send to specific user_id<br/>(all their connections)"]
162+ Broadcast["Broadcast Scope<br/>Send to all connected users"]
163+ end
164+
165+ Session --> User
166+ Relationship --> User
167+ System --> User
168+ System --> Broadcast
169+
170+ style Session fill:#b3e5fc,stroke:#01579b,stroke-width:2px,color:#000
171+ style Relationship fill:#f8bbd0,stroke:#880e4f,stroke-width:2px,color:#000
172+ style System fill:#ffcdd2,stroke:#b71c1c,stroke-width:2px,color:#000
173+ style User fill:#c8e6c9,stroke:#1b5e20,stroke-width:2px,color:#000
174+ style Broadcast fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
175+ ```
176+
177+ ### Connection Lifecycle
178+
179+ ``` mermaid
180+ stateDiagram-v2
181+ [*] --> Connecting: User opens browser
182+
183+ Connecting --> Authenticating: GET /api/sse
184+ Authenticating --> Registered: Session cookie valid
185+ Authenticating --> [*]: Auth failed (401)
186+
187+ Registered --> Active: Connection in DashMap
188+
189+ Active --> ReceivingEvents: Listening for events
190+ ReceivingEvents --> Active: Event received
191+
192+ Active --> KeepAlive: Every 15 seconds
193+ KeepAlive --> Active: Heartbeat sent
194+
195+ Active --> Disconnecting: Browser closed/<br/>Network error
196+ Disconnecting --> CleanedUp: unregister_connection()
197+ CleanedUp --> [*]
198+
199+ Active --> ForceDisconnect: 24h timeout (nginx)
200+ ForceDisconnect --> CleanedUp
201+
202+ note right of Active
203+ Connection stored in DashMap:
204+ • connection_id (UUID)
205+ • user_id (from session)
206+ • sender (Channel)
207+ end note
208+
209+ note right of KeepAlive
210+ Prevents nginx from closing
211+ idle connections
212+ end note
213+ ```
214+
215+ ---
216+
30217## Phase 0: Docker Compose Documentation
31218
32219### 0.1 Add SSE Scaling Warning to docker-compose.yaml
@@ -965,54 +1152,6 @@ mod tests {
9651152
9661153---
9671154
968- ## Architecture Diagram
969-
970- ```
971- ┌─────────────────────────────────────────────────────────────┐
972- │ Frontend │
973- │ ┌──────────────────┐ ┌──────────────────┐ │
974- │ │ Browser Tab 1 │ │ Browser Tab 2 │ │
975- │ │ EventSource │ │ EventSource │ │
976- │ │ (user session) │ │ (user session) │ │
977- │ └────────┬─────────┘ └────────┬─────────┘ │
978- └───────────┼──────────────────────────┼──────────────────────┘
979- │ │
980- │ GET /sse (with cookie) │ GET /sse (with cookie)
981- │ │
982- ┌───────────┼──────────────────────────┼──────────────────────┐
983- │ ▼ ▼ Backend │
984- │ ┌────────────────────────────────────────────────┐ │
985- │ │ SSE Handler (handler.rs) │ │
986- │ │ - Extract user from AuthenticatedUser │ │
987- │ │ - Create channel for connection │ │
988- │ │ - Register with SseManager │ │
989- │ └──────────────────┬─────────────────────────────┘ │
990- │ │ │
991- │ ▼ │
992- │ ┌────────────────────────────────────────────────┐ │
993- │ │ SseManager (manager.rs) │ │
994- │ │ ┌──────────────────────────────────────────┐ │ │
995- │ │ │ DashMap<ConnectionId, Metadata> │ │ │
996- │ │ │ - connection_1 → {user_id, sender} │ │ │
997- │ │ │ - connection_2 → {user_id, sender} │ │ │
998- │ │ └──────────────────────────────────────────┘ │ │
999- │ │ │ │
1000- │ │ send_message(SseMessage) │ │
1001- │ │ → Filter connections by scope │ │
1002- │ │ → Send to matching channels │ │
1003- │ └──────────────────▲───────────────────────────┘ │
1004- │ │ │
1005- │ ┌──────────────────┴───────────────────────────┐ │
1006- │ │ Action Controller (action_controller.rs) │ │
1007- │ │ - Create action in DB │ │
1008- │ │ - Determine OTHER user in relationship │ │
1009- │ │ - Send User-scoped SseMessage │ │
1010- │ └───────────────────────────────────────────────┘ │
1011- └─────────────────────────────────────────────────────────────┘
1012- ```
1013-
1014- ---
1015-
10161155## Key Design Decisions Summary
10171156
10181157| Decision | Choice | Rationale |
0 commit comments