roooooms 2

This commit is contained in:
2025-09-06 22:22:36 -04:00
parent 03b751dda6
commit 2aa5352350
15 changed files with 473 additions and 1004 deletions

View File

@@ -6,6 +6,7 @@
"@felix/bcrypt": "jsr:@felix/bcrypt@^1.0.5", "@felix/bcrypt": "jsr:@felix/bcrypt@^1.0.5",
"@hono/hono": "jsr:@hono/hono@^4.8.5", "@hono/hono": "jsr:@hono/hono@^4.8.5",
"@hono/zod-validator": "jsr:@hono/zod-validator@^0.7.2", "@hono/zod-validator": "jsr:@hono/zod-validator@^0.7.2",
"@oneday/http-status": "jsr:@oneday/http-status@^0.2.0",
"@proxnet/undead-logging": "jsr:@proxnet/undead-logging@^1.5.0", "@proxnet/undead-logging": "jsr:@proxnet/undead-logging@^1.5.0",
"@std/assert": "jsr:@std/assert@1", "@std/assert": "jsr:@std/assert@1",
"sharp": "npm:sharp@^0.34.3", "sharp": "npm:sharp@^0.34.3",

937
deno.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1272,7 +1272,7 @@
"Name": "RecCenter", "Name": "RecCenter",
"ReplicationId": "02ed2947-2db9-62c4-49b0-76d70fd432bb", "ReplicationId": "02ed2947-2db9-62c4-49b0-76d70fd432bb",
"Description": "A social hub to meet and mingle with friends new and old.", "Description": "A social hub to meet and mingle with friends new and old.",
"Accessibility": 2, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": false, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
@@ -1303,7 +1303,7 @@
"Description": "Take turns drawing, acting, and guessing funny phrases with your friends!", "Description": "Take turns drawing, acting, and guessing funny phrases with your friends!",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": false, "SupportsScreens": false,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1332,7 +1332,7 @@
"Description": "A leisurely stroll through the grass. Throw your disc into the goal. Sounds easy, right?", "Description": "A leisurely stroll through the grass. Throw your disc into the goal. Sounds easy, right?",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1361,7 +1361,7 @@
"Description": "Throw your disc through hazards and around wind machines on this challenging course!", "Description": "Throw your disc through hazards and around wind machines on this challenging course!",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1390,7 +1390,7 @@
"Description": "Throw dodgeballs to knock out your friends in this gym classic!", "Description": "Throw dodgeballs to knock out your friends in this gym classic!",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1419,7 +1419,7 @@
"Description": "A simple rally game between two players in a plexiglass tube with a zero-g ball.", "Description": "A simple rally game between two players in a plexiglass tube with a zero-g ball.",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": false, "SupportsScreens": false,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1448,7 +1448,7 @@
"Description": "Red and Blue teams splat each other in capture the flag and team battle.", "Description": "Red and Blue teams splat each other in capture the flag and team battle.",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": true, "SupportsLevelVoting": true,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1542,7 +1542,7 @@
"Description": "Red and Blue teams splat each other in capture the flag and team battle.", "Description": "Red and Blue teams splat each other in capture the flag and team battle.",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": true, "SupportsLevelVoting": true,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": false, "SupportsScreens": false,
"SupportsWalkVR": false, "SupportsWalkVR": false,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1623,7 +1623,7 @@
"Description": "The goblin king stole Coach's Golden Trophy. Team up and embark on an epic quest to recover it!", "Description": "The goblin king stole Coach's Golden Trophy. Team up and embark on an epic quest to recover it!",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1652,7 +1652,7 @@
"Description": "Robot invaders threaten the galaxy! Team up with your friends and bring the laser heat!", "Description": "Robot invaders threaten the galaxy! Team up with your friends and bring the laser heat!",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1681,7 +1681,7 @@
"Description": "Can your band of adventurers brave the enchanted wilds, and lift the curse of the crimson cauldron?", "Description": "Can your band of adventurers brave the enchanted wilds, and lift the curse of the crimson cauldron?",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1710,7 +1710,7 @@
"Description": "Can your pirate crew get to the Isle, defeat its fearsome guardian, and escape with the gold?", "Description": "Can your pirate crew get to the Isle, defeat its fearsome guardian, and escape with the gold?",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1739,7 +1739,7 @@
"Description": "Teams of three run around slamming themselves into an over-sized soccer ball. Goal!", "Description": "Teams of three run around slamming themselves into an over-sized soccer ball. Goal!",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1768,7 +1768,7 @@
"Description": "Teams battle each other and waves of robots.", "Description": "Teams battle each other and waves of robots.",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": true, "SupportsLevelVoting": true,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -1924,7 +1924,7 @@
"Name": "PerformanceHall", "Name": "PerformanceHall",
"ReplicationId": "22ab0d3c-3d7d-70e4-eb5c-c8c47cca1906", "ReplicationId": "22ab0d3c-3d7d-70e4-eb5c-c8c47cca1906",
"Description": "A theater for plays, music, comedy and other performances.", "Description": "A theater for plays, music, comedy and other performances.",
"Accessibility": 2, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": true,
"SupportsScreens": true, "SupportsScreens": true,
@@ -2361,7 +2361,7 @@
"Description": "Throw dodgeballs to knock out your friends in this gym classic!", "Description": "Throw dodgeballs to knock out your friends in this gym classic!",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": false, "SupportsScreens": false,
"SupportsWalkVR": false, "SupportsWalkVR": false,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -2390,7 +2390,7 @@
"Description": "Gather your vampire hunting crew, conquer a legendary castle, and restore peace to Rec Room!", "Description": "Gather your vampire hunting crew, conquer a legendary castle, and restore peace to Rec Room!",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,
@@ -2419,7 +2419,7 @@
"Description": "Hang out with friends, bowl a few games, eat snacks!", "Description": "Hang out with friends, bowl a few games, eat snacks!",
"Accessibility": 1, "Accessibility": 1,
"SupportsLevelVoting": false, "SupportsLevelVoting": false,
"CloningAllowed": true, "CloningAllowed": false,
"SupportsScreens": true, "SupportsScreens": true,
"SupportsWalkVR": true, "SupportsWalkVR": true,
"SupportsTeleportVR": true, "SupportsTeleportVR": true,

View File

@@ -12,7 +12,9 @@ import { PushNotificationId } from "./server/socket/signalr/types.ts";
import { genericResponse } from "./util/api.ts"; import { genericResponse } from "./util/api.ts";
import { getNetConfig } from "./net.ts"; import { getNetConfig } from "./net.ts";
import { TokenFormat, TokenType } from "./server/platforms/types.ts"; import { TokenFormat, TokenType } from "./server/platforms/types.ts";
import { Context } from "node:vm"; import { HonoEnv } from "./util/types.ts";
import { Context } from "@hono/hono";
import { compress } from "@hono/hono/compress";
LoggingConfiguration.resetTimeFormat = TimeFormat.Unix; LoggingConfiguration.resetTimeFormat = TimeFormat.Unix;
LoggingConfiguration.resetLogTiming = LogTiming.Microtask; LoggingConfiguration.resetLogTiming = LogTiming.Microtask;
@@ -48,8 +50,10 @@ await routeImporter(AppRoot.app, 'src/', [
'routes/img' 'routes/img'
]); ]);
AppRoot.app.use(compress());
// deno-lint-ignore require-await // deno-lint-ignore require-await
AppRoot.app.use('*', async (c: Context) => { AppRoot.app.use('*', async (c: Context<HonoEnv>) => {
return c.json(genericResponse(false, "Resource Not Found"), 404); return c.json(genericResponse(false, "Resource Not Found"), 404);
}); });

View File

@@ -0,0 +1,26 @@
import z from "zod";
import { createHonoRoute } from "../../../util/import.ts";
import { typedZValidator } from "../../../util/validators.ts";
import Server from "../../../server/server.ts";
export const route = createHonoRoute("/rooms");
const getRoomDetailsParamSchema = z.object({
id: z.coerce.number().max(Math.pow(2, 31))
});
route.app.get('/v4/details/:id', typedZValidator('param', getRoomDetailsParamSchema), async (c, nxt) => {
const room = await Server.Rooms.get(parseInt(c.req.param('id')));
if (room === null) return await nxt();
else return c.json(await room.export());
});
const getRoomByNameParamSchema = z.object({
name: z.string()
});
route.app.get('/v2/name/:name', typedZValidator('param', getRoomByNameParamSchema), async (c, nxt) => {
const id = await Server.Rooms.getIdFromName(c.req.param('name'));
if (id == null) return await nxt();
const room = await Server.Rooms.get(id);
if (room == null) return await nxt();
else return c.json((await room.export()).Room);
});

View File

@@ -1,5 +1,5 @@
import z from "zod"; import z from "zod";
import { authenticate, genericResponse } from "../../../util/api.ts"; import { authenticate } from "../../../util/api.ts";
import { createHonoRoute } from "../../../util/import.ts"; import { createHonoRoute } from "../../../util/import.ts";
import { typedZValidator } from "../../../util/validators.ts"; import { typedZValidator } from "../../../util/validators.ts";
import { HonoEnv } from "../../../util/types.ts"; import { HonoEnv } from "../../../util/types.ts";
@@ -23,7 +23,6 @@ const settingsSetSchema = z.object({
}); });
route.app.post('/v2/set', typedZValidator('json', settingsSetSchema), async c => { route.app.post('/v2/set', typedZValidator('json', settingsSetSchema), async c => {
const { Key, Value } = c.req.valid('json'); const { Key, Value } = c.req.valid('json');
if (!Key) return c.json(genericResponse(false, "Internal Server Error"), 500);
await c.get('profile').Settings.setSetting(Key, Value); await c.get('profile').Settings.setSetting(Key, Value);
return c.status(200); return c.status(200);

View File

@@ -2,39 +2,50 @@ import z from "zod";
import Server from "../../server/server.ts"; import Server from "../../server/server.ts";
import { createHonoRoute } from "../../util/import.ts"; import { createHonoRoute } from "../../util/import.ts";
import { typedZValidator } from "../../util/validators.ts"; import { typedZValidator } from "../../util/validators.ts";
import { FileType, File } from "../../server/content/base.ts";
import sharp from "sharp"; import sharp from "sharp";
import path from "node:path"; import path from "node:path";
import { RootPath } from "../../util/path.ts"; import { RootPath } from "../../util/path.ts";
import Logging from "@proxnet/undead-logging"; import Logging from "@proxnet/undead-logging";
import { statusResponse } from "../../util/api.ts";
import { HTTPStatus } from "@oneday/http-status";
import { Buffer } from "node:buffer";
export const route = createHonoRoute('/img'); export const route = createHonoRoute('/img');
async function convertImage(query: {cropSquare?: boolean | undefined;width?: number | undefined;height?: number | undefined;}, data: Uint8Array<ArrayBufferLike>): Promise<Uint8Array<ArrayBufferLike>> { const log = new Logging("ImageRoute");
const image = sharp(data);
const rootMetadata = await image.metadata();
const squareSize = Math.min(rootMetadata.width, rootMetadata.height);
if (query.cropSquare) image.resize(squareSize, squareSize);
const newImage = sharp(await image.png().toBuffer()); async function convertImage(query: ImageQuery, data: Uint8Array<ArrayBufferLike>): Promise<Uint8Array<ArrayBufferLike> | null> {
if (query.width && query.height) try {
newImage.resize(query.width, query.height); const image = sharp(data);
else if (query.width) const rootMetadata = await image.metadata();
newImage.resize(query.width); const squareSize = Math.min(rootMetadata.width, rootMetadata.height);
else if (query.height) if (query.cropSquare) image.resize(squareSize, squareSize);
newImage.resize(undefined, query.height);
return await newImage.png().toBuffer(); const newImage = sharp(await image.png().toBuffer());
if (query.width && query.height)
newImage.resize(query.width, query.height);
else if (query.width)
newImage.resize(query.width);
else if (query.height)
newImage.resize(undefined, query.height);
return await newImage.png().toBuffer();
} catch (err) {
log.w(`Image transformation failed: ${(err as Error).stack}`);
return null;
}
} }
const imgNameParamSchema = z.object({ const imgNameParamSchema = z.object({
imgName: z.string().min(4).includes('.') imgName: z.string().min(5).includes('.')
}); });
const imgQuerySchema = z.object({ const imgQuerySchema = z.object({
cropSquare: z.coerce.boolean().optional(), cropSquare: z.coerce.boolean().optional(),
width: z.coerce.number().min(64).max(3840).optional(), width: z.coerce.number().min(64).max(3840).optional(),
height: z.coerce.number().min(64).max(2160).optional(), height: z.coerce.number().min(64).max(2160).optional(),
}); });
type ImageQuery = z.infer<typeof imgQuerySchema>;
route.app.get('/:imgName', route.app.get('/:imgName',
typedZValidator('param', imgNameParamSchema), typedZValidator('param', imgNameParamSchema),
typedZValidator('query', imgQuerySchema), typedZValidator('query', imgQuerySchema),
@@ -43,29 +54,29 @@ route.app.get('/:imgName',
const { imgName } = c.req.valid('param'); const { imgName } = c.req.valid('param');
const query = c.req.valid('query'); const query = c.req.valid('query');
const file: File | null = await Server.Content.getFile(`img/${imgName}`); const datas: Uint8Array<ArrayBufferLike>[] = (await Promise.all<Uint8Array<ArrayBufferLike> | null>([
let raw: Uint8Array<ArrayBuffer> | null = null; new Promise(resolve => {
if (!file) { Deno.readFile(path.join(RootPath, "res/baseimg/", imgName)).then(img => {
try { resolve(img);
raw = await Deno.readFile(path.join(RootPath, "res/baseimg/", imgName)); }).catch(() => {
} catch { resolve(null);
raw = null; });
} }),
} new Promise(resolve => {
Server.Content.getFile(`img/${imgName}`).then(file => {
if (file) resolve(file.Data);
else resolve(null);
}).catch(() => {
resolve(null);
});
})
])).filter(val => val !== null);
if (!raw && file && file.Meta.Type !== FileType.Image) return c.status(404); if (datas.length == 0) return statusResponse(c, HTTPStatus.NotFound);
else {
try { const result = await convertImage(query, datas[0]);
let result: Uint8Array<ArrayBufferLike> | null = null; if (result == null) return statusResponse(c, HTTPStatus.InternalServerError, "Image transformation failed. Contact an administrator.");
if (file) result = await convertImage(query, file.Data); return c.body(Buffer.from(result), 200, { "Cache-Control": "public, no-transform, max-age=1800", "Content-Type": "image/png" });
else if (raw) result = await convertImage(query, raw);
if (result) return c.body(result, 200, { "Cache-Control": "public, no-transform, max-age=1800", "Content-Type": "image/png" });
else return c.status(404);
} catch (err) {
new Logging("ImageRoute").w(`Sharp error: ${err}`);
return c.status(500);
} }
} }

View File

@@ -45,8 +45,8 @@ export class Instance {
getPlayers() { getPlayers() {
return this.#players.values().toArray(); return this.#players.values().toArray();
} }
playerIsHere(profile: Profile) { hasPlayer(profile: Profile) {
return Boolean(this.getPlayers().find(prof => prof.same(profile))); return this.#players.values().some(prof => prof.getId() === profile.getId());
} }
removePlayer(profile: Profile) { removePlayer(profile: Profile) {
this.#players.delete(profile); this.#players.delete(profile);

View File

@@ -3,19 +3,146 @@ import { ServerContentBase } from "../ContentBase.ts";
import KV from "../persistence/kv.ts"; import KV from "../persistence/kv.ts";
import type Profile from "../profiles/profile.ts"; import type Profile from "../profiles/profile.ts";
import { RoomFactory } from "./internal/RoomFactory.ts"; import { RoomFactory } from "./internal/RoomFactory.ts";
import { FactoryMode } from "./internal/RoomDataTypes.ts"; import { FactoryMode, HardwareSupports, RoomDataTypes, WriteMode } from "./internal/RoomDataTypes.ts";
import { AGRoom, AGRoomLocation, AGRoomRuntimeConfig } from "./internal/ClientRoomTypes.ts";
import Command from "../commands/command.ts";
import z from "zod";
import { RoomLocation } from "../instances/base.ts";
export class ServerRoomsBase extends ServerContentBase { export class ServerRoomsBase extends ServerContentBase {
#subroomKv = new KV('subrooms', true); #subroomKv = new KV('subrooms', true);
#log = new Logging("Rooms"); #log = new Logging("Rooms");
static agRoomIdsKey = "agrooms";
static baseRoomIdsKey = "baserooms";
static roomNamesKey = "room_names"; static roomNamesKey = "room_names";
static playerDormsKey = "dorms"; static playerDormsKey = "dorms";
protected override async start() { #agrooms: Set<number> = new Set();
#baserooms: Set<number> = new Set();
override async start() {
await this.#subroomKv.init(); await this.#subroomKv.init();
this.#log.i('[sub]rooms database initialized'); this.#log.i('[sub]rooms database initialized');
const agrooms = await this.kv.getKv().get<Set<number>>([ServerRoomsBase.agRoomIdsKey]);
if (agrooms.value !== null) this.#agrooms = agrooms.value;
this.#log.i(`${this.#agrooms.size} AG rooms exist`);
const baserooms = await this.kv.getKv().get<Set<number>>([ServerRoomsBase.baseRoomIdsKey]);
if (baserooms.value !== null) this.#baserooms = baserooms.value;
this.server.Commands.addRootCommand(new Command({
key: ["rooms", "r", "room"],
subcommands: [
new Command({
key: ["initag", "initagrooms", "initagroom", "iag"],
zod: z.tuple([]).rest(z.string()),
exec: async (...arrayPath: string[]) => {
const path = arrayPath.join(' ');
try {
const config = JSON.parse((await Deno.readTextFile(path)).toString()) as AGRoomRuntimeConfig;
this.#log.d('Starting AG room initialization');
this.initBuiltinRooms(config.Rooms, config.Locations);
} catch (err) {
return err as Error;
}
},
help: "Initialize AG rooms with AGRoomRuntimeConfig from provided file path"
}),
new Command({
key: ["getplayerdorm", "playerdorm", "pd", "dorm"],
zod: z.tuple([z.coerce.number().min(1).max(Math.pow(2, 31))]),
exec: async (playerId: number) => {
const factory = await this.getPlayerDorm(this.server.Profiles.get(playerId))
},
help: "Get the domroom information for a certain profile/player"
})
],
}))
}
async #writeAgRooms() {
await this.kv.getKv().set([ServerRoomsBase.agRoomIdsKey], this.#agrooms);
}
async #writeBaseRooms() {
await this.kv.getKv().set([ServerRoomsBase.baseRoomIdsKey], this.#baserooms);
}
async initBuiltinRooms(rooms: AGRoom[], locations: AGRoomLocation[]) {
await Promise.all(rooms.map(async room => {
if (room.Accessibility == RoomDataTypes.RoomAccessibility.Private) return;
if ([
"ArtTesting",
"AnimationRecordingStudio",
"Calibration",
"ARRoom",
"Registration",
"DormRoom"
].includes(room.Name)) return;
const roomFactory = await this.write();
if (roomFactory == null) {
this.#log.w(`No factory given while writing builtin room "${room.Name}"!`);
return;
}
const supportsVRLow = room.Scenes.map(scene => locations.find(loc => loc.ReplicationId == scene.RoomSceneLocationId))
.filter(val => typeof val !== 'undefined').some(loc => loc.SupportsVRLow) ?? false;
const supportsMobile = room.Scenes.map(scene => locations.find(loc => loc.ReplicationId == scene.RoomSceneLocationId))
.filter(val => typeof val !== 'undefined').some(loc => loc.SupportsMobile) ?? false;
roomFactory.Name = room.Name;
roomFactory.Description = room.Description;
roomFactory.Accessibility = room.Accessibility;
roomFactory.State = RoomDataTypes.RoomState.Active;
roomFactory.Description = room.Description;
roomFactory.IsAGRoom = true;
roomFactory.CloningAllowed = room.CloningAllowed;
roomFactory.ImageName = `${room.Name}.png`
const supportPromises: Promise<unknown>[] = [];
roomFactory.removeAllHardwareSupport();
if (room.SupportsScreens) supportPromises.push(roomFactory.addHardwareSupport("screens"));
if (room.SupportsWalkVR) supportPromises.push(roomFactory.addHardwareSupport("walk_vr"));
if (room.SupportsTeleportVR) supportPromises.push(roomFactory.addHardwareSupport("teleport_vr"));
if (supportsMobile) supportPromises.push(roomFactory.addHardwareSupport("mobile"));
if (supportsVRLow) supportPromises.push(roomFactory.addHardwareSupport("low_vr"));
await Promise.all(room.Scenes.map(async scene => {
const subroomFactory = await roomFactory.newSubroom();
subroomFactory.addSave("");
subroomFactory.RoomId = roomFactory.getRoomId();
subroomFactory.Name = scene.Name;
subroomFactory.RoomSceneLocationId = scene.RoomSceneLocationId;
subroomFactory.IsSandbox = scene.IsSandbox;
subroomFactory.CanMatchmakeInto = scene.CanMatchmakeInto;
subroomFactory.MaxPlayers = scene.MaxPlayers;
await subroomFactory.write();
roomFactory.addSubroom(subroomFactory.RoomSceneId);
}));
await Promise.all(supportPromises);
this.#agrooms.add(roomFactory.getRoomId());
await roomFactory.write();
if (room.CloningAllowed) this.#baserooms.add(roomFactory.getRoomId());
}));
await this.#writeAgRooms();
await this.#writeBaseRooms();
this.#log.i(`${this.#agrooms.size} AG rooms added: [${this.#agrooms.values().toArray().join(',')}]`);
}
async getAvailableRoomId() {
let id = Math.round(Math.random() * Math.pow(2, 31));
while ((await this.kv.getKv().get<unknown>([RoomFactory.roomsKey, id, 'meta'])).value !== null) id = await this.getAvailableRoomId();
return id;
} }
getKv() { getKv() {
@@ -30,11 +157,48 @@ export class ServerRoomsBase extends ServerContentBase {
async getPlayerDorm(profile: Profile) { async getPlayerDorm(profile: Profile) {
const id = await this.kv.getKv().get<number>([ServerRoomsBase.playerDormsKey, profile.getId()]); const id = await this.kv.getKv().get<number>([ServerRoomsBase.playerDormsKey, profile.getId()]);
if (id.value == null) return null; if (id.value == null) {
const roomFactory = await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Write, writeMode: WriteMode.WriteIfFree, id: await this.getAvailableRoomId() });
if (!roomFactory) return null;
roomFactory.setRoomProperties({
Name: `DormRoom`,
Description: "Your private dorm.",
CreatorPlayerId: profile.getId(),
ImageName: "",
State: RoomDataTypes.RoomState.Active,
Accessibility: RoomDataTypes.RoomAccessibility.Private,
SupportsLevelVoting: false,
IsAGRoom: false,
IsDormRoom: true,
CloningAllowed: false,
AllowsJuniors: true,
RoomWarningMask: 0,
CustomRoomWarning: "",
DisableMicAutoMute: null
});
await roomFactory.addHardwareSupport(...HardwareSupports);
roomFactory.setTags(new Set(["dormroom"]));
const subroomFactory = await roomFactory.newSubroom();
subroomFactory.CanMatchmakeInto = true;
subroomFactory.IsSandbox = true;
subroomFactory.MaxPlayers = 4;
subroomFactory.Name = "Home";
subroomFactory.RoomId = roomFactory.getRoomId();
subroomFactory.RoomSceneLocationId = RoomLocation.DormRoom;
subroomFactory.addSave("");
roomFactory.addSubroom(subroomFactory.RoomSceneId);
await subroomFactory.write();
await roomFactory.write();
return roomFactory;
}
else return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id.value }); else return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id.value });
} }
async getFromName(name: string) { async getByName(name: string) {
const id = await this.getIdFromName(name); const id = await this.getIdFromName(name);
if (id == null) return null; if (id == null) return null;
return await this.get(id); return await this.get(id);
@@ -44,4 +208,8 @@ export class ServerRoomsBase extends ServerContentBase {
return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id }); return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id });
} }
async write(mode?: WriteMode) {
return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Write, writeMode: mode ? mode : WriteMode.WriteIfFree, id: await this.getAvailableRoomId() });
}
} }

View File

@@ -56,3 +56,8 @@ export interface AGRoomLocation {
}[] }[]
} }
} }
export interface AGRoomRuntimeConfig {
Locations: AGRoomLocation[],
Rooms: AGRoom[]
}

View File

@@ -8,6 +8,9 @@ export enum FactoryMode {
Write = 'write' Write = 'write'
} }
/**
* All clients RecRoom can run on.
*/
export const HardwareSupports = ["screens", "walk_vr", "teleport_vr", "low_vr", "mobile"] as const; export const HardwareSupports = ["screens", "walk_vr", "teleport_vr", "low_vr", "mobile"] as const;
export type HardwareSupport = typeof HardwareSupports[number]; export type HardwareSupport = typeof HardwareSupports[number];
@@ -138,8 +141,7 @@ export interface DatabaseRoomContent {
} }
export interface DatabaseRoom { export interface DatabaseRoom {
Hardware: Set<HardwareSupport>, Hardware: Set<HardwareSupport>,
Subrooms: number[], Tags: Set<string>,
Tags: Set<number>,
CoOwners: Set<number>, CoOwners: Set<number>,
InvitedCoOwners: Set<number>, InvitedCoOwners: Set<number>,
Hosts: Set<number>, Hosts: Set<number>,
@@ -154,14 +156,13 @@ export interface DatabaseSubroom {
IsSandbox: boolean, IsSandbox: boolean,
MaxPlayers: number, MaxPlayers: number,
CanMatchmakeInto: boolean, CanMatchmakeInto: boolean,
LatestSaveId: number, LatestSaveId: number | null,
Saves: RoomSave[]
} }
export type RoomSaveMap = Map<number, RoomSave>
export interface RoomSave { export interface RoomSave {
SaveId: number,
DataBlobName: string, DataBlobName: string,
SavedAt: string SavedAt: Date
} }
export * as RoomDataTypes from "./RoomDataTypes.ts"; export * as RoomDataTypes from "./RoomDataTypes.ts";

View File

@@ -1,17 +1,17 @@
import Logging from "@proxnet/undead-logging"; import Logging from "@proxnet/undead-logging";
import type KV from "../../persistence/kv.ts"; import type KV from "../../persistence/kv.ts";
import { type ServerBase } from "../../server.ts"; import { type ServerBase } from "../../server.ts";
import { DatabaseRoom, FactoryMode, GalvanicTagDTO, RoomDataTypes, TagDTO, TagType, WriteMode } from "./RoomDataTypes.ts"; import { DatabaseRoom, FactoryMode, GalvanicTagDTO, HardwareSupports, RoomAccessibility, RoomDataTypes, RoomState, TagDTO, TagType, WriteMode } from "./RoomDataTypes.ts";
import { SubroomFactory } from "./SubroomFactory.ts"; import { SubroomFactory } from "./SubroomFactory.ts";
import { ServerRoomsBase } from "../base.ts"; import { ServerRoomsBase } from "../base.ts";
const log = new Logging("RoomFactory"); const log = new Logging("RoomFactory");
const roomDebug = true; const roomDebug = false;
interface FactoryOptionsBase { interface FactoryOptionsBase {
mode: FactoryMode, mode: FactoryMode,
id?: number, id: number,
name?: string name?: string
} }
export interface WriteFactoryOptions extends FactoryOptionsBase { export interface WriteFactoryOptions extends FactoryOptionsBase {
@@ -36,6 +36,7 @@ export class RoomFactory {
writeMode: WriteMode = WriteMode.WriteIfFree; writeMode: WriteMode = WriteMode.WriteIfFree;
#obj: DatabaseRoom | null = null; #obj: DatabaseRoom | null = null;
#subrooms: Set<number> | null = null;
#hardwareSupport: Set<RoomDataTypes.HardwareSupport> | null = null; #hardwareSupport: Set<RoomDataTypes.HardwareSupport> | null = null;
#tags: Set<string> | null = null; #tags: Set<string> | null = null;
@@ -49,6 +50,22 @@ export class RoomFactory {
} }
/**
* Initialize the factory. Retrieves the room from the database and populates factory values.
*
* Does not fetch subroom content, only available subroom IDs.
*
* When using write mode, values are initialized to defaults.
*
* Defaults:
* - All hardware is supported
* - Cloning is not allowed
* - Room is not AG or dorm
* - State is Moderation_Closed
* - Accessibility is Unlisted
* - CreatorPlayerId is 1 (Coach)
* - Name and Description are empty strings
*/
async init(options: FactoryOptions) { async init(options: FactoryOptions) {
if (typeof options.id == 'undefined' && typeof options.name == 'undefined') if (typeof options.id == 'undefined' && typeof options.name == 'undefined')
throw new Error("Must specify a room ID or a room name"); throw new Error("Must specify a room ID or a room name");
@@ -63,11 +80,38 @@ export class RoomFactory {
} }
const obj = await this.#kv.getKv().get<DatabaseRoom>([RoomFactory.roomsKey, this.#roomId, 'meta']); const obj = await this.#kv.getKv().get<DatabaseRoom>([RoomFactory.roomsKey, this.#roomId, 'meta']);
if (obj.value == null) return null; if (options.mode == FactoryMode.Fetch && obj.value == null) return null;
else this.#obj = obj.value; else this.#obj = options.mode == FactoryMode.Fetch ? obj.value : {
Hardware: new Set(HardwareSupports),
Tags: new Set(),
CoOwners: new Set(),
InvitedCoOwners: new Set(),
Hosts: new Set(),
InvitedHosts: new Set(),
Room: {
Name: "",
Description: "",
CreatorPlayerId: 1,
ImageName: "",
State: RoomState.Moderation_Closed,
Accessibility: RoomAccessibility.Unlisted,
SupportsLevelVoting: false,
IsAGRoom: false,
IsDormRoom: false,
CloningAllowed: false,
AllowsJuniors: true,
RoomWarningMask: 0,
CustomRoomWarning: "",
DisableMicAutoMute: null
}
};
const subrooms = await this.#kv.getKv().get<Set<number>>([RoomFactory.roomsKey, this.#roomId, "subrooms"]);
if (options.mode == FactoryMode.Write || subrooms.value == null) this.#subrooms = new Set();
else this.#subrooms = subrooms.value;
const hardwareSupport = await this.#kv.getKv().get<Set<RoomDataTypes.HardwareSupport>>([RoomFactory.roomsKey, this.#roomId, 'hardware']); const hardwareSupport = await this.#kv.getKv().get<Set<RoomDataTypes.HardwareSupport>>([RoomFactory.roomsKey, this.#roomId, 'hardware']);
if (hardwareSupport.value == null) return null; if (hardwareSupport.value == null) this.#hardwareSupport = new Set(HardwareSupports);
else this.#hardwareSupport = hardwareSupport.value; else this.#hardwareSupport = hardwareSupport.value;
const tags = await this.#kv.getKv().get<Set<string>>([RoomFactory.roomsKey, this.#roomId, 'tags']); const tags = await this.#kv.getKv().get<Set<string>>([RoomFactory.roomsKey, this.#roomId, 'tags']);
@@ -102,7 +146,7 @@ export class RoomFactory {
const key = [RoomFactory.roomsKey, this.#roomId, 'meta']; const key = [RoomFactory.roomsKey, this.#roomId, 'meta'];
const val = await this.#kv.getKv().get(key); const val = await this.#kv.getKv().get(key);
if (val == null) await w(); if (val.value == null) await w();
else { else {
if (this.writeMode == WriteMode.Overwrite) await w(); if (this.writeMode == WriteMode.Overwrite) await w();
else throw new Error("Room already exists"); else throw new Error("Room already exists");
@@ -117,7 +161,7 @@ export class RoomFactory {
const galvTags = this.getGalvanicTags(); const galvTags = this.getGalvanicTags();
const subroomExports = (await Promise.all( const subroomExports = (await Promise.all(
this.getSubrooms().map(subroom => this.getSubroom(subroom)) this.getSubrooms().values().map(subroom => this.getSubroom(subroom))
)).map(factory => factory.export()); )).map(factory => factory.export());
return { return {
@@ -154,14 +198,36 @@ export class RoomFactory {
VisitCount: await this.getVisitCount() VisitCount: await this.getVisitCount()
} }
} }
setRoomProperties(props: RoomDataTypes.DatabaseRoomContent) {
Object.assign(this, props);
}
getRoomId() {
if (!this.#roomId) throw this.#cannotAccessBeforeInitError;
return this.#roomId;
}
getSubrooms() { getSubrooms() {
if (!this.#obj) throw this.#cannotAccessBeforeInitError; if (!this.#subrooms) throw this.#cannotAccessBeforeInitError;
return this.#obj.Subrooms; return this.#subrooms;
} }
async getSubroom(id: number) { async getSubroom(id: number) {
if (!this.#subrooms) throw this.#cannotAccessBeforeInitError;
if (!this.#subrooms.has(id)) throw new Error("Subroom not available to this room");
return await new SubroomFactory(this.#server, this.#kv).init({ mode: FactoryMode.Fetch, writeMode: WriteMode.WriteIfFree, id }); return await new SubroomFactory(this.#server, this.#kv).init({ mode: FactoryMode.Fetch, writeMode: WriteMode.WriteIfFree, id });
} }
getAvailableSubroomId() {
let id = Math.round(Math.random() * Math.pow(2, 31));
if (this.getSubrooms().has(id)) id = this.getAvailableSubroomId();
return id;
}
async newSubroom(mode?: WriteMode) {
return await new SubroomFactory(this.#server, this.#kv).init({ mode: FactoryMode.Write, writeMode: mode ? mode : WriteMode.WriteIfFree, id: this.getAvailableSubroomId() });
}
addSubroom(id: number) {
if (!this.#subrooms) throw this.#cannotAccessBeforeInitError;
this.#subrooms.add(id);
}
get Name() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Name } get Name() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Name }
set Name(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.Name = data } set Name(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.Name = data }
@@ -178,8 +244,8 @@ export class RoomFactory {
get State() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.State } get State() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.State }
set State(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.State = data } set State(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.State = data }
get RoomAccessibility() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Accessibility } get Accessibility() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Accessibility }
set RoomAccessibility(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.Accessibility = data } set Accessibility(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.Accessibility = data }
get SupportsLevelVoting() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.SupportsLevelVoting } get SupportsLevelVoting() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.SupportsLevelVoting }
set SupportsLevelVoting(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.SupportsLevelVoting = data } set SupportsLevelVoting(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.SupportsLevelVoting = data }
@@ -229,6 +295,20 @@ export class RoomFactory {
this.#hardwareSupport.delete(hardware); this.#hardwareSupport.delete(hardware);
await this.#saveHardwareSupport(); await this.#saveHardwareSupport();
} }
/**
* Removes support for every hardware type in the room. When saving immediately after this, no client can legally join the room.
*
* In production, ensure at least one hardware type is supported.
*/
removeAllHardwareSupport() {
this.#hardwareSupport = new Set();
}
/**
* Adds support for every hardware type in the room.
*/
addAllHardwareSupport() {
this.#hardwareSupport = new Set(HardwareSupports);
}
/** /**
* Visits are fetched during every access, not during init * Visits are fetched during every access, not during init
@@ -252,8 +332,8 @@ export class RoomFactory {
const tags: GalvanicTagDTO[] = []; const tags: GalvanicTagDTO[] = [];
if (this.IsAGRoom) tags.push({ Tag: "recroomoriginal", Type: TagType.AGOnly }); if (this.IsAGRoom) tags.push({ Tag: "recroomoriginal", Type: TagType.AGOnly });
if (this.IsDormRoom) tags.push({ Tag: "dormroom", Type: TagType.Auto }); if (this.IsDormRoom) tags.push({ Tag: "dormroom", Type: TagType.AGOnly });
if (this.Name === "Paintball" || this.Name === "PaintballVR") tags.push({ Tag: "paintball", Type: TagType.Auto }); if (this.Name === "Paintball" || this.Name === "PaintballVR") tags.push({ Tag: "paintball", Type: TagType.AGOnly });
const hardwareSupport = this.getHardwareSupport(); const hardwareSupport = this.getHardwareSupport();
if (hardwareSupport.has('screens')) tags.push({ Tag: "screen", Type: TagType.Auto }); if (hardwareSupport.has('screens')) tags.push({ Tag: "screen", Type: TagType.Auto });
if (hardwareSupport.has('walk_vr')) tags.push({ Tag: "walkvr", Type: TagType.Auto }); if (hardwareSupport.has('walk_vr')) tags.push({ Tag: "walkvr", Type: TagType.Auto });

View File

@@ -1,6 +1,7 @@
import Logging from "@proxnet/undead-logging";
import type KV from "../../persistence/kv.ts"; import type KV from "../../persistence/kv.ts";
import { type ServerBase } from "../../server.ts"; import { type ServerBase } from "../../server.ts";
import { DatabaseSubroom, FactoryMode, RoomDataTypes, RoomSave, WriteMode } from "./RoomDataTypes.ts"; import { DatabaseSubroom, FactoryMode, RoomDataTypes, RoomSave, RoomSaveMap, WriteMode } from "./RoomDataTypes.ts";
export interface SubroomFactoryOptions { export interface SubroomFactoryOptions {
mode: FactoryMode, mode: FactoryMode,
@@ -8,6 +9,8 @@ export interface SubroomFactoryOptions {
id: number id: number
} }
const log = new Logging("SubroomFactoryBase");
export class SubroomFactory { export class SubroomFactory {
#server: ServerBase; #server: ServerBase;
@@ -19,7 +22,7 @@ export class SubroomFactory {
writeMode: WriteMode = WriteMode.WriteIfFree; writeMode: WriteMode = WriteMode.WriteIfFree;
#obj: DatabaseSubroom | null = null; #obj: DatabaseSubroom | null = null;
#saves: RoomSave[] | null = null; #saves: RoomSaveMap | null = null;
#cannotAccessBeforeInitError = new Error("Cannot access properties before initialization"); #cannotAccessBeforeInitError = new Error("Cannot access properties before initialization");
#cannotWriteBeforeInitError = new Error("Cannot write before initialization"); #cannotWriteBeforeInitError = new Error("Cannot write before initialization");
@@ -32,13 +35,22 @@ export class SubroomFactory {
} }
async init(options: SubroomFactoryOptions) { async init(options: SubroomFactoryOptions) {
const data = await this.#kv.getKv().get<DatabaseSubroom>([options.id, 'meta']); const data = await this.#kv.getKv().get<DatabaseSubroom>([options.id, 'meta']);
if (data == null && this.factoryMode == FactoryMode.Fetch) throw new Error("No such subroom"); if (options.mode == FactoryMode.Fetch && data == null) throw new Error("No such subroom");
const saves = await this.#kv.getKv().get<RoomSave[]>([options.id, 'saves']); const saves = await this.#kv.getKv().get<RoomSaveMap>([options.id, 'saves']);
this.#saves = saves.value; this.#saves = saves.value ?? new Map<number, RoomSave>();
this.#obj = data.value; this.#obj = options.mode == FactoryMode.Fetch ? data.value : {
RoomId: 0,
RoomSceneLocationId: "",
Name: "Subroom data init failed, contact an admin!",
IsSandbox: false,
MaxPlayers: 8,
CanMatchmakeInto: true,
LatestSaveId: null
}; // use template object when writing
this.#subroomId = options.id; this.#subroomId = options.id;
return this; return this;
@@ -66,7 +78,7 @@ export class SubroomFactory {
IsSandbox: this.IsSandbox, IsSandbox: this.IsSandbox,
MaxPlayers: this.MaxPlayers, MaxPlayers: this.MaxPlayers,
CanMatchmakeInto: this.CanMatchmakeInto, CanMatchmakeInto: this.CanMatchmakeInto,
DataModifiedAt: save.SavedAt, DataModifiedAt: save.SavedAt.toISOString(),
DataBlobName: save.DataBlobName DataBlobName: save.DataBlobName
} }
} }
@@ -98,24 +110,41 @@ export class SubroomFactory {
if (!this.#saves) throw this.#cannotAccessBeforeInitError; if (!this.#saves) throw this.#cannotAccessBeforeInitError;
let newId = Math.round(Math.random() * Math.pow(2, 31)); let newId = Math.round(Math.random() * Math.pow(2, 31));
while (this.#saves.some(save => save.SaveId == newId)) newId = this.#getAvailableSaveId(); while (this.#saves.has(newId)) newId = this.#getAvailableSaveId();
return newId; return newId;
} }
addSave(dataBlobName: string) { /**
* Add a save (history) to the scene. Automatically resets the LatestSaveId.
*
* Use empty string for no datablob
*
* @param dataBlobName Filename of datablob on CDN
*/
addSave(dataBlobName?: string) {
if (!this.#saves) throw this.#cannotAccessBeforeInitError; if (!this.#saves) throw this.#cannotAccessBeforeInitError;
this.#saves.push({
SaveId: this.#getAvailableSaveId(), const newId = this.#getAvailableSaveId();
DataBlobName: dataBlobName, this.#saves.set(newId, {
SavedAt: new Date().toISOString() DataBlobName: dataBlobName ?? "",
SavedAt: new Date()
}); });
this.LatestSaveId = newId;
} }
getSaves() { getSaves() {
if (!this.#saves) throw this.#cannotAccessBeforeInitError; if (!this.#saves) throw this.#cannotAccessBeforeInitError;
return this.#saves; return this.#saves;
} }
getLatestSave() { getLatestSave() {
if (!this.#saves) throw this.#cannotAccessBeforeInitError; if (!this.#saves || !this.#obj) throw this.#cannotAccessBeforeInitError;
return this.#saves.find(save => save.SaveId == this.LatestSaveId); else if (!this.#obj.LatestSaveId) throw new Error(`No save is marked as the latest save`);
else {
if (this.#saves.size === 0) {
log.w(`No save could be found when fetching the latest save for subroomid ${this.#subroomId}!`);
return null;
} else if (this.#saves.size === 1) return this.#saves.values().toArray()[0];
else return this.#saves.get(this.#obj.LatestSaveId);
}
} }
} }

View File

@@ -5,9 +5,15 @@ import z from "zod";
import { verify } from "@hono/hono/jwt"; import { verify } from "@hono/hono/jwt";
import Server from "../server/server.ts"; import Server from "../server/server.ts";
import { TokenFormat } from "../server/platforms/types.ts"; import { TokenFormat } from "../server/platforms/types.ts";
import { HTTPStatus, httpStatusText } from "@oneday/http-status";
import { ContentfulStatusCode } from "@hono/hono/utils/http-status";
const log = new Logging("APIUtils"); const log = new Logging("APIUtils");
export function statusResponse(c: Context, code: HTTPStatus, msg?: string) {
return c.json(genericResponse(code < 400, msg ?? httpStatusText(code)), code as ContentfulStatusCode);
}
export function genericResponse(success: boolean, msg?: string, data?: null) { export function genericResponse(success: boolean, msg?: string, data?: null) {
return { success, msg, data } return { success, msg, data }
}; };

View File

@@ -44,10 +44,10 @@ export async function importer<T>(importKey: string, prefix: string, paths: stri
return items; return items;
} }
export function createHonoRoute(path: string): RouteImport { export function createHonoRoute(path: string) {
const route: RouteImport = { const route: RouteImport = {
path, path,
app: new Hono<HonoEnv>() app: new Hono<HonoEnv>()
} }
return route return route;
} }