mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 14:41:28 -07:00
avatar endpoints
This commit is contained in:
+4
-41
@@ -4,12 +4,10 @@ import { useWorkersLogger } from 'workers-tagged-logger'
|
||||
import { withNotFound, withOnError } from '@repo/hono-helpers'
|
||||
|
||||
import apiConfigV2 from '../static/api-config-v2.json'
|
||||
import defaultAvatar from '../static/default-avatar.json'
|
||||
import gameConfigsV1All from '../static/gameconfigs-v1-all.json'
|
||||
import storefrontGiftDrop2 from '../static/storefronts-v3-giftdropstore-2.json'
|
||||
import storefrontGiftDrop3 from '../static/storefronts-v3-giftdropstore-3.json'
|
||||
import storefrontGiftDrop300 from '../static/storefronts-v3-giftdropstore-300.json'
|
||||
import { DEFAULT_AVATAR_ITEMS } from './default-avatar-items'
|
||||
import { defaultSettings } from './default-settings'
|
||||
import { validateAndGetAccountId } from './jwt'
|
||||
import { getRoomById, getRoomByName, getRoomsByCreator, getRoomsByIds } from './rooms-db'
|
||||
@@ -229,45 +227,10 @@ const app = new Hono<App>({ strict: false })
|
||||
return c.json([])
|
||||
})
|
||||
|
||||
// ---- Avatar ---------------------------------------------------------------
|
||||
.get('/api/avatar/v4/items', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
// Owned items would be concatenated here; none without a DB binding.
|
||||
return c.json(DEFAULT_AVATAR_ITEMS)
|
||||
})
|
||||
.get('/api/avatar/v2', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
// TODO: load/create PlayerAvatar for `id`. Must return a populated outfit —
|
||||
// the client NREs on an empty OutfitSelections — so serve a valid default.
|
||||
return c.json(defaultAvatar)
|
||||
})
|
||||
.post('/api/avatar/v2/set', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
const update = await c.req.json<Record<string, unknown>>().catch(() => null)
|
||||
if (update === null) return c.body(null, 400)
|
||||
// TODO: persist; echo the accepted avatar back. Fall back to the valid
|
||||
// default avatar fields when the client omits them.
|
||||
return c.json({
|
||||
OwnerAccountId: id,
|
||||
OutfitSelections: update.OutfitSelections ?? defaultAvatar.OutfitSelections,
|
||||
FaceFeatures: update.FaceFeatures ?? defaultAvatar.FaceFeatures,
|
||||
SkinColor: update.SkinColor ?? defaultAvatar.SkinColor,
|
||||
HairColor: update.HairColor ?? defaultAvatar.HairColor,
|
||||
})
|
||||
})
|
||||
.get('/api/avatar/v3/saved', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
return c.json([]) // TODO: query SavedOutfits
|
||||
})
|
||||
.get('/api/avatar/v2/gifts', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
return c.json([]) // TODO: query pending ReceivedGifts
|
||||
})
|
||||
// ---- Avatar gifts ---------------------------------------------------------
|
||||
// The avatar read endpoints (`v4/items`, `v2`, `v2/set`, `v3/saved`, `v2/gifts`)
|
||||
// live in the `econ` worker, which the client calls on the econ host — not here.
|
||||
// Only the gift generate/consume actions remain on this worker.
|
||||
.post('/api/avatar/v2/gifts/generate', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
|
||||
@@ -4,7 +4,6 @@ import { beforeAll, describe, expect, test } from 'vitest'
|
||||
|
||||
import '../../api.app'
|
||||
|
||||
import { DEFAULT_AVATAR_ITEMS } from '../../default-avatar-items'
|
||||
|
||||
import type { Env } from '../../context'
|
||||
|
||||
@@ -246,26 +245,17 @@ describe('public endpoints', () => {
|
||||
|
||||
describe('auth-gated endpoints', () => {
|
||||
test('401 without a bearer token', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v4/items`)
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/settings/v2`)
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
|
||||
test('401 with a garbage token', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v4/items`, {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/settings/v2`, {
|
||||
headers: { Authorization: 'Bearer not-a-real-token' },
|
||||
})
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
|
||||
test('GET /api/avatar/v4/items returns default items with a valid token', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v4/items`, {
|
||||
headers: await bearer(),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
const items = (await res.json()) as unknown[]
|
||||
expect(items).toHaveLength(DEFAULT_AVATAR_ITEMS.length)
|
||||
})
|
||||
|
||||
test('GET /api/settings/v2 returns the default settings for the account', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/settings/v2`, {
|
||||
headers: await bearer(),
|
||||
@@ -276,12 +266,6 @@ describe('auth-gated endpoints', () => {
|
||||
for (const s of settings) expect(s.PlayerId).toBe(42)
|
||||
})
|
||||
|
||||
test('GET /api/avatar/v2 returns a default avatar', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v2`, { headers: await bearer() })
|
||||
const body = (await res.json()) as { OutfitSelections: string }
|
||||
expect(body.OutfitSelections.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('GET /api/checklist/v1/current 401s without a token, returns [] with one', async () => {
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/checklist/v1/current`)
|
||||
expect(anon.status).toBe(401)
|
||||
|
||||
Reference in New Issue
Block a user