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'(.*?)', 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