# cilt-uct/brightspace-middleware-lite architecture

> Architecture diagram of the GitHub repository cilt-uct/brightspace-middleware-lite, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-26). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/cilt-uct/brightspace-middleware-lite
- Repository: https://github.com/cilt-uct/brightspace-middleware-lite

## Overview

1. **What this service is for**  
   This repo implements a middleware service between the Tsugi migration tool and D2L Brightspace. Its core job is to wrap Brightspace calls with the correct OAuth 2.0 access token and keep that token refreshed over time.

2. **Top-level deployment shape**  
   The app is containerized and split into a **reverse proxy** layer and a **Flask web service** layer. `app/services/nginx` contains nginx config/Dockerfile, while `app/services/web` contains the Python application served through Gunicorn. `docker-compose.yml` ties these pieces together.

3. **Web application boundary**  
   The Flask service is split into two presentation surfaces:  
   - `project/site/*` for server-rendered UI pages such as login, register, logout, and system pages  
   - `project/api/*` for the programmatic API used by the migration workflow  
   Templates and static assets live under `project/templates` and `project/static`, so the UI is mostly server-side rendered with some JavaScript helpers.

4. **Brightspace integration layer**  
   The `project/d2l` package is the main adapter to Brightspace. It is subdivided by domain: `courses`, `content`, `roles`, and `users`, plus shared pieces like `client`, `oauth`, `response`, `decorators`, and `exceptions`. This is the boundary where the app translates internal requests into Brightspace API calls.

5. **Local persistence and state**  
   The `project/db` package handles persistence, with modules for the main client, logs, and system data. The repository includes `app/database.sql`, indicating a local relational schema used to store service state such as tokens, settings, logs, and possibly user/account records.

6. **OAuth and token lifecycle**  
   OAuth is treated as a first-class concern. The `d2l/oauth.py` module likely handles authorization and token exchange, while the scheduler process (`project/run_scheduler.py`) exists specifically to refresh tokens automatically. This means the service is not just a stateless proxy; it owns credential lifecycle state.

7. **API resource model**  
   The API is organized around Brightspace-style resources: users, courses, roles, contents, and settings. That suggests the middleware exposes a domain-oriented interface rather than a generic proxy, mapping migration operations onto Brightspace entities.

8. **Background processing**  
   The README explicitly calls out an APScheduler-based component. Architecturally, this is the asynchronous support plane for the service: it runs independently of request/response traffic and keeps authentication material valid in the background.

9. **Security and request guards**  
   There are project-level decorators plus D2L-specific decorators, implying access control and token/state validation around both UI and API routes. Forms and system pages suggest there is user-facing authentication/registration in addition to machine-driven API usage.

10. **Front-end and client-side assets**  
   The front end is mostly traditional Flask/Jinja with bundled CSS/JS libraries. There is a `flask_jsglue` integration and a `js_bridge.js` template, which indicates the server exposes some route/config data to client-side code rather than building a full SPA.

11. **Configuration and runtime entrypoints**  
   Runtime configuration is driven by environment files and app-level config modules (`project/config.py`, `project/site/settings.py`, `project/api/settings.py`). The service has explicit entrypoints for Gunicorn and a shell-based startup script, so the app is designed to run as a long-lived containerized web process plus a separate scheduler process.

12. **Overall data flow**  
   A typical request path is: UI/API request enters nginx, reaches Flask/Gunicorn, hits a site or API route, which may query local DB state, then calls the D2L client layer to invoke Brightspace with the current OAuth token. Separately, the scheduler refreshes tokens and updates stored state so subsequent migration calls continue to work.

Main entry points: [`app/services/web/project/site/routes.py`](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/site/routes.py) (Site routes), [`app/services/web`](https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/web) (Flask app), [`app/services/web/project/db/client.py`](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/db/client.py) (DB client), [`app/services/web/project/api/routes.py`](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/api/routes.py) (API routes)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_deployment["Deployment"]
  node_nginx["Nginx<br/>reverse proxy"]
  node_web["Flask app<br/>gunicorn app"]
  node_compose["Compose<br/>orchestration<br/>[docker-compose.yml]"]
  node_scheduler["Scheduler<br/>background jobs<br/>[run_scheduler.py]"]
end

subgraph group_webapp["Web App"]
  node_gunicorn["Gunicorn<br/>app server<br/>[gunicorn_conf.py]"]
  node_entrypoint["Entrypoint<br/>startup script<br/>[entrypoint.sh]"]
  node_site_routes["Site routes<br/>UI routes<br/>[routes.py]"]
  node_api_routes["API routes<br/>resource routes<br/>[routes.py]"]
  node_forms["Forms<br/>[forms.py]"]
  node_site_templates["Templates<br/>jinja views"]
  node_site_static["Static assets<br/>css/js assets"]
  node_glue["JS glue<br/>client bridge<br/>[js_bridge.js]"]
  node_site_settings["Settings<br/>config<br/>[settings.py]"]
  node_api_settings["API settings<br/>config<br/>[settings.py]"]
end

subgraph group_integration["Brightspace"]
  node_d2l_client["D2L client<br/>api client<br/>[client.py]"]
  node_d2l_oauth["OAuth<br/>token lifecycle<br/>[oauth.py]"]
  node_d2l_domains["D2L domains<br/>resource adapters"]
  node_d2l_guards["D2L guards<br/>decorators/errors<br/>[decorators.py]"]
end

subgraph group_persistence["Persistence"]
  node_db_client[("DB client<br/>persistence client<br/>[client.py]")]
  node_db_models["DB models<br/>state schema<br/>[models.py]"]
  node_schema["Schema<br/>[database.sql]"]
end

node_compose -->|"starts"| node_nginx
node_compose -->|"starts"| node_web
node_nginx -->|"proxies to"| node_web
node_web -->|"served by"| node_gunicorn
node_web -->|"bootstraps"| node_entrypoint
node_web -->|"serves"| node_site_routes
node_web -->|"serves"| node_api_routes
node_site_routes -->|"uses"| node_forms
node_site_routes -->|"renders"| node_site_templates
node_site_routes -.->|"injects"| node_glue
node_site_routes -.->|"loads"| node_site_static
node_api_routes -->|"configured by"| node_api_settings
node_site_routes -->|"configured by"| node_site_settings
node_api_routes -->|"calls"| node_d2l_domains
node_site_routes -->|"reads/writes"| node_db_client
node_api_routes -->|"reads/writes"| node_db_client
node_db_client -->|"uses"| node_db_models
node_db_models -->|"defined by"| node_schema
node_d2l_domains -->|"uses"| node_d2l_client
node_d2l_client -->|"authenticates with"| node_d2l_oauth
node_d2l_oauth -->|"stores tokens in"| node_db_client
node_scheduler -->|"refreshes"| node_d2l_oauth
node_scheduler -->|"updates"| node_db_client
node_d2l_client -.->|"validated by"| node_d2l_guards

click node_nginx "https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/nginx"
click node_web "https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/web"
click node_compose "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/docker-compose.yml"
click node_gunicorn "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/gunicorn_conf.py"
click node_entrypoint "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/entrypoint.sh"
click node_site_routes "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/site/routes.py"
click node_api_routes "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/api/routes.py"
click node_forms "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/forms/forms.py"
click node_site_templates "https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/web/project/templates"
click node_site_static "https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/web/project/static"
click node_glue "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/templates/jsglue/js_bridge.js"
click node_site_settings "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/site/settings.py"
click node_api_settings "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/api/settings.py"
click node_d2l_client "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/d2l/client.py"
click node_d2l_oauth "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/d2l/oauth.py"
click node_d2l_domains "https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/web/project/d2l"
click node_d2l_guards "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/d2l/decorators.py"
click node_db_client "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/db/client.py"
click node_db_models "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/db/models.py"
click node_schema "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/database.sql"
click node_scheduler "https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/run_scheduler.py"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_nginx,node_web,node_compose,node_scheduler toneBlue
class node_gunicorn,node_entrypoint,node_site_routes,node_api_routes,node_forms,node_site_templates,node_site_static,node_glue,node_site_settings,node_api_settings toneAmber
class node_d2l_client,node_d2l_oauth,node_d2l_domains,node_d2l_guards toneMint
class node_db_client,node_db_models,node_schema toneRose
```

## Components

### Deployment

Containerized runtime and process entrypoints

- [Nginx](https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/nginx): reverse proxy · Front-door proxy and routing layer · `app/services/nginx`
- [Flask app](https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/web): gunicorn app · Main Python web service · `app/services/web`
- [Compose](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/docker-compose.yml): orchestration · Container wiring for proxy and web service · `app/docker-compose.yml`
- [Scheduler](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/run_scheduler.py): background jobs · APScheduler process that refreshes tokens in the background · `app/services/web/project/run_scheduler.py`

### Web App

Flask application and presentation layers

- [Gunicorn](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/gunicorn_conf.py): app server · Process configuration for serving Flask · `app/services/web/gunicorn_conf.py`
- [Entrypoint](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/entrypoint.sh): startup script · Container startup and bootstrapping · `app/services/web/entrypoint.sh`
- [Site routes](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/site/routes.py): UI routes · Server-rendered pages for login, register, logout, and system views · `app/services/web/project/site/routes.py`
- [API routes](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/api/routes.py): resource routes · Programmatic API for migration workflow · `app/services/web/project/api/routes.py`
- [Forms](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/forms/forms.py): input forms · Login and registration form models · `app/services/web/project/forms/forms.py`
- [Templates](https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/web/project/templates): jinja views · Server-rendered HTML pages and error views · `app/services/web/project/templates`
- [Static assets](https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/web/project/static): css/js assets · Bundled styles, scripts, and third-party front-end files · `app/services/web/project/static`
- [JS glue](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/templates/jsglue/js_bridge.js): client bridge · Exposes server data to browser-side code · `app/services/web/project/templates/jsglue/js_bridge.js`
- [Settings](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/site/settings.py): config · Web app and UI configuration · `app/services/web/project/site/settings.py`
- [API settings](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/api/settings.py): config · API-specific configuration · `app/services/web/project/api/settings.py`

### Brightspace

Adapter layer for D2L Brightspace APIs

- [D2L client](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/d2l/client.py): api client · Shared Brightspace request client and response handling · `app/services/web/project/d2l/client.py`
- [OAuth](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/d2l/oauth.py): token lifecycle · Authorization and token exchange/refresh · `app/services/web/project/d2l/oauth.py`
- [D2L domains](https://github.com/cilt-uct/brightspace-middleware-lite/tree/flask/app/services/web/project/d2l): resource adapters · Course, content, role, and user-specific Brightspace adapters · `app/services/web/project/d2l`
- [D2L guards](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/d2l/decorators.py): decorators/errors · Request guards and integration exceptions · `app/services/web/project/d2l/decorators.py`

### Persistence

Local database and stored service state

- [DB client](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/db/client.py): persistence client · Database access layer for app state · `app/services/web/project/db/client.py`
- [DB models](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/services/web/project/db/models.py): state schema · Stored records for logs, system data, and service state · `app/services/web/project/db/models.py`
- [Schema](https://github.com/cilt-uct/brightspace-middleware-lite/blob/flask/app/database.sql): sql schema · Relational schema for service state and tokens · `app/database.sql`

## Connections

- Compose → Nginx: starts · Compose launches the reverse proxy container
- Compose → Flask app: starts · Compose launches the web service container
- Nginx → Flask app: proxies to · Routes external traffic into the Flask service
- Flask app → Gunicorn: served by · Web container runs Flask behind Gunicorn
- Flask app → Entrypoint: bootstraps · Startup script prepares the web process
- Flask app → Site routes: serves · Server-rendered UI surface
- Flask app → API routes: serves · Programmatic migration API surface
- Site routes → Forms: uses · Login and registration handling
- Site routes → Templates: renders · UI pages and error templates
- Site routes → JS glue: injects · Passes route/config data to browser code
- Site routes → Static assets: loads · Client-side scripts and styles
- API routes → API settings: configured by · API behavior is driven by app settings
- Site routes → Settings: configured by · UI behavior is driven by app settings
- API routes → D2L domains: calls · Resource handlers map API operations to Brightspace domains
- Site routes → DB client: reads/writes · UI flows depend on stored service state
- API routes → DB client: reads/writes · API operations persist and load state
- DB client → DB models: uses · Database layer operates on persisted models
- DB models → Schema: defined by · Relational schema backing service state
- D2L domains → D2L client: uses · Domain adapters issue Brightspace requests through the shared client
- D2L client → OAuth: authenticates with · Client attaches OAuth access tokens
- OAuth → DB client: stores tokens in · Token lifecycle state is persisted locally
- Scheduler → OAuth: refreshes · Background job keeps credentials valid
- Scheduler → DB client: updates · Persists refreshed token and runtime state
- D2L client → D2L guards: validated by · Integration layer applies guards and error handling
