0.1.90 — per-app OAuth redirect, MCP skill.md, custom note meta, experimental GitHub sync
Per-app OAuth redirect URI
- Issue.
OAuthConfigApiViewreturned a single globalredirect_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_URISandGITHUB_AUTHORIZED_REDIRECT_URIS(comma-separated). The OAuth config and login/bind endpoints now match the requestOrigin(orReferer) 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 intoSettingsSerializer(read+write). The MCPinitializeJSON-RPC response surfaces it as theinstructionsfield 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
_McpSkillSectionineditor_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.dartaddsonSaveMcpSkill; merges the saved value into_settingsso the UI reflects round-trip without a refetch.
Custom note meta variables
- Backend. Added
Note.custom_meta(TextField, blank). Surfaced inNoteDetailSerializer.custom_metaand accepted byNoteWriteSerializerNoteByUuidApiView.update/NoteDetailApiView.update. Migration:notes/0018_note_custom_meta.py.
- Frontend (editor only this round).
_NoteMetadataDialogineditor_app/lib/modules/note_metadata.dartadds 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.dartnow sendscustom_metaas a separate field ononSave(...), removes it frommetadata_jsonbefore encoding so the same key/value isn't double-stored.
Experimental: GitHub data-sync (export-only)
- Goal. A user can
git clonetheir 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_sync—materialize(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_tokenraises a scaffold error untilpyjwt+cryptographyare added torequirements.txt. - API.
GET/DELETE /api/v1/integrations/github/status/,POST /api/v1/integrations/github/callback/,POST /api/v1/integrations/github/push/. - Frontend.
_ApiSettingsPageadds an "Experimental — GitHub Sync" card with disabled "Connect to GitHub" button and a pointer todocs/integrations/github-sync.md. - Doc.
docs/integrations/github-sync.mdcovers 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 --checkagainst the changed apps reports no model drift caused by this round (existing drift onsession.id/noteattachment.idis 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 inNoteAttachmentByUuidApiTestsare unrelated (the user has unstagedM backend/notes/tests.py).- Smoke-test of
_pick_redirect_uriconfirmed 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_representationconfirmedmcp_skill_mdround-trips. - Smoke-test of
from notechondria import api_urlsafter 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 ineditor_apponly.portal_appandplanner_appstill need the same widgets added under their respective settings pages. - GitHub sync — actual push.
_refresh_installation_tokenraises untilpyjwt + cryptographyship; "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.