mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 06:31:27 -07:00
pfps
This commit is contained in:
@@ -98,6 +98,30 @@ export async function getAccountsByIds(db: D1Database, ids: number[]): Promise<A
|
||||
return parseAll(results)
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge `overrides` into the account row for `id` and persist it. Reads the
|
||||
* current account (falling back to a synthesized default), applies the
|
||||
* overrides, and writes the whole JSON blob back — inserting the row when the
|
||||
* account isn't in the table yet. Returns the updated account.
|
||||
*/
|
||||
export async function updateAccount(
|
||||
db: D1Database,
|
||||
id: number,
|
||||
overrides: Partial<Account>
|
||||
): Promise<Account> {
|
||||
const current = (await getAccount(db, id)) ?? defaultAccount(id)
|
||||
const updated: Account = { ...current, ...overrides, AccountId: id }
|
||||
const data = JSON.stringify(updated)
|
||||
const res = await db
|
||||
.prepare('UPDATE accounts SET data = ?2 WHERE account_id = ?1')
|
||||
.bind(id, data)
|
||||
.run()
|
||||
if (!res.meta.changes) {
|
||||
await db.prepare('INSERT INTO accounts (data) VALUES (?1)').bind(data).run()
|
||||
}
|
||||
return updated
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and persist a new account. The id is the next free integer (above the
|
||||
* seeded system accounts); the username is auto-assigned (players don't choose
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useWorkersLogger } from 'workers-tagged-logger'
|
||||
|
||||
import { withNotFound, withOnError } from '@repo/hono-helpers'
|
||||
|
||||
import { createAccount, defaultAccount, getAccount, getAccountsByIds } from './accounts-db'
|
||||
import { createAccount, defaultAccount, getAccount, getAccountsByIds, updateAccount } from './accounts-db'
|
||||
import { validateAndGetAccountId } from './jwt'
|
||||
|
||||
import type { Context } from 'hono'
|
||||
@@ -77,7 +77,6 @@ const app = new Hono<App>()
|
||||
// as null (the C# has no JsonIgnore on those, and they aren't enums).
|
||||
return c.json({
|
||||
...account,
|
||||
ProfileImage: 'hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg',
|
||||
Email: null,
|
||||
Phone: null,
|
||||
Birthday: null,
|
||||
@@ -163,7 +162,11 @@ const app = new Hono<App>()
|
||||
.put('/account/me/profileimage', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
await formField(c, 'imageName') // TODO: persist on the account row.
|
||||
const imageName = await formField(c, 'imageName')
|
||||
if (!imageName) return c.body(null, 400)
|
||||
// Persist the new avatar key on the account row (the C# also fires an
|
||||
// AccountUpdate websocket — no notify binding here, so it's omitted).
|
||||
await updateAccount(c.env.DB, id, { ProfileImage: imageName })
|
||||
return c.json({ success: true })
|
||||
})
|
||||
|
||||
|
||||
@@ -170,4 +170,32 @@ describe('auth-gated endpoints', () => {
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({ success: true })
|
||||
})
|
||||
|
||||
test('PUT /account/me/profileimage 401s without a token', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/account/me/profileimage`, {
|
||||
...form({ imageName: 'abc.jpg' }),
|
||||
})
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
|
||||
test('PUT /account/me/profileimage 400s without an imageName', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/account/me/profileimage`, {
|
||||
...form({}),
|
||||
headers: { ...(await bearer('777')), 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
test('PUT /account/me/profileimage persists the avatar on the account', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/account/me/profileimage`, {
|
||||
...form({ imageName: 'deadbeef.jpg' }),
|
||||
headers: { ...(await bearer('777')), 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({ success: true })
|
||||
|
||||
// The stored value is returned by the self account (no hardcoded override).
|
||||
const me = await exports.default.fetch(`${ORIGIN}/account/me`, { headers: await bearer('777') })
|
||||
expect(((await me.json()) as { ProfileImage: string }).ProfileImage).toBe('deadbeef.jpg')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user