Hargrave Free School Staff Portal & LLM Agent
An enterprise workforce administration system built for distributed virtual organizations. Features OIDC auth with group rank gating, automated leave management, disciplinary tracking, real-time Server-Sent Events (SSE), and a cookie-aware Groq conversational AI assistant.
OAuth 2.0 / OIDC
Group Rank Gating (≥ 10)
Signed Cookies
HTTP-Only SameSite JWT
HTTP SSE Stream
Node EventEmitter Driver
Groq Function Proxy
Context-Bound Tool Calling
System Architecture Map
Figma-style interactive layout. Click & drag to pan, scroll or use toolbar controls to zoom.
flowchart TD
classDef clientStyle fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#fff;
classDef authStyle fill:#1e1b4b,stroke:#6366f1,stroke-width:2px,color:#fff;
classDef absenceStyle fill:#064e3b,stroke:#10b981,stroke-width:2px,color:#fff;
classDef discStyle fill:#4c0519,stroke:#f43f5e,stroke-width:2px,color:#fff;
classDef aiStyle fill:#312e81,stroke:#818cf8,stroke-width:2px,color:#fff;
classDef sseStyle fill:#451a03,stroke:#f59e0b,stroke-width:2px,color:#fff;
classDef dbStyle fill:#172554,stroke:#38bdf8,stroke-width:2px,color:#fff;
subgraph Client ["Frontend Layer (Next.js 15 / React 19)"]
UI_Dashboard["Dashboard UI Pages\n(/dashboard, /absences, /profile)"]:::clientStyle
Zustand_Stores["Zustand State Stores\n(useAuth, useAbsenceStore, useNotificationStore)"]:::clientStyle
SSE_Listener["useNotificationListener Hook\n(EventSource Connection)"]:::clientStyle
AI_Chat_UI["AIChatBox Component\n(Floating Assistant Window)"]:::clientStyle
end
subgraph AuthModule ["Auth Engine (OIDC / Roblox API)"]
GET_Login["GET /api/auth/login\n(OIDC State & Nonce Cookies)"]:::authStyle
GET_Callback["GET /api/auth/callback\n(Code Exchange & Token Verification)"]:::authStyle
Roblox_Group_API["Roblox Group API Verification\n(GET /v2/users/{id}/groups/roles)"]:::authStyle
Auth_Middleware["authenticate Middleware\n(Verify Signed HS256 JWT in Cookie)"]:::authStyle
end
subgraph AbsenceModule ["Absences API (/api/absences)"]
POST_Absence["POST /\n(Submit Request via Zod Validation)"]:::absenceStyle
GET_MyAbsences["GET / & /all\n(Fetch Personal / Admin Listings)"]:::absenceStyle
PATCH_Review["PATCH /review\n(Supervisor Approve / Reject)"]:::absenceStyle
PATCH_Extend["PATCH /extend & /end\n(Extend Period / Manual Conclude)"]:::absenceStyle
end
subgraph DiscModule ["Disciplinaries API (/api/disciplinaries)"]
POST_Disc["POST /issue\n(Issue Warning / Violation)"]:::discStyle
GET_Disc["GET /get\n(Fetch History by Target User ID)"]:::discStyle
end
subgraph AIModule ["AI Engine & Tool Proxy (/api/chat)"]
POST_Chat["POST /query\n(Process User Input Prompt)"]:::aiStyle
Groq_LLM["Groq Cloud SDK\n(llama-3.3-70b-versatile Model)"]:::aiStyle
Tool_Proxy["Internal API Proxy Engine\n(Executes Requests with User Cookie Payload)"]:::aiStyle
end
subgraph SSEModule ["Real-Time Event Engine"]
GET_Stream["GET /api/notifications/stream\n(HTTP SSE Endpoint)"]:::sseStyle
Event_Emitter["Node.js EventEmitter\n(notificationEvents.emit)"]:::sseStyle
end
subgraph Database ["PostgreSQL 18 Database Layer"]
DB_Users[("users Table\nPK: id (UUID)\nNK: roblox_id (VARCHAR)")]:::dbStyle
DB_Absences[("absences Table\nPK: id (UUID)\nFK: user_roblox_id, supervisor_id")]:::dbStyle
DB_Disciplinaries[("disciplinaries Table\nPK: id (UUID)\nFK: user_id, issuer_id")]:::dbStyle
DB_Notifications[("notifications Table\nPK: id (UUID)\nIndex: idx_unread_notifications")]:::dbStyle
end
UI_Dashboard -->|"1. Initiate OAuth"| GET_Login
GET_Login -->|"2. Redirect Callback"| GET_Callback
GET_Callback -->|"3. Check Group Rank >= 10"| Roblox_Group_API
Roblox_Group_API -->|"4. Upsert User Record"| DB_Users
GET_Callback -->|"5. Issue Signed Cookie (auth_token)"| Zustand_Stores
UI_Dashboard -->|"HTTP Request + Signed Cookie"| Auth_Middleware
Auth_Middleware --> POST_Absence
Auth_Middleware --> GET_MyAbsences
Auth_Middleware --> PATCH_Review
Auth_Middleware --> PATCH_Extend
Auth_Middleware --> POST_Disc
Auth_Middleware --> GET_Disc
Auth_Middleware --> POST_Chat
POST_Absence -->|"INSERT INTO absences"| DB_Absences
GET_MyAbsences -->|"SELECT JOIN users"| DB_Absences
PATCH_Review -->|"UPDATE status, comment"| DB_Absences
POST_Disc -->|"INSERT INTO disciplinaries"| DB_Disciplinaries
GET_Disc -->|"SELECT WHERE user_id"| DB_Disciplinaries
PATCH_Review -->|"Trigger Event"| Event_Emitter
POST_Disc -->|"Trigger Event"| Event_Emitter
Event_Emitter -->|"Write Record"| DB_Notifications
Event_Emitter -->|"Emit SSE Event"| GET_Stream
GET_Stream -->|"Server-Sent Events Stream"| SSE_Listener
SSE_Listener -->|"Update Unread State"| Zustand_Stores
AI_Chat_UI -->|"Submit Prompt"| POST_Chat
POST_Chat -->|"Send Prompt + Tools"| Groq_LLM
Groq_LLM -->|"Request Tool Execution"| Tool_Proxy
Tool_Proxy -->|"Internal Request w/ Cookie"| GET_MyAbsences
Tool_Proxy -->|"Internal Request w/ Cookie"| GET_Disc
Tool_Proxy -->|"Return Raw JSON Context"| Groq_LLM
Groq_LLM -->|"Return Summarized Response"| AI_Chat_UI
Roblox OIDC & Rank Verification
OAuth2 Authorization Flow & Session Security
Standard username/password auth is replaced with Roblox OpenID Connect (OIDC)[cite: 2]. During callback processing, the server queries the Roblox Group API to verify the user holds a staff rank (≥ 10) before granting access[cite: 2]. Validated accounts are upserted into PostgreSQL and issued a 7-day HS256 JWT stored inside a signed, HTTP-only, SameSite cookie[cite: 2].
Agentic AI Tool Proxying
Groq LLM & Session-Aware Tool Calling
The portal features an embedded assistant powered by
Groq (llama-3.3-70b-versatile)[cite: 2]. When answering questions about leave or
disciplinaries, the LLM requests function execution
(get_user_absences,
get_user_disciplinaries)[cite: 2]. The backend proxies these requests
internally while attaching the caller's session cookie,
guaranteeing data context separation[cite: 2].
Real-Time Event Streams (SSE)
Persistent Connections vs Polling
Rather than relying on client polling overhead, live
notifications are delivered over Server-Sent Events
(GET /api/notifications/stream)[cite: 2]. When supervisors review requests or issue
disciplinaries, Node's
EventEmitter
pushes payload updates over open client streams
instantly[cite: 2, 3].
PostgreSQL Schema & Indexing
Referential Integrity & Partial Indexes
Uses primary key UUIDs (gen_random_uuid()) paired with external natural key lookups (users.roblox_id)[cite: 1, 2]. Read queries are optimized using
PostgreSQL array types for user permissions and a
partial index (idx_unread_notifications) targeting unread notification records[cite: 1].
API Endpoint Mapping
| Method | Endpoint | Validation Schema | Description |
|---|---|---|---|
| GET | /api/auth/login | N/A | Initiates Roblox OIDC redirect flow[cite: 2]. |
| POST | /api/absences | submitAbsenceSchema | Validates ISO dates and stores leave requests[cite: 2]. |
| PATCH | /api/absences/review | reviewAbsenceSchema | Supervisor status updates & triggers SSE notifications[cite: 2]. |
| POST | /api/disciplinaries/issue | createDisciplinarySchema | Records warning infractions against staff members[cite: 2]. |
| POST | /api/chat/query | ChatMessageSchema | Executes Groq agent query loop with tool calls[cite: 2]. |
| GET | /api/notifications/stream | N/A | Opens persistent HTTP Server-Sent Event stream[cite: 2]. |