Skip to content

Commit 28ee261

Browse files
committed
refactor: simplify user ensure updates
1 parent a4b850e commit 28ee261

File tree

2 files changed

+41
-25
lines changed

2 files changed

+41
-25
lines changed

convex/users.test.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@ vi.mock('./lib/access', async () => {
88
const { requireUser } = await import('./lib/access')
99
const { ensureHandler } = await import('./users')
1010

11+
function makeCtx() {
12+
const patch = vi.fn()
13+
const get = vi.fn()
14+
return { ctx: { db: { patch, get } } as never, patch, get }
15+
}
16+
1117
describe('ensureHandler', () => {
1218
afterEach(() => {
1319
vi.mocked(requireUser).mockReset()
1420
})
1521

1622
it('updates handle and display name when GitHub login changes', async () => {
23+
const { ctx, patch } = makeCtx()
1724
vi.mocked(requireUser).mockResolvedValue({
1825
userId: 'users:1',
1926
user: {
@@ -27,11 +34,7 @@ describe('ensureHandler', () => {
2734
},
2835
} as never)
2936

30-
const patch = vi.fn()
31-
const get = vi.fn()
32-
const ctx = { db: { patch, get } }
33-
34-
await ensureHandler(ctx as never)
37+
await ensureHandler(ctx)
3538

3639
expect(patch).toHaveBeenCalledWith('users:1', {
3740
handle: 'new-handle',
@@ -41,6 +44,7 @@ describe('ensureHandler', () => {
4144
})
4245

4346
it('does not override a custom display name when syncing handle', async () => {
47+
const { ctx, patch } = makeCtx()
4448
vi.mocked(requireUser).mockResolvedValue({
4549
userId: 'users:2',
4650
user: {
@@ -53,11 +57,7 @@ describe('ensureHandler', () => {
5357
},
5458
} as never)
5559

56-
const patch = vi.fn()
57-
const get = vi.fn()
58-
const ctx = { db: { patch, get } }
59-
60-
await ensureHandler(ctx as never)
60+
await ensureHandler(ctx)
6161

6262
expect(patch).toHaveBeenCalledWith('users:2', {
6363
handle: 'new-handle',
@@ -66,6 +66,7 @@ describe('ensureHandler', () => {
6666
})
6767

6868
it('fills display name from existing handle when missing', async () => {
69+
const { ctx, patch } = makeCtx()
6970
vi.mocked(requireUser).mockResolvedValue({
7071
userId: 'users:3',
7172
user: {
@@ -79,11 +80,7 @@ describe('ensureHandler', () => {
7980
},
8081
} as never)
8182

82-
const patch = vi.fn()
83-
const get = vi.fn()
84-
const ctx = { db: { patch, get } }
85-
86-
await ensureHandler(ctx as never)
83+
await ensureHandler(ctx)
8784

8885
expect(patch).toHaveBeenCalledWith('users:3', {
8986
displayName: 'steady-handle',

convex/users.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,23 +77,35 @@ export const ensure = mutation({
7777
handler: ensureHandler,
7878
})
7979

80-
export async function ensureHandler(ctx: MutationCtx) {
81-
const { userId, user } = await requireUser(ctx)
80+
function normalizeHandle(handle: string | undefined) {
81+
const normalized = handle?.trim()
82+
return normalized ? normalized : undefined
83+
}
84+
85+
function deriveHandle(args: { existingHandle?: string; githubLogin?: string; email?: string }) {
86+
// Prefer the GitHub login; only fall back to email-derived handle when we don't already have one.
87+
if (args.githubLogin) return args.githubLogin
88+
if (!args.existingHandle && args.email) return args.email.split('@')[0]?.trim() || undefined
89+
return undefined
90+
}
91+
92+
function computeEnsureUpdates(user: Doc<'users'>) {
8293
const updates: Record<string, unknown> = {}
8394

84-
const existingHandle = user.handle?.trim() || undefined
85-
const nameHandle = user.name?.trim() || undefined
86-
const emailHandle = user.email?.split('@')[0]?.trim() || undefined
87-
// `user.name` is the GitHub login (see convex/auth.ts profile mapping).
88-
// Only fall back to deriving from email if we don't already have a handle.
89-
const derivedHandle = nameHandle || (!existingHandle ? emailHandle : undefined)
95+
const existingHandle = normalizeHandle(user.handle)
96+
const githubLogin = normalizeHandle(user.name)
97+
const derivedHandle = deriveHandle({
98+
existingHandle,
99+
githubLogin,
100+
email: user.email,
101+
})
90102
const baseHandle = derivedHandle ?? existingHandle
91103

92-
if (derivedHandle && (!existingHandle || existingHandle !== derivedHandle)) {
104+
if (derivedHandle && existingHandle !== derivedHandle) {
93105
updates.handle = derivedHandle
94106
}
95107

96-
const displayName = user.displayName?.trim() || undefined
108+
const displayName = normalizeHandle(user.displayName)
97109
if (!displayName && baseHandle) {
98110
updates.displayName = baseHandle
99111
} else if (derivedHandle && displayName === existingHandle) {
@@ -106,6 +118,13 @@ export async function ensureHandler(ctx: MutationCtx) {
106118

107119
if (!user.createdAt) updates.createdAt = user._creationTime
108120

121+
return updates
122+
}
123+
124+
export async function ensureHandler(ctx: MutationCtx) {
125+
const { userId, user } = await requireUser(ctx)
126+
const updates = computeEnsureUpdates(user)
127+
109128
if (Object.keys(updates).length > 0) {
110129
updates.updatedAt = Date.now()
111130
await ctx.db.patch(userId, updates)

0 commit comments

Comments
 (0)