attempting to fix some consumables not appearing in avatar immediately

This commit is contained in:
Devin Zuczek
2026-07-15 17:25:22 -04:00
parent 5345bd0517
commit 916e42c33c
10 changed files with 494 additions and 112 deletions
+3 -18
View File
@@ -1,7 +1,5 @@
import { Hono } from 'hono'
import { consumeGift } from '@repo/domain'
import { authedId, unauthorized } from '../http'
import {
createInvention,
@@ -50,9 +48,9 @@ async function creatorsInvention(
}
// ---- 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.
// The avatar read endpoints (`v4/items`, `v2`, `v2/set`, `v3/saved`, `v2/gifts`) and
// gift-box consume live in the `econ` worker, which the client calls on the econ host
// — not here. Only the gift `generate` action remains on this worker.
export const avatarRoutes = new Hono<App>({ strict: false })
.post('/api/avatar/v2/gifts/generate', async (c) => {
const id = await authedId(c)
@@ -89,19 +87,6 @@ export const avatarRoutes = new Hono<App>({ strict: false })
Message: message,
})
})
.post('/api/avatar/v2/gifts/consume', async (c) => {
const id = await authedId(c)
const body = await c.req.parseBody().catch(() => ({}) as Record<string, unknown>)
const giftId = typeof body.Id === 'string' ? Number.parseInt(body.Id, 10) || 0 : 0
// Opening a box just deletes it — the item was granted into the player's inventory
// when they bought it (see the `econ` worker's buyItem), so there's nothing to grant.
// Answers the `{ error, success, value }` envelope a captured real consume returns
// (not an empty body — the client parses it to finish opening the box). A missing/zero
// id, no token, or a box that's already gone (or isn't theirs) is a scoped no-op, not
// an error. Mirrors the econ worker's consume route (the client may call either host).
if (id !== null && giftId !== 0) await consumeGift(c.env.DB, id, giftId)
return c.json({ error: '', success: true, value: null })
})
// Custom avatar item gates — real Rec Room client endpoints with no backing
// implementation yet; we enable them. Flip to `false` to disable the
-72
View File
@@ -4,8 +4,6 @@ import { beforeAll, describe, expect, test } from 'vitest'
import '../../api.app'
import { createGift, getPendingGifts, RECEIVED_GIFT_SCHEMA_DDL } from '@repo/domain'
import { createImage, SCHEMA_DDL as IMAGES_SCHEMA_DDL } from '../../images-db'
import { SCHEMA_DDL as INVENTIONS_SCHEMA_DDL } from '../../inventions-db'
import { SCHEMA_DDL as RELATIONSHIPS_SCHEMA_DDL } from '../../relationships-db'
@@ -79,10 +77,6 @@ beforeAll(async () => {
// Inventions table (owned by the api worker) — invention save/mine use it.
for (const stmt of INVENTIONS_SCHEMA_DDL) await env.DB.prepare(stmt).run()
// Received-gift boxes (schema owned by the `econ` worker, on the shared DB) — the
// gift consume endpoint deletes from it.
for (const stmt of RECEIVED_GIFT_SCHEMA_DDL) await env.DB.prepare(stmt).run()
})
// Mint a token the way the `auth` worker does, signing with the shared test key seeded into the JWT_SECRET store, so the
@@ -281,72 +275,6 @@ describe('public endpoints', () => {
expect(await res.json()).toEqual([])
})
test('POST /api/avatar/v2/gifts/consume deletes the players gift box', async () => {
// Seed a box for account 42 directly, then consume it.
const { id: giftId } = await createGift(env.DB, 42, {
ConsumableItemDesc: '',
ConsumableCount: 0,
AvatarItemDesc: 'd0a9262f-5504-46a7-bb10-7507503db58e,,,',
AvatarItemType: 0,
CurrencyType: 0,
Currency: 0,
Xp: 0,
PackageType: 0,
Message: 'A gift for you <3',
EquipmentPrefabName: '',
EquipmentModificationGuid: '',
GiftRarity: 50,
Platform: -1,
PlatformsToSpawnOn: -1,
BalanceType: null,
})
// Consume is fire-and-forget: always 200 with the success envelope. The box is gone after.
const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v2/gifts/consume`, {
method: 'POST',
headers: await bearer('42'),
body: new URLSearchParams({ Id: String(giftId), UnlockedLevel: '0' }),
})
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ error: '', success: true, value: null })
expect(await getPendingGifts(env.DB, 42)).toHaveLength(0)
// Consuming it again is a no-op — still 200, nothing changes.
const again = await exports.default.fetch(`${ORIGIN}/api/avatar/v2/gifts/consume`, {
method: 'POST',
headers: await bearer('42'),
body: new URLSearchParams({ Id: String(giftId) }),
})
expect(again.status).toBe(200)
})
test('POST /api/avatar/v2/gifts/consume leaves another players box untouched', async () => {
const { id: giftId } = await createGift(env.DB, 99, {
ConsumableItemDesc: '',
ConsumableCount: 0,
AvatarItemDesc: 'a,,,',
AvatarItemType: 0,
CurrencyType: 0,
Currency: 0,
Xp: 0,
PackageType: 0,
Message: '',
EquipmentPrefabName: '',
EquipmentModificationGuid: '',
GiftRarity: 0,
Platform: -1,
PlatformsToSpawnOn: -1,
BalanceType: null,
})
// Account 42 consuming account 99's box is a scoped no-op (still 200), and 99 keeps it.
const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v2/gifts/consume`, {
method: 'POST',
headers: await bearer('42'),
body: new URLSearchParams({ Id: String(giftId) }),
})
expect(res.status).toBe(200)
expect((await getPendingGifts(env.DB, 99)).some((g) => g.Id === giftId)).toBe(true)
})
test('GET /api/customAvatarItems/v1/isCreationAllowedForAccount returns a success envelope', async () => {
const res = await exports.default.fetch(
`${ORIGIN}/api/customAvatarItems/v1/isCreationAllowedForAccount`