mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 07:01:27 -07:00
remove old /roomserver/ endpoints
This commit is contained in:
+1
-70
@@ -17,7 +17,7 @@ import {
|
|||||||
getPlayerFeed,
|
getPlayerFeed,
|
||||||
} from './images-db'
|
} from './images-db'
|
||||||
import { validateAndGetAccountId } from './jwt'
|
import { validateAndGetAccountId } from './jwt'
|
||||||
import { getRoomById, getRoomByName, getRoomsByCreator, getRoomsByIds } from './rooms-db'
|
import { getRoomById } from './rooms-db'
|
||||||
|
|
||||||
import type { Context } from 'hono'
|
import type { Context } from 'hono'
|
||||||
import type { App } from './context'
|
import type { App } from './context'
|
||||||
@@ -85,39 +85,6 @@ function queryIds(c: Context<App>): number[] {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Photon access-token response (`/roomserver/photon_access_token`). The 2023
|
|
||||||
* client calls this to get its room permissions + the instance id it's spawning
|
|
||||||
* into; a 404 here leaves the player stuck on a black screen. `PhotonAccessToken`
|
|
||||||
* is empty — the client uses its baked-in Photon credentials. Our synthesized
|
|
||||||
* instances always use roomInstanceId 1.
|
|
||||||
*/
|
|
||||||
function photonAccessToken() {
|
|
||||||
const perm = (Permission: string, Role: number, Override: boolean) => ({
|
|
||||||
Override,
|
|
||||||
Permission,
|
|
||||||
Role,
|
|
||||||
Type: 0,
|
|
||||||
Value: 'True',
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
Permissions: [
|
|
||||||
perm('CAN_USE_ROOM_RESET_BUTTON', 0, true),
|
|
||||||
perm('CAN_USE_DELETE_ALL_BUTTON', 0, true),
|
|
||||||
perm('CAN_SAVE_INVENTIONS', 0, true),
|
|
||||||
perm('CAN_SPAWN_INVENTIONS', 0, true),
|
|
||||||
perm('CAN_USE_PLAY_GIZMOS_TOGGLE', 0, true),
|
|
||||||
perm('CAN_USE_MAKER_PEN', 30, false),
|
|
||||||
perm('CAN_USE_ROOM_RESET_BUTTON', 30, true),
|
|
||||||
perm('CAN_USE_DELETE_ALL_BUTTON', 30, true),
|
|
||||||
perm('CAN_SAVE_INVENTIONS', 30, true),
|
|
||||||
perm('CAN_SPAWN_INVENTIONS', 30, true),
|
|
||||||
perm('CAN_USE_PLAY_GIZMOS_TOGGLE', 30, true),
|
|
||||||
],
|
|
||||||
PhotonAccessToken: '',
|
|
||||||
RoomInstanceId: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Default reputation for an account — the fallback used with no DB. */
|
/** Default reputation for an account — the fallback used with no DB. */
|
||||||
function defaultReputation(id: number) {
|
function defaultReputation(id: number) {
|
||||||
@@ -606,40 +573,4 @@ const app = new Hono<App>({ strict: false })
|
|||||||
return c.json(hasRole)
|
return c.json(hasRole)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ---- Room server ----------------------------------------------------------
|
|
||||||
// Room data is read from the shared `recflare` D1 (owned by the rooms worker).
|
|
||||||
// Register specific paths before the `/:id` param route.
|
|
||||||
.get('/roomserver/rooms/bulk', async (c) => {
|
|
||||||
const idParam = c.req.query('id')
|
|
||||||
const nameParam = c.req.query('name')
|
|
||||||
if (!idParam && !nameParam) {
|
|
||||||
return c.text("Either 'id' or 'name' query parameter is required", 400)
|
|
||||||
}
|
|
||||||
if (idParam) {
|
|
||||||
const ids = idParam
|
|
||||||
.split(',')
|
|
||||||
.map((s) => Number.parseInt(s.trim(), 10))
|
|
||||||
.filter((n) => !Number.isNaN(n))
|
|
||||||
return c.json(await getRoomsByIds(c.env.DB, ids))
|
|
||||||
}
|
|
||||||
const room = await getRoomByName(c.env.DB, nameParam ?? '')
|
|
||||||
return c.json(room ? [room] : [])
|
|
||||||
})
|
|
||||||
// Photon access token + room permissions the client needs to spawn into a room.
|
|
||||||
.get('/roomserver/photon_access_token', (c) => c.json(photonAccessToken()))
|
|
||||||
.get('/roomserver/rooms/hot', (c) => c.json({ Results: [], TotalResults: 0 }))
|
|
||||||
.get('/roomserver/roomsandplaylists/hot', (c) => c.json({ Results: [], TotalResults: 0 }))
|
|
||||||
.get('/roomserver/rooms/createdby/me', async (c) =>
|
|
||||||
c.json(await getRoomsByCreator(c.env.DB, (await authedId(c)) ?? 1))
|
|
||||||
)
|
|
||||||
.get('/roomserver/rooms/:id/interactionby/me', (c) =>
|
|
||||||
c.json({ Cheered: false, Favorited: false })
|
|
||||||
)
|
|
||||||
.get('/roomserver/rooms/:id', async (c) => {
|
|
||||||
const roomId = Number.parseInt(c.req.param('id'), 10)
|
|
||||||
if (Number.isNaN(roomId)) return c.notFound()
|
|
||||||
const room = await getRoomById(c.env.DB, roomId)
|
|
||||||
return room ? c.json(room) : c.notFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type Env = SharedHonoEnv & {
|
|||||||
*/
|
*/
|
||||||
DOMAIN: string
|
DOMAIN: string
|
||||||
// Shared rooms database (schema/migrations owned by the `rooms` worker). Used
|
// Shared rooms database (schema/migrations owned by the `rooms` worker). Used
|
||||||
// read-only here for the /roomserver/rooms/* endpoints.
|
// read-only here to resolve room roles for `/api/rooms/v1/verifyRole`.
|
||||||
DB: D1Database
|
DB: D1Database
|
||||||
// Image bucket (shared with the `img` worker, which serves objects back by
|
// Image bucket (shared with the `img` worker, which serves objects back by
|
||||||
// key). Uploaded saved images are written here.
|
// key). Uploaded saved images are written here.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Read helpers for the shared `recflare` D1 database. The schema, migrations,
|
* Read helpers for the shared `recflare` D1 database. The schema, migrations,
|
||||||
* and seed are owned by the `rooms` worker (apps/rooms/src/rooms-db.ts +
|
* and seed are owned by the `rooms` worker (apps/rooms/src/rooms-db.ts +
|
||||||
* migrations); this worker binds the same database read-only for its
|
* migrations); this worker binds the same database read-only to resolve room
|
||||||
* `/roomserver/rooms/*` endpoints. Keep these queries in sync with the rooms
|
* roles for the `/api/rooms/v1/verifyRole` endpoint. Keep these queries in sync
|
||||||
* worker's.
|
* with the rooms worker's.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** A stored room — the parsed JSON blob (full client-facing room response). */
|
/** A stored room — the parsed JSON blob (full client-facing room response). */
|
||||||
@@ -14,37 +14,9 @@ interface RoomRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parseOne = (row: RoomRow | null): Room | null => (row ? (JSON.parse(row.data) as Room) : null)
|
const parseOne = (row: RoomRow | null): Room | null => (row ? (JSON.parse(row.data) as Room) : null)
|
||||||
const parseAll = (rows: RoomRow[]): Room[] => rows.map((r) => JSON.parse(r.data) as Room)
|
|
||||||
|
|
||||||
export async function getRoomById(db: D1Database, roomId: number): Promise<Room | null> {
|
export async function getRoomById(db: D1Database, roomId: number): Promise<Room | null> {
|
||||||
return parseOne(
|
return parseOne(
|
||||||
await db.prepare('SELECT data FROM rooms WHERE room_id = ?1').bind(roomId).first<RoomRow>()
|
await db.prepare('SELECT data FROM rooms WHERE room_id = ?1').bind(roomId).first<RoomRow>()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRoomByName(db: D1Database, name: string): Promise<Room | null> {
|
|
||||||
return parseOne(
|
|
||||||
await db
|
|
||||||
.prepare('SELECT data FROM rooms WHERE name_lower = ?1')
|
|
||||||
.bind(name.toLowerCase())
|
|
||||||
.first<RoomRow>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getRoomsByIds(db: D1Database, ids: number[]): Promise<Room[]> {
|
|
||||||
if (ids.length === 0) return []
|
|
||||||
const placeholders = ids.map((_, i) => `?${i + 1}`).join(',')
|
|
||||||
const { results } = await db
|
|
||||||
.prepare(`SELECT data FROM rooms WHERE room_id IN (${placeholders})`)
|
|
||||||
.bind(...ids)
|
|
||||||
.all<RoomRow>()
|
|
||||||
return parseAll(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getRoomsByCreator(db: D1Database, accountId: number): Promise<Room[]> {
|
|
||||||
const { results } = await db
|
|
||||||
.prepare('SELECT data FROM rooms WHERE creator_account_id = ?1')
|
|
||||||
.bind(accountId)
|
|
||||||
.all<RoomRow>()
|
|
||||||
return parseAll(results)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,16 +15,9 @@ declare module 'cloudflare:test' {
|
|||||||
|
|
||||||
const ORIGIN = 'https://example.com'
|
const ORIGIN = 'https://example.com'
|
||||||
|
|
||||||
// The /roomserver/rooms/* routes read from the shared recflare D1. Set up the
|
// `/api/rooms/v1/verifyRole` reads room roles from the shared recflare D1. Set
|
||||||
// schema (matching the rooms worker's migration) + a couple of rooms for tests.
|
// up the schema (matching the rooms worker's migration) + a couple of rooms.
|
||||||
const TEST_ROOMS = [
|
const TEST_ROOMS = [
|
||||||
{
|
|
||||||
RoomId: 1,
|
|
||||||
Name: 'DormRoom',
|
|
||||||
IsDorm: true,
|
|
||||||
CreatorAccountId: 1,
|
|
||||||
SubRooms: [{ SubRoomId: 1, UnitySceneId: '76d98498-60a1-430c-ab76-b54a29b7a163' }],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
RoomId: 2,
|
RoomId: 2,
|
||||||
Name: 'RecCenter',
|
Name: 'RecCenter',
|
||||||
@@ -307,25 +300,7 @@ describe('auth-gated endpoints', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('room server', () => {
|
describe('rooms', () => {
|
||||||
test('GET /roomserver/rooms/bulk requires id or name', async () => {
|
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/roomserver/rooms/bulk`)
|
|
||||||
expect(res.status).toBe(400)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('GET /roomserver/rooms/bulk with id returns rooms from D1', async () => {
|
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/roomserver/rooms/bulk?id=1,2`)
|
|
||||||
expect(res.status).toBe(200)
|
|
||||||
const rooms = (await res.json()) as Array<{ RoomId: number; Name: string }>
|
|
||||||
expect(rooms.map((r) => r.RoomId).sort((a, b) => a - b)).toEqual([1, 2])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('GET /roomserver/rooms/bulk?name= resolves from D1', async () => {
|
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/roomserver/rooms/bulk?name=reccenter`)
|
|
||||||
const rooms = (await res.json()) as Array<{ Name: string }>
|
|
||||||
expect(rooms.map((r) => r.Name)).toEqual(['RecCenter'])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('POST /api/rooms/v1/verifyRole checks creator + room roles', async () => {
|
test('POST /api/rooms/v1/verifyRole checks creator + room roles', async () => {
|
||||||
const verify = async (fields: Record<string, string>, sub?: string): Promise<boolean> => {
|
const verify = async (fields: Record<string, string>, sub?: string): Promise<boolean> => {
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/api/rooms/v1/verifyRole`, {
|
const res = await exports.default.fetch(`${ORIGIN}/api/rooms/v1/verifyRole`, {
|
||||||
@@ -353,46 +328,6 @@ describe('room server', () => {
|
|||||||
// Unknown room → false.
|
// Unknown room → false.
|
||||||
expect(await verify({ roomId: '99999', role: '0' }, '42')).toBe(false)
|
expect(await verify({ roomId: '99999', role: '0' }, '42')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('GET /roomserver/photon_access_token returns permissions + instance id', async () => {
|
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/roomserver/photon_access_token`)
|
|
||||||
expect(res.status).toBe(200)
|
|
||||||
const body = (await res.json()) as {
|
|
||||||
Permissions: unknown[]
|
|
||||||
PhotonAccessToken: string
|
|
||||||
RoomInstanceId: number
|
|
||||||
}
|
|
||||||
expect(Array.isArray(body.Permissions)).toBe(true)
|
|
||||||
expect(body.Permissions.length).toBeGreaterThan(0)
|
|
||||||
expect(body).toMatchObject({ PhotonAccessToken: '', RoomInstanceId: 1 })
|
|
||||||
})
|
|
||||||
|
|
||||||
test('GET /roomserver/rooms/hot returns an empty result set', async () => {
|
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/roomserver/rooms/hot`)
|
|
||||||
expect(await res.json()).toEqual({ Results: [], TotalResults: 0 })
|
|
||||||
})
|
|
||||||
|
|
||||||
test('GET /roomserver/rooms/:id returns the room from D1', async () => {
|
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/roomserver/rooms/1`)
|
|
||||||
expect(res.status).toBe(200)
|
|
||||||
const room = (await res.json()) as {
|
|
||||||
RoomId: number
|
|
||||||
IsDorm: boolean
|
|
||||||
SubRooms: Array<{ UnitySceneId: string }>
|
|
||||||
}
|
|
||||||
expect(room).toMatchObject({ RoomId: 1, IsDorm: true })
|
|
||||||
expect(room.SubRooms[0].UnitySceneId).toBe('76d98498-60a1-430c-ab76-b54a29b7a163')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('GET /roomserver/rooms/:id 404s for an unknown room', async () => {
|
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/roomserver/rooms/99999`)
|
|
||||||
expect(res.status).toBe(404)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('GET /roomserver/rooms/:id/interactionby/me', async () => {
|
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/roomserver/rooms/5/interactionby/me`)
|
|
||||||
expect(await res.json()).toEqual({ Cheered: false, Favorited: false })
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('images', () => {
|
describe('images', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user