Backend (backend/)
Django 4.2 + Django REST Framework + PostgreSQL. Single backend that serves all three Flutter frontends (editor, planner, portal) over one versioned API surface.
Related: agent rules (§0), Render free-tier deploy, Northflank deploy, PostgreSQL migration.
Runtime shape
- Python pinned to 3.9 (the Dockerfile bases on
python:3.9.18-bullseye). PEP 604 unions (X | Y) don't work at runtime — usetyping.Optional. See index.md §0. - Settings:
backend/notechondria/settings.pyfor prod,backend/notechondria/settings_test.pyfor test (in-memory sqlite, MD5 password hasher, LOGGING stubbed). - ASGI/WSGI:
backend/notechondria/wsgi.py, launched by gunicorn viabackend/entrypoint.sh(which also runsmigrate --check, seeds the platform, collects static, thenexec "$@"). - Middleware order (settings.py
MIDDLEWARE):RequestTimingMiddleware→SecurityMiddleware→WhiteNoiseMiddleware→SessionMiddleware→ApiCorsMiddleware→CommonMiddleware→CsrfViewMiddleware→AuthenticationMiddleware→MessageMiddleware→XFrameOptionsMiddleware.
Django apps
Four project-local apps registered in INSTALLED_APPS. Per-app
docs cover models, views, services, and example request/response
payloads:
| App | Path | Role | Per-app doc |
|---|---|---|---|
creators | backend/creators/ | Accounts, OAuth, API keys, settings, identity-code verification. | creators.md |
notes | backend/notes/ | Notes, courses, planner events, calendar feeds, recycle bin, attachments. | notes.md |
mcp | backend/mcp/ | Model-Context-Protocol server: 21 tools, API-key auth, 39 tests. | mcp.md |
gptutils | backend/gptutils/ | Parked — AI chat models stay, call sites stubbed. | development/ai_integration.md |
Plus DRF itself (rest_framework). rest_framework.authtoken
stays in INSTALLED_APPS for migration compatibility but is no
longer the active auth source — 0.1.65 swapped the default to
creators.authentication.MultiSessionAuthentication backed by the
new creators.Session model. Legacy authtoken_token rows are
ignored at auth time and cleared on each deploy.
URL topology
Two-level routing: project URLs at backend/notechondria/urls.py for
site-wide endpoints, API v1 under
backend/notechondria/api_urls.py.
| Route | File | Purpose |
|---|---|---|
/ | urls.py → api_views.health_check | Liveness probe. |
/handshake/ | urls.py → api_views.handshake | Handshake (also at /api/v1/handshake/). |
/api/v1/ | api_urls.py | All application endpoints, including /auth/sessions/ (multi-device manager, see creators.md). |
/mcp/ | mcp/urls.py | Model-Context-Protocol server. |
/auth/google/callback | urls.py → api_views.oauth_callback | Google OAuth redirect. |
/auth/github/callback | urls.py → api_views.oauth_callback | GitHub OAuth redirect. |
/admin/ | Django admin. | Built-in. |
Full API surface is documented in
docs/api/backend_api_spec.md.
Per-app deep dives
The detailed model/view/services/example-payload reference for each app lives in its own file:
creators.md— accounts, sessions, OAuth, API keys, settings, identity-code verification.notes.md— notes, courses, planner events, calendar feeds, recycle bin, attachments, activity heatmap, version history. Includesnotes/services.pyhelpers (normalize_calendar_url,seed_inbox_and_welcome_note,parse_ical_datetime).mcp.md— Model-Context-Protocol tool surface.
The remaining gptutils app is stubbed — see below.
gptutils app (stubbed)
backend/gptutils/. Parked. The OpenAI SDK and tiktoken were
removed from both requirements.txt and requirements-render.txt
in 0.1.18; gpt_request_parser.py is a stub that raises
NotImplementedError. Models, views, forms, templates, migrations,
and URL routes are intact for future reuse when the external AI
microservice lands. See
docs/development/ai_integration.md.
Handshake
Endpoint: GET /api/v1/handshake/ (also exposed at /handshake/ for
clients that haven't prefixed /api/v1).
Implementation: backend/notechondria/api_views.py::handshake.
Returns:
{
"service": "notechondria-backend",
"api_version": "v1",
"version": "<./VERSION content>",
"capabilities": {
"auth": 1, "notes": 1, "courses": 1, "planner": 1,
"calendar_feeds": 1, "attachments": 1, "mcp": 1
}
}
Consumers call this before committing a user-entered API base URL.
Frontend implementation lives in each
frontend/*_app/lib/core/client.dart's verifyHandshake. See
editor_app.md#api-client-contract.
Request timing middleware
backend/notechondria/middleware.py::RequestTimingMiddleware.
-
Mounted first in
MIDDLEWAREso it captures full end-to-end wall time, not just view time. -
Emits one line per request on the
notechondria.accesslogger:<status> <duration_ms> <METHOD> <path> -
Level + ANSI color:
- 5xx OR
duration >= 2000 ms→logger.critical, red. - 4xx OR
duration >= 500 ms→logger.warning, yellow. - everything else →
logger.info, cyan.
- 5xx OR
-
Colors only emit when
stdout.isatty()(Jenkins/Render/Northflank captured stdout stays clean). Force-enable withDJANGO_ACCESS_LOG_FORCE_COLOR=1. -
Thresholds (
_WARN_MS,_CRITICAL_MS) are module-level constants.
API CORS middleware
backend/notechondria/middleware.py::ApiCorsMiddleware.
Adds Access-Control-Allow-* headers on /api/* and /media/* when
the request Origin matches ALLOWED_HOSTS, localhost/127.0.0.1,
or any CSRF_TRUSTED_ORIGINS entry's host. Short-circuits OPTIONS
with a 204.
Entrypoint (backend/entrypoint.sh)
Runs before gunicorn on every container start:
- Wait up to 300 s for PostgreSQL TCP.
- Validate DB credentials via
psycopg2.connect(). - Normalize Windows-style paths in
DJANGO_PRODUCTION_{STATIC,MEDIA}_ROOT. manage.py migrate --check— if non-zero, runmigrate --noinput.manage.py bootstrap_platform(seeresolve_codex_pathin notes.md).- Wipe all
creators.Sessionrows (0.1.65) — owner opted into refreshing tokens on deploy after moving to Northflank-only hosting. First-boot-safe: the inline Python block swallows the "table does not exist" error if migrations are still applying. manage.py collectstatic --noinput --clear+ a verification block that re-copies admin/DRF static assets if missing.- If
$# -eq 0, exec a default gunicorn (added 0.1.18 so Northflank'sconfigType: "default"works even without a CMD). Otherwiseexec "$@".
Deploy paths
- Render free-tier (backend only) —
render-deploy.shsources env, thendeployment/render/scripts/render_backend_start.shruns migrate + bootstrap + collectstatic + gunicorn. - Docker / Jenkins (full-stack self-hosted) —
backend/docker-compose.yml+ gateway nginx;deployment/jenkins/scripts/holds prep/test/deploy helpers. - Northflank —
northflank.jsontemplate provisions a PostgreSQL addon + a Combined Service built frombackend/Dockerfile;configType: "default"relies on the entrypoint's gunicorn fallback. See deployment/northflank.md.
Tests
- Run:
DJANGO_SETTINGS_MODULE=notechondria.settings_test python manage.py test(frombackend/). settings_test.pymust keep a non-emptySECRET_KEY— index.md §0 rule.- See testing/backend_test_plan.md for the detailed plan.
Known drift and open work
See index.md §6. Backend-specific live items:
dj-database-urlis required at runtime whenDATABASE_URLis set — that's the case for Render and Northflank. Kept inrequirements.txt(not render-only) after the 0.1.18 Northflank deploy crash.manage.py migrate --checkon each boot occasionally warns"Your models in app(s): 'notes' have changes that are not yet reflected in a migration"— cosmetic, non-blocking, models and migrations were last edited in the same commit.