feat(auth): guest login with random telegraph operator name

No email required — players pick male/female/mystery, get assigned
a humorous period-appropriate operator name (e.g. "Dusty Clicksworth",
"Ada Dottsworth", "The Phantom Sender"), can reroll, then hit
"Begin Transmitting!" for instant play. Email/magic link moved to
a collapsible section for cross-device access only.

- server: POST /api/auth/guest creates guest profile + session
- server/db: profiles.email now nullable, is_guest column added,
  migration recreates table for existing installs
- client: names.ts with 15 names per gender category
- client: LoginPage redesigned — gender picker → name display → play
- client: useAuth gains setAuth() for direct session injection
- 55 server + 44 client tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 04:22:05 +00:00
parent 8232e75fa9
commit fe5cbaec2b
7 changed files with 341 additions and 39 deletions
+31
View File
@@ -43,6 +43,37 @@ after(() => {
return new Promise(resolve => server.close(resolve))
})
describe('POST /api/auth/guest', () => {
test('creates guest profile and returns token + profile', async () => {
const r = await request(server, 'POST', '/api/auth/guest', { display_name: 'Dusty Clicksworth' })
assert.equal(r.status, 200)
assert.ok(r.body.token)
assert.equal(r.body.profile.display_name, 'Dusty Clicksworth')
assert.equal(r.body.profile.is_guest, true)
assert.equal(r.body.profile.email, null)
})
test('session token from guest login works with /api/auth/me', async () => {
const g = await request(server, 'POST', '/api/auth/guest', { display_name: 'Flash Henderson' })
const me = await request(server, 'GET', '/api/auth/me', null, {
Authorization: `Bearer ${g.body.token}`,
})
assert.equal(me.status, 200)
assert.equal(me.body.display_name, 'Flash Henderson')
assert.equal(me.body.is_guest, true)
})
test('returns 400 if display_name missing', async () => {
const r = await request(server, 'POST', '/api/auth/guest', {})
assert.equal(r.status, 400)
})
test('returns 400 if display_name too short', async () => {
const r = await request(server, 'POST', '/api/auth/guest', { display_name: 'X' })
assert.equal(r.status, 400)
})
})
describe('POST /api/auth/request', () => {
test('returns 200 ok for valid email', async () => {
const r = await request(server, 'POST', '/api/auth/request', { email: 'test@example.com' })