about summary refs log tree commit diff
path: root/src/state/queries/list-memberships.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/queries/list-memberships.ts')
-rw-r--r--src/state/queries/list-memberships.ts193
1 files changed, 193 insertions, 0 deletions
diff --git a/src/state/queries/list-memberships.ts b/src/state/queries/list-memberships.ts
new file mode 100644
index 000000000..6cae3fa2e
--- /dev/null
+++ b/src/state/queries/list-memberships.ts
@@ -0,0 +1,193 @@
+/**
+ * NOTE
+ *
+ * This query is a temporary solution to our lack of server API for
+ * querying user membership in an API. It is extremely inefficient.
+ *
+ * THIS SHOULD ONLY BE USED IN MODALS FOR MODIFYING A USER'S LIST MEMBERSHIP!
+ * Use the list-members query for rendering a list's members.
+ *
+ * It works by fetching *all* of the user's list item records and querying
+ * or manipulating that cache. For users with large lists, it will fall
+ * down completely, so be very conservative about how you use it.
+ *
+ * -prf
+ */
+
+import {AtUri} from '@atproto/api'
+import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
+
+import {useSession, getAgent} from '#/state/session'
+import {RQKEY as LIST_MEMBERS_RQKEY} from '#/state/queries/list-members'
+import {STALE} from '#/state/queries'
+
+// sanity limit is SANITY_PAGE_LIMIT*PAGE_SIZE total records
+const SANITY_PAGE_LIMIT = 1000
+const PAGE_SIZE = 100
+// ...which comes 100,000k list members
+
+export const RQKEY = () => ['list-memberships']
+
+export interface ListMembersip {
+  membershipUri: string
+  listUri: string
+  actorDid: string
+}
+
+/**
+ * This API is dangerous! Read the note above!
+ */
+export function useDangerousListMembershipsQuery() {
+  const {currentAccount} = useSession()
+  return useQuery<ListMembersip[]>({
+    staleTime: STALE.MINUTES.FIVE,
+    queryKey: RQKEY(),
+    async queryFn() {
+      if (!currentAccount) {
+        return []
+      }
+      let cursor
+      let arr: ListMembersip[] = []
+      for (let i = 0; i < SANITY_PAGE_LIMIT; i++) {
+        const res = await getAgent().app.bsky.graph.listitem.list({
+          repo: currentAccount.did,
+          limit: PAGE_SIZE,
+          cursor,
+        })
+        arr = arr.concat(
+          res.records.map(r => ({
+            membershipUri: r.uri,
+            listUri: r.value.list,
+            actorDid: r.value.subject,
+          })),
+        )
+        cursor = res.cursor
+        if (!cursor) {
+          break
+        }
+      }
+      return arr
+    },
+  })
+}
+
+/**
+ * Returns undefined for pending, false for not a member, and string for a member (the URI of the membership record)
+ */
+export function getMembership(
+  memberships: ListMembersip[] | undefined,
+  list: string,
+  actor: string,
+): string | false | undefined {
+  if (!memberships) {
+    return undefined
+  }
+  const membership = memberships.find(
+    m => m.listUri === list && m.actorDid === actor,
+  )
+  return membership ? membership.membershipUri : false
+}
+
+export function useListMembershipAddMutation() {
+  const {currentAccount} = useSession()
+  const queryClient = useQueryClient()
+  return useMutation<
+    {uri: string; cid: string},
+    Error,
+    {listUri: string; actorDid: string}
+  >({
+    mutationFn: async ({listUri, actorDid}) => {
+      if (!currentAccount) {
+        throw new Error('Not logged in')
+      }
+      const res = await getAgent().app.bsky.graph.listitem.create(
+        {repo: currentAccount.did},
+        {
+          subject: actorDid,
+          list: listUri,
+          createdAt: new Date().toISOString(),
+        },
+      )
+      // TODO
+      // we need to wait for appview to update, but there's not an efficient
+      // query for that, so we use a timeout below
+      // -prf
+      return res
+    },
+    onSuccess(data, variables) {
+      // manually update the cache; a refetch is too expensive
+      let memberships = queryClient.getQueryData<ListMembersip[]>(RQKEY())
+      if (memberships) {
+        memberships = memberships
+          // avoid dups
+          .filter(
+            m =>
+              !(
+                m.actorDid === variables.actorDid &&
+                m.listUri === variables.listUri
+              ),
+          )
+          .concat([
+            {
+              ...variables,
+              membershipUri: data.uri,
+            },
+          ])
+        queryClient.setQueryData(RQKEY(), memberships)
+      }
+      // invalidate the members queries (used for rendering the listings)
+      // use a timeout to wait for the appview (see above)
+      setTimeout(() => {
+        queryClient.invalidateQueries({
+          queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
+        })
+      }, 1e3)
+    },
+  })
+}
+
+export function useListMembershipRemoveMutation() {
+  const {currentAccount} = useSession()
+  const queryClient = useQueryClient()
+  return useMutation<
+    void,
+    Error,
+    {listUri: string; actorDid: string; membershipUri: string}
+  >({
+    mutationFn: async ({membershipUri}) => {
+      if (!currentAccount) {
+        throw new Error('Not logged in')
+      }
+      const membershipUrip = new AtUri(membershipUri)
+      await getAgent().app.bsky.graph.listitem.delete({
+        repo: currentAccount.did,
+        rkey: membershipUrip.rkey,
+      })
+      // TODO
+      // we need to wait for appview to update, but there's not an efficient
+      // query for that, so we use a timeout below
+      // -prf
+    },
+    onSuccess(data, variables) {
+      // manually update the cache; a refetch is too expensive
+      let memberships = queryClient.getQueryData<ListMembersip[]>(RQKEY())
+      if (memberships) {
+        memberships = memberships.filter(
+          m =>
+            !(
+              m.actorDid === variables.actorDid &&
+              m.listUri === variables.listUri
+            ),
+        )
+        queryClient.setQueryData(RQKEY(), memberships)
+      }
+      // invalidate the members queries (used for rendering the listings)
+      // use a timeout to wait for the appview (see above)
+      setTimeout(() => {
+        queryClient.invalidateQueries({
+          queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
+        })
+      }, 1e3)
+    },
+  })
+}