mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-27 07:58:55 -07:00
[sql] improvements
This commit is contained in:
@@ -347,6 +347,40 @@ const app = new Hono<App>()
|
||||
}
|
||||
)
|
||||
|
||||
// Exact lookup by username — what the site's `/u/<name>` profile pages and the staff
|
||||
// `@username` fields resolve a player through. An indexed equality on `username_lower`;
|
||||
// before this existed they went through the prefix search above and filtered the
|
||||
// exact hit client-side, which cost a 20-row index walk per profile view. Same path
|
||||
// as the official API's `GET /account/username/{username}`.
|
||||
.get(
|
||||
'/account/username/:name',
|
||||
describeRoute({
|
||||
tags: ['Lookup'],
|
||||
summary: 'A single public account, by exact username',
|
||||
description:
|
||||
'Case-insensitive exact match; a leading `@` is ignored. 404 when no account has the name.',
|
||||
parameters: [
|
||||
{
|
||||
name: 'name',
|
||||
in: 'path',
|
||||
required: true,
|
||||
description: 'The username, exactly (case-insensitive)',
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: json(AccountDto, 'The public account'),
|
||||
404: { description: 'No account has that username' },
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const name = c.req.param('name').trim().replace(/^@/, '')
|
||||
const account = name === '' ? null : await getAccountByUsername(c.env.DB, name)
|
||||
if (!account) return c.json({ error: `There's no player called @${name}.` }, 404)
|
||||
return c.json(toAccountDto(account))
|
||||
}
|
||||
)
|
||||
|
||||
// ---- Bulk / single lookup ------------------------------------------------
|
||||
// Register the static `bulk` path before the `/account/:id` param route.
|
||||
.get('/account/bulk', describeRoute(bulkRouteSpec('query')), bulkAccounts)
|
||||
|
||||
@@ -153,6 +153,24 @@ describe('public endpoints', () => {
|
||||
expect(await empty.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /account/username/:name is an exact, case-insensitive lookup', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/account/username/coach`)
|
||||
expect(res.status).toBe(200)
|
||||
const account = (await res.json()) as { accountId: number; username: string }
|
||||
expect(account).toMatchObject({ accountId: 1, username: 'Coach' })
|
||||
// A leading `@` is tolerated — staff fields take names in that form.
|
||||
const at = await exports.default.fetch(
|
||||
`${ORIGIN}/account/username/${encodeURIComponent('@Coach')}`
|
||||
)
|
||||
expect(((await at.json()) as { accountId: number }).accountId).toBe(1)
|
||||
// A prefix is NOT a match: that's what `/account/search` is for.
|
||||
const prefix = await exports.default.fetch(`${ORIGIN}/account/username/coa`)
|
||||
expect(prefix.status).toBe(404)
|
||||
const missing = await exports.default.fetch(`${ORIGIN}/account/username/zzzznope`)
|
||||
expect(missing.status).toBe(404)
|
||||
expect(await missing.json()).toEqual({ error: "There's no player called @zzzznope." })
|
||||
})
|
||||
|
||||
test('GET /account/:id/bio returns an empty bio', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/account/7/bio`)
|
||||
expect(await res.json()).toEqual({ accountId: 7, bio: '' })
|
||||
@@ -689,6 +707,7 @@ describe('auth-gated endpoints', () => {
|
||||
'GET /account/bulk',
|
||||
'GET /account/me',
|
||||
'GET /account/search',
|
||||
'GET /account/username/{name}',
|
||||
'GET /account/{id}',
|
||||
'GET /account/{id}/bio',
|
||||
'GET /accountprivacysettings/{id}',
|
||||
|
||||
Reference in New Issue
Block a user