- OAuthCode model + migration for authorization code flow
- OAuth client blueprint (auto-registered for non-federation apps)
- Per-app first-party session cookies (fixes Safari ITP)
- /oauth/authorize endpoint support in URL helpers
- account_url() helper + Jinja global
- Templates: federation_url('/auth/...') → account_url('/...')
- Widget registry: account page links use account_url
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
91 lines
2.6 KiB
Python
91 lines
2.6 KiB
Python
"""Singleton widget registry for cross-domain UI composition.
|
|
|
|
Usage::
|
|
|
|
from shared.services.widget_registry import widgets
|
|
|
|
# Register at app startup (after domain services)
|
|
widgets.add_container_nav(NavWidget(...))
|
|
|
|
# Query in templates / context processors
|
|
for w in widgets.container_nav:
|
|
ctx = await w.context_fn(session, container_type="page", ...)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from shared.contracts.widgets import (
|
|
NavWidget,
|
|
CardWidget,
|
|
AccountPageWidget,
|
|
AccountNavLink,
|
|
)
|
|
|
|
|
|
class _WidgetRegistry:
|
|
"""Central registry holding all widget descriptors.
|
|
|
|
Widgets are added at startup and read at request time.
|
|
Properties return sorted-by-order copies.
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
self._container_nav: list[NavWidget] = []
|
|
self._container_card: list[CardWidget] = []
|
|
self._account_pages: list[AccountPageWidget] = []
|
|
self._account_nav: list[AccountNavLink] = []
|
|
|
|
# -- registration ---------------------------------------------------------
|
|
|
|
def add_container_nav(self, w: NavWidget) -> None:
|
|
self._container_nav.append(w)
|
|
|
|
def add_container_card(self, w: CardWidget) -> None:
|
|
self._container_card.append(w)
|
|
|
|
def add_account_page(self, w: AccountPageWidget) -> None:
|
|
self._account_pages.append(w)
|
|
# Auto-create a matching internal nav link
|
|
slug = w.slug
|
|
|
|
def _href(s=slug):
|
|
from shared.infrastructure.urls import account_url
|
|
return account_url(f"/{s}/")
|
|
|
|
self._account_nav.append(AccountNavLink(
|
|
label=w.label,
|
|
order=w.order,
|
|
href_fn=_href,
|
|
external=False,
|
|
))
|
|
|
|
def add_account_link(self, link: AccountNavLink) -> None:
|
|
self._account_nav.append(link)
|
|
|
|
# -- read access (sorted copies) ------------------------------------------
|
|
|
|
@property
|
|
def container_nav(self) -> list[NavWidget]:
|
|
return sorted(self._container_nav, key=lambda w: w.order)
|
|
|
|
@property
|
|
def container_cards(self) -> list[CardWidget]:
|
|
return sorted(self._container_card, key=lambda w: w.order)
|
|
|
|
@property
|
|
def account_pages(self) -> list[AccountPageWidget]:
|
|
return sorted(self._account_pages, key=lambda w: w.order)
|
|
|
|
@property
|
|
def account_nav(self) -> list[AccountNavLink]:
|
|
return sorted(self._account_nav, key=lambda w: w.order)
|
|
|
|
def account_page_by_slug(self, slug: str) -> AccountPageWidget | None:
|
|
for w in self._account_pages:
|
|
if w.slug == slug:
|
|
return w
|
|
return None
|
|
|
|
|
|
# Module-level singleton — import this everywhere.
|
|
widgets = _WidgetRegistry()
|