0.1.90 — per-app OAuth redirect, MCP skill.md, custom note meta, experimental GitHub sync

Per-app OAuth redirect URI

  • Issue. OAuthConfigApiView returned a single global redirect_uri, so portal_app and planner_app sign-in always redirected the user back to the editor host instead of their own app.
  • Fix. New env vars GOOGLE_AUTHORIZED_REDIRECT_URIS and GITHUB_AUTHORIZED_REDIRECT_URIS (comma-separated). The OAuth config and login/bind endpoints now match the request Origin (or Referer) against each entry's host and return the matching URI. Falls back to the legacy single-value env var when nothing matches.
  • Backend touchpoints. creators/api.py (_request_origin, _pick_redirect_uri, OAuthConfigApiView, GoogleOAuthApiView, GitHubOAuthApiView, BindGoogleApiView, BindGithubApiView), notechondria/settings.py.

MCP skill editor (skill.md)

  • Backend. Added Creator.mcp_skill_md (TextField, blank). Wired into SettingsSerializer (read+write). The MCP initialize JSON-RPC response surfaces it as the instructions field so MCP-connected agents read the user's import/export playbook on connect. Migration: creators/0029_creator_mcp_skill_md_githubintegration.py.
  • Frontend (editor only this round). New _McpSkillSection in editor_app/lib/modules/settings_sections.dart, mounted under Settings → API settings beneath the API-key card. Includes a monospace text area, Save (PATCH /api/v1/settings/) and Copy buttons, and an "unsaved changes" hint.
  • Plumbing. editor_app/lib/core/build_helpers.dart adds onSaveMcpSkill; merges the saved value into _settings so the UI reflects round-trip without a refetch.

Custom note meta variables

  • Backend. Added Note.custom_meta (TextField, blank). Surfaced in NoteDetailSerializer.custom_meta and accepted by NoteWriteSerializer
    • NoteByUuidApiView.update / NoteDetailApiView.update. Migration: notes/0018_note_custom_meta.py.
  • Frontend (editor only this round). _NoteMetadataDialog in editor_app/lib/modules/note_metadata.dart adds an expandable "Custom meta variables" section: each row is a (key, value) TextField pair with delete; "Add variable" appends. Empty keys are dropped on save. Stored as a JSON object string.
  • Save path. note_editor.dart now sends custom_meta as a separate field on onSave(...), removes it from metadata_json before encoding so the same key/value isn't double-stored.

Experimental: GitHub data-sync (export-only)

  • Goal. A user can git clone their own repo and recover the full account state if our server is wiped. Static assets (avatars, attachments, cover images) stay on our CDN; everything else (Creator profile, app settings, MCP skill, courses, notes incl. blocks/custom meta, planner events, calendar feeds) materializes into the repo.
  • Model. New creators.GithubIntegration (one-to-one with Creator): installation_id, account_login, repo_full_name, repo_default_branch, access_token + expiry, last_push_at, last_push_sha, last_error.
  • Service. creators.services.github_syncmaterialize(creator) builds the file tree (profile/, courses/, notes/<uuid>.md + <uuid>.meta.json, planner/events.json, planner/feeds.json, manifest.json, README.md); commit_and_push(integration, files) PUTs each path via the GitHub Contents API. _refresh_installation_token raises a scaffold error until pyjwt + cryptography are added to requirements.txt.
  • API. GET/DELETE /api/v1/integrations/github/status/, POST /api/v1/integrations/github/callback/, POST /api/v1/integrations/github/push/.
  • Frontend. _ApiSettingsPage adds an "Experimental — GitHub Sync" card with disabled "Connect to GitHub" button and a pointer to docs/integrations/github-sync.md.
  • Doc. docs/integrations/github-sync.md covers required env vars, repo layout, restore flow, and known gaps.

Backend settings to add (operator action)

GOOGLE_AUTHORIZED_REDIRECT_URIS=https://editor.example.com/,https://portal.example.com/,https://planner.example.com/
GITHUB_AUTHORIZED_REDIRECT_URIS=https://editor.example.com/,https://portal.example.com/,https://planner.example.com/
GITHUB_DATA_SYNC_APP_NAME=notechondria-data-sync
GITHUB_DATA_SYNC_APP_CLIENT_ID=...
GITHUB_DATA_SYNC_APP_CLIENT_SECRET=...
GITHUB_DATA_SYNC_APP_PRIVATE_KEY=...
GITHUB_DATA_SYNC_APP_INSTALL_URL=https://github.com/apps/notechondria-data-sync/installations/new

Each redirect URI must be pre-registered in the corresponding OAuth provider console. Run migrations after deploy:

python manage.py migrate creators
python manage.py migrate notes

Verification

  • python manage.py makemigrations --dry-run --check against the changed apps reports no model drift caused by this round (existing drift on session.id / noteattachment.id is pre-existing user work, not touched).
  • python manage.py test notes.tests creators.tests.OAuthBindRejectionTests creators.tests.CreatorModelTests creators.tests.RegistrationFlowTests creators.tests.PasswordResetFlowTests creators.tests.InvitationCodeTests creators.tests.VerificationCodeModelTests: 82 / 88 pass; 6 pre-existing failures in NoteAttachmentByUuidApiTests are unrelated (the user has unstaged M backend/notes/tests.py).
  • Smoke-test of _pick_redirect_uri confirmed editor / portal / planner Origin headers each map to their own URI; unknown origins fall through to the first allowed entry.
  • Smoke-test of SettingsSerializer.to_representation confirmed mcp_skill_md round-trips.
  • Smoke-test of from notechondria import api_urls after wiring the three new GitHub-sync routes: 64 routes load cleanly.

Known gaps deferred to a later round

  • Settings UI parity. The _McpSkillSection, custom-meta expandable list, and Experimental GitHub Sync card are wired in editor_app only. portal_app and planner_app still need the same widgets added under their respective settings pages.
  • GitHub sync — actual push. _refresh_installation_token raises until pyjwt + cryptography ship; "Connect to GitHub" button stays disabled. Frontend repo-picker UI still TODO.
  • Restore tooling. Read-side scripts (clone → POST settings → POST notes) not built yet.