"""Account pages blueprint. Moved from federation/bp/auth — newsletters, fragment pages (tickets, bookings). Mounted at root /. """ from __future__ import annotations from quart import ( Blueprint, request, make_response, redirect, g, ) from sqlalchemy import select from shared.models import UserNewsletter from shared.models.ghost_membership_entities import GhostNewsletter from shared.infrastructure.urls import login_url from shared.infrastructure.fragments import fetch_fragment, fetch_fragments from shared.sexp.helpers import sexp_response oob = { "oob_extends": "oob_elements.html", "extends": "_types/root/_index.html", "parent_id": "root-header-child", "child_id": "auth-header-child", "header": "_types/auth/header/_header.html", "parent_header": "_types/root/header/_header.html", "nav": "_types/auth/_nav.html", "main": "_types/auth/_main_panel.html", } def register(url_prefix="/"): account_bp = Blueprint("account", __name__, url_prefix=url_prefix) @account_bp.context_processor async def context(): events_nav, cart_nav, artdag_nav = await fetch_fragments([ ("events", "account-nav-item", {}), ("cart", "account-nav-item", {}), ("artdag", "nav-item", {}), ], required=False) return {"oob": oob, "account_nav": events_nav + cart_nav + artdag_nav} @account_bp.get("/") async def account(): from shared.browser.app.utils.htmx import is_htmx_request from shared.sexp.page import get_template_context from sexp.sexp_components import render_account_page, render_account_oob if not g.get("user"): return redirect(login_url("/")) ctx = await get_template_context() if not is_htmx_request(): html = await render_account_page(ctx) return await make_response(html) else: sexp_src = await render_account_oob(ctx) return sexp_response(sexp_src) @account_bp.get("/newsletters/") async def newsletters(): from shared.browser.app.utils.htmx import is_htmx_request if not g.get("user"): return redirect(login_url("/newsletters/")) result = await g.s.execute( select(GhostNewsletter).order_by(GhostNewsletter.name) ) all_newsletters = result.scalars().all() sub_result = await g.s.execute( select(UserNewsletter).where( UserNewsletter.user_id == g.user.id, ) ) user_subs = {un.newsletter_id: un for un in sub_result.scalars().all()} newsletter_list = [] for nl in all_newsletters: un = user_subs.get(nl.id) newsletter_list.append({ "newsletter": nl, "un": un, "subscribed": un.subscribed if un else False, }) from shared.sexp.page import get_template_context from sexp.sexp_components import render_newsletters_page, render_newsletters_oob ctx = await get_template_context() if not is_htmx_request(): html = await render_newsletters_page(ctx, newsletter_list) return await make_response(html) else: sexp_src = await render_newsletters_oob(ctx, newsletter_list) return sexp_response(sexp_src) @account_bp.post("/newsletter//toggle/") async def toggle_newsletter(newsletter_id: int): if not g.get("user"): return "", 401 result = await g.s.execute( select(UserNewsletter).where( UserNewsletter.user_id == g.user.id, UserNewsletter.newsletter_id == newsletter_id, ) ) un = result.scalar_one_or_none() if un: un.subscribed = not un.subscribed else: un = UserNewsletter( user_id=g.user.id, newsletter_id=newsletter_id, subscribed=True, ) g.s.add(un) await g.s.flush() from sexp.sexp_components import render_newsletter_toggle return sexp_response(render_newsletter_toggle(un)) # Catch-all for fragment-provided pages — must be last @account_bp.get("//") async def fragment_page(slug): from shared.browser.app.utils.htmx import is_htmx_request from quart import abort if not g.get("user"): return redirect(login_url(f"/{slug}/")) fragment_html = await fetch_fragment( "events", "account-page", params={"slug": slug, "user_id": str(g.user.id)}, ) if not fragment_html: abort(404) from shared.sexp.page import get_template_context from sexp.sexp_components import render_fragment_page, render_fragment_oob ctx = await get_template_context() if not is_htmx_request(): html = await render_fragment_page(ctx, fragment_html) return await make_response(html) else: sexp_src = await render_fragment_oob(ctx, fragment_html) return sexp_response(sexp_src) return account_bp