about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2024-07-12 14:55:34 -0500
committerGitHub <noreply@github.com>2024-07-12 14:55:34 -0500
commit3627a249ffb32e4c0f84597f8f9adf228ee90a8f (patch)
tree68c0d838c7d3624bafed4ea3e7593ca2dd1d8da0
parent7c1c24ef1badb746ecbe707d390bec4934940ced (diff)
downloadvoidsky-3627a249ffb32e4c0f84597f8f9adf228ee90a8f.tar.zst
Remove invalid labelers when subscribing/unsubscribing (#4771)
* Remove invalid labelers when subscribing/unsubscribing

* Let the async lock cook

* Use link to associate, leave copy as is
-rw-r--r--src/lib/constants.ts2
-rw-r--r--src/screens/Profile/Header/ProfileHeaderLabeler.tsx50
-rw-r--r--src/state/queries/labeler.ts49
3 files changed, 74 insertions, 27 deletions
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 7516c2c28..45b3dfd95 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -133,3 +133,5 @@ export const GIF_SEARCH = (params: string) =>
   `${GIF_SERVICE}/tenor/v2/search?${params}`
 export const GIF_FEATURED = (params: string) =>
   `${GIF_SERVICE}/tenor/v2/featured?${params}`
+
+export const MAX_LABELERS = 20
diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx
index d266decb3..51e555a6e 100644
--- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx
@@ -10,6 +10,8 @@ import {
 import {msg, Plural, plural, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+import {MAX_LABELERS} from '#/lib/constants'
 import {isAppLabeler} from '#/lib/moderation'
 import {logger} from '#/logger'
 import {Shadow} from '#/state/cache/types'
@@ -75,14 +77,14 @@ let ProfileHeaderLabeler = ({
     [profile, moderationOpts],
   )
   const {data: preferences} = usePreferencesQuery()
-  const {mutateAsync: toggleSubscription, variables} =
-    useLabelerSubscriptionMutation()
+  const {
+    mutateAsync: toggleSubscription,
+    variables,
+    reset,
+  } = useLabelerSubscriptionMutation()
   const isSubscribed =
     variables?.subscribe ??
     preferences?.moderationPrefs.labelers.find(l => l.did === profile.did)
-  const canSubscribe =
-    isSubscribed ||
-    (preferences ? preferences?.moderationPrefs.labelers.length <= 20 : false)
   const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
   const {mutateAsync: unlikeMod, isPending: isUnlikePending} =
     useUnlikeMutation()
@@ -130,17 +132,17 @@ let ProfileHeaderLabeler = ({
   const onPressSubscribe = React.useCallback(
     () =>
       requireAuth(async (): Promise<void> => {
-        if (!canSubscribe) {
-          cantSubscribePrompt.open()
-          return
-        }
         try {
           await toggleSubscription({
             did: profile.did,
             subscribe: !isSubscribed,
           })
         } catch (e: any) {
-          // setSubscriptionError(e.message)
+          reset()
+          if (e.message === 'MAX_LABELERS') {
+            cantSubscribePrompt.open()
+            return
+          }
           logger.error(`Failed to subscribe to labeler`, {message: e.message})
         }
       }),
@@ -149,8 +151,8 @@ let ProfileHeaderLabeler = ({
       toggleSubscription,
       isSubscribed,
       profile,
-      canSubscribe,
       cantSubscribePrompt,
+      reset,
     ],
   )
 
@@ -199,14 +201,13 @@ let ProfileHeaderLabeler = ({
                     style={[
                       {
                         paddingVertical: gtMobile ? 12 : 10,
-                        backgroundColor:
-                          isSubscribed || !canSubscribe
-                            ? state.hovered || state.pressed
-                              ? t.palette.contrast_50
-                              : t.palette.contrast_25
-                            : state.hovered || state.pressed
-                            ? tokens.color.temp_purple_dark
-                            : tokens.color.temp_purple,
+                        backgroundColor: isSubscribed
+                          ? state.hovered || state.pressed
+                            ? t.palette.contrast_50
+                            : t.palette.contrast_25
+                          : state.hovered || state.pressed
+                          ? tokens.color.temp_purple_dark
+                          : tokens.color.temp_purple,
                       },
                       a.px_lg,
                       a.rounded_sm,
@@ -215,11 +216,9 @@ let ProfileHeaderLabeler = ({
                     <Text
                       style={[
                         {
-                          color: canSubscribe
-                            ? isSubscribed
-                              ? t.palette.contrast_700
-                              : t.palette.white
-                            : t.palette.contrast_400,
+                          color: isSubscribed
+                            ? t.palette.contrast_700
+                            : t.palette.white,
                         },
                         a.font_bold,
                         a.text_center,
@@ -317,6 +316,9 @@ let ProfileHeaderLabeler = ({
 ProfileHeaderLabeler = memo(ProfileHeaderLabeler)
 export {ProfileHeaderLabeler}
 
+/**
+ * Keep this in sync with the value of {@link MAX_LABELERS}
+ */
 function CantSubscribePrompt({
   control,
 }: {
diff --git a/src/state/queries/labeler.ts b/src/state/queries/labeler.ts
index 058e8fcde..53e923a85 100644
--- a/src/state/queries/labeler.ts
+++ b/src/state/queries/labeler.ts
@@ -2,9 +2,13 @@ import {AppBskyLabelerDefs} from '@atproto/api'
 import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
 import {z} from 'zod'
 
+import {MAX_LABELERS} from '#/lib/constants'
 import {labelersDetailedInfoQueryKeyRoot} from '#/lib/react-query'
 import {STALE} from '#/state/queries'
-import {preferencesQueryKey} from '#/state/queries/preferences'
+import {
+  preferencesQueryKey,
+  usePreferencesQuery,
+} from '#/state/queries/preferences'
 import {useAgent} from '#/state/session'
 
 const labelerInfoQueryKeyRoot = 'labeler-info'
@@ -77,6 +81,7 @@ export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) {
 export function useLabelerSubscriptionMutation() {
   const queryClient = useQueryClient()
   const agent = useAgent()
+  const preferences = usePreferencesQuery()
 
   return useMutation({
     async mutationFn({did, subscribe}: {did: string; subscribe: boolean}) {
@@ -86,14 +91,52 @@ export function useLabelerSubscriptionMutation() {
         subscribe: z.boolean(),
       }).parse({did, subscribe})
 
+      /**
+       * If a user has invalid/takendown/deactivated labelers, we need to
+       * remove them. We don't have a great way to do this atm on the server,
+       * so we do it here.
+       *
+       * We also need to push validation into this method, since we need to
+       * check {@link MAX_LABELERS} _after_ we've removed invalid or takendown
+       * labelers.
+       */
+      const labelerDids = (
+        preferences.data?.moderationPrefs?.labelers ?? []
+      ).map(l => l.did)
+      const invalidLabelers: string[] = []
+      if (labelerDids.length) {
+        const profiles = await agent.getProfiles({actors: labelerDids})
+        if (profiles.data) {
+          for (const did of labelerDids) {
+            const exists = profiles.data.profiles.find(p => p.did === did)
+            if (exists) {
+              // profile came back but it's not a valid labeler
+              if (exists.associated && !exists.associated.labeler) {
+                invalidLabelers.push(did)
+              }
+            } else {
+              // no response came back, might be deactivated or takendown
+              invalidLabelers.push(did)
+            }
+          }
+        }
+      }
+      if (invalidLabelers.length) {
+        await Promise.all(invalidLabelers.map(did => agent.removeLabeler(did)))
+      }
+
       if (subscribe) {
+        const labelerCount = labelerDids.length - invalidLabelers.length
+        if (labelerCount >= MAX_LABELERS) {
+          throw new Error('MAX_LABELERS')
+        }
         await agent.addLabeler(did)
       } else {
         await agent.removeLabeler(did)
       }
     },
-    onSuccess() {
-      queryClient.invalidateQueries({
+    async onSuccess() {
+      await queryClient.invalidateQueries({
         queryKey: preferencesQueryKey,
       })
     },