From 07ce2780c63458225459524b7fdb90e425322b36 Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Tue, 17 Feb 2026 22:02:47 +0100 Subject: [PATCH] Refactor user profile handling by removing deprecated fields and improving link management --- backend/api/src/update-me.ts | 61 +++++++++---------- backend/api/tests/unit/update-me.unit.test.ts | 31 +--------- common/src/api/schema.ts | 16 ----- web/components/optional-profile-form.tsx | 6 +- web/hooks/use-user.ts | 14 ++--- 5 files changed, 42 insertions(+), 86 deletions(-) diff --git a/backend/api/src/update-me.ts b/backend/api/src/update-me.ts index 6f8613ad..31583cee 100644 --- a/backend/api/src/update-me.ts +++ b/backend/api/src/update-me.ts @@ -1,14 +1,14 @@ -import { toUserAPIResponse } from 'common/api/user-types' -import { RESERVED_PATHS } from 'common/envs/constants' -import { cleanDisplayName, cleanUsername } from 'common/util/clean-username' -import { removeUndefinedProps } from 'common/util/object' -import { cloneDeep, mapValues } from 'lodash' -import { createSupabaseDirectClient } from 'shared/supabase/init' -import { getUser, getUserByUsername } from 'shared/utils' -import { APIError, APIHandler } from './helpers/endpoint' -import { updateUser } from 'shared/supabase/users' -import { broadcastUpdatedUser } from 'shared/websockets/helpers' -import { strip } from 'common/socials' +import {toUserAPIResponse} from 'common/api/user-types' +import {RESERVED_PATHS} from 'common/envs/constants' +import {cleanDisplayName, cleanUsername} from 'common/util/clean-username' +import {removeUndefinedProps} from 'common/util/object' +import {cloneDeep, mapValues} from 'lodash' +import {createSupabaseDirectClient} from 'shared/supabase/init' +import {getUser, getUserByUsername} from 'shared/utils' +import {APIError, APIHandler} from './helpers/endpoint' +import {updateUser} from 'shared/supabase/users' +import {broadcastUpdatedUser} from 'shared/websockets/helpers' +import {strip} from 'common/socials' export const updateMe: APIHandler<'me/update'> = async (props, auth) => { const update = cloneDeep(props) @@ -35,10 +35,6 @@ export const updateMe: APIHandler<'me/update'> = async (props, auth) => { const { name, username, avatarUrl, link = {}, ...rest } = update await updateUser(pg, auth.uid, removeUndefinedProps(rest)) - if (update.website != undefined) link.site = update.website - if (update.twitterHandle != undefined) link.x = update.twitterHandle - if (update.discordHandle != undefined) link.discord = update.discordHandle - const stripped = mapValues( link, (value, site) => value && strip(site as any, value) @@ -69,23 +65,26 @@ export const updateMe: APIHandler<'me/update'> = async (props, auth) => { newLinks = data?.link } - if (name || username || avatarUrl) { - if (name) { - await pg.none(`update users set name = $1 where id = $2`, [ - name, - auth.uid, - ]) - } - if (username) { - await pg.none(`update users set username = $1 where id = $2`, [ - username, - auth.uid, - ]) - } - if (avatarUrl) { - await updateUser(pg, auth.uid, { avatarUrl }) - } + if (name) { + await pg.none(`update users + set name = $1 + where id = $2`, [name, auth.uid]) + } + if (username) { + await pg.none(`update users + set username = $1 + where id = $2`, [ + username, + auth.uid, + ]) + } + if (avatarUrl) { + await updateUser(pg, auth.uid, {avatarUrl}) + } + // Ensure clients listening on `user/{id}` (e.g. AuthContext via useWebsocketUser) + // get notified about link-only changes as well. + if (name || username || avatarUrl || newLinks != null) { broadcastUpdatedUser( removeUndefinedProps({ id: auth.uid, diff --git a/backend/api/tests/unit/update-me.unit.test.ts b/backend/api/tests/unit/update-me.unit.test.ts index cd0c104a..ef4cedee 100644 --- a/backend/api/tests/unit/update-me.unit.test.ts +++ b/backend/api/tests/unit/update-me.unit.test.ts @@ -8,8 +8,8 @@ jest.mock('shared/supabase/users'); jest.mock('shared/websockets/helpers'); jest.mock('common/envs/constants'); -import { updateMe } from "api/update-me"; -import { toUserAPIResponse } from "common/api/user-types"; +import {updateMe} from "api/update-me"; +import {toUserAPIResponse} from "common/api/user-types"; import * as cleanUsernameModules from "common/util/clean-username"; import * as supabaseInit from "shared/supabase/init"; import * as objectUtils from "common/util/object"; @@ -17,7 +17,7 @@ import * as lodashModules from "lodash"; import * as sharedUtils from "shared/utils"; import * as supabaseUsers from "shared/supabase/users"; import * as websocketHelperModules from "shared/websockets/helpers"; -import { AuthedUser } from "api/helpers/endpoint"; +import {AuthedUser} from "api/helpers/endpoint"; describe('updateMe', () => { let mockPg = {} as any; @@ -44,12 +44,7 @@ describe('updateMe', () => { name: "mockName", username: "mockUsername", avatarUrl: "mockAvatarUrl", - bio: "mockBio", link: {"mockLink" : "mockLinkValue"}, - optOutBetWarnings:true, - website: "mockWebsite", - twitterHandle: "mockTwitterHandle", - discordHandle: "mockDiscordHandle", }; const mockStripped = { bio: "mockBio" @@ -152,12 +147,7 @@ describe('updateMe', () => { name: "mockName", username: "mockUsername", avatarUrl: "mockAvatarUrl", - bio: "mockBio", link: {"mockLink" : "mockLinkValue"}, - optOutBetWarnings:true, - website: "mockWebsite", - twitterHandle: "mockTwitterHandle", - discordHandle: "mockDiscordHandle", }; (lodashModules.cloneDeep as jest.Mock).mockReturnValue(mockUpdate); @@ -176,12 +166,7 @@ describe('updateMe', () => { name: "mockName", username: "mockUsername", avatarUrl: "mockAvatarUrl", - bio: "mockBio", link: {"mockLink" : "mockLinkValue"}, - optOutBetWarnings:true, - website: "mockWebsite", - twitterHandle: "mockTwitterHandle", - discordHandle: "mockDiscordHandle", }; (lodashModules.cloneDeep as jest.Mock).mockReturnValue(mockUpdate); @@ -202,12 +187,7 @@ describe('updateMe', () => { name: "mockName", username: "mockUsername", avatarUrl: "mockAvatarUrl", - bio: "mockBio", link: {"mockLink" : "mockLinkValue"}, - optOutBetWarnings:true, - website: "mockWebsite", - twitterHandle: "mockTwitterHandle", - discordHandle: "mockDiscordHandle", }; const arrySpy = jest.spyOn(Array.prototype, 'includes'); @@ -231,12 +211,7 @@ describe('updateMe', () => { name: "mockName", username: "mockUsername", avatarUrl: "mockAvatarUrl", - bio: "mockBio", link: {"mockLink" : "mockLinkValue"}, - optOutBetWarnings:true, - website: "mockWebsite", - twitterHandle: "mockTwitterHandle", - discordHandle: "mockDiscordHandle", }; const arrySpy = jest.spyOn(Array.prototype, 'includes'); diff --git a/common/src/api/schema.ts b/common/src/api/schema.ts index 577cbe29..0235e6d7 100644 --- a/common/src/api/schema.ts +++ b/common/src/api/schema.ts @@ -208,23 +208,7 @@ export const API = (_apiTypeCheck = { name: z.string().trim().min(1).optional(), username: z.string().trim().min(1).optional(), avatarUrl: z.string().optional(), - bio: z.string().optional(), link: z.record(z.string().nullable()).optional(), - // settings - optOutBetWarnings: z.boolean().optional(), - isAdvancedTrader: z.boolean().optional(), - //internal - shouldShowWelcome: z.boolean().optional(), - hasSeenContractFollowModal: z.boolean().optional(), - hasSeenLoanModal: z.boolean().optional(), - - // Legacy fields (deprecated) - /** @deprecated Use links.site instead */ - website: z.string().optional(), - /** @deprecated Use links.x instead */ - twitterHandle: z.string().optional(), - /** @deprecated Use links.discord instead */ - discordHandle: z.string().optional(), }), returns: {} as FullUser, summary: 'Update authenticated user profile and settings', diff --git a/web/components/optional-profile-form.tsx b/web/components/optional-profile-form.tsx index 31125e57..ef8e0629 100644 --- a/web/components/optional-profile-form.tsx +++ b/web/components/optional-profile-form.tsx @@ -90,9 +90,7 @@ export const OptionalProfileUserForm = (props: { setHeightInches(Math.round(h % 12)) } - const [newLinks, setNewLinks] = useState>( - user.link - ) + const [newLinks, setNewLinks] = useState>(user.link) const [newLinkPlatform, setNewLinkPlatform] = useState('') const [newLinkValue, setNewLinkValue] = useState('') @@ -201,7 +199,7 @@ export const OptionalProfileUserForm = (props: { i++ } if (profile) { - await sleep(5000) + await sleep(5000) // attempt to mitigate profile not found at /${username} upon creation router.push(`/${user.username}${fromSignup ? '?fromSignup=true' : ''}`) } else { console.log("Profile not found after fetching, going back home...") diff --git a/web/hooks/use-user.ts b/web/hooks/use-user.ts index 16d3dd7d..98b820eb 100644 --- a/web/hooks/use-user.ts +++ b/web/hooks/use-user.ts @@ -1,10 +1,10 @@ 'use client' -import { PrivateUser, User } from 'common/user' -import { useContext, useEffect, useState } from 'react' -import { AuthContext } from 'web/components/auth-context' -import { useIsPageVisible } from './use-page-visible' -import { useApiSubscription } from './use-api-subscription' -import { getFullUserById, getPrivateUserSafe } from 'web/lib/supabase/users' +import {PrivateUser, User} from 'common/user' +import {useContext, useEffect, useState} from 'react' +import {AuthContext} from 'web/components/auth-context' +import {useIsPageVisible} from './use-page-visible' +import {useApiSubscription} from './use-api-subscription' +import {getFullUserById, getPrivateUserSafe} from 'web/lib/supabase/users' export const useUser = () => { const authUser = useContext(AuthContext) @@ -29,7 +29,7 @@ export const useWebsocketUser = (userId: string | undefined) => { useApiSubscription({ topics: [`user/${userId ?? '_'}`], onBroadcast: ({ data }) => { - console.debug(data) + console.debug('User broadcast', {data}) setUser((user) => { if (!user || !data.user) { return user