import { error } from "@sveltejs/kit"; import { ok } from "@atcute/client"; import { isBlob } from "@atcute/lexicons/interfaces"; import { mainSchema as uploadBlobSchema } from "@atcute/atproto/types/repo/uploadBlob"; import type { Did, ResourceUri } from "@atcute/lexicons/syntax"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { createClient } from "$lib/auth/agent"; import { awaitDeletedRecord, awaitIndexedOrSaved } from "$lib/api/awaitRecord"; import type { BobbinContext } from "$lib/api/client"; import { resolveMiniDoc } from "$lib/api/identity"; import { orNull } from "$lib/api/load"; import { getString, type RecordView, type StringRecord } from "$lib/api/records"; import { cidOf, nsidOf, recordKeyOf } from "$lib/api/syntax"; import { rkeyFromUri } from "$lib/api/uri"; import { deleteRecord, putRecord, type WrittenRecord } from "$lib/api/write"; const STRING_COLLECTION = nsidOf("org.tangled.snippet.string"); export interface StringSubject { did: Did; handle: string; pds: string; uri: ResourceUri; record: RecordView; } export const loadStringSubject = async ( ctx: BobbinContext, handle: string, rkey: string ): Promise => { const doc = await orNull(resolveMiniDoc(ctx, handle)); if (!doc) error(404, "String not found"); const uri = `at://${doc.did}/${STRING_COLLECTION}/${rkey}` as ResourceUri; const record = await orNull(getString(ctx, uri)); if (!record) error(404, "String not found"); return { did: doc.did as Did, handle: doc.handle, pds: doc.pds, uri, record }; }; // bobbin indexes the record only; the content blob lives on the owner's pds export const fetchStringContents = async ( fetch: typeof globalThis.fetch, { did, pds, record }: StringSubject ): Promise => { const url = new URL("/xrpc/com.atproto.sync.getBlob", pds); url.searchParams.set("did", did); const { content } = record.value.snippet; url.searchParams.set("cid", isBlob(content) ? content.ref.$link : content.cid); const res = await fetch(url); if (!res.ok) error(502, "String contents unavailable"); return res.text(); }; export const uploadStringContents = async ( agent: OAuthUserAgent, contents: string ): Promise => { const input = new Blob([contents], { type: "text/plain" }); const { blob } = await ok(createClient(agent).call(uploadBlobSchema, { input })); return blob; }; export const putString = async ( agent: OAuthUserAgent, bobbin: BobbinContext, rkey: string, record: StringRecord, swapRecord?: string ): Promise => { const written = await putRecord( agent, STRING_COLLECTION, recordKeyOf(rkey), record, swapRecord ? cidOf(swapRecord) : undefined ); await awaitIndexedOrSaved(bobbin, written); return written; }; export const deleteString = async ( agent: OAuthUserAgent, bobbin: BobbinContext, uri: ResourceUri, swapRecord?: string ): Promise => { await deleteRecord( agent, STRING_COLLECTION, rkeyFromUri(uri), swapRecord ? cidOf(swapRecord) : undefined ); return awaitDeletedRecord(bobbin, uri); };