"""Admin-side (write) page service. The public site only has one editable page — "About" — so this service is intentionally narrower than :class:`AdminPostsService`. The slug is a fixed literal (``"about"``) and cannot be changed through the admin. Only the title and body may be edited. Every write: - re-renders Markdown → sanitized HTML into ``body_html_cached`` so the public read path stays a single SELECT. - bumps ``updated_at``. - emits an ``AuditService`` ``page_updated`` event. - invalidates the public :class:`PageService` (and, defensively, the :class:`PostService`) cache so the next request sees the new copy. """ from __future__ import annotations from datetime import datetime, timezone from typing import Optional import structlog from sqlalchemy import Engine, text from app.models.entities import Page from app.models.mappers import row_to_page from app.services.audit import AuditService from app.services.markdown import MarkdownService from app.services.pages import PageService from app.services.posts import PostService _log = structlog.get_logger(__name__) # The single editable page's slug. Hard-coded here (not injected) so # the CLI contract is impossible to misuse — there is no way to point # this service at a different slug. ABOUT_SLUG: str = "about" class AdminPagesService: """Write-side service for the About page.""" def __init__( self, engine: Engine, markdown: MarkdownService, page_service: PageService, post_service: PostService, audit: AuditService, ) -> None: self._engine: Engine = engine self._markdown: MarkdownService = markdown self._page_service: PageService = page_service self._post_service: PostService = post_service self._audit: AuditService = audit # ------------------------------------------------------------------ # Reads # ------------------------------------------------------------------ def get_about(self) -> Optional[Page]: """Return the current About page row, or ``None`` if absent.""" with self._engine.connect() as conn: row = conn.execute( text( "SELECT id, slug, title, body_md, body_html_cached," " updated_at, published" " FROM pages WHERE slug = :slug LIMIT 1" ), {"slug": ABOUT_SLUG}, ).mappings().first() return row_to_page(row) if row is not None else None # ------------------------------------------------------------------ # Writes # ------------------------------------------------------------------ def update_about( self, *, title: str, body_md: str, actor_user_id: int, ) -> Optional[Page]: """Update the About page's title + body. Slug is immutable — the admin form does not expose it. """ existing = self.get_about() if existing is None: return None clean_title = (title or "").strip() clean_body = body_md or "" body_html = self._markdown.render(clean_body) now_iso = datetime.now(timezone.utc).isoformat() with self._engine.begin() as conn: conn.execute( text( "UPDATE pages" " SET title = :title, body_md = :body_md," " body_html_cached = :body_html," " updated_at = :updated_at" " WHERE slug = :slug" ), { "title": clean_title, "body_md": clean_body, "body_html": body_html, "updated_at": now_iso, "slug": ABOUT_SLUG, }, ) self._audit.record( "page_updated", user_id=actor_user_id, detail={"slug": ABOUT_SLUG}, ) self._page_service.invalidate_all() self._post_service.invalidate_all() return self.get_about()