-
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 7 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
| 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(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -56,26 +58,72 @@ app.put("/kv/:key", async (c) => { | |||||||||||||||||||||||||
| return c.json({ message: "Key-Value pair updated successfully", key, value }); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| app.get("/players/:uuid/joinData", async (c) => { | ||||||||||||||||||||||||||
| app.get("/players/:uuid/document", async (c) => { | ||||||||||||||||||||||||||
| const uuid = c.req.param("uuid"); | ||||||||||||||||||||||||||
| const isJoinEvent = c.req.query("joinEvent") === "true"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const playerData = await c.env.runQuery(api.players.getPlayerByUuid, { uuid }); | ||||||||||||||||||||||||||
| let firstTimeJoin = false; | ||||||||||||||||||||||||||
| let playerData = await c.env.runQuery(internal.players.getPlayerByUuid, { uuid }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (!playerData) { | ||||||||||||||||||||||||||
| firstTimeJoin = true; | ||||||||||||||||||||||||||
| 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 }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| await c.env.runAction(internal.players.createPlayer, { | ||||||||||||||||||||||||||
| uuid, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| 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 }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return c.json({ | ||||||||||||||||||||||||||
| firstTimeJoin, | ||||||||||||||||||||||||||
| playerData: firstTimeJoin ? null : playerData, | ||||||||||||||||||||||||||
| 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, | ||||||||||||||||||||||||||
| lastJoinTime: playerData.lastJoinedAt, | ||||||||||||||||||||||||||
| stats: playerData.stats, | ||||||||||||||||||||||||||
| banData: banData | ||||||||||||||||||||||||||
| ? { | ||||||||||||||||||||||||||
| isBanned: true, | ||||||||||||||||||||||||||
| reason: banData.reason, | ||||||||||||||||||||||||||
| expiresAt: banData.expiresAt, | ||||||||||||||||||||||||||
| bannedAt: banData.bannedAt, | ||||||||||||||||||||||||||
| bannedBy: banData.bannedBy, | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| : null, | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return c.json(document); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| app.put("/players/:uuid/document", zValidator("json", playerDocumentSchema), async (c) => { | ||||||||||||||||||||||||||
| const uuid = c.req.param("uuid"); | ||||||||||||||||||||||||||
| const document = c.req.valid("json"); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| await c.env.runMutation(internal.players.savePlayerDocument, { | ||||||||||||||||||||||||||
| uuid: uuid as any, | ||||||||||||||||||||||||||
| document, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
Comment on lines
+112
to
154
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 casts; return a response on PUT
-app.put("/players/:uuid/document", zValidator("json", playerDocumentSchema), async (c) => {
+app.put("/players/:uuid/document", zValidator("json", playerDocumentSchema), async (c) => {
const uuid = c.req.param("uuid");
const document = c.req.valid("json");
await c.env.runMutation(internal.players.savePlayerDocument, {
- uuid: uuid as any,
+ uuid,
document,
});
+ return c.json({ message: "Player document saved" });
});
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| app.delete("/players/:uuid/document", async (c) => { | ||||||||||||||||||||||||||
| 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" }); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
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,58 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| 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, { | ||||||||||||||||||||||||||||||||||||||||||||||||
| lastJoinedAt: args.document.lastJoinTime, | ||||||||||||||||||||||||||||||||||||||||||||||||
| 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) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return await ctx.db.delete(args.uuid); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const getPlayerByUuid = internalQuery({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| args: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| uuid: v.string(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -16,7 +64,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(), | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -34,9 +94,18 @@ export const createPlayer = internalAction({ | |||||||||||||||||||||||||||||||||||||||||||||||
| returns: v.id("players"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| handler: async (ctx, args) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`https://playerdb.co/api/player/minecraft/${args.uuid}`); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
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, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import z from "zod/v3"; | ||
|
|
||
| export const playerDocumentSchema = z.object({ | ||
| isFirstTimeOnServer: z.boolean(), | ||
| avatarUrl: z.string(), | ||
| lastJoinTime: z.string(), | ||
| stats: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])), | ||
| banData: z.nullable( | ||
| z.object({ | ||
| isBanned: z.boolean(), | ||
| reason: z.string(), | ||
| expiresAt: z.optional(z.string()), | ||
| bannedAt: z.string(), | ||
| bannedBy: z.string(), | ||
| }), | ||
| ), | ||
| }); | ||
|
|
||
| export type PlayerDocument = z.infer<typeof playerDocumentSchema>; | ||
|
Comment on lines
+1
to
+19
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. Import Zod v4 correctly and use v4 helpers for stronger validation. Current import targets v3 while package.json installs v4. Switch to v4 and use helpers like z.url() and z.dateTime(). -import z from "zod/v3";
+import { z } from "zod";
export const playerDocumentSchema = z.object({
isFirstTimeOnServer: z.boolean(),
- avatarUrl: z.string(),
- lastJoinTime: z.string(),
+ avatarUrl: z.url(),
+ lastJoinTime: z.dateTime(), // RFC 3339 timestamp string
stats: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])),
banData: z.nullable(
z.object({
isBanned: z.boolean(),
reason: z.string(),
- expiresAt: z.optional(z.string()),
- bannedAt: z.string(),
+ expiresAt: z.optional(z.dateTime()),
+ bannedAt: z.dateTime(),
bannedBy: z.string(),
}),
),
});
export type PlayerDocument = z.infer<typeof playerDocumentSchema>;
🤖 Prompt for AI Agents
Owner
Author
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. Convex only support zod v3 currently, so we have to import zod/v3 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. @BetrixDev Thank you for the clarification! You're absolutely right - I wasn't aware of Convex's current limitation to Zod v3 support. That explains why the import is from I appreciate you correcting me on this technical constraint. The schema structure you have is appropriate for Zod v3, and the import path is correct given Convex's current compatibility requirements. ✏️ Learnings added
🧠 Learnings used |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |
| "typescript": "^5.9.2" | ||
| }, | ||
| "dependencies": { | ||
| "@hono/zod-validator": "^0.7.3", | ||
|
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 chainAlign Zod v4 usage and verify @hono/zod-validator compatibility. Repo depends on zod ^4, but backend/schemas.ts imports "zod/v3". Please unify on Zod v4 to avoid dual runtime types. Also confirm @hono/zod-validator@0.7.3 compatibility with hono@4 and zod@4. Run to locate v3 imports and confirm versions: And sanity‑check library compatibility: 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Zod deps in package.json files:"
fd package.json -tf -X jq -r '.dependencies.zod? // empty + " @ " + (.name // input_filename)'
echo
echo "Any v3 subpath imports:"
rg -nP "from\\s+['\"]zod/v3['\"]|require\\(['\"]zod/v3['\"]\\)"Length of output: 196 🌐 Web query: 💡 Result: Short answer: No official published 0.7.3 appears on npm; @hono/zod-validator v0.7.x has added Zod v4 support (via a PR/issue) and is intended for use with Hono 4.x, but you should use the latest 0.7.x release on npm (0.7.2 at the time of my check) or confirm a 0.7.3 publish before depending on that exact tag. Sources:
Unify Zod to v4 and fix zod/v3 imports
🤖 Prompt for AI Agents |
||
| "convex": "^1.25.4", | ||
| "convex-helpers": "^0.1.104", | ||
| "hono": "^4.9.8", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,4 @@ | ||
| AXIOM_API_TOKEN="" | ||
| AXIOM_DATASET_NAME="" | ||
| AXIOM_DATASET_NAME="" | ||
| HTTP_AUTH_TOKEN="" | ||
| HTTP_BASE_URL="" |
Uh oh!
There was an error while loading. Please reload this page.