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 — use typing.Optional. See index.md §0.
  • Settings: backend/notechondria/settings.py for prod, backend/notechondria/settings_test.py for test (in-memory sqlite, MD5 password hasher, LOGGING stubbed).
  • ASGI/WSGI: backend/notechondria/wsgi.py, launched by gunicorn via backend/entrypoint.sh (which also runs migrate --check, seeds the platform, collects static, then exec "$@").
  • Middleware order (settings.py MIDDLEWARE): RequestTimingMiddlewareSecurityMiddlewareWhiteNoiseMiddlewareSessionMiddlewareApiCorsMiddlewareCommonMiddlewareCsrfViewMiddlewareAuthenticationMiddlewareMessageMiddlewareXFrameOptionsMiddleware.

Django apps

Four project-local apps registered in INSTALLED_APPS. Per-app docs cover models, views, services, and example request/response payloads:

AppPathRolePer-app doc
creatorsbackend/creators/Accounts, OAuth, API keys, settings, identity-code verification.creators.md
notesbackend/notes/Notes, courses, planner events, calendar feeds, recycle bin, attachments.notes.md
mcpbackend/mcp/Model-Context-Protocol server: 21 tools, API-key auth, 39 tests.mcp.md
gptutilsbackend/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.

RouteFilePurpose
/urls.pyapi_views.health_checkLiveness probe.
/handshake/urls.pyapi_views.handshakeHandshake (also at /api/v1/handshake/).
/api/v1/api_urls.pyAll application endpoints, including /auth/sessions/ (multi-device manager, see creators.md).
/mcp/mcp/urls.pyModel-Context-Protocol server.
/auth/google/callbackurls.pyapi_views.oauth_callbackGoogle OAuth redirect.
/auth/github/callbackurls.pyapi_views.oauth_callbackGitHub 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. Includes notes/services.py helpers (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 MIDDLEWARE so it captures full end-to-end wall time, not just view time.

  • Emits one line per request on the notechondria.access logger:

    <status>  <duration_ms>  <METHOD>  <path>
    
  • Level + ANSI color:

    • 5xx OR duration >= 2000 mslogger.critical, red.
    • 4xx OR duration >= 500 mslogger.warning, yellow.
    • everything else → logger.info, cyan.
  • Colors only emit when stdout.isatty() (Jenkins/Render/Northflank captured stdout stays clean). Force-enable with DJANGO_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:

  1. Wait up to 300 s for PostgreSQL TCP.
  2. Validate DB credentials via psycopg2.connect().
  3. Normalize Windows-style paths in DJANGO_PRODUCTION_{STATIC,MEDIA}_ROOT.
  4. manage.py migrate --check — if non-zero, run migrate --noinput.
  5. manage.py bootstrap_platform (see resolve_codex_path in notes.md).
  6. Wipe all creators.Session rows (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.
  7. manage.py collectstatic --noinput --clear + a verification block that re-copies admin/DRF static assets if missing.
  8. If $# -eq 0, exec a default gunicorn (added 0.1.18 so Northflank's configType: "default" works even without a CMD). Otherwise exec "$@".

Deploy paths

  • Render free-tier (backend only)render-deploy.sh sources env, then deployment/render/scripts/render_backend_start.sh runs migrate + bootstrap + collectstatic + gunicorn.
  • Docker / Jenkins (full-stack self-hosted)backend/docker-compose.yml + gateway nginx; deployment/jenkins/scripts/ holds prep/test/deploy helpers.
  • Northflanknorthflank.json template provisions a PostgreSQL addon + a Combined Service built from backend/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 (from backend/).
  • settings_test.py must keep a non-empty SECRET_KEYindex.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-url is required at runtime when DATABASE_URL is set — that's the case for Render and Northflank. Kept in requirements.txt (not render-only) after the 0.1.18 Northflank deploy crash.
  • manage.py migrate --check on 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.