mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 22:51:30 -07:00
add rooms
This commit is contained in:
@@ -4,6 +4,9 @@ import { useWorkersLogger } from 'workers-tagged-logger'
|
||||
import { withNotFound, withOnError } from '@repo/hono-helpers'
|
||||
|
||||
import defaultAvatarItems from '../static/default-avatar-items.json'
|
||||
import myProgress from '../static/my-progress.json'
|
||||
import storefrontGiftDrop3 from '../static/storefronts-v3-giftdropstore-3.json'
|
||||
import weeklyChallenge from '../static/weekly-challenge.json'
|
||||
import { validateAndGetAccountId } from './jwt'
|
||||
|
||||
import type { Context } from 'hono'
|
||||
@@ -55,6 +58,10 @@ const app = new Hono<App>()
|
||||
// Default-unlocked avatar items, served from the bundled static JSON.
|
||||
.get('/api/avatar/v1/defaultunlocked', (c) => c.json(defaultAvatarItems))
|
||||
|
||||
// Default base avatar items. The C# reads the same JSON/defaultAvatarItems.json
|
||||
// file as defaultunlocked, so it returns the identical catalog.
|
||||
.get('/api/avatar/v1/defaultbaseavataritems', (c) => c.json(defaultAvatarItems))
|
||||
|
||||
// The player's avatar items — owned items concatenated with the default
|
||||
// catalog. No DB binding yet, so owned is empty and this is just the catalog.
|
||||
.get('/api/avatar/v4/items', async (c) => {
|
||||
@@ -64,6 +71,16 @@ const app = new Hono<App>()
|
||||
return c.json(defaultAvatarItems)
|
||||
})
|
||||
|
||||
// The player's owned custom avatar items. No auth in the C#, which returns
|
||||
// `{ items: [] }`. The client downloads these when custom-item creation is
|
||||
// allowed; a 404 here surfaces as "Failed to download unlocked avatar items".
|
||||
.get('/econ/customAvatarItems/v1/owned', (c) => c.json({ items: [] }))
|
||||
|
||||
// The player's objectives progress. The C# serves a static JSON file
|
||||
// (JSON/tempmyprogress.json) verbatim with no auth — same default for everyone
|
||||
// until there's a DB binding to track per-player progress.
|
||||
.get('/api/objectives/v1/myprogress', (c) => c.json(myProgress))
|
||||
|
||||
// The player's avatar. No DB binding yet, so it always returns the default
|
||||
// the C# seeds for a player with no PlayerAvatar row.
|
||||
.get('/api/avatar/v2', async (c) => {
|
||||
@@ -73,4 +90,74 @@ const app = new Hono<App>()
|
||||
return c.json({ OutfitSelections: '', FaceFeatures: '{}', SkinColor: '', HairColor: '' })
|
||||
})
|
||||
|
||||
// The player's saved outfits. [Authorize]; empty without a DB binding.
|
||||
.get('/api/avatar/v3/saved', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
// TODO: query SavedOutfits once a DB binding exists.
|
||||
return c.json([])
|
||||
})
|
||||
|
||||
// Pending avatar gifts for the player. [Authorize]; empty without a DB binding.
|
||||
.get('/api/avatar/v2/gifts', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
// TODO: query pending ReceivedGifts once a DB binding exists.
|
||||
return c.json([])
|
||||
})
|
||||
|
||||
// Unlocked equipment. The C# returns "[]" with no auth.
|
||||
.get('/api/equipment/v2/getUnlocked', (c) => c.json([]))
|
||||
|
||||
// Not in CannedNet — room consumables/currencies for a given room. Stubbed
|
||||
// as empty lists so the client doesn't 404.
|
||||
.get('/api/roomconsumables/v1/roomConsumable/room/:roomId', (c) => c.json([]))
|
||||
.get('/api/roomconsumables/v1/roomConsumable/room/:roomId/me', (c) => c.json([]))
|
||||
.get('/api/roomcurrencies/v1/currencies', (c) => c.json([]))
|
||||
.get('/api/roomcurrencies/v1/getAllBalances', (c) => c.json([]))
|
||||
|
||||
// Persist player settings. [Authorize]; the C# replaces the player's settings
|
||||
// and returns Ok(). No DB binding yet, so accept-and-ack.
|
||||
.post('/api/settings/v2/set', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
// TODO: replace stored settings for `id` once a DB binding exists.
|
||||
return c.body(null, 200)
|
||||
})
|
||||
|
||||
// Unlocked consumables. [Authorize]; empty without a DB binding.
|
||||
.get('/api/consumables/v2/getUnlocked', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
// TODO: query ConsumableItems once a DB binding exists.
|
||||
return c.json([])
|
||||
})
|
||||
|
||||
// Token balance. [Authorize]; empty without a DB binding.
|
||||
.get('/api/storefronts/v4/balance/2', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
// TODO: query TokenBalances once a DB binding exists.
|
||||
return c.json([])
|
||||
})
|
||||
|
||||
// Gift-drop storefront. The C# falls back to JSON/storefront3.json when no
|
||||
// storefront row exists; that's the bundled static catalog here.
|
||||
.get('/api/storefronts/v3/giftdropstore/3', (c) => c.json(storefrontGiftDrop3))
|
||||
|
||||
// Current weekly challenge. Served from the bundled static JSON (the C#'s
|
||||
// JSON/weeklychallenge.json) until per-rotation challenge data is wired up.
|
||||
.get('/api/challenge/v2/getCurrent', (c) => c.json(weeklyChallenge))
|
||||
|
||||
// Pending game rewards. The C# returns "[]".
|
||||
.get('/api/gamerewards/v1/pending', (c) => c.json([]))
|
||||
|
||||
// The player's room keys. The C# returns "[]".
|
||||
.get('/api/roomkeys/v1/mine', (c) => c.json([]))
|
||||
|
||||
// Subscription lookup. The C# returns both fields null with no auth.
|
||||
.post('/api/CampusCard/v1/UpdateAndGetSubscription', (c) =>
|
||||
c.json({ subscription: null, platformAccountSubscribedPlayerId: null })
|
||||
)
|
||||
|
||||
export default app
|
||||
|
||||
@@ -41,6 +41,15 @@ describe('econ endpoints', () => {
|
||||
expect(body[0]).toHaveProperty('AvatarItemDesc')
|
||||
})
|
||||
|
||||
test('GET /api/avatar/v1/defaultbaseavataritems returns the same catalog', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v1/defaultbaseavataritems`)
|
||||
expect(res.status).toBe(200)
|
||||
const body = (await res.json()) as unknown[]
|
||||
expect(Array.isArray(body)).toBe(true)
|
||||
expect(body.length).toBeGreaterThan(0)
|
||||
expect(body[0]).toHaveProperty('AvatarItemDesc')
|
||||
})
|
||||
|
||||
test('GET /api/avatar/v4/items 401s without a token', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v4/items`)
|
||||
expect(res.status).toBe(401)
|
||||
@@ -74,6 +83,144 @@ describe('econ endpoints', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('GET /econ/customAvatarItems/v1/owned returns { items: [] } (no auth)', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/econ/customAvatarItems/v1/owned`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({ items: [] })
|
||||
})
|
||||
|
||||
test('GET /api/objectives/v1/myprogress returns the default progress (no auth)', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/objectives/v1/myprogress`)
|
||||
expect(res.status).toBe(200)
|
||||
const body = (await res.json()) as { Objectives: unknown[]; ObjectiveGroups: unknown[] }
|
||||
expect(Array.isArray(body.Objectives)).toBe(true)
|
||||
expect(Array.isArray(body.ObjectiveGroups)).toBe(true)
|
||||
})
|
||||
|
||||
test('GET /api/avatar/v3/saved 401s without a token, returns [] with one', async () => {
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/avatar/v3/saved`)
|
||||
expect(anon.status).toBe(401)
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v3/saved`, {
|
||||
headers: await bearer(),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /api/avatar/v2/gifts 401s without a token, returns [] with one', async () => {
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/avatar/v2/gifts`)
|
||||
expect(anon.status).toBe(401)
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v2/gifts`, {
|
||||
headers: await bearer(),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /api/equipment/v2/getUnlocked returns [] (no auth)', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/equipment/v2/getUnlocked`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /api/roomconsumables/v1/roomConsumable/room/:id returns []', async () => {
|
||||
const res = await exports.default.fetch(
|
||||
`${ORIGIN}/api/roomconsumables/v1/roomConsumable/room/1`
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /api/roomcurrencies/v1/currencies returns []', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/roomcurrencies/v1/currencies?roomId=1`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /api/roomconsumables/v1/roomConsumable/room/:id/me returns []', async () => {
|
||||
const res = await exports.default.fetch(
|
||||
`${ORIGIN}/api/roomconsumables/v1/roomConsumable/room/1/me`
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /api/roomcurrencies/v1/getAllBalances returns []', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/roomcurrencies/v1/getAllBalances?roomId=1`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('POST /api/settings/v2/set 401s without a token, 200s with one', async () => {
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/settings/v2/set`, { method: 'POST' })
|
||||
expect(anon.status).toBe(401)
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/settings/v2/set`, {
|
||||
method: 'POST',
|
||||
headers: await bearer(),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
})
|
||||
|
||||
test('GET /api/consumables/v2/getUnlocked 401s without a token, returns []', async () => {
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/consumables/v2/getUnlocked`)
|
||||
expect(anon.status).toBe(401)
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/consumables/v2/getUnlocked`, {
|
||||
headers: await bearer(),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /api/storefronts/v4/balance/2 401s without a token, returns []', async () => {
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/storefronts/v4/balance/2`)
|
||||
expect(anon.status).toBe(401)
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/storefronts/v4/balance/2`, {
|
||||
headers: await bearer(),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /api/storefronts/v3/giftdropstore/3 returns the storefront catalog', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/storefronts/v3/giftdropstore/3`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toBeTruthy()
|
||||
})
|
||||
|
||||
test('GET /api/challenge/v2/getCurrent returns the weekly challenge', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/challenge/v2/getCurrent`)
|
||||
expect(res.status).toBe(200)
|
||||
const body = (await res.json()) as { ChallengeMapId: number; Challenges: unknown[] }
|
||||
expect(body).toHaveProperty('ChallengeMapId')
|
||||
expect(Array.isArray(body.Challenges)).toBe(true)
|
||||
})
|
||||
|
||||
test('GET /api/gamerewards/v1/pending returns []', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/gamerewards/v1/pending`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /api/roomkeys/v1/mine returns []', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/roomkeys/v1/mine`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('POST /api/CampusCard/v1/UpdateAndGetSubscription returns null fields', async () => {
|
||||
const res = await exports.default.fetch(
|
||||
`${ORIGIN}/api/CampusCard/v1/UpdateAndGetSubscription`,
|
||||
{
|
||||
method: 'POST',
|
||||
}
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
subscription: null,
|
||||
platformAccountSubscribedPlayerId: null,
|
||||
})
|
||||
})
|
||||
|
||||
test('unknown path returns 404', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/nope`)
|
||||
expect(res.status).toBe(404)
|
||||
|
||||
Reference in New Issue
Block a user