diff options
author | Eric Bailey <git@esb.lol> | 2024-07-12 14:55:34 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-12 14:55:34 -0500 |
commit | 3627a249ffb32e4c0f84597f8f9adf228ee90a8f (patch) | |
tree | 68c0d838c7d3624bafed4ea3e7593ca2dd1d8da0 | |
parent | 7c1c24ef1badb746ecbe707d390bec4934940ced (diff) | |
download | voidsky-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.ts | 2 | ||||
-rw-r--r-- | src/screens/Profile/Header/ProfileHeaderLabeler.tsx | 50 | ||||
-rw-r--r-- | src/state/queries/labeler.ts | 49 |
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, }) }, |