api,bot: impl sync endpoint
vi did:web:vt3e.cat
Sun, 05 Jul 2026 22:38:25 +0100
5 files changed,
44 insertions(+),
5 deletions(-)
M
apps/api/src/app.ts
→
apps/api/src/app.ts
@@ -28,6 +28,7 @@ .use(routes.authRoutes)
.group("/api", (app) => app .get("/health", () => "ok") + .use(routes.syncRoutes) .resolve(({ session, status }) => { if (!session) return status(401, { message: "unauthorized" }); return { session };
M
apps/api/src/routes/index.ts
→
apps/api/src/routes/index.ts
@@ -1,2 +1,3 @@
export { authRoutes } from "./auth"; export { guildRoutes } from "./guilds"; +export { syncRoutes } from "./sync";
A
apps/api/src/routes/sync.ts
@@ -0,0 +1,33 @@
+import { editActor, getActorByPin } from "@stealth-developers/db"; +import Elysia, { t } from "elysia"; + +export const syncRoutes = new Elysia({ prefix: "/sync" }).post( + "/verify", + async ({ body, set }) => { + const user = await getActorByPin(body.pin); + if (!user) { + set.status = 404; + return { error: "Pin not found" }; + } + + const expiryDate = new Date(user.pinExpiresIn!); + if (expiryDate < new Date()) { + set.status = 401; + return { error: "Pin expired" }; + } + + await editActor(user.id, { + robloxId: body.roblox_user_id, + pin: null, + pinExpiresIn: null, + }); + + return { success: true }; + }, + { + body: t.Object({ + pin: t.String(), + roblox_user_id: t.String(), + }), + }, +);
M
apps/bot/src/feats/index.ts
→
apps/bot/src/feats/index.ts
@@ -1,5 +1,5 @@
import { bugCommands, bugComponents } from "./bugs"; -// import { syncCommand } from "./link"; +import { syncCommand } from "./link"; import { miscCommands } from "./misc"; import { robloxCommands } from "./roblox"; import { ticketCommands, ticketComponents } from "./tickets";@@ -9,6 +9,6 @@ ...ticketCommands,
...miscCommands, ...bugCommands, ...robloxCommands, - // syncCommand, + syncCommand, ]; export const components = [...ticketComponents, ...bugComponents];
M
apps/bot/src/feats/link/index.ts
→
apps/bot/src/feats/link/index.ts
@@ -1,10 +1,11 @@
+import { Result } from "@sapphire/result"; import { editActor, getActor } from "@stealth-developers/db"; import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; import { logger } from "@/lib"; import { useGetData } from "@/middleware"; -import { getUser } from "../roblox/lib"; +import { client } from "../roblox/client"; export const syncCommand = useGetData.command("sync", { description: "Link your Roblox account to your Discord account",@@ -103,12 +104,15 @@ content: "You are not linked to a Roblox account.",
flags: ["Ephemeral"], }); - const [account, accountError] = await getUser(actor.robloxId); - if (accountError) + const accountRes = await Result.fromAsync(client.getUserProfile(actor.robloxId)); + + if (accountRes.isErr()) return interaction.reply({ content: "Failed to fetch your Roblox account.", flags: ["Ephemeral"], }); + + const account = accountRes.unwrap(); interaction.reply({ content: `You are linked to Roblox account **@${account.name}**.`,