about summary refs log tree commit diff
path: root/src/state/queries/starter-packs.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/queries/starter-packs.ts')
-rw-r--r--src/state/queries/starter-packs.ts317
1 files changed, 317 insertions, 0 deletions
diff --git a/src/state/queries/starter-packs.ts b/src/state/queries/starter-packs.ts
new file mode 100644
index 000000000..241bc6419
--- /dev/null
+++ b/src/state/queries/starter-packs.ts
@@ -0,0 +1,317 @@
+import {
+  AppBskyActorDefs,
+  AppBskyFeedDefs,
+  AppBskyGraphDefs,
+  AppBskyGraphGetStarterPack,
+  AppBskyGraphStarterpack,
+  AtUri,
+  BskyAgent,
+} from '@atproto/api'
+import {StarterPackView} from '@atproto/api/dist/client/types/app/bsky/graph/defs'
+import {
+  QueryClient,
+  useMutation,
+  useQuery,
+  useQueryClient,
+} from '@tanstack/react-query'
+
+import {until} from 'lib/async/until'
+import {createStarterPackList} from 'lib/generate-starterpack'
+import {
+  createStarterPackUri,
+  httpStarterPackUriToAtUri,
+  parseStarterPackUri,
+} from 'lib/strings/starter-pack'
+import {invalidateActorStarterPacksQuery} from 'state/queries/actor-starter-packs'
+import {invalidateListMembersQuery} from 'state/queries/list-members'
+import {useAgent} from 'state/session'
+
+const RQKEY_ROOT = 'starter-pack'
+const RQKEY = (did?: string, rkey?: string) => {
+  if (did?.startsWith('https://') || did?.startsWith('at://')) {
+    const parsed = parseStarterPackUri(did)
+    return [RQKEY_ROOT, parsed?.name, parsed?.rkey]
+  } else {
+    return [RQKEY_ROOT, did, rkey]
+  }
+}
+
+export function useStarterPackQuery({
+  uri,
+  did,
+  rkey,
+}: {
+  uri?: string
+  did?: string
+  rkey?: string
+}) {
+  const agent = useAgent()
+
+  return useQuery<StarterPackView>({
+    queryKey: RQKEY(did, rkey),
+    queryFn: async () => {
+      if (!uri) {
+        uri = `at://${did}/app.bsky.graph.starterpack/${rkey}`
+      } else if (uri && !uri.startsWith('at://')) {
+        uri = httpStarterPackUriToAtUri(uri) as string
+      }
+
+      const res = await agent.app.bsky.graph.getStarterPack({
+        starterPack: uri,
+      })
+      return res.data.starterPack
+    },
+    enabled: Boolean(uri) || Boolean(did && rkey),
+  })
+}
+
+export async function invalidateStarterPack({
+  queryClient,
+  did,
+  rkey,
+}: {
+  queryClient: QueryClient
+  did: string
+  rkey: string
+}) {
+  await queryClient.invalidateQueries({queryKey: RQKEY(did, rkey)})
+}
+
+interface UseCreateStarterPackMutationParams {
+  name: string
+  description?: string
+  descriptionFacets: []
+  profiles: AppBskyActorDefs.ProfileViewBasic[]
+  feeds?: AppBskyFeedDefs.GeneratorView[]
+}
+
+export function useCreateStarterPackMutation({
+  onSuccess,
+  onError,
+}: {
+  onSuccess: (data: {uri: string; cid: string}) => void
+  onError: (e: Error) => void
+}) {
+  const queryClient = useQueryClient()
+  const agent = useAgent()
+
+  return useMutation<
+    {uri: string; cid: string},
+    Error,
+    UseCreateStarterPackMutationParams
+  >({
+    mutationFn: async params => {
+      let listRes
+      listRes = await createStarterPackList({...params, agent})
+      return await agent.app.bsky.graph.starterpack.create(
+        {
+          repo: agent.session?.did,
+        },
+        {
+          ...params,
+          list: listRes?.uri,
+          createdAt: new Date().toISOString(),
+        },
+      )
+    },
+    onSuccess: async data => {
+      await whenAppViewReady(agent, data.uri, v => {
+        return typeof v?.data.starterPack.uri === 'string'
+      })
+      await invalidateActorStarterPacksQuery({
+        queryClient,
+        did: agent.session!.did,
+      })
+      onSuccess(data)
+    },
+    onError: async error => {
+      onError(error)
+    },
+  })
+}
+
+export function useEditStarterPackMutation({
+  onSuccess,
+  onError,
+}: {
+  onSuccess: () => void
+  onError: (error: Error) => void
+}) {
+  const queryClient = useQueryClient()
+  const agent = useAgent()
+
+  return useMutation<
+    void,
+    Error,
+    UseCreateStarterPackMutationParams & {
+      currentStarterPack: AppBskyGraphDefs.StarterPackView
+      currentListItems: AppBskyGraphDefs.ListItemView[]
+    }
+  >({
+    mutationFn: async params => {
+      const {
+        name,
+        description,
+        descriptionFacets,
+        feeds,
+        profiles,
+        currentStarterPack,
+        currentListItems,
+      } = params
+
+      if (!AppBskyGraphStarterpack.isRecord(currentStarterPack.record)) {
+        throw new Error('Invalid starter pack')
+      }
+
+      const removedItems = currentListItems.filter(
+        i =>
+          i.subject.did !== agent.session?.did &&
+          !profiles.find(p => p.did === i.subject.did && p.did),
+      )
+
+      if (removedItems.length !== 0) {
+        await agent.com.atproto.repo.applyWrites({
+          repo: agent.session!.did,
+          writes: removedItems.map(i => ({
+            $type: 'com.atproto.repo.applyWrites#delete',
+            collection: 'app.bsky.graph.listitem',
+            rkey: new AtUri(i.uri).rkey,
+          })),
+        })
+      }
+
+      const addedProfiles = profiles.filter(
+        p => !currentListItems.find(i => i.subject.did === p.did),
+      )
+
+      if (addedProfiles.length > 0) {
+        await agent.com.atproto.repo.applyWrites({
+          repo: agent.session!.did,
+          writes: addedProfiles.map(p => ({
+            $type: 'com.atproto.repo.applyWrites#create',
+            collection: 'app.bsky.graph.listitem',
+            value: {
+              $type: 'app.bsky.graph.listitem',
+              subject: p.did,
+              list: currentStarterPack.list?.uri,
+              createdAt: new Date().toISOString(),
+            },
+          })),
+        })
+      }
+
+      const rkey = parseStarterPackUri(currentStarterPack.uri)!.rkey
+      await agent.com.atproto.repo.putRecord({
+        repo: agent.session!.did,
+        collection: 'app.bsky.graph.starterpack',
+        rkey,
+        record: {
+          name,
+          description,
+          descriptionFacets,
+          list: currentStarterPack.list?.uri,
+          feeds,
+          createdAt: currentStarterPack.record.createdAt,
+          updatedAt: new Date().toISOString(),
+        },
+      })
+    },
+    onSuccess: async (_, {currentStarterPack}) => {
+      const parsed = parseStarterPackUri(currentStarterPack.uri)
+      await whenAppViewReady(agent, currentStarterPack.uri, v => {
+        return currentStarterPack.cid !== v?.data.starterPack.cid
+      })
+      await invalidateActorStarterPacksQuery({
+        queryClient,
+        did: agent.session!.did,
+      })
+      if (currentStarterPack.list) {
+        await invalidateListMembersQuery({
+          queryClient,
+          uri: currentStarterPack.list.uri,
+        })
+      }
+      await invalidateStarterPack({
+        queryClient,
+        did: agent.session!.did,
+        rkey: parsed!.rkey,
+      })
+      onSuccess()
+    },
+    onError: error => {
+      onError(error)
+    },
+  })
+}
+
+export function useDeleteStarterPackMutation({
+  onSuccess,
+  onError,
+}: {
+  onSuccess: () => void
+  onError: (error: Error) => void
+}) {
+  const agent = useAgent()
+  const queryClient = useQueryClient()
+
+  return useMutation({
+    mutationFn: async ({listUri, rkey}: {listUri?: string; rkey: string}) => {
+      if (!agent.session) {
+        throw new Error(`Requires logged in user`)
+      }
+
+      if (listUri) {
+        await agent.app.bsky.graph.list.delete({
+          repo: agent.session.did,
+          rkey: new AtUri(listUri).rkey,
+        })
+      }
+      await agent.app.bsky.graph.starterpack.delete({
+        repo: agent.session.did,
+        rkey,
+      })
+    },
+    onSuccess: async (_, {listUri, rkey}) => {
+      const uri = createStarterPackUri({
+        did: agent.session!.did,
+        rkey,
+      })
+
+      if (uri) {
+        await whenAppViewReady(agent, uri, v => {
+          return Boolean(v?.data?.starterPack) === false
+        })
+      }
+
+      if (listUri) {
+        await invalidateListMembersQuery({queryClient, uri: listUri})
+      }
+      await invalidateActorStarterPacksQuery({
+        queryClient,
+        did: agent.session!.did,
+      })
+      await invalidateStarterPack({
+        queryClient,
+        did: agent.session!.did,
+        rkey,
+      })
+      onSuccess()
+    },
+    onError: error => {
+      onError(error)
+    },
+  })
+}
+
+async function whenAppViewReady(
+  agent: BskyAgent,
+  uri: string,
+  fn: (res?: AppBskyGraphGetStarterPack.Response) => boolean,
+) {
+  await until(
+    5, // 5 tries
+    1e3, // 1s delay between tries
+    fn,
+    () => agent.app.bsky.graph.getStarterPack({starterPack: uri}),
+  )
+}