Files
rose-ash/blog/bp/blog/services/posts_data.py
giles f551fc7453
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 34m5s
Convert last Python fragment handlers to SX defhandlers: 100% declarative fragment API
- Add dict recursion to _convert_result for service methods returning dict[K, list[DTO]]
- New container-cards.sx: parses post_ids/slugs, calls confirmed-entries-for-posts, emits card-widget markers
- New account-page.sx: dispatches on slug for tickets/bookings panels with status pills and empty states
- Fix blog _parse_card_fragments to handle SxExpr via str() cast
- Remove events Python fragment handlers and simplify app.py to plain auto_mount

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:42:19 +00:00

135 lines
4.1 KiB
Python

import re
from ..ghost_db import DBClient # adjust import path
from shared.infrastructure.data_client import fetch_data
from shared.infrastructure.fragments import fetch_fragment
from quart import g
async def posts_data(
session,
page, search, sort, selected_tags, selected_authors, liked,
drafts=False, drafts_user_id=None, count_drafts_for_user_id=None,
selected_groups=(),
):
client = DBClient(session)
# --- Tag-group resolution ---
tag_groups = await client.list_tag_groups_with_counts()
# Collect all assigned tag IDs across groups
all_assigned_tag_ids = []
for grp in tag_groups:
all_assigned_tag_ids.extend(grp["tag_ids"])
# Build slug-lookup for groups
group_by_slug = {grp["slug"]: grp for grp in tag_groups}
# Resolve selected group → post filtering
# Groups and tags are mutually exclusive — groups override tags when set
effective_tags = selected_tags
etc_mode_tag_ids = None # set when "etc" is selected
if selected_groups:
group_slug = selected_groups[0]
if group_slug == "etc":
# etc = posts NOT covered by any group (includes untagged)
etc_mode_tag_ids = all_assigned_tag_ids
effective_tags = ()
elif group_slug in group_by_slug:
effective_tags = tuple(group_by_slug[group_slug]["tag_slugs"])
# Compute "etc" virtual group
etc_count = await client.count_etc_posts(all_assigned_tag_ids)
if etc_count > 0 or (selected_groups and selected_groups[0] == "etc"):
tag_groups.append({
"id": None,
"name": "etc",
"slug": "etc",
"feature_image": None,
"colour": None,
"sort_order": 999999,
"post_count": etc_count,
"tag_slugs": [],
"tag_ids": [],
})
posts, pagination = await client.list_posts(
limit=10,
page=page,
selected_tags=effective_tags,
selected_authors=selected_authors,
search=search,
drafts=drafts,
drafts_user_id=drafts_user_id,
exclude_covered_tag_ids=etc_mode_tag_ids,
)
# Get all post IDs in this batch
post_ids = [p["id"] for p in posts]
# Add is_liked field to each post for current user
if g.user and post_ids:
liked_ids_list = await fetch_data("likes", "liked-ids", params={
"user_id": g.user.id, "target_type": "post",
}, required=False) or []
liked_post_ids = set(liked_ids_list)
for post in posts:
post["is_liked"] = post["id"] in liked_post_ids
else:
for post in posts:
post["is_liked"] = False
# Fetch card decoration fragments from events
card_widgets_html = {}
if post_ids:
post_slugs = [p.get("slug", "") for p in posts]
cards_html = await fetch_fragment("events", "container-cards", params={
"post_ids": ",".join(str(pid) for pid in post_ids),
"post_slugs": ",".join(post_slugs),
})
if cards_html:
card_widgets_html = _parse_card_fragments(cards_html)
tags=await client.list_tags(
limit=50000
)
authors=await client.list_authors(
limit=50000
)
# Draft count for the logged-in user (None → admin sees all)
draft_count = 0
if count_drafts_for_user_id is not False:
draft_count = await client.count_drafts(user_id=count_drafts_for_user_id)
return {
"posts": posts,
"page": pagination.get("page", page),
"total_pages": pagination.get("pages", 1),
"search_count": pagination.get("search_count"),
"tags": tags,
"authors": authors,
"draft_count": draft_count,
"tag_groups": tag_groups,
"selected_groups": selected_groups,
"card_widgets_html": card_widgets_html,
}
# Regex to extract per-post blocks delimited by comment markers
_CARD_MARKER_RE = re.compile(
r'<!-- card-widget:(\d+) -->(.*?)<!-- /card-widget:\1 -->',
re.DOTALL,
)
def _parse_card_fragments(html: str) -> dict[str, str]:
"""Parse the container-cards fragment into {post_id_str: html} dict."""
result = {}
for m in _CARD_MARKER_RE.finditer(str(html)):
post_id_str = m.group(1)
inner = m.group(2).strip()
if inner:
result[post_id_str] = inner
return result