How cilt-uct/brightspace-middleware-lite works
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: site/routes.py Site routes · web Flask app · client.py DB client · api/routes.py API routes
Components 21
Deployment
Containerized runtime and process entrypoints
- Nginx · Front-door proxy and routing layer
app/services/nginx - Flask app · Main Python web service
app/services/web - Compose · Container wiring for proxy and web service
app/docker-compose.yml - Scheduler · APScheduler process that refreshes tokens in the background
app/services/web/project/run_scheduler.py
Web App
Flask application and presentation layers
- Gunicorn · Process configuration for serving Flask
app/services/web/gunicorn_conf.py - Entrypoint · Container startup and bootstrapping
app/services/web/entrypoint.sh - Site routes · Server-rendered pages for login, register, logout, and system views
app/services/web/project/site/routes.py - API routes · Programmatic API for migration workflow
app/services/web/project/api/routes.py - Forms · Login and registration form models
app/services/web/project/forms/forms.py - Templates · Server-rendered HTML pages and error views
app/services/web/project/templates - Static assets · Bundled styles, scripts, and third-party front-end files
app/services/web/project/static - JS glue · Exposes server data to browser-side code
app/services/web/project/templates/jsglue/js_bridge.js - Settings · Web app and UI configuration
app/services/web/project/site/settings.py - API settings · API-specific configuration
app/services/web/project/api/settings.py
Brightspace
Adapter layer for D2L Brightspace APIs
- D2L client · Shared Brightspace request client and response handling
app/services/web/project/d2l/client.py - OAuth · Authorization and token exchange/refresh
app/services/web/project/d2l/oauth.py - D2L domains · Course, content, role, and user-specific Brightspace adapters
app/services/web/project/d2l - D2L guards · Request guards and integration exceptions
app/services/web/project/d2l/decorators.py
Persistence
Local database and stored service state
Connections 24
- 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