This repository has no description
1import { ok } from "@atcute/client";
2import { mainSchema as createRecordSchema } from "@atcute/atproto/types/repo/createRecord";
3import { mainSchema as deleteRecordSchema } from "@atcute/atproto/types/repo/deleteRecord";
4import type { Did, Nsid, RecordKey } from "@atcute/lexicons/syntax";
5import type { OAuthUserAgent } from "@atcute/oauth-browser-client";
6import { createClient } from "$lib/auth/agent";
7import { ClientResponseError, type BobbinContext } from "./client";
8import { jsonGet } from "./_request";
9import { httpStatusFor } from "./load";
10import { rkeyFromUri } from "./uri";
11import type * as ShTangledFeedGetStar from "./lexicons/types/sh/tangled/feed/getStar";
12import type * as ShTangledGraphFollow from "./lexicons/types/sh/tangled/graph/follow";
13import type * as ShTangledGraphGetFollow from "./lexicons/types/sh/tangled/graph/getFollow";
14import type * as ShTangledGraphVouch from "./lexicons/types/sh/tangled/graph/vouch";
15
16export type FollowRecord = ShTangledGraphFollow.Main;
17export type VouchRecord = ShTangledGraphVouch.Main;
18
19const FOLLOW_COLLECTION = "sh.tangled.graph.follow" as Nsid;
20const STAR_COLLECTION = "sh.tangled.feed.star" as Nsid;
21
22// 404 means no follow exists, other errors propagate
23export const getFollowRkey = async (
24 ctx: BobbinContext,
25 actor: string,
26 subject: string
27): Promise<string | null> => {
28 try {
29 const { uri } = await jsonGet<ShTangledGraphGetFollow.$output>(
30 ctx,
31 "sh.tangled.graph.getFollow",
32 { actor: actor as Did, subject: subject as Did } satisfies ShTangledGraphGetFollow.$params
33 );
34 return rkeyFromUri(uri);
35 } catch (cause) {
36 if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null;
37 throw cause;
38 }
39};
40
41// 404 means no star exists, other errors propagate
42export const getStarRkey = async (
43 ctx: BobbinContext,
44 actor: string,
45 subject: string
46): Promise<string | null> => {
47 try {
48 const { uri } = await jsonGet<ShTangledFeedGetStar.$output>(ctx, "sh.tangled.feed.getStar", {
49 actor: actor as Did,
50 subject
51 } satisfies ShTangledFeedGetStar.$params);
52 return rkeyFromUri(uri);
53 } catch (cause) {
54 if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null;
55 throw cause;
56 }
57};
58
59const createGenericRecord = async <T extends { $type: string }>(
60 agent: OAuthUserAgent,
61 collection: Nsid,
62 record: T
63): Promise<string> => {
64 const rpc = createClient(agent);
65 const result = await ok(
66 rpc.call(createRecordSchema, {
67 input: { repo: agent.sub, collection, record }
68 })
69 );
70 return rkeyFromUri(result.uri);
71};
72
73const deleteGenericRecord = async (
74 agent: OAuthUserAgent,
75 collection: Nsid,
76 rkey: string
77): Promise<void> => {
78 const rpc = createClient(agent);
79 await ok(
80 rpc.call(deleteRecordSchema, {
81 input: { repo: agent.sub, collection, rkey: rkey as RecordKey }
82 })
83 );
84};
85
86export const createFollow = (agent: OAuthUserAgent, subject: string): Promise<string> =>
87 createGenericRecord(agent, FOLLOW_COLLECTION, {
88 $type: "sh.tangled.graph.follow",
89 subject: subject as Did,
90 createdAt: new Date().toISOString()
91 });
92
93export const deleteFollow = (agent: OAuthUserAgent, rkey: string): Promise<void> =>
94 deleteGenericRecord(agent, FOLLOW_COLLECTION, rkey);
95
96export const createStar = (agent: OAuthUserAgent, repoDid: string): Promise<string> =>
97 createGenericRecord(agent, STAR_COLLECTION, {
98 $type: "sh.tangled.feed.star",
99 subject: {
100 $type: "sh.tangled.feed.star#repo",
101 did: repoDid as Did
102 },
103 createdAt: new Date().toISOString()
104 });
105
106export const deleteStar = (agent: OAuthUserAgent, rkey: string): Promise<void> =>
107 deleteGenericRecord(agent, STAR_COLLECTION, rkey);