M4 Calendar: reminders form, invitations, short publish links, settings UX
Calendar settings (Settings > Calendars):
- Reminders are now {method, minutes}: Add-reminder opens a boxed form with a
method dropdown (Show an alert / Email me) + time dropdown, bell icons.
Backend normalizes the legacy list[int] transparently.
- New Invitations section (auto-add emailed events): policy radios, "Add new
events from" (My contacts / Anyone), "Add to calendar", mark-read+file toggle.
Persisted to user settings (inbound iMIP processing still deferred).
- Time zone options show GMT codes, e.g. "Central European Summer Time (GMT+2)".
- Default calendar now defaults to "Personal" instead of a generic placeholder.
- Colour picker is a circular-swatch-only dropdown (sized so swatches don't overlap).
- Delete calendar / remove subscription now confirm via a modal.
- Create form: dropped the breadcrumb; heading stays "Create calendar" even after
a Publish toggle auto-creates the calendar.
Publish links: replaced the 244-char vault-sealed token with a 16-char random
slug (secrets.token_urlsafe). New sdk.public_session() (privileged, non-RLS) lets
the public /public/{token} feed resolve the slug directly with explicit filters.
Settings shell: "<- Mail" is now a context-aware "<- Back" via ?from=; Calendar
opens Settings with from=/p/calendar so Back returns to the calendar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
This commit is contained in:
@@ -22,6 +22,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.accounts.ssrf import guarded_get
|
||||
from app.config import Settings, get_settings
|
||||
from app.db import session_scope
|
||||
from app.events import get_event_bus
|
||||
from app.identity.deps import current_user, require_csrf
|
||||
from app.indexer import blobstore
|
||||
@@ -95,6 +96,16 @@ class PluginSdk:
|
||||
across users (see :mod:`app.plugins.db`)."""
|
||||
return plugin_session(user_id)
|
||||
|
||||
def public_session(self) -> AbstractAsyncContextManager[AsyncSession]:
|
||||
"""A privileged, NON-RLS session for PUBLIC, unauthenticated endpoints that
|
||||
resolve their own opaque token and therefore must look up rows before any
|
||||
user is known (e.g. a public ``.ics`` feed keyed by an unguessable slug).
|
||||
|
||||
There is no row-level safety net here: EVERY query MUST filter explicitly by
|
||||
the token (or an id derived from it). Never use this for authenticated
|
||||
handlers — use :meth:`session` so RLS scopes queries to the user."""
|
||||
return session_scope()
|
||||
|
||||
def kv(self, user_id: uuid.UUID) -> PluginKvStore:
|
||||
"""The namespaced KV store for this plugin and ``user_id`` (SPEC §11)."""
|
||||
return PluginKvStore(self.name, user_id)
|
||||
|
||||
Reference in New Issue
Block a user