From c16fb9389925f1ad240ee17a3d5cf2bfb7f12d50 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 25 May 2026 16:30:48 +0200 Subject: [PATCH] fix(frontend): allow null slices in client/summary schemas Go's encoding/json emits nil []T as null, not []. The initial ClientPageResponseSchema and ClientHydrateSchema rejected null inboundIds / summary.online / summary.depleted / etc., causing [zod] warnings on every empty list. Add nullableStringArray / nullableNumberArray helpers that accept null and transform to [] so consuming code keeps seeing arrays. Mark ClientRecord.traffic and .reverse nullable too (reverse is explicitly null in MarshalJSON when storage is empty). --- frontend/src/schemas/client.ts | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/frontend/src/schemas/client.ts b/frontend/src/schemas/client.ts index 881e609c..5489f690 100644 --- a/frontend/src/schemas/client.ts +++ b/frontend/src/schemas/client.ts @@ -1,5 +1,8 @@ import { z } from 'zod'; +const nullableStringArray = z.array(z.string()).nullable().transform((v) => v ?? []); +const nullableNumberArray = z.array(z.number()).nullable().transform((v) => v ?? []); + export const ClientTrafficSchema = z.object({ up: z.number().optional(), down: z.number().optional(), @@ -10,21 +13,24 @@ export const ClientTrafficSchema = z.object({ }); export const ClientRecordSchema = z.object({ + id: z.number().optional(), email: z.string(), subId: z.string().optional(), uuid: z.string().optional(), password: z.string().optional(), auth: z.string().optional(), flow: z.string().optional(), + security: z.string().optional(), totalGB: z.number().optional(), expiryTime: z.number().optional(), limitIp: z.number().optional(), tgId: z.union([z.number(), z.string()]).optional(), comment: z.string().optional(), enable: z.boolean().optional(), - inboundIds: z.array(z.number()).optional(), - traffic: ClientTrafficSchema.optional(), - reverse: z.object({ tag: z.string().optional() }).loose().optional(), + reset: z.number().optional(), + inboundIds: nullableNumberArray.optional(), + traffic: ClientTrafficSchema.nullable().optional(), + reverse: z.object({ tag: z.string().optional() }).loose().nullable().optional(), createdAt: z.number().optional(), updatedAt: z.number().optional(), }).loose(); @@ -42,24 +48,26 @@ export const InboundOptionsSchema = z.array(InboundOptionSchema); export const ClientsSummarySchema = z.object({ total: z.number(), active: z.number(), - online: z.array(z.string()), - depleted: z.array(z.string()), - expiring: z.array(z.string()), - deactive: z.array(z.string()), + online: nullableStringArray, + depleted: nullableStringArray, + expiring: nullableStringArray, + deactive: nullableStringArray, }); +const nullableClientArray = z.array(ClientRecordSchema).nullable().transform((v) => v ?? []); + export const ClientPageResponseSchema = z.object({ - items: z.array(ClientRecordSchema), + items: nullableClientArray, total: z.number(), filtered: z.number(), page: z.number(), pageSize: z.number(), - summary: ClientsSummarySchema.optional(), + summary: ClientsSummarySchema.nullable().optional(), }); export const ClientHydrateSchema = z.object({ client: ClientRecordSchema, - inboundIds: z.array(z.number()), + inboundIds: nullableNumberArray, }); export const BulkAdjustResultSchema = z.object({ @@ -73,7 +81,7 @@ export const DelDepletedResultSchema = z.object({ deleted: z.number().optional(), }); -export const OnlinesSchema = z.array(z.string()); +export const OnlinesSchema = nullableStringArray; export type ClientRecord = z.infer; export type ClientTraffic = z.infer;