/* Galvanic Corrosion - Rec Room custom server for communities. Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { APIUtils, NoBody } from "../../apiutils.ts"; import express from "express"; import { Profile } from "../../data/profile/base/profiles.ts"; import { z } from "zod"; import { AuthType } from "../../data/UserTypes.ts"; import Server from "../../data/server/server.ts"; export const route = APIUtils.createRouter("/account"); const CreateAccountRequestBodySchema = z.object({ platform: z.string(), platformId: z.string(), deviceId: z.string() }); const rateLimit = new APIUtils.RateLimiter(); route.router.post("/create", rateLimit.middle(), APIUtils.Authentication, express.urlencoded({ extended: true }), APIUtils.validateRequestBody(CreateAccountRequestBodySchema), async (_rq, rs) => { const newAcc = await Profile.init(); if (newAcc == null) { rs.json({ success: false }); return; } rs.locals.user.addAssociatedProfile(newAcc.getId()); rs.json({ success: true, value: await newAcc.export(), }); }, ); route.router.get("/bulk", rateLimit.middle(), async (rq: express.Request, rs: express.Response) => { if (typeof rq.query.id == "object") { const ids = Object.values(rq.query.id).filter((val) => typeof val == "string").map((val) => parseInt(val, 10)).filter((val) => !isNaN(val)); rs.json([...await Profile.getExportAccountsBulk(ids)]); } else if (typeof rq.query.id == "string") { const id = parseInt(rq.query.id); if (isNaN(id)) { rs.json(APIUtils.genericResponseFormat(true, "Query data error")); return; } else { rs.json([await Profile.getExportAccount(id)].filter((val) => val !== null)); return; } } else { rs.json([]); return; } }, ); route.router.get("/me", APIUtils.Authentication, async (_rq, rs) => { const exportAccount = await rs.locals.profile.export(); if (exportAccount == null) rs.sendStatus(500); else rs.json(exportAccount); }, ); interface DisplayNameUpdate { displayName: string } const DisplayNameUpdateSchema = z.object({ displayName: z.string().max(24, "DisplayName too long!") }) route.router.put("/me/displayname", APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), express.urlencoded({ extended: true }), APIUtils.validateRequestBody(DisplayNameUpdateSchema), (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { rs.locals.profile.setDisplayName(rq.body.displayName); nxt(); }, APIUtils.RecNetResponse(true, "Updated DisplayName.") ); interface BioFetchParams { id?: string } route.router.get('/:id/bio', APIUtils.Authentication, async (rq: express.Request, rs: express.Response) => { const unparsedId = rq.params.id; if (!unparsedId) { rs.sendStatus(500); return; } const parsedId = parseInt(unparsedId); if (isNaN(parsedId)) { rs.sendStatus(400); return; } const player = Server.UnifiedProfile.get(parsedId); if (!player) { rs.status(404).json(APIUtils.genericResponseFormat(true, "Profile not found")); return; } rs.json({ accountId: parsedId, bio: await player.getBio(), }); } );