Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201import type { Did, Nsid } from "@atcute/lexicons/syntax";import type { OAuthUserAgent } from "@atcute/oauth-browser-client";import { ClientResponseError, type BobbinContext } from "$lib/api/client";import { jsonGet } from "$lib/api/_request";import { httpStatusFor } from "$lib/api/load";import { didFromUri, rkeyFromUri } from "$lib/api/uri";import { createRecord, deleteRecord, putRecord } from "$lib/api/write";import type { VouchRecord } from "$lib/api/records";import type * as ShTangledFeedGetStar from "$lib/api/lexicons/types/sh/tangled/feed/getStar";import type * as ShTangledGraphFollow from "$lib/api/lexicons/types/sh/tangled/graph/follow";import type * as ShTangledGraphGetFollow from "$lib/api/lexicons/types/sh/tangled/graph/getFollow";import type * as ShTangledGraphListNetworkVouches from "$lib/api/lexicons/types/sh/tangled/graph/listNetworkVouches";
export type FollowRecord = ShTangledGraphFollow.Main;export type { VouchRecord };
const FOLLOW_COLLECTION = "sh.tangled.graph.follow" as Nsid;const STAR_COLLECTION = "sh.tangled.feed.star" as Nsid;const VOUCH_COLLECTION = "sh.tangled.graph.vouch" as Nsid;export const getFollowRkey = async ( ctx: BobbinContext, actor: string, subject: string): Promise<string | null> => { try { const { uri } = await jsonGet<ShTangledGraphGetFollow.$output>( ctx, "sh.tangled.graph.getFollow", { actor: actor as Did, subject: subject as Did } satisfies ShTangledGraphGetFollow.$params ); return rkeyFromUri(uri); } catch (cause) { if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null; throw cause; }};
// 404 means no star exists, other errors propagateexport const getStarRkey = async ( ctx: BobbinContext, actor: string, subject: string): Promise<string | null> => { try { const { uri } = await jsonGet<ShTangledFeedGetStar.$output>( ctx, "sh.tangled.feed.getStar", { actor: actor as Did, subject } satisfies ShTangledFeedGetStar.$params ); return rkeyFromUri(uri); } catch (cause) { if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null; throw cause; }};
export const createFollow = async (agent: OAuthUserAgent, subject: string): Promise<string> => { const { uri } = await createRecord(agent, FOLLOW_COLLECTION, { $type: "sh.tangled.graph.follow", subject: subject as Did, createdAt: new Date().toISOString() }); return rkeyFromUri(uri);};
export const deleteFollow = async (agent: OAuthUserAgent, rkey: string): Promise<void> => { await deleteRecord(agent, FOLLOW_COLLECTION, rkey);};
export const createStar = async (agent: OAuthUserAgent, repoDid: string): Promise<string> => { const { uri } = await createRecord(agent, STAR_COLLECTION, { $type: "sh.tangled.feed.star", subject: { $type: "sh.tangled.feed.star#repo", did: repoDid as Did }, createdAt: new Date().toISOString() }); return rkeyFromUri(uri);};
export const deleteStar = async (agent: OAuthUserAgent, rkey: string): Promise<void> => { await deleteRecord(agent, STAR_COLLECTION, rkey);};const SUBSCRIPTION_COLLECTION = "org.tangled.feed.subscription" as Nsid;
export const createSubscription = async ( agent: OAuthUserAgent, repoDid: string): Promise<string> => { const { uri } = await createRecord(agent, SUBSCRIPTION_COLLECTION, { $type: SUBSCRIPTION_COLLECTION, subject: { $type: "org.tangled.feed.subscription#repo", did: repoDid as Did }, createdAt: new Date().toISOString() }); return rkeyFromUri(uri);};
export const deleteSubscription = async (agent: OAuthUserAgent, rkey: string): Promise<void> => { await deleteRecord(agent, SUBSCRIPTION_COLLECTION, rkey);};
export const createEntitySubscription = async ( agent: OAuthUserAgent, entityUri: string): Promise<string> => { const { uri } = await createRecord(agent, SUBSCRIPTION_COLLECTION, { $type: SUBSCRIPTION_COLLECTION, subject: { $type: "org.tangled.feed.subscription#uri", uri: entityUri }, createdAt: new Date().toISOString() }); return rkeyFromUri(uri);};
// 404 means no subscription exists, other errors propagate.export const getSubscriptionRkey = async ( ctx: BobbinContext, actor: string, subject: string): Promise<string | null> => { try { const { uri } = await jsonGet<{ uri: string }>( ctx, "org.tangled.feed.subscription.getForActor", { actor: actor as Did, subject } ); return rkeyFromUri(uri); } catch (cause) { if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null; throw cause; }};
// creating always mints a record and unsubscribe only deletes one, so// reuse an existing subscription instead of stackingexport const ensureEntitySubscription = async ( agent: OAuthUserAgent, ctx: BobbinContext, actor: string, entityUri: string): Promise<string> => { const existing = await getSubscriptionRkey(ctx, actor, entityUri); return existing ?? (await createEntitySubscription(agent, entityUri));};
export type VouchKind = "vouch" | "denounce";
export interface VouchInput { kind: VouchKind; reason?: string; evidences?: string[];}
export const putVouch = async ( agent: OAuthUserAgent, subject: string, input: VouchInput): Promise<string> => { const { uri } = await putRecord(agent, VOUCH_COLLECTION, subject, { $type: "sh.tangled.graph.vouch", kind: input.kind, ...(input.reason ? { reason: input.reason } : {}), ...(input.evidences?.length ? { evidences: input.evidences } : {}), createdAt: new Date().toISOString() }); return rkeyFromUri(uri);};
export const deleteVouch = async (agent: OAuthUserAgent, subject: string): Promise<void> => { await deleteRecord(agent, VOUCH_COLLECTION, subject);};export const getViewerVouch = async ( ctx: BobbinContext, viewer: string, subject: string): Promise<VouchInput | null> => { const { items } = await jsonGet<ShTangledGraphListNetworkVouches.$output>( ctx, "sh.tangled.graph.listNetworkVouches", { actor: viewer as Did, limit: 100 } satisfies ShTangledGraphListNetworkVouches.$params ); const match = items.find( (item) => didFromUri(item.uri) === viewer && rkeyFromUri(item.uri) === subject ); if (!match) return null; const value = match.value as VouchRecord; return { kind: value.kind === "denounce" ? "denounce" : "vouch", reason: value.reason };};