Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

authored by

Eric Bailey and committed by
GitHub
3627a249 7c1c24ef

+74 -27
+2
src/lib/constants.ts
··· 133 133 `${GIF_SERVICE}/tenor/v2/search?${params}` 134 134 export const GIF_FEATURED = (params: string) => 135 135 `${GIF_SERVICE}/tenor/v2/featured?${params}` 136 + 137 + export const MAX_LABELERS = 20
+26 -24
src/screens/Profile/Header/ProfileHeaderLabeler.tsx
··· 10 10 import {msg, Plural, plural, Trans} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 12 13 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 + import {MAX_LABELERS} from '#/lib/constants' 13 15 import {isAppLabeler} from '#/lib/moderation' 14 16 import {logger} from '#/logger' 15 17 import {Shadow} from '#/state/cache/types' ··· 75 77 [profile, moderationOpts], 76 78 ) 77 79 const {data: preferences} = usePreferencesQuery() 78 - const {mutateAsync: toggleSubscription, variables} = 79 - useLabelerSubscriptionMutation() 80 + const { 81 + mutateAsync: toggleSubscription, 82 + variables, 83 + reset, 84 + } = useLabelerSubscriptionMutation() 80 85 const isSubscribed = 81 86 variables?.subscribe ?? 82 87 preferences?.moderationPrefs.labelers.find(l => l.did === profile.did) 83 - const canSubscribe = 84 - isSubscribed || 85 - (preferences ? preferences?.moderationPrefs.labelers.length <= 20 : false) 86 88 const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation() 87 89 const {mutateAsync: unlikeMod, isPending: isUnlikePending} = 88 90 useUnlikeMutation() ··· 130 132 const onPressSubscribe = React.useCallback( 131 133 () => 132 134 requireAuth(async (): Promise<void> => { 133 - if (!canSubscribe) { 134 - cantSubscribePrompt.open() 135 - return 136 - } 137 135 try { 138 136 await toggleSubscription({ 139 137 did: profile.did, 140 138 subscribe: !isSubscribed, 141 139 }) 142 140 } catch (e: any) { 143 - // setSubscriptionError(e.message) 141 + reset() 142 + if (e.message === 'MAX_LABELERS') { 143 + cantSubscribePrompt.open() 144 + return 145 + } 144 146 logger.error(`Failed to subscribe to labeler`, {message: e.message}) 145 147 } 146 148 }), ··· 149 151 toggleSubscription, 150 152 isSubscribed, 151 153 profile, 152 - canSubscribe, 153 154 cantSubscribePrompt, 155 + reset, 154 156 ], 155 157 ) 156 158 ··· 199 201 style={[ 200 202 { 201 203 paddingVertical: gtMobile ? 12 : 10, 202 - backgroundColor: 203 - isSubscribed || !canSubscribe 204 - ? state.hovered || state.pressed 205 - ? t.palette.contrast_50 206 - : t.palette.contrast_25 207 - : state.hovered || state.pressed 208 - ? tokens.color.temp_purple_dark 209 - : tokens.color.temp_purple, 204 + backgroundColor: isSubscribed 205 + ? state.hovered || state.pressed 206 + ? t.palette.contrast_50 207 + : t.palette.contrast_25 208 + : state.hovered || state.pressed 209 + ? tokens.color.temp_purple_dark 210 + : tokens.color.temp_purple, 210 211 }, 211 212 a.px_lg, 212 213 a.rounded_sm, ··· 215 216 <Text 216 217 style={[ 217 218 { 218 - color: canSubscribe 219 - ? isSubscribed 220 - ? t.palette.contrast_700 221 - : t.palette.white 222 - : t.palette.contrast_400, 219 + color: isSubscribed 220 + ? t.palette.contrast_700 221 + : t.palette.white, 223 222 }, 224 223 a.font_bold, 225 224 a.text_center, ··· 317 316 ProfileHeaderLabeler = memo(ProfileHeaderLabeler) 318 317 export {ProfileHeaderLabeler} 319 318 319 + /** 320 + * Keep this in sync with the value of {@link MAX_LABELERS} 321 + */ 320 322 function CantSubscribePrompt({ 321 323 control, 322 324 }: {
+46 -3
src/state/queries/labeler.ts
··· 2 2 import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' 3 3 import {z} from 'zod' 4 4 5 + import {MAX_LABELERS} from '#/lib/constants' 5 6 import {labelersDetailedInfoQueryKeyRoot} from '#/lib/react-query' 6 7 import {STALE} from '#/state/queries' 7 - import {preferencesQueryKey} from '#/state/queries/preferences' 8 + import { 9 + preferencesQueryKey, 10 + usePreferencesQuery, 11 + } from '#/state/queries/preferences' 8 12 import {useAgent} from '#/state/session' 9 13 10 14 const labelerInfoQueryKeyRoot = 'labeler-info' ··· 77 81 export function useLabelerSubscriptionMutation() { 78 82 const queryClient = useQueryClient() 79 83 const agent = useAgent() 84 + const preferences = usePreferencesQuery() 80 85 81 86 return useMutation({ 82 87 async mutationFn({did, subscribe}: {did: string; subscribe: boolean}) { ··· 86 91 subscribe: z.boolean(), 87 92 }).parse({did, subscribe}) 88 93 94 + /** 95 + * If a user has invalid/takendown/deactivated labelers, we need to 96 + * remove them. We don't have a great way to do this atm on the server, 97 + * so we do it here. 98 + * 99 + * We also need to push validation into this method, since we need to 100 + * check {@link MAX_LABELERS} _after_ we've removed invalid or takendown 101 + * labelers. 102 + */ 103 + const labelerDids = ( 104 + preferences.data?.moderationPrefs?.labelers ?? [] 105 + ).map(l => l.did) 106 + const invalidLabelers: string[] = [] 107 + if (labelerDids.length) { 108 + const profiles = await agent.getProfiles({actors: labelerDids}) 109 + if (profiles.data) { 110 + for (const did of labelerDids) { 111 + const exists = profiles.data.profiles.find(p => p.did === did) 112 + if (exists) { 113 + // profile came back but it's not a valid labeler 114 + if (exists.associated && !exists.associated.labeler) { 115 + invalidLabelers.push(did) 116 + } 117 + } else { 118 + // no response came back, might be deactivated or takendown 119 + invalidLabelers.push(did) 120 + } 121 + } 122 + } 123 + } 124 + if (invalidLabelers.length) { 125 + await Promise.all(invalidLabelers.map(did => agent.removeLabeler(did))) 126 + } 127 + 89 128 if (subscribe) { 129 + const labelerCount = labelerDids.length - invalidLabelers.length 130 + if (labelerCount >= MAX_LABELERS) { 131 + throw new Error('MAX_LABELERS') 132 + } 90 133 await agent.addLabeler(did) 91 134 } else { 92 135 await agent.removeLabeler(did) 93 136 } 94 137 }, 95 - onSuccess() { 96 - queryClient.invalidateQueries({ 138 + async onSuccess() { 139 + await queryClient.invalidateQueries({ 97 140 queryKey: preferencesQueryKey, 98 141 }) 99 142 },