[api] quest asset split on unity target (#108)

* [api] quest asset split

* [api] unity swap + hash removal
This commit is contained in:
devin
2026-09-20 23:40:08 -04:00
committed by GitHub
parent 2909550cd6
commit 95bca2458a
10 changed files with 327 additions and 35 deletions
+12 -1
View File
@@ -23,6 +23,8 @@ import { validateAndGetAccountId, validateAndGetPlus, validateAndGetVersion } fr
import {
getCustomAvatarItems,
isQuestAssetTarget,
toQuestCustomAvatarItem,
toUgcPurchasable,
UGC_ITEM_TYPE_CUSTOM_AVATAR_ITEM,
} from '../../api/src/custom-avatar-items-db'
@@ -2661,6 +2663,9 @@ const app = new Hono<App>({ strict: false })
'`CustomAvatarItem` record out of the `custom_avatar_item` table, oldest first by when',
'it became theirs, in the `{ Results, TotalResults }` envelope. No paging is applied',
'(the client sends none), so `TotalResults` is the list’s length.',
'`unityAssetTarget` 2 (Android/Oculus) serves each save’s `UnityAsset`/`UnityAsset2`',
'under `quest/`, the Android build of the same assetbundle — the same switch `api`’s',
'search and bulk reads make; 0 (PC), any other value, and none get the names as stored.',
].join(' '),
security: AUTHED,
responses: {
@@ -2671,7 +2676,13 @@ const app = new Hono<App>({ strict: false })
async (c) => {
const id = await authedId(c)
if (id === null) return unauthorized(c)
const Results = await getOwnedCustomAvatarItems(c.env.DB, id)
const owned = await getOwnedCustomAvatarItems(c.env.DB, id)
// The client renders what the player is WEARING from this list, and it is read before
// any `api` lookup — so a Quest served PC names here downloads PC assetbundles however
// the search and bulk reads answer.
const Results = isQuestAssetTarget(c.req.query('unityAssetTarget'))
? owned.map(toQuestCustomAvatarItem)
: owned
return c.json({ Results, TotalResults: Results.length })
}
)
@@ -20,6 +20,7 @@ import {
import {
createCustomAvatarItem,
SCHEMA_DDL as CUSTOM_AVATAR_ITEM_SCHEMA_DDL,
importCustomAvatarItem,
} from '../../../../api/src/custom-avatar-items-db'
import { SCHEMA_DDL as INVENTION_SCHEMA_DDL } from '../../../../api/src/inventions-db'
// The notification-type ids the hub carries, from the worker that owns them — asserting
@@ -588,6 +589,56 @@ describe('econ endpoints', () => {
],
TotalResults: 4,
})
// A first-party item is rendered from its saves' assetbundles. A caller asking for the
// Quest target (`unityAssetTarget=2`, Android/Oculus) is pointed at the `quest/` builds;
// target 0 (PC) gets the names as stored.
const save = {
CustomAvatarItemSaveId: 1,
CustomAvatarItemId: '83fe651f-15b3-46a7-8afc-adec32c35568',
UnityAssetId: crypto.randomUUID(),
BodyType: 2,
OutfitType: 100,
QAState: 0,
CreatedAt: '2024-09-26T21:32:48.523Z',
ModifiedAt: '2024-09-26T21:32:48.523Z',
Description: null,
ThumbnailFileName: 'avatar/f2y1ndzuvm5ke2hjmn4cwfwfl.png',
AdditionalConfiguration: '{}',
UnityAsset: '3rdxsypmi0bdkxzrt1qmz1dpa.assetbundle',
UnityAssetHash: 'hash-a',
UnityAsset2: null,
UnityAsset2Hash: null,
}
const wings = await importCustomAvatarItem(env.DB, {
...first,
CustomAvatarItemId: save.CustomAvatarItemId,
RankedEntityId: save.CustomAvatarItemId,
CreatorAccountId: 1,
Name: 'Skeletal Wings',
BaseAvatarItemId: null,
BaseAvatarItemColor: null,
DesignFilename: null,
ThumbnailImageFilename: null,
OutfitType: 100,
CurrentSaves: [save],
})
await grantCustomAvatarItem(env.DB, 612, wings.CustomAvatarItemId)
const ownedAssets = async (query: string) => {
const res = await exports.default.fetch(`${ORIGIN}/econ/customAvatarItems/v1/owned${query}`, {
headers: await bearer('612'),
})
const { Results } = (await res.json()) as {
Results: Array<{ CurrentSaves: Array<{ UnityAsset: string; UnityAsset2: string | null }> }>
}
return Results[0].CurrentSaves.map((s) => [s.UnityAsset, s.UnityAsset2])
}
expect(await ownedAssets('?skip=0&take=1000&unityAssetTarget=0')).toEqual([
[save.UnityAsset, null],
])
expect(await ownedAssets('?skip=0&take=1000&unityAssetTarget=2&unityAssetVersion=3')).toEqual([
[`quest/${save.UnityAsset}`, null],
])
})
test('GET /api/objectives/v1/myprogress returns the default progress (no auth)', async () => {