Skip to content

Commit 71c6705

Browse files
christianhpoesteipete
authored andcommitted
fix: sync handle on user ensure
1 parent a577697 commit 71c6705

File tree

2 files changed

+127
-18
lines changed

2 files changed

+127
-18
lines changed

convex/users.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { afterEach, describe, expect, it, vi } from 'vitest'
2+
3+
vi.mock('./lib/access', () => ({
4+
requireUser: vi.fn(),
5+
}))
6+
7+
const { requireUser } = await import('./lib/access')
8+
const { ensureHandler } = await import('./users')
9+
10+
describe('ensureHandler', () => {
11+
afterEach(() => {
12+
vi.mocked(requireUser).mockReset()
13+
})
14+
15+
it('updates handle and display name when GitHub login changes', async () => {
16+
vi.mocked(requireUser).mockResolvedValue({
17+
userId: 'users:1',
18+
user: {
19+
_creationTime: 1,
20+
handle: 'old-handle',
21+
displayName: 'old-handle',
22+
name: 'new-handle',
23+
email: 'old@example.com',
24+
role: 'user',
25+
createdAt: 1,
26+
},
27+
} as never)
28+
29+
const patch = vi.fn()
30+
const get = vi.fn()
31+
const ctx = { db: { patch, get } }
32+
33+
await ensureHandler(ctx as never)
34+
35+
expect(patch).toHaveBeenCalledWith('users:1', {
36+
handle: 'new-handle',
37+
displayName: 'new-handle',
38+
updatedAt: expect.any(Number),
39+
})
40+
})
41+
42+
it('does not override a custom display name when syncing handle', async () => {
43+
vi.mocked(requireUser).mockResolvedValue({
44+
userId: 'users:2',
45+
user: {
46+
_creationTime: 1,
47+
handle: 'old-handle',
48+
displayName: 'Custom Name',
49+
name: 'new-handle',
50+
role: 'user',
51+
createdAt: 1,
52+
},
53+
} as never)
54+
55+
const patch = vi.fn()
56+
const get = vi.fn()
57+
const ctx = { db: { patch, get } }
58+
59+
await ensureHandler(ctx as never)
60+
61+
expect(patch).toHaveBeenCalledWith('users:2', {
62+
handle: 'new-handle',
63+
updatedAt: expect.any(Number),
64+
})
65+
})
66+
67+
it('fills display name from existing handle when missing', async () => {
68+
vi.mocked(requireUser).mockResolvedValue({
69+
userId: 'users:3',
70+
user: {
71+
_creationTime: 1,
72+
handle: 'steady-handle',
73+
displayName: undefined,
74+
name: undefined,
75+
email: undefined,
76+
role: 'user',
77+
createdAt: 1,
78+
},
79+
} as never)
80+
81+
const patch = vi.fn()
82+
const get = vi.fn()
83+
const ctx = { db: { patch, get } }
84+
85+
await ensureHandler(ctx as never)
86+
87+
expect(patch).toHaveBeenCalledWith('users:3', {
88+
displayName: 'steady-handle',
89+
updatedAt: expect.any(Number),
90+
})
91+
})
92+
})

convex/users.ts

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,26 +74,43 @@ export const me = query({
7474

7575
export const ensure = mutation({
7676
args: {},
77-
handler: async (ctx) => {
78-
const { userId, user } = await requireUser(ctx)
79-
const updates: Record<string, unknown> = {}
80-
81-
const handle = user.handle || user.name || user.email?.split('@')[0]
82-
if (!user.handle && handle) updates.handle = handle
83-
if (!user.displayName) updates.displayName = handle
84-
if (!user.role) {
85-
updates.role = handle === ADMIN_HANDLE ? 'admin' : DEFAULT_ROLE
86-
}
87-
if (!user.createdAt) updates.createdAt = user._creationTime
77+
handler: ensureHandler,
78+
})
8879

89-
if (Object.keys(updates).length > 0) {
90-
updates.updatedAt = Date.now()
91-
await ctx.db.patch(userId, updates)
92-
}
80+
export async function ensureHandler(ctx: MutationCtx) {
81+
const { userId, user } = await requireUser(ctx)
82+
const updates: Record<string, unknown> = {}
9383

94-
return ctx.db.get(userId)
95-
},
96-
})
84+
const existingHandle = user.handle?.trim() || undefined
85+
const nameHandle = user.name?.trim() || undefined
86+
const emailHandle = user.email?.split('@')[0]?.trim() || undefined
87+
const derivedHandle = nameHandle || emailHandle
88+
const baseHandle = derivedHandle ?? existingHandle
89+
90+
if (derivedHandle && (!existingHandle || existingHandle !== derivedHandle)) {
91+
updates.handle = derivedHandle
92+
}
93+
94+
const displayName = user.displayName?.trim() || undefined
95+
if (!displayName && baseHandle) {
96+
updates.displayName = baseHandle
97+
} else if (derivedHandle && displayName === existingHandle) {
98+
updates.displayName = derivedHandle
99+
}
100+
101+
if (!user.role) {
102+
updates.role = baseHandle === ADMIN_HANDLE ? 'admin' : DEFAULT_ROLE
103+
}
104+
105+
if (!user.createdAt) updates.createdAt = user._creationTime
106+
107+
if (Object.keys(updates).length > 0) {
108+
updates.updatedAt = Date.now()
109+
await ctx.db.patch(userId, updates)
110+
}
111+
112+
return ctx.db.get(userId)
113+
}
97114

98115
export const updateProfile = mutation({
99116
args: {

0 commit comments

Comments
 (0)