From fe5ada615c70ae0d788a1fb6411eddc1800955d8 Mon Sep 17 00:00:00 2001 From: Aly Raffauf Date: Wed, 6 May 2026 10:45:42 -0400 Subject: [PATCH] move backlink filters to constellation params --- core/constellation.py | 5 +++++ core/resolver.py | 7 +++---- tui/screens/sysop/delete.py | 12 +++++++++--- web/src/lib/atproto.ts | 2 ++ web/src/lib/deletebbs.ts | 16 ++++++++++++---- web/src/lib/news.ts | 13 ++++++++----- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/core/constellation.py b/core/constellation.py index 89af789..244d77b 100644 --- a/core/constellation.py +++ b/core/constellation.py @@ -13,6 +13,7 @@ async def get_backlinks( source: str, limit: int = 50, cursor: str | None = None, + did: str | None = None, ) -> BacklinksResponse: """Query Constellation for records that link to a subject.""" params: dict[str, str | int] = { @@ -22,6 +23,8 @@ async def get_backlinks( } if cursor is not None: params["cursor"] = cursor + if did is not None: + params["did"] = did resp = await client.get( f"{BASE_URL}/blue.microcosm.links.getBacklinks", params=params, @@ -75,6 +78,7 @@ async def get_root_posts( scope_uri: str, limit: int = 50, cursor: str | None = None, + did: str | None = None, ) -> BacklinksResponse: """Get root posts (threads or news) pointing at a scope (board or site).""" return await get_backlinks( @@ -83,6 +87,7 @@ async def get_root_posts( source=f"{lexicon.POST}:scope", limit=limit, cursor=cursor, + did=did, ) diff --git a/core/resolver.py b/core/resolver.py index 4f910d0..328621a 100644 --- a/core/resolver.py +++ b/core/resolver.py @@ -62,7 +62,7 @@ async def _resolve_bbs(client: httpx.AsyncClient, handle: str) -> BBS: board_tasks.append( get_record(client, parsed.did, parsed.collection, parsed.rkey) ) - news_task = get_root_posts(client, site_uri) + news_task = get_root_posts(client, site_uri, did=identity.did) results = await asyncio.gather(*board_tasks, news_task, return_exceptions=True) board_records = results[: len(board_uris)] @@ -83,12 +83,11 @@ async def _resolve_bbs(client: httpx.AsyncClient, handle: str) -> BBS: ) ) - # Hydrate news posts (only from the sysop's repo) + # Hydrate news posts (only from the sysop's repo — server-side filtered) if isinstance(news_result, BaseException): news_records = [] else: - sysop_news = [ref for ref in news_result.records if ref.did == identity.did] - news_records = await get_records_batch(client, sysop_news) + news_records = await get_records_batch(client, news_result.records) news = [post_from_record(record, identity) for record in news_records] news.sort(key=lambda post: post.created_at, reverse=True) diff --git a/tui/screens/sysop/delete.py b/tui/screens/sysop/delete.py index 256a41a..c57907b 100644 --- a/tui/screens/sysop/delete.py +++ b/tui/screens/sysop/delete.py @@ -57,15 +57,21 @@ class SysopDeleteScreen(Screen): # Delete sysop's news posts (posts scoped to site) site_uri = make_at_uri(session["did"], lexicon.SITE, "self") try: - backlinks = await get_root_posts(client, site_uri) - for ref in backlinks.records: - if ref.did == session["did"]: + cursor: str | None = None + while True: + backlinks = await get_root_posts( + client, site_uri, limit=100, cursor=cursor, did=session["did"] + ) + for ref in backlinks.records: try: await delete_record( client, session, lexicon.POST, ref.rkey, updater ) except Exception: failed.append(f"post/{ref.rkey}") + cursor = backlinks.cursor + if not cursor: + break except Exception: failed.append("news lookup") diff --git a/web/src/lib/atproto.ts b/web/src/lib/atproto.ts index 3a86ee6..bcec9cf 100644 --- a/web/src/lib/atproto.ts +++ b/web/src/lib/atproto.ts @@ -213,9 +213,11 @@ export async function getBacklinks( source: string, limit = 50, cursor?: string, + did?: string, ): Promise { let url = `${CONSTELLATION}/blue.microcosm.links.getBacklinks?subject=${encodeURIComponent(subject)}&source=${encodeURIComponent(source)}&limit=${limit}`; if (cursor) url += `&cursor=${encodeURIComponent(cursor)}`; + if (did) url += `&did=${encodeURIComponent(did)}`; return fetchJson(url); } diff --git a/web/src/lib/deletebbs.ts b/web/src/lib/deletebbs.ts index 7c851c9..e660e12 100644 --- a/web/src/lib/deletebbs.ts +++ b/web/src/lib/deletebbs.ts @@ -28,16 +28,24 @@ export async function deleteBBS(agent: Client, did: string, pdsUrl: string) { // Delete sysop's news posts (posts scoped to the site) const siteUri = makeAtUri(did, SITE, "self"); try { - const backlinks = await getBacklinks(siteUri, `${POST}:scope`, 100); - for (const ref of backlinks.records) { - if (ref.did === did) { + let cursor: string | undefined; + do { + const backlinks = await getBacklinks( + siteUri, + `${POST}:scope`, + 100, + cursor, + did, + ); + for (const ref of backlinks.records) { try { await deleteRecord(agent, POST, ref.rkey); } catch { failed.push(`post/${ref.rkey}`); } } - } + cursor = backlinks.cursor; + } while (cursor); } catch { failed.push("news lookup"); } diff --git a/web/src/lib/news.ts b/web/src/lib/news.ts index 184d168..b70c5ae 100644 --- a/web/src/lib/news.ts +++ b/web/src/lib/news.ts @@ -8,13 +8,16 @@ import type { NewsPost } from "./bbs"; export async function fetchNews(bbsDid: string): Promise { const siteUri = makeAtUri(bbsDid, SITE, "self"); - const backlinks = await getBacklinks(siteUri, `${POST}:scope`, 50).catch( - () => null, - ); + const backlinks = await getBacklinks( + siteUri, + `${POST}:scope`, + 50, + undefined, + bbsDid, + ).catch(() => null); if (!backlinks) return []; - const sysopRefs = backlinks.records.filter((ref) => ref.did === bbsDid); - const records = await getRecordsBatch(sysopRefs); + const records = await getRecordsBatch(backlinks.records); const news: NewsPost[] = records .filter(isPostRecord) -- 2.51.2