Back to Library
Full-Stack
Project Completed: July 2026

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.

Next.js 15 React 19 Express 5 / Bun PostgreSQL 18 Groq AI (Llama 3.3) Zustand v5 Tailwind CSS v4 Zod v4
Authentication

OAuth 2.0 / OIDC

Group Rank Gating (≥ 10)

Session Security

Signed Cookies

HTTP-Only SameSite JWT

Real-Time Engine

HTTP SSE Stream

Node EventEmitter Driver

AI Capabilities

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].