All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 34m5s
- 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>
135 lines
4.1 KiB
Python
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
|