Retro Bulletin Board Systems on atproto. Web app and TUI. lazy mirror of alyraffauf/atbbs atbbs.xyz
forums python tui atproto bbs
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 116 lines 2.8 kB view raw
1/** Resolve a handle to a fully hydrated BBS via Slingshot/Constellation. */ 2 3import { 4 getRecord, 5 resolveIdentity, 6 type MiniDoc, 7 type ATRecord, 8} from "./atproto"; 9import { queryClient } from "./queryClient"; 10import { SITE } from "./lexicon"; 11import { parseAtUri } from "./util"; 12import { isBoardRecord, isSiteRecord } from "./recordGuards"; 13 14export class BBSNotFoundError extends Error {} 15export class NoBBSError extends Error {} 16 17export interface Board { 18 slug: string; 19 name: string; 20 description: string; 21 createdAt: string; 22 updatedAt?: string; 23} 24 25export interface PostAttachment { 26 file: { ref: { $link: string } }; 27 name: string; 28} 29 30export interface NewsPost { 31 uri: string; 32 rkey: string; 33 title: string; 34 body: string; 35 createdAt: string; 36 attachments?: PostAttachment[]; 37} 38 39export interface Site { 40 name: string; 41 description: string; 42 intro: string; 43 boards: Board[]; 44 createdAt: string; 45 updatedAt?: string; 46} 47 48export interface BBS { 49 identity: MiniDoc; 50 site: Site; 51} 52 53export function invalidateAllBBSCaches() { 54 queryClient.invalidateQueries({ queryKey: ["bbs"] }); 55 queryClient.invalidateQueries({ queryKey: ["bbs-moderation"] }); 56 queryClient.invalidateQueries({ queryKey: ["sysop-moderation"] }); 57} 58 59export async function resolveBBS(handle: string): Promise<BBS> { 60 let identity: MiniDoc; 61 try { 62 identity = await resolveIdentity(handle); 63 } catch { 64 throw new BBSNotFoundError(`Could not resolve handle: ${handle}`); 65 } 66 if (!identity.pds) { 67 throw new BBSNotFoundError(`No PDS for ${handle}`); 68 } 69 70 let siteRecord: ATRecord; 71 try { 72 siteRecord = await getRecord(identity.did, SITE, "self"); 73 } catch { 74 throw new NoBBSError(`${handle} isn't running a BBS.`); 75 } 76 77 if (!isSiteRecord(siteRecord)) { 78 throw new NoBBSError(`${handle} has an invalid site record.`); 79 } 80 const siteValue = siteRecord.value; 81 const boardUris: string[] = siteValue.boards ?? []; 82 83 const boardResults = await Promise.allSettled( 84 boardUris.map((uri) => { 85 const parsed = parseAtUri(uri); 86 return getRecord(parsed.did, parsed.collection, parsed.rkey); 87 }), 88 ); 89 90 const boards: Board[] = []; 91 boardResults.forEach((result, index) => { 92 if (result.status !== "fulfilled") return; 93 if (!isBoardRecord(result.value)) return; 94 const board = result.value.value; 95 const parsed = parseAtUri(boardUris[index]); 96 boards.push({ 97 slug: parsed.rkey, 98 name: board.name, 99 description: board.description, 100 createdAt: board.createdAt, 101 updatedAt: board.updatedAt, 102 }); 103 }); 104 105 return { 106 identity, 107 site: { 108 name: siteValue.name, 109 description: siteValue.description, 110 intro: siteValue.intro, 111 boards, 112 createdAt: siteValue.createdAt ?? "", 113 updatedAt: siteValue.updatedAt, 114 }, 115 }; 116}