mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 14:41:28 -07:00
[api] update hot avatar custom endpoint
This commit is contained in:
@@ -229,6 +229,26 @@ export async function listFeaturedCustomAvatarItems(
|
|||||||
return results.map(toDto)
|
return results.map(toDto)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "hot" (trending) feed (`GET /api/customAvatarItems/v1/hot`): every PUBLISHED item —
|
||||||
|
* `Accessibility` 0 is the unpublished state and is the only thing held back. Newest
|
||||||
|
* first, standing in for a trend ranking there is nothing to compute one from yet (no
|
||||||
|
* purchase or wear counts are recorded).
|
||||||
|
*/
|
||||||
|
export async function listHotCustomAvatarItems(
|
||||||
|
db: D1Database,
|
||||||
|
limit = 50
|
||||||
|
): Promise<CustomAvatarItem[]> {
|
||||||
|
const { results } = await db
|
||||||
|
.prepare(
|
||||||
|
`SELECT * FROM custom_avatar_item WHERE accessibility != 0
|
||||||
|
ORDER BY created_at DESC, custom_avatar_item_id LIMIT ?1`
|
||||||
|
)
|
||||||
|
.bind(limit)
|
||||||
|
.all<Row>()
|
||||||
|
return results.map(toDto)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What an account has authored (`GET /api/customAvatarItems/v2/fromCreator/:id`), newest
|
* What an account has authored (`GET /api/customAvatarItems/v2/fromCreator/:id`), newest
|
||||||
* first, with the total for the client's paginated envelope. `includeUnpublished` is for
|
* first, with the total for the client's paginated envelope. `includeUnpublished` is for
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
getCustomAvatarItem,
|
getCustomAvatarItem,
|
||||||
listCustomAvatarItemsByCreator,
|
listCustomAvatarItemsByCreator,
|
||||||
listFeaturedCustomAvatarItems,
|
listFeaturedCustomAvatarItems,
|
||||||
|
listHotCustomAvatarItems,
|
||||||
updateCustomAvatarItem,
|
updateCustomAvatarItem,
|
||||||
} from '../custom-avatar-items-db'
|
} from '../custom-avatar-items-db'
|
||||||
import { authedId, unauthorized } from '../http'
|
import { authedId, unauthorized } from '../http'
|
||||||
@@ -446,16 +447,21 @@ export const avatarRoutes = new Hono<App>({ strict: false })
|
|||||||
async (c) => c.json(await listFeaturedCustomAvatarItems(c.env.DB))
|
async (c) => c.json(await listFeaturedCustomAvatarItems(c.env.DB))
|
||||||
)
|
)
|
||||||
|
|
||||||
// The "hot" (trending) custom-avatar-item feed. No items yet → an empty list.
|
// The "hot" (trending) custom-avatar-item feed: every published (`Accessibility` != 0)
|
||||||
|
// item from the `custom_avatar_item` table. There is nothing to rank a trend from yet,
|
||||||
|
// so it is the accessible items, newest first.
|
||||||
.get(
|
.get(
|
||||||
'/api/customAvatarItems/v1/hot',
|
'/api/customAvatarItems/v1/hot',
|
||||||
describeRoute({
|
describeRoute({
|
||||||
tags: ['Avatar'],
|
tags: ['Avatar'],
|
||||||
summary: 'Trending custom avatar items',
|
summary: 'Trending custom avatar items',
|
||||||
description: 'The “hot” feed. No custom items exist yet, so it is empty.',
|
description:
|
||||||
responses: { 200: json(JsonArray, 'An empty list') },
|
'The “hot” feed: the published items (`Accessibility` 0 is unpublished and is left ' +
|
||||||
|
'out), newest first, up to 50. No purchase or wear counts are recorded, so there is ' +
|
||||||
|
'no trend to rank by and recency stands in for one.',
|
||||||
|
responses: { 200: json(CustomAvatarItemList, 'The items, newest first') },
|
||||||
}),
|
}),
|
||||||
(c) => c.json([])
|
async (c) => c.json(await listHotCustomAvatarItems(c.env.DB))
|
||||||
)
|
)
|
||||||
|
|
||||||
// A batch lookup of custom avatar items by id. The reference filters a static catalog
|
// A batch lookup of custom avatar items by id. The reference filters a static catalog
|
||||||
|
|||||||
@@ -1007,10 +1007,49 @@ describe('public endpoints', () => {
|
|||||||
expect(await res.json()).toEqual([])
|
expect(await res.json()).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('GET /api/customAvatarItems/v1/hot returns []', async () => {
|
test('GET /api/customAvatarItems/v1/hot lists the published items, newest first', async () => {
|
||||||
|
await env.DB.prepare('DELETE FROM custom_avatar_item').run()
|
||||||
|
const empty = await exports.default.fetch(`${ORIGIN}/api/customAvatarItems/v1/hot`)
|
||||||
|
expect(empty.status).toBe(200)
|
||||||
|
expect(await empty.json()).toEqual([])
|
||||||
|
|
||||||
|
const older = await createCustomAvatarItem(
|
||||||
|
env.DB,
|
||||||
|
item('Older', 1),
|
||||||
|
new Date('2026-08-01T00:00:00Z')
|
||||||
|
)
|
||||||
|
const newer = await createCustomAvatarItem(
|
||||||
|
env.DB,
|
||||||
|
item('Newer', 1),
|
||||||
|
new Date('2026-08-02T00:00:00Z')
|
||||||
|
)
|
||||||
|
const unpublished = await createCustomAvatarItem(env.DB, item('Unpublished', 0))
|
||||||
|
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/api/customAvatarItems/v1/hot`)
|
const res = await exports.default.fetch(`${ORIGIN}/api/customAvatarItems/v1/hot`)
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(await res.json()).toEqual([])
|
const got = (await res.json()) as Array<{ CustomAvatarItemId: string; Name: string }>
|
||||||
|
// Unfeatured but published: the hot feed does not care about the featured flag.
|
||||||
|
expect(got.map((i) => i.CustomAvatarItemId)).toEqual([
|
||||||
|
newer.CustomAvatarItemId,
|
||||||
|
older.CustomAvatarItemId,
|
||||||
|
])
|
||||||
|
expect(got.map((i) => i.CustomAvatarItemId)).not.toContain(unpublished.CustomAvatarItemId)
|
||||||
|
expect(got[0]).toMatchObject({ Name: 'Newer', IsFeatured: false, CurrentSaves: [] })
|
||||||
|
|
||||||
|
function item(name: string, accessibility: number) {
|
||||||
|
return {
|
||||||
|
customAvatarItemId: crypto.randomUUID(),
|
||||||
|
creatorAccountId: 205,
|
||||||
|
name,
|
||||||
|
description: '',
|
||||||
|
price: 0,
|
||||||
|
baseAvatarItemId: 1,
|
||||||
|
baseAvatarItemColor: '#fff',
|
||||||
|
accessibility,
|
||||||
|
designFilename: 'design_x.bin',
|
||||||
|
thumbnailImageFilename: 'thumb_x.png',
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('GET /api/customAvatarItems/v2/fromCreator/:id shows unpublished items only to the creator', async () => {
|
test('GET /api/customAvatarItems/v2/fromCreator/:id shows unpublished items only to the creator', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user