Conversation
Helpers for the admin views in subsequent commits: - change_public_url / is_public_url_available + RESERVED_PUBLIC_URLS - delete_site (soft: deleted=True, public_url cleared; pages/revisions preserved for a potential recovery path) - get_design / update_design (allowlists known fields so callers can splat form data without worrying about extra keys) - get_pages_for_export — latest revision per non-deleted page, joined with pages.name and revisions.created; used by /admin/export. First commit of M5. Admin views land in subsequent commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET renders the title / subtitle / email / security form pre-populated
from the site row; POST validates and patches via update_site, then
redirects to the site root. Both require auth.gate("admin") — an
anonymous GET redirects to /site/signin with return_to preserved, and
an anonymous POST gets a 401.
public_url is shown read-only on the settings page; changing it
belongs to /admin/change-site-address (Step 3 of this PR) which has
to coordinate with sessions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Change-site-address validates that the new slug is lowercase alphanumeric + dashes, not on the reserved list, and not already taken; the JSON probe shares the same checks so the form can call it live before submit. Empty new value clears public_url so the site falls back to its secret URL only. Successful change redirects to /admin/settings on the canonical URL — either the new subdomain or, when the slug was cleared, the secret- URL path — because the request's host no longer resolves after the DB update. Cookie domain is `.jottit.test` (set by Flask from SERVER_NAME), so the session survives the cross-subdomain hop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET renders the current-password + new-password form; POST verifies the current password via argon2, sets the new one, and redirects to /admin/settings. Wrong current password gets 401, empty new password gets 400. Distinct from the token-based recovery flow in /site/change-password: that path arrives via an email link with `?d=token` and no current password; this one is for a user who's already signed in. Sessions are keyed by site_id, not password, so the user stays signed in across the change (unlike the 2007 original whose session digest bound to the password and had to be re-issued). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full-fidelity port of the 13 design fields: four fonts, three colors, four sizes, plus hue and brightness. GET prefills from the design row; POST validates each field by type (hex colors, numeric sizes in [25, 500], hue in [0, 360], brightness in [0, 300]) and rejects fonts containing HTML metacharacters as a soft XSS guard. Empty form values leave the existing column untouched (update_design's sentinel is `None`, which the validator-then-coercer pipeline collapses to "not in the dict at all"). After a successful save the user is redirected back to /admin/design so they see the persisted values. JS-driven color picker / live preview waits for M9; the form is plain inputs for now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delete is soft: marks sites.deleted=True, clears public_url so the slug frees up, signs the visitor out of just that site, and redirects to the apex homepage (the site no longer resolves at its old URL). Pages and revisions stay on disk in case a future admin path wants to restore. site_resolver now treats deleted=True sites as unresolved — they disappear from both subdomain and secret-URL routes, same as if the slug had never existed. That's the change that makes "delete actually hides the site" end-to-end. Export builds a zip in memory with one .md per non-deleted page (latest revision only) and streams it as an attachment. Filename inside the zip swaps slashes/backslashes for underscores so a page name like `../oops` can't escape the flat namespace. Closes out M5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Port the admin section: settings, change site address (public_url), change password (signed-in), design (fonts/colors), soft delete, and a zipped-markdown export. Server-rendered throughout — JS deferred to M9.
Decisions
deleted=trueand clearspublic_urlso the slug frees up..mdbundle, one file per page (latest revision only).Test plan
change_public_url,is_public_url_available,delete_site,get_design,update_design,get_pages_for_export)/admin/settings/admin/change-site-address+/admin/url-available/admin/change-password(signed-in)/admin/design/admin/delete+/admin/export🤖 Generated with Claude Code