-
Notifications
You must be signed in to change notification settings - Fork 0
Player documents #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Player documents #136
Changes from 11 commits
caa1237
ae00a2e
aa13ca3
70e53a5
681d91f
1c455dd
7c9dcad
67e5094
42a8b3f
71e03d7
89e7b33
6599721
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { NoOp } from "convex-helpers/server/customFunctions"; | ||
| import { zCustomMutation, zCustomQuery } from "convex-helpers/server/zod"; | ||
| import { internalMutation, internalQuery, mutation, query } from "./_generated/server"; | ||
|
|
||
| export const zQuery = zCustomQuery(query, NoOp); | ||
|
|
||
| export const zMutation = zCustomMutation(mutation, NoOp); | ||
|
|
||
| export const zInternalQuery = zCustomQuery(internalQuery, NoOp); | ||
|
|
||
| export const zInternalMutation = zCustomMutation(internalMutation, NoOp); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,11 @@ | ||||||||||||||||||||||||||
| import { zValidator } from "@hono/zod-validator"; | ||||||||||||||||||||||||||
| import { HonoWithConvex, HttpRouterWithHono } from "convex-helpers/server/hono"; | ||||||||||||||||||||||||||
| import { Hono } from "hono"; | ||||||||||||||||||||||||||
| import { bearerAuth } from "hono/bearer-auth"; | ||||||||||||||||||||||||||
| import { timing } from "hono/timing"; | ||||||||||||||||||||||||||
| import { api, internal } from "./_generated/api"; | ||||||||||||||||||||||||||
| import { ActionCtx } from "./_generated/server"; | ||||||||||||||||||||||||||
| import { PlayerDocument, playerDocumentSchema } from "./schemas"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const app: HonoWithConvex<ActionCtx> = new Hono(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -28,6 +30,7 @@ app.get("/kv/:key", async (c) => { | |||||||||||||||||||||||||
| if (value === undefined) { | ||||||||||||||||||||||||||
| return c.json({ error: "Key not found" }, 404); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return c.json({ key, value }); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -56,26 +59,115 @@ app.put("/kv/:key", async (c) => { | |||||||||||||||||||||||||
| return c.json({ message: "Key-Value pair updated successfully", key, value }); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| app.get("/players/:uuid/joinData", async (c) => { | ||||||||||||||||||||||||||
| const uuid = c.req.param("uuid"); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const playerData = await c.env.runQuery(api.players.getPlayerByUuid, { uuid }); | ||||||||||||||||||||||||||
| let firstTimeJoin = false; | ||||||||||||||||||||||||||
| app.get("/players/:uuid/document", async (c) => { | ||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||
| const uuid = c.req.param("uuid"); | ||||||||||||||||||||||||||
| const isJoinEvent = c.req.query("joinEvent") === "true"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let playerData = await c.env.runQuery(internal.players.getPlayerByUuid, { uuid }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const isFirstTimeOnServer = !playerData; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (isFirstTimeOnServer) { | ||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||
| await c.env.runAction(internal.players.createPlayer, { | ||||||||||||||||||||||||||
| uuid, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||
| if (error instanceof Error && error.message.includes("Player not found on playerdb.co")) { | ||||||||||||||||||||||||||
| return c.json({ message: `"${uuid}" is not a valid minecraft uuid` }, { status: 404 }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return c.json({ message: "Player data was not able to be created" }, { status: 500 }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } else if (isJoinEvent) { | ||||||||||||||||||||||||||
| await c.env.runMutation(internal.players.updatePlayerJoin, { uuid }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (playerData) { | ||||||||||||||||||||||||||
| playerData = { | ||||||||||||||||||||||||||
| ...playerData, | ||||||||||||||||||||||||||
| lastJoinDate: new Date().toISOString(), | ||||||||||||||||||||||||||
| stats: { | ||||||||||||||||||||||||||
| ...playerData.stats, | ||||||||||||||||||||||||||
| joinCount: (playerData.stats.joinCount as number) || 0 + 1, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| playerData ??= await c.env.runQuery(internal.players.getPlayerByUuid, { uuid }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (!playerData) { | ||||||||||||||||||||||||||
| return c.json({ message: "Player data was not able to be created" }, { status: 500 }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const banData = await c.env.runQuery(internal.players.getPlayerBanByUuid, { uuid }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const document: PlayerDocument = { | ||||||||||||||||||||||||||
| avatarUrl: playerData.avatarUrl, | ||||||||||||||||||||||||||
| isFirstTimeOnServer, | ||||||||||||||||||||||||||
| lastJoinDate: playerData.lastJoinDate, | ||||||||||||||||||||||||||
| stats: playerData.stats, | ||||||||||||||||||||||||||
| banData: banData | ||||||||||||||||||||||||||
| ? { | ||||||||||||||||||||||||||
| isBanned: true, | ||||||||||||||||||||||||||
| reason: banData.reason, | ||||||||||||||||||||||||||
| expiresAt: banData.expiresAt, | ||||||||||||||||||||||||||
| bannedAt: banData.bannedAt, | ||||||||||||||||||||||||||
| bannedBy: banData.bannedBy, | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| : null, | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return c.json(document); | ||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||
| return c.json( | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| message: "Player document was not able to be fetched", | ||||||||||||||||||||||||||
| error, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { status: 500 }, | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (!playerData) { | ||||||||||||||||||||||||||
| firstTimeJoin = true; | ||||||||||||||||||||||||||
| app.put("/players/:uuid/document", zValidator("json", playerDocumentSchema), async (c) => { | ||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||
| const uuid = c.req.param("uuid"); | ||||||||||||||||||||||||||
| const document = c.req.valid("json"); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| await c.env.runAction(internal.players.createPlayer, { | ||||||||||||||||||||||||||
| uuid, | ||||||||||||||||||||||||||
| await c.env.runMutation(internal.players.savePlayerDocument, { | ||||||||||||||||||||||||||
| uuid: uuid as any, | ||||||||||||||||||||||||||
| document, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| await c.env.runMutation(internal.players.updatePlayerJoin, { uuid }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return c.json({ message: "Player document saved" }); | ||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||
| return c.json( | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| message: "Player document was not able to be saved", | ||||||||||||||||||||||||||
| error, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { status: 500 }, | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return c.json({ | ||||||||||||||||||||||||||
| firstTimeJoin, | ||||||||||||||||||||||||||
| playerData: firstTimeJoin ? null : playerData, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| app.delete("/players/:uuid/document", async (c) => { | ||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||
| const uuid = c.req.param("uuid"); | ||||||||||||||||||||||||||
| await c.env.runMutation(internal.players.deletePlayerDocument, { uuid: uuid as any }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return c.json({ message: "Player data deleted successfully" }); | ||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||
| return c.json( | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| message: "Player data was not able to be deleted", | ||||||||||||||||||||||||||
| error, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { status: 500 }, | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
Comment on lines
+122
to
+171
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unsafe cast on DELETE Aligns with delete mutation accepting external UUID. app.delete("/players/:uuid/document", async (c) => {
const uuid = c.req.param("uuid");
- await c.env.runMutation(internal.players.deletePlayerDocument, { uuid: uuid as any });
+ await c.env.runMutation(internal.players.deletePlayerDocument, { uuid });
return c.json({ message: "Player data deleted successfully" });
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| export default new HttpRouterWithHono(app); | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,69 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { zid } from "convex-helpers/server/zod"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { v } from "convex/values"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { z } from "zod"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { z } from "zod/v3"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { internal } from "./_generated/api"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { Id } from "./_generated/dataModel"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { internalAction, internalMutation, query } from "./_generated/server"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { internalAction, internalMutation, internalQuery } from "./_generated/server"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { zInternalMutation } from "./common"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { playerDocumentSchema } from "./schemas"; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const getPlayerByUuid = query({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| export const savePlayerDocument = zInternalMutation({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| args: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| uuid: zid("players"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| document: playerDocumentSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| handler: async (ctx, args) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (args.document.banData) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const existingBan = await ctx.db | ||||||||||||||||||||||||||||||||||||||||||||||||
| .query("playerBans") | ||||||||||||||||||||||||||||||||||||||||||||||||
| .withIndex("by_uuid", (q) => q.eq("uuid", args.uuid)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .unique(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (existingBan) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| await ctx.db.patch(existingBan._id, { | ||||||||||||||||||||||||||||||||||||||||||||||||
| reason: args.document.banData.reason, | ||||||||||||||||||||||||||||||||||||||||||||||||
| expiresAt: args.document.banData.expiresAt, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||
| await ctx.db.insert("playerBans", { | ||||||||||||||||||||||||||||||||||||||||||||||||
| uuid: args.uuid, | ||||||||||||||||||||||||||||||||||||||||||||||||
| reason: args.document.banData.reason, | ||||||||||||||||||||||||||||||||||||||||||||||||
| expiresAt: args.document.banData.expiresAt, | ||||||||||||||||||||||||||||||||||||||||||||||||
| bannedAt: args.document.banData.bannedAt, | ||||||||||||||||||||||||||||||||||||||||||||||||
| bannedBy: args.document.banData.bannedBy, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return await ctx.db.patch(args.uuid, { | ||||||||||||||||||||||||||||||||||||||||||||||||
| lastJoinDate: args.document.lastJoinDate, | ||||||||||||||||||||||||||||||||||||||||||||||||
| stats: args.document.stats, | ||||||||||||||||||||||||||||||||||||||||||||||||
| avatarUrl: args.document.avatarUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainFix uuid type mismatch (external UUID), patch by internal _id, add returns, and handle unban. Current code treats external UUID as a Convex Id and never unbans. -export const savePlayerDocument = zInternalMutation({
- args: {
- uuid: zid("players"),
- document: playerDocumentSchema,
- },
- handler: async (ctx, args) => {
- if (args.document.banData) {
- const existingBan = await ctx.db
- .query("playerBans")
- .withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
- .unique();
-
- if (existingBan) {
- await ctx.db.patch(existingBan._id, {
- reason: args.document.banData.reason,
- expiresAt: args.document.banData.expiresAt,
- });
- } else {
- await ctx.db.insert("playerBans", {
- uuid: args.uuid,
- reason: args.document.banData.reason,
- expiresAt: args.document.banData.expiresAt,
- bannedAt: args.document.banData.bannedAt,
- bannedBy: args.document.banData.bannedBy,
- });
- }
- }
-
- return await ctx.db.patch(args.uuid, {
- lastJoinedAt: args.document.lastJoinTime,
- stats: args.document.stats,
- avatarUrl: args.document.avatarUrl,
- });
- },
-});
+export const savePlayerDocument = zInternalMutation({
+ args: {
+ uuid: v.string(), // external Minecraft UUID
+ document: playerDocumentSchema,
+ },
+ returns: v.null(),
+ handler: async (ctx, args) => {
+ const player = await ctx.db
+ .query("players")
+ .withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
+ .unique();
+ if (!player) throw new Error("Player not found");
+
+ const existingBan = await ctx.db
+ .query("playerBans")
+ .withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
+ .unique();
+
+ if (args.document.banData) {
+ if (existingBan) {
+ await ctx.db.patch(existingBan._id, {
+ reason: args.document.banData.reason,
+ expiresAt: args.document.banData.expiresAt,
+ });
+ } else {
+ await ctx.db.insert("playerBans", {
+ uuid: args.uuid,
+ reason: args.document.banData.reason,
+ expiresAt: args.document.banData.expiresAt,
+ bannedAt: args.document.banData.bannedAt,
+ bannedBy: args.document.banData.bannedBy,
+ });
+ }
+ } else if (existingBan) {
+ await ctx.db.delete(existingBan._id); // unban
+ }
+
+ await ctx.db.patch(player._id, {
+ lastJoinedAt: args.document.lastJoinTime,
+ stats: args.document.stats,
+ avatarUrl: args.document.avatarUrl,
+ });
+ return null;
+ },
+});🏁 Script executed: #!/bin/bash
# Show type mismatch usage across HTTP and convex layers.
rg -n 'savePlayerDocument' -C2
rg -n 'zid\("players"\)' packages/backend/convex/players.tsLength of output: 683 Fix UUID type mismatch: accept external UUID, patch by internal _id, return null, and handle unban. http.ts is calling internal.players.savePlayerDocument with an external UUID (packages/backend/convex/http.ts:116–118 — 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const deletePlayerDocument = internalMutation({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| args: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| uuid: v.id("players"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| handler: async (ctx, args) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const player = await ctx.db | ||||||||||||||||||||||||||||||||||||||||||||||||
| .query("players") | ||||||||||||||||||||||||||||||||||||||||||||||||
| .withIndex("by_uuid", (q) => q.eq("uuid", args.uuid)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .unique(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (!player) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Player not found"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| await ctx.db.delete(player._id); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const getPlayerByUuid = internalQuery({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| args: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| uuid: v.string(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -16,7 +75,19 @@ export const getPlayerByUuid = query({ | |||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const playerDbSchema = z.object({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| export const getPlayerBanByUuid = internalQuery({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| args: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| uuid: v.string(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| handler: async (ctx, args) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return await ctx.db | ||||||||||||||||||||||||||||||||||||||||||||||||
| .query("playerBans") | ||||||||||||||||||||||||||||||||||||||||||||||||
| .withIndex("by_uuid", (q) => q.eq("uuid", args.uuid)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .unique(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+78
to
+88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add returns validator to ban query Same rationale as above. export const getPlayerBanByUuid = internalQuery({
args: {
uuid: v.string(),
},
+ returns: v.optional(v.any()),
handler: async (ctx, args) => {
return await ctx.db
.query("playerBans")
.withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
.unique();
},
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const playerDbApiSchema = z.object({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| data: z.object({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| player: z.object({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| username: z.string(), | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -33,10 +104,24 @@ export const createPlayer = internalAction({ | |||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| returns: v.id("players"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| handler: async (ctx, args) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`https://playerdb.co/api/player/minecraft/${args.uuid}`); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const controller = new AbortController(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const timeout = setTimeout(() => controller.abort(), 10000); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`https://playerdb.co/api/player/minecraft/${args.uuid}`, { | ||||||||||||||||||||||||||||||||||||||||||||||||
| signal: controller.signal, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }).finally(() => clearTimeout(timeout)); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+107
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainGood timeout/abort handling around fetch The Check for any lingering casts like Also applies to: 114-121 🏁 Script executed: #!/bin/bash
rg -nP 'uuid\s+as\s+any|playersSaveDocument|savePlayerDocument' -S packagesLength of output: 382 Good timeout/abort handling around fetch — remove AbortController/timeout are fine; the HTTP layer still bypasses the new validator with
Remove the |
||||||||||||||||||||||||||||||||||||||||||||||||
| if (!response.ok) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (response.status >= 400 && response.status < 500) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Player not found on playerdb.co"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Failed to fetch player data from playerdb.co"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const json = await response.json(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = playerDbSchema.parse(json); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = playerDbApiSchema.parse(json); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const minecraftPlayerData: Id<"players"> = await ctx.runMutation( | ||||||||||||||||||||||||||||||||||||||||||||||||
| internal.players.insertPlayer, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -72,10 +157,10 @@ export const insertPlayer = internalMutation({ | |||||||||||||||||||||||||||||||||||||||||||||||
| return await ctx.db.insert("players", { | ||||||||||||||||||||||||||||||||||||||||||||||||
| username: args.player.username, | ||||||||||||||||||||||||||||||||||||||||||||||||
| uuid: args.player.uuid, | ||||||||||||||||||||||||||||||||||||||||||||||||
| firstJoinedAt: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| firstJoinDate: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| lastJoinDate: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| skinTextureUrl: args.minecraftPlayerData.skinTextureUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||
| avatarUrl: args.minecraftPlayerData.avatarUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||
| lastJoinedAt: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| stats: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| joinCount: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -100,7 +185,7 @@ export const updatePlayerJoin = internalMutation({ | |||||||||||||||||||||||||||||||||||||||||||||||
| const currentJoinCount = (player.stats?.joinCount as number) || 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| await ctx.db.patch(player._id, { | ||||||||||||||||||||||||||||||||||||||||||||||||
| lastJoinedAt: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| lastJoinDate: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| stats: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| ...player.stats, | ||||||||||||||||||||||||||||||||||||||||||||||||
| joinCount: currentJoinCount + 1, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unsafe casts; return a response on PUT
as anymasks a real type mismatch. Also, the handler returns nothing; send a success response.🤖 Prompt for AI Agents