const GRAPHQL_URL = process.env.GRAPHQL_URL || "https://quickslice-production-cc52.up.railway.app/graphql"; const DOCUMENT_QUERY = ` query GetDocument($handle: String!, $slug: String!) { networkSlicesToolsDocument( first: 1 where: { actorHandle: { eq: $handle } slug: { eq: $slug } } ) { edges { node { uri cid did title slug actorHandle blocks appBskyActorProfileByDid { displayName avatar { url } } } } } } `; export interface DocumentBlock { $type: string; text?: string; } export interface Document { uri: string; cid: string; did: string; title: string; slug: string; actorHandle: string; blocks: DocumentBlock[]; appBskyActorProfileByDid?: { displayName?: string; avatar?: { url: string; }; }; } export async function fetchDocument( handle: string, slug: string ): Promise { const res = await fetch(GRAPHQL_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: DOCUMENT_QUERY, variables: { handle, slug }, }), }); const json = await res.json(); if (json.errors) { throw new Error(json.errors[0].message); } const edges = json.data?.networkSlicesToolsDocument?.edges; if (!edges || edges.length === 0) { return null; } return edges[0].node; } export function extractPreview(blocks: DocumentBlock[], maxLength = 500): string { const textBlocks = blocks.filter( (b) => b.$type === "network.slices.tools.document#paragraph" && b.text ); let preview = ""; for (const block of textBlocks) { if (block.text) { preview += block.text + " "; } if (preview.length >= maxLength) break; } const trimmed = preview.trim(); if (trimmed.length > maxLength) { return trimmed.substring(0, maxLength) + "..."; } return trimmed; } // Lexicon queries const LEXICON_GRAPHQL_URL = process.env.LEXICON_GRAPHQL_URL || "https://quickslice-production-4b57.up.railway.app/graphql"; const LEXICON_QUERY = ` query GetLexicon($nsid: String!) { comAtprotoLexiconSchema(first: 1, where: { id: { eq: $nsid } }) { edges { node { id description defs actorHandle } } } } `; export interface Lexicon { id: string; description?: string; defs: Record; actorHandle: string; } export async function fetchLexicon(nsid: string): Promise { const res = await fetch(LEXICON_GRAPHQL_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: LEXICON_QUERY, variables: { nsid }, }), }); const json = await res.json(); if (json.errors) { throw new Error(json.errors[0].message); } const edges = json.data?.comAtprotoLexiconSchema?.edges; if (!edges || edges.length === 0) { return null; } return edges[0].node; } export function getLexiconType(lexicon: Lexicon): string { const main = lexicon.defs?.main as Record | undefined; if (main?.type && typeof main.type === "string") { return main.type; } return "unknown"; } export function getLexiconPreviewDef(lexicon: Lexicon): unknown { // Prefer defs.main, otherwise use first def if (lexicon.defs?.main) { return lexicon.defs.main; } const keys = Object.keys(lexicon.defs || {}); if (keys.length > 0) { return lexicon.defs[keys[0]]; } return {}; }