refactor(dto): centralize backend schema defaults
This commit is contained in:
@@ -104,4 +104,22 @@ describe("UnlockPrivateResponse", () => {
|
||||
expect(response.requiredCredits).toBe(0);
|
||||
expect(response.shortfallCredits).toBe(0);
|
||||
});
|
||||
|
||||
it("defaults nullable lock detail through schema helpers", () => {
|
||||
const response = UnlockPrivateResponse.from({
|
||||
unlocked: false,
|
||||
content: "",
|
||||
reason: "insufficient_balance",
|
||||
lockDetail: null,
|
||||
});
|
||||
|
||||
expect(response.lockDetail).toEqual({
|
||||
locked: false,
|
||||
showContent: true,
|
||||
showUpgrade: false,
|
||||
reason: null,
|
||||
hint: null,
|
||||
detail: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -76,6 +76,22 @@ describe("User", () => {
|
||||
expect("dailyQuotas" in user.toJson()).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults nullable nested user fields through schema helpers", () => {
|
||||
const user = User.fromJson({
|
||||
id: "user-1",
|
||||
username: "guest_user",
|
||||
personalityTraits: null,
|
||||
});
|
||||
|
||||
expect(user.personalityTraits.toJson()).toEqual({
|
||||
caring: 0.5,
|
||||
playful: 0.5,
|
||||
serious: 0.5,
|
||||
cheerful: 0.5,
|
||||
romantic: 0.5,
|
||||
});
|
||||
});
|
||||
|
||||
it("parses the latest backend entitlements payload", () => {
|
||||
const entitlements = UserEntitlements.fromJson({
|
||||
userId: "user-1",
|
||||
@@ -122,4 +138,29 @@ describe("User", () => {
|
||||
expect(entitlements.creditBalance).toBe(120);
|
||||
expect(entitlements.historyUnlock.costs.photo).toBe(40);
|
||||
});
|
||||
|
||||
it("defaults nullable entitlement sections through schema helpers", () => {
|
||||
const entitlements = UserEntitlements.fromJson({
|
||||
userId: "user-1",
|
||||
policy: null,
|
||||
costs: null,
|
||||
quotas: null,
|
||||
historyUnlock: null,
|
||||
});
|
||||
|
||||
expect(entitlements.policy.membershipState).toBe("");
|
||||
expect(entitlements.costs.private_message).toBe(0);
|
||||
expect(entitlements.quotas.normalChatFreeDaily).toBe(0);
|
||||
expect(entitlements.historyUnlock).toMatchObject({
|
||||
enabled: false,
|
||||
order: "",
|
||||
chargeMode: "",
|
||||
insufficientBalanceBehavior: "",
|
||||
costs: {
|
||||
private_message: 0,
|
||||
voice_message: 0,
|
||||
photo: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrFalse } from "../nullable-defaults";
|
||||
|
||||
export const AppleLoginRequestSchema = z.object({
|
||||
identityToken: z.string(),
|
||||
platform: z.string(),
|
||||
isTestAccount: z.boolean().default(false),
|
||||
isTestAccount: booleanOrFalse,
|
||||
});
|
||||
|
||||
export type AppleLoginRequestInput = z.input<typeof AppleLoginRequestSchema>;
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrFalse, stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const FacebookLoginRequestSchema = z.object({
|
||||
accessToken: z.string(),
|
||||
platform: z.string(),
|
||||
guestId: z.string().default(""),
|
||||
isTestAccount: z.boolean().default(false),
|
||||
guestId: stringOrEmpty,
|
||||
isTestAccount: booleanOrFalse,
|
||||
});
|
||||
|
||||
export type FacebookLoginRequestInput = z.input<typeof FacebookLoginRequestSchema>;
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { stringOrNull } from "../nullable-defaults";
|
||||
|
||||
export const FacebookUserDataSchema = z.object({
|
||||
id: z.string().nullable().default(null),
|
||||
name: z.string().nullable().default(null),
|
||||
email: z.string().nullable().default(null),
|
||||
pictureUrl: z.string().nullable().default(null),
|
||||
id: stringOrNull,
|
||||
name: stringOrNull,
|
||||
email: stringOrNull,
|
||||
pictureUrl: stringOrNull,
|
||||
});
|
||||
|
||||
export type FacebookUserDataInput = z.input<typeof FacebookUserDataSchema>;
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrFalse, stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const FbIdLoginRequestSchema = z.object({
|
||||
fbId: z.string(),
|
||||
avatarUrl: z.string().default(""),
|
||||
isTestAccount: z.boolean().default(false),
|
||||
avatarUrl: stringOrEmpty,
|
||||
isTestAccount: booleanOrFalse,
|
||||
});
|
||||
|
||||
export type FbIdLoginRequestInput = z.input<typeof FbIdLoginRequestSchema>;
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrFalse, stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const GoogleLoginRequestSchema = z.object({
|
||||
idToken: z.string(),
|
||||
platform: z.string(),
|
||||
guestId: z.string().default(""),
|
||||
isTestAccount: z.boolean().default(false),
|
||||
guestId: stringOrEmpty,
|
||||
isTestAccount: booleanOrFalse,
|
||||
});
|
||||
|
||||
export type GoogleLoginRequestInput = z.input<typeof GoogleLoginRequestSchema>;
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrFalse } from "../nullable-defaults";
|
||||
|
||||
export const GuestLoginRequestSchema = z.object({
|
||||
deviceId: z.string(),
|
||||
isTestAccount: z.boolean().default(false),
|
||||
isTestAccount: booleanOrFalse,
|
||||
});
|
||||
|
||||
export type GuestLoginRequestInput = z.input<typeof GuestLoginRequestSchema>;
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
*
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrTrue, numberOr, schemaOrNull } from "../nullable-defaults";
|
||||
import { UserSchema } from "../user/user";
|
||||
|
||||
export const GuestLoginResponseSchema = z.object({
|
||||
token: z.string(),
|
||||
deviceId: z.string(),
|
||||
userId: z.string(),
|
||||
isDeviceUser: z.boolean().default(true),
|
||||
expiresIn: z.number().default(2592000),
|
||||
user: UserSchema.optional(),
|
||||
isDeviceUser: booleanOrTrue,
|
||||
expiresIn: numberOr(2592000),
|
||||
user: schemaOrNull(UserSchema),
|
||||
});
|
||||
|
||||
export type GuestLoginResponseInput = z.input<typeof GuestLoginResponseSchema>;
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrFalse, stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const LoginRequestSchema = z.object({
|
||||
email: z.string().default(""),
|
||||
username: z.string().default(""),
|
||||
email: stringOrEmpty,
|
||||
username: stringOrEmpty,
|
||||
password: z.string(),
|
||||
platform: z.string(),
|
||||
guestId: z.string().default(""),
|
||||
isTestAccount: z.boolean().default(false),
|
||||
guestId: stringOrEmpty,
|
||||
isTestAccount: booleanOrFalse,
|
||||
});
|
||||
|
||||
export type LoginRequestInput = z.input<typeof LoginRequestSchema>;
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
*
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { stringOrEmpty } from "../nullable-defaults";
|
||||
import { UserSchema } from "../user/user";
|
||||
|
||||
export const LoginResponseSchema = z.object({
|
||||
user: UserSchema,
|
||||
token: z.string(),
|
||||
refreshToken: z.string().default(""),
|
||||
refreshToken: stringOrEmpty,
|
||||
});
|
||||
|
||||
export type LoginResponseInput = z.input<typeof LoginResponseSchema>;
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrTrue } from "../nullable-defaults";
|
||||
|
||||
export const LogoutResponseSchema = z.object({
|
||||
success: z.boolean().default(true),
|
||||
success: booleanOrTrue,
|
||||
});
|
||||
|
||||
export type LogoutResponseInput = z.input<typeof LogoutResponseSchema>;
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrFalse, stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const RegisterRequestSchema = z.object({
|
||||
username: z.string(),
|
||||
email: z.string(),
|
||||
password: z.string(),
|
||||
platform: z.string(),
|
||||
guestId: z.string().default(""),
|
||||
isTestAccount: z.boolean().default(false),
|
||||
guestId: stringOrEmpty,
|
||||
isTestAccount: booleanOrFalse,
|
||||
});
|
||||
|
||||
export type RegisterRequestInput = z.input<typeof RegisterRequestSchema>;
|
||||
|
||||
@@ -3,17 +3,19 @@
|
||||
*
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { arrayOrEmpty, booleanOrFalse, numberOrZero } from "../nullable-defaults";
|
||||
import { ChatMessageSchema } from "./chat_message";
|
||||
|
||||
export const ChatHistoryResponseSchema = z.object({
|
||||
messages: z.array(ChatMessageSchema),
|
||||
total: z.number().default(0),
|
||||
limit: z.number(),
|
||||
offset: z.number(),
|
||||
isVip: z.boolean().default(false),
|
||||
privateFreeLimit: z.number().default(0),
|
||||
privateUsedToday: z.number().default(0),
|
||||
privateCanViewFree: z.boolean().default(false),
|
||||
messages: arrayOrEmpty(ChatMessageSchema),
|
||||
total: numberOrZero,
|
||||
limit: numberOrZero,
|
||||
offset: numberOrZero,
|
||||
isVip: booleanOrFalse,
|
||||
privateFreeLimit: numberOrZero,
|
||||
privateUsedToday: numberOrZero,
|
||||
privateCanViewFree: booleanOrFalse,
|
||||
});
|
||||
|
||||
export type ChatHistoryResponseInput = z.input<typeof ChatHistoryResponseSchema>;
|
||||
|
||||
@@ -3,17 +3,19 @@
|
||||
*
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { stringOr, stringOrEmpty, stringOrNull } from "../nullable-defaults";
|
||||
import { ChatImageSchema, ChatLockDetailSchema } from "./chat_payloads";
|
||||
|
||||
export const ChatMessageSchema = z
|
||||
.object({
|
||||
role: z.string(),
|
||||
type: z.string().default("text"),
|
||||
content: z.string(),
|
||||
id: z.string().default(""),
|
||||
createdAt: z.string().default(""),
|
||||
created_at: z.string().optional(),
|
||||
audioUrl: z.string().nullable().default(null),
|
||||
role: stringOrEmpty,
|
||||
type: stringOr("text"),
|
||||
content: stringOrEmpty,
|
||||
id: stringOrEmpty,
|
||||
createdAt: stringOrEmpty,
|
||||
created_at: stringOrNull,
|
||||
audioUrl: stringOrNull,
|
||||
image: ChatImageSchema,
|
||||
lockDetail: ChatLockDetailSchema,
|
||||
})
|
||||
|
||||
@@ -3,30 +3,44 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
export const ChatImageSchema = z
|
||||
.object({
|
||||
type: z.string().nullable().default(null),
|
||||
url: z.string().nullable().default(null),
|
||||
})
|
||||
.default({ type: null, url: null });
|
||||
import {
|
||||
booleanOrFalse,
|
||||
booleanOrTrue,
|
||||
schemaOr,
|
||||
stringOrNull,
|
||||
unknownRecordOrNull,
|
||||
} from "../nullable-defaults";
|
||||
|
||||
export const ChatLockDetailSchema = z
|
||||
.object({
|
||||
locked: z.boolean().default(false),
|
||||
showContent: z.boolean().default(true),
|
||||
showUpgrade: z.boolean().default(false),
|
||||
reason: z.string().nullable().default(null),
|
||||
hint: z.string().nullable().default(null),
|
||||
detail: z.record(z.string(), z.unknown()).nullable().default(null),
|
||||
})
|
||||
.default({
|
||||
const CHAT_IMAGE_DEFAULTS = { type: null, url: null } as const;
|
||||
|
||||
const CHAT_LOCK_DETAIL_DEFAULTS = {
|
||||
locked: false,
|
||||
showContent: true,
|
||||
showUpgrade: false,
|
||||
reason: null,
|
||||
hint: null,
|
||||
detail: null,
|
||||
});
|
||||
} as const;
|
||||
|
||||
export const ChatImageSchema = schemaOr(
|
||||
z.object({
|
||||
type: stringOrNull,
|
||||
url: stringOrNull,
|
||||
}),
|
||||
CHAT_IMAGE_DEFAULTS,
|
||||
);
|
||||
|
||||
export const ChatLockDetailSchema = schemaOr(
|
||||
z.object({
|
||||
locked: booleanOrFalse,
|
||||
showContent: booleanOrTrue,
|
||||
showUpgrade: booleanOrFalse,
|
||||
reason: stringOrNull,
|
||||
hint: stringOrNull,
|
||||
detail: unknownRecordOrNull,
|
||||
}),
|
||||
CHAT_LOCK_DETAIL_DEFAULTS,
|
||||
);
|
||||
|
||||
export type ChatImageInput = z.input<typeof ChatImageSchema>;
|
||||
export type ChatImageData = z.output<typeof ChatImageSchema>;
|
||||
|
||||
@@ -3,19 +3,26 @@
|
||||
*
|
||||
*/
|
||||
import { z } from "zod";
|
||||
import { booleanOrFalse, numberOrZero, stringOrNull } from "../nullable-defaults";
|
||||
import {
|
||||
booleanOrFalse,
|
||||
booleanOrTrue,
|
||||
numberOrLazy,
|
||||
numberOrZero,
|
||||
stringOrEmpty,
|
||||
stringOrNull,
|
||||
} from "../nullable-defaults";
|
||||
import { ChatImageSchema, ChatLockDetailSchema } from "./chat_payloads";
|
||||
|
||||
export const ChatSendResponseSchema = z.object({
|
||||
reply: z.string(),
|
||||
audioUrl: z.string().default(""),
|
||||
messageId: z.string(),
|
||||
reply: stringOrEmpty,
|
||||
audioUrl: stringOrEmpty,
|
||||
messageId: stringOrEmpty,
|
||||
isGuest: booleanOrFalse,
|
||||
// 函数式 default —— 每次 parse 时重新调 Date.now()(后端不返回 timestamp 时用当下时间)
|
||||
timestamp: z.number().default(() => Date.now()),
|
||||
timestamp: numberOrLazy(() => Date.now()),
|
||||
image: ChatImageSchema,
|
||||
lockDetail: ChatLockDetailSchema,
|
||||
canSendMessage: z.boolean().default(true),
|
||||
canSendMessage: booleanOrTrue,
|
||||
cannotSendReason: stringOrNull,
|
||||
creditBalance: numberOrZero,
|
||||
creditsCharged: numberOrZero,
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { numberOrZero } from "../nullable-defaults";
|
||||
|
||||
export const ChatSyncDataSchema = z.object({
|
||||
syncedCount: z.number(),
|
||||
totalHistory: z.number(),
|
||||
syncedCount: numberOrZero,
|
||||
totalHistory: numberOrZero,
|
||||
});
|
||||
|
||||
export type ChatSyncDataInput = z.input<typeof ChatSyncDataSchema>;
|
||||
|
||||
@@ -4,15 +4,17 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrTrue, numberOrZero, stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const ImageUploadResponseSchema = z.object({
|
||||
success: z.boolean().default(true),
|
||||
imageId: z.string(),
|
||||
thumbUrl: z.string(),
|
||||
mediumUrl: z.string(),
|
||||
originalUrl: z.string(),
|
||||
width: z.number(),
|
||||
height: z.number(),
|
||||
bytes: z.number(),
|
||||
success: booleanOrTrue,
|
||||
imageId: stringOrEmpty,
|
||||
thumbUrl: stringOrEmpty,
|
||||
mediumUrl: stringOrEmpty,
|
||||
originalUrl: stringOrEmpty,
|
||||
width: numberOrZero,
|
||||
height: numberOrZero,
|
||||
bytes: numberOrZero,
|
||||
});
|
||||
|
||||
export type ImageUploadResponseInput = z.input<typeof ImageUploadResponseSchema>;
|
||||
|
||||
@@ -4,16 +4,18 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrFalse, numberOrZero, stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const SendMessageRequestSchema = z.object({
|
||||
message: z.string().default(""),
|
||||
image: z.string().default(""),
|
||||
imageId: z.string().default(""),
|
||||
imageThumbUrl: z.string().default(""),
|
||||
imageMediumUrl: z.string().default(""),
|
||||
imageOriginalUrl: z.string().default(""),
|
||||
imageWidth: z.number().default(0),
|
||||
imageHeight: z.number().default(0),
|
||||
useWebSocket: z.boolean().default(false),
|
||||
message: stringOrEmpty,
|
||||
image: stringOrEmpty,
|
||||
imageId: stringOrEmpty,
|
||||
imageThumbUrl: stringOrEmpty,
|
||||
imageMediumUrl: stringOrEmpty,
|
||||
imageOriginalUrl: stringOrEmpty,
|
||||
imageWidth: numberOrZero,
|
||||
imageHeight: numberOrZero,
|
||||
useWebSocket: booleanOrFalse,
|
||||
});
|
||||
|
||||
export type SendMessageRequestInput = z.input<typeof SendMessageRequestSchema>;
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const SttDataSchema = z.object({
|
||||
text: z.string(),
|
||||
text: stringOrEmpty,
|
||||
});
|
||||
|
||||
export type SttDataInput = z.input<typeof SttDataSchema>;
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const SyncMessageSchema = z.object({
|
||||
role: z.string(),
|
||||
content: z.string(),
|
||||
timestamp: z.string(),
|
||||
role: stringOrEmpty,
|
||||
content: stringOrEmpty,
|
||||
timestamp: stringOrEmpty,
|
||||
});
|
||||
|
||||
export type SyncMessageInput = z.input<typeof SyncMessageSchema>;
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
export const UnlockHistoryCostsByMessageSchema = z.record(z.string(), z.number());
|
||||
import { arrayOrEmpty, booleanOrFalse, numberOrZero, recordOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const UnlockHistoryCostsByMessageSchema = recordOrEmpty(z.number());
|
||||
|
||||
export const UnlockHistoryReasonSchema = z.enum([
|
||||
"ok",
|
||||
@@ -12,19 +14,19 @@ export const UnlockHistoryReasonSchema = z.enum([
|
||||
]);
|
||||
|
||||
export const UnlockHistoryResponseSchema = z.object({
|
||||
unlocked: z.boolean(),
|
||||
unlocked: booleanOrFalse,
|
||||
reason: UnlockHistoryReasonSchema,
|
||||
totalLocked: z.number().default(0),
|
||||
unlockedCount: z.number().default(0),
|
||||
privateCount: z.number().default(0),
|
||||
imageCount: z.number().default(0),
|
||||
voiceCount: z.number().default(0),
|
||||
requiredCredits: z.number().default(0),
|
||||
currentCredits: z.number().default(0),
|
||||
remainingCredits: z.number().default(0),
|
||||
shortfallCredits: z.number().default(0),
|
||||
costsByMessage: UnlockHistoryCostsByMessageSchema.default({}),
|
||||
messageIds: z.array(z.string()).default([]),
|
||||
totalLocked: numberOrZero,
|
||||
unlockedCount: numberOrZero,
|
||||
privateCount: numberOrZero,
|
||||
imageCount: numberOrZero,
|
||||
voiceCount: numberOrZero,
|
||||
requiredCredits: numberOrZero,
|
||||
currentCredits: numberOrZero,
|
||||
remainingCredits: numberOrZero,
|
||||
shortfallCredits: numberOrZero,
|
||||
costsByMessage: UnlockHistoryCostsByMessageSchema,
|
||||
messageIds: arrayOrEmpty(z.string()),
|
||||
});
|
||||
|
||||
export type UnlockHistoryReason = z.output<typeof UnlockHistoryReasonSchema>;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { numberOrZero, stringOrEmpty } from "../nullable-defaults";
|
||||
import { booleanOrFalse, numberOrZero, stringOrEmpty } from "../nullable-defaults";
|
||||
import { ChatLockDetailSchema } from "./chat_payloads";
|
||||
|
||||
/**
|
||||
* 单条历史付费 / 私密消息解锁响应。
|
||||
*/
|
||||
export const UnlockPrivateReasonSchema = z.string().default("");
|
||||
export const UnlockPrivateReasonSchema = stringOrEmpty;
|
||||
|
||||
export const UnlockPrivateResponseSchema = z.object({
|
||||
unlocked: z.boolean(),
|
||||
unlocked: booleanOrFalse,
|
||||
content: stringOrEmpty,
|
||||
reason: UnlockPrivateReasonSchema,
|
||||
creditBalance: numberOrZero,
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const AppEventSchema = z.object({
|
||||
userId: z.string(),
|
||||
browser: z.string(),
|
||||
userAgent: z.string(),
|
||||
userId: stringOrEmpty,
|
||||
browser: stringOrEmpty,
|
||||
userAgent: stringOrEmpty,
|
||||
});
|
||||
|
||||
export type AppEventInput = z.input<typeof AppEventSchema>;
|
||||
|
||||
@@ -4,12 +4,18 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
booleanOrFalse,
|
||||
numberOrZero,
|
||||
stringOrEmpty,
|
||||
} from "../nullable-defaults";
|
||||
|
||||
export const PwaEventSchema = z.object({
|
||||
deviceId: z.string(),
|
||||
deviceType: z.string(),
|
||||
timestamp: z.number(),
|
||||
pwaInstalled: z.boolean().default(false),
|
||||
pwaSupported: z.boolean().default(false),
|
||||
deviceId: stringOrEmpty,
|
||||
deviceType: stringOrEmpty,
|
||||
timestamp: numberOrZero,
|
||||
pwaInstalled: booleanOrFalse,
|
||||
pwaSupported: booleanOrFalse,
|
||||
});
|
||||
|
||||
export type PwaEventInput = z.input<typeof PwaEventSchema>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Zod 默认值辅助器(处理 `null | undefined` → 标量默认)
|
||||
* Zod 默认值辅助器(处理 `null | undefined` → 默认值)
|
||||
*
|
||||
* 防御性解析:后端对 Facebook / Apple 等 OAuth 用户可能返回 `null`(如 `email: null`),
|
||||
* Zod 默认的 `.default("")` 只接受 `undefined`,遇到 `null` 会抛 ZodError → 登录静默失败。
|
||||
@@ -11,26 +11,131 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
/** `string | null | undefined` → `string`(默认空串) */
|
||||
export const stringOrEmpty = z
|
||||
type SchemaDefault<T extends z.ZodType> = Exclude<z.output<T>, undefined>;
|
||||
|
||||
/** `string | null | undefined` → `string` */
|
||||
export function stringOr(defaultValue: string) {
|
||||
return z
|
||||
.string()
|
||||
.nullable()
|
||||
.transform((v) => v ?? "")
|
||||
.default("");
|
||||
.transform((v) => v ?? defaultValue)
|
||||
.default(defaultValue);
|
||||
}
|
||||
|
||||
/** `string | null | undefined` → `string`(默认空串) */
|
||||
export const stringOrEmpty = stringOr("");
|
||||
|
||||
/** required `string | null` → `string`(缺字段仍报错,null 收敛为空串) */
|
||||
export const requiredStringOrEmpty = z
|
||||
.string()
|
||||
.nullable()
|
||||
.transform((v) => v ?? "");
|
||||
|
||||
/** `string | null | undefined` → `string | null`(默认 null) */
|
||||
export const stringOrNull = z.string().nullable().default(null);
|
||||
|
||||
/** `number | null | undefined` → `number`(默认 0) */
|
||||
export const numberOrZero = z
|
||||
/** `number | null | undefined` → `number` */
|
||||
export function numberOr(defaultValue: number) {
|
||||
return z
|
||||
.number()
|
||||
.nullable()
|
||||
.transform((v) => v ?? 0)
|
||||
.default(0);
|
||||
.transform((v) => v ?? defaultValue)
|
||||
.default(defaultValue);
|
||||
}
|
||||
|
||||
/** `boolean | null | undefined` → `boolean`(默认 false) */
|
||||
export const booleanOrFalse = z
|
||||
/** `number | null | undefined` → `number`,默认值在 parse 时惰性计算 */
|
||||
export function numberOrLazy(defaultValue: () => number) {
|
||||
return z
|
||||
.number()
|
||||
.nullable()
|
||||
.transform((v) => v ?? defaultValue())
|
||||
.default(defaultValue);
|
||||
}
|
||||
|
||||
/** `number | null | undefined` → `number`(默认 0) */
|
||||
export const numberOrZero = numberOr(0);
|
||||
|
||||
/** required `number | null` → `number`(缺字段仍报错,null 收敛为 0) */
|
||||
export const requiredNumberOrZero = z
|
||||
.number()
|
||||
.nullable()
|
||||
.transform((v) => v ?? 0);
|
||||
|
||||
/** `number | null | undefined` → `number | null`(默认 null) */
|
||||
export const numberOrNull = z.number().nullable().default(null);
|
||||
|
||||
/** `boolean | null | undefined` → `boolean` */
|
||||
export function booleanOr(defaultValue: boolean) {
|
||||
return z
|
||||
.boolean()
|
||||
.nullable()
|
||||
.transform((v) => v ?? false)
|
||||
.default(false);
|
||||
.transform((v) => v ?? defaultValue)
|
||||
.default(defaultValue);
|
||||
}
|
||||
|
||||
/** `boolean | null | undefined` → `boolean`(默认 false) */
|
||||
export const booleanOrFalse = booleanOr(false);
|
||||
|
||||
/** `boolean | null | undefined` → `boolean`(默认 true) */
|
||||
export const booleanOrTrue = booleanOr(true);
|
||||
|
||||
/** required `boolean | null` → `boolean`(缺字段仍报错,null 收敛为 false) */
|
||||
export const requiredBooleanOrFalse = z
|
||||
.boolean()
|
||||
.nullable()
|
||||
.transform((v) => v ?? false);
|
||||
|
||||
/** `T[] | null | undefined` → `T[]`(默认空数组) */
|
||||
export function arrayOrEmpty<T extends z.ZodType>(schema: T) {
|
||||
return z
|
||||
.array(schema)
|
||||
.nullable()
|
||||
.transform((v) => v ?? [])
|
||||
.default(() => []);
|
||||
}
|
||||
|
||||
/** `Record<string, T> | null | undefined` → `Record<string, T>`(默认空对象) */
|
||||
export function recordOrEmpty<T extends z.ZodType>(schema: T) {
|
||||
return z
|
||||
.record(z.string(), schema)
|
||||
.nullable()
|
||||
.transform((v) => v ?? {})
|
||||
.default(() => ({}));
|
||||
}
|
||||
|
||||
/** `Record<string, unknown> | null | undefined` → `Record<string, unknown>` */
|
||||
export const unknownRecordOrEmpty = recordOrEmpty(z.unknown());
|
||||
|
||||
/** `Record<string, unknown> | null | undefined` → `Record<string, unknown> | null` */
|
||||
export const unknownRecordOrNull = z
|
||||
.record(z.string(), z.unknown())
|
||||
.nullable()
|
||||
.default(null);
|
||||
|
||||
function resolveDefault<T>(defaultValue: T | (() => T)): T {
|
||||
return typeof defaultValue === "function"
|
||||
? (defaultValue as () => T)()
|
||||
: defaultValue;
|
||||
}
|
||||
|
||||
/** `T | null | undefined` → `T`(默认对象会继续经过 schema 解析) */
|
||||
export function schemaOr<T extends z.ZodType>(
|
||||
schema: T,
|
||||
defaultValue: SchemaDefault<T> | (() => SchemaDefault<T>),
|
||||
) {
|
||||
const preprocessed = z.preprocess(
|
||||
(value) => value ?? resolveDefault(defaultValue),
|
||||
schema,
|
||||
);
|
||||
|
||||
if (typeof defaultValue === "function") {
|
||||
return preprocessed.default(defaultValue as () => SchemaDefault<T>);
|
||||
}
|
||||
|
||||
return preprocessed.default(defaultValue);
|
||||
}
|
||||
|
||||
/** `T | null | undefined` → `T | null`(默认 null) */
|
||||
export function schemaOrNull<T extends z.ZodType>(schema: T) {
|
||||
return schema.nullable().default(null);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { stringOrNull } from "../nullable-defaults";
|
||||
|
||||
const paymentUrlKeys = [
|
||||
"cashierUrl",
|
||||
"cashier_url",
|
||||
@@ -20,17 +22,17 @@ const paymentUrlKeys = [
|
||||
export const CreatePaymentOrderResponseSchema = z.object({
|
||||
orderId: z.string(),
|
||||
payParams: z.record(z.string(), z.unknown()),
|
||||
cashierUrl: z.string().optional(),
|
||||
cashier_url: z.string().optional(),
|
||||
checkoutUrl: z.string().optional(),
|
||||
checkout_url: z.string().optional(),
|
||||
paymentUrl: z.string().optional(),
|
||||
payment_url: z.string().optional(),
|
||||
approvalUrl: z.string().optional(),
|
||||
approval_url: z.string().optional(),
|
||||
redirectUrl: z.string().optional(),
|
||||
redirect_url: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
cashierUrl: stringOrNull,
|
||||
cashier_url: stringOrNull,
|
||||
checkoutUrl: stringOrNull,
|
||||
checkout_url: stringOrNull,
|
||||
paymentUrl: stringOrNull,
|
||||
payment_url: stringOrNull,
|
||||
approvalUrl: stringOrNull,
|
||||
approval_url: stringOrNull,
|
||||
redirectUrl: stringOrNull,
|
||||
redirect_url: stringOrNull,
|
||||
url: stringOrNull,
|
||||
}).transform((data) => {
|
||||
const payParams = { ...data.payParams };
|
||||
|
||||
|
||||
@@ -3,20 +3,26 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
booleanOrFalse,
|
||||
numberOrNull,
|
||||
stringOrNull,
|
||||
} from "../nullable-defaults";
|
||||
|
||||
export const PaymentPlanSchema = z.object({
|
||||
planId: z.string(),
|
||||
planName: z.string(),
|
||||
orderType: z.string(),
|
||||
vipDays: z.number().nullable(),
|
||||
dolAmount: z.number().nullable(),
|
||||
vipDays: numberOrNull,
|
||||
dolAmount: numberOrNull,
|
||||
creditBalance: z.number(),
|
||||
amountCents: z.number(),
|
||||
originalAmountCents: z.number().nullable().default(null),
|
||||
dailyPriceCents: z.number().nullable().default(null),
|
||||
originalAmountCents: numberOrNull,
|
||||
dailyPriceCents: numberOrNull,
|
||||
currency: z.string(),
|
||||
isFirstRechargeOffer: z.boolean().default(false),
|
||||
firstRechargeDiscountPercent: z.number().nullable().default(null),
|
||||
promotionType: z.string().nullable().default(null),
|
||||
isFirstRechargeOffer: booleanOrFalse,
|
||||
firstRechargeDiscountPercent: numberOrNull,
|
||||
promotionType: stringOrNull,
|
||||
});
|
||||
|
||||
export type PaymentPlanInput = z.input<typeof PaymentPlanSchema>;
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrFalse, numberOrZero, stringOrEmpty } from "../nullable-defaults";
|
||||
import {
|
||||
arrayOrEmpty,
|
||||
booleanOrFalse,
|
||||
numberOrZero,
|
||||
schemaOrNull,
|
||||
stringOrEmpty,
|
||||
} from "../nullable-defaults";
|
||||
import { PaymentPlanSchema } from "./payment_plan";
|
||||
|
||||
export const FirstRechargeOfferSchema = z.object({
|
||||
@@ -13,9 +19,9 @@ export const FirstRechargeOfferSchema = z.object({
|
||||
});
|
||||
|
||||
export const PaymentPlansResponseSchema = z.object({
|
||||
isFirstRecharge: z.boolean().default(false),
|
||||
firstRechargeOffer: FirstRechargeOfferSchema.nullable().default(null),
|
||||
plans: z.array(PaymentPlanSchema),
|
||||
isFirstRecharge: booleanOrFalse,
|
||||
firstRechargeOffer: schemaOrNull(FirstRechargeOfferSchema),
|
||||
plans: arrayOrEmpty(PaymentPlanSchema),
|
||||
});
|
||||
|
||||
export type PaymentPlansResponseInput = z.input<
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { booleanOrFalse, stringOrNull } from "../nullable-defaults";
|
||||
|
||||
export const PaymentVipStatusResponseSchema = z.object({
|
||||
isVip: z.boolean(),
|
||||
vipExpiresAt: z.string().nullable(),
|
||||
isVip: booleanOrFalse,
|
||||
vipExpiresAt: stringOrNull,
|
||||
});
|
||||
|
||||
export type PaymentVipStatusResponseInput = z.input<
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const AvatarDataSchema = z.object({
|
||||
avatarUrl: z.string().default(""),
|
||||
userId: z.string(),
|
||||
avatarUrl: stringOrEmpty,
|
||||
userId: stringOrEmpty,
|
||||
});
|
||||
|
||||
export type AvatarDataInput = z.input<typeof AvatarDataSchema>;
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { numberOrZero, stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const CreditsDataSchema = z.object({
|
||||
userId: z.string(),
|
||||
dolBalance: z.number(),
|
||||
userId: stringOrEmpty,
|
||||
dolBalance: numberOrZero,
|
||||
});
|
||||
|
||||
export type CreditsDataInput = z.input<typeof CreditsDataSchema>;
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { arrayOrEmpty, numberOrZero } from "../nullable-defaults";
|
||||
|
||||
export const CreditsHistoryDataSchema = z.object({
|
||||
records: z.array(z.record(z.string(), z.unknown())),
|
||||
total: z.number(),
|
||||
limit: z.number(),
|
||||
offset: z.number(),
|
||||
records: arrayOrEmpty(z.record(z.string(), z.unknown())),
|
||||
total: numberOrZero,
|
||||
limit: numberOrZero,
|
||||
offset: numberOrZero,
|
||||
});
|
||||
|
||||
export type CreditsHistoryDataInput = z.input<typeof CreditsHistoryDataSchema>;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { numberOr } from "../nullable-defaults";
|
||||
|
||||
export const PERSONALITY_TRAITS_DEFAULTS = {
|
||||
cheerful: 0.5,
|
||||
caring: 0.5,
|
||||
@@ -13,11 +15,11 @@ export const PERSONALITY_TRAITS_DEFAULTS = {
|
||||
} as const;
|
||||
|
||||
export const PersonalityTraitsSchema = z.object({
|
||||
cheerful: z.number().default(0.5),
|
||||
caring: z.number().default(0.5),
|
||||
playful: z.number().default(0.5),
|
||||
serious: z.number().default(0.5),
|
||||
romantic: z.number().default(0.5),
|
||||
cheerful: numberOr(0.5),
|
||||
caring: numberOr(0.5),
|
||||
playful: numberOr(0.5),
|
||||
serious: numberOr(0.5),
|
||||
romantic: numberOr(0.5),
|
||||
});
|
||||
|
||||
export type PersonalityTraitsInput = z.input<typeof PersonalityTraitsSchema>;
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { numberOrZero, stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const RecentMemorySchema = z.object({
|
||||
type: z.string(),
|
||||
content: z.string(),
|
||||
importance: z.number(),
|
||||
createdAt: z.string().default(""),
|
||||
type: stringOrEmpty,
|
||||
content: stringOrEmpty,
|
||||
importance: numberOrZero,
|
||||
createdAt: stringOrEmpty,
|
||||
});
|
||||
|
||||
export type RecentMemoryInput = z.input<typeof RecentMemorySchema>;
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import { stringOrEmpty } from "../nullable-defaults";
|
||||
|
||||
export const UpdateProfileRequestSchema = z.object({
|
||||
username: z.string().default(""),
|
||||
nickname: z.string().default(""),
|
||||
preferredLanguage: z.string().default(""),
|
||||
currentMood: z.string().default(""),
|
||||
username: stringOrEmpty,
|
||||
nickname: stringOrEmpty,
|
||||
preferredLanguage: stringOrEmpty,
|
||||
currentMood: stringOrEmpty,
|
||||
});
|
||||
|
||||
export type UpdateProfileRequestInput = z.input<typeof UpdateProfileRequestSchema>;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { z } from "zod";
|
||||
import { PersonalityTraitsSchema, PERSONALITY_TRAITS_DEFAULTS } from "./personality_traits";
|
||||
import {
|
||||
numberOrZero,
|
||||
schemaOr,
|
||||
stringOrEmpty,
|
||||
} from "../nullable-defaults";
|
||||
|
||||
@@ -25,7 +26,7 @@ export const UserSchema = z.object({
|
||||
dailyFreePrivateLimit: numberOrZero,
|
||||
dailyFreePrivateUsed: numberOrZero,
|
||||
dailyFreePrivateRemaining: numberOrZero,
|
||||
personalityTraits: PersonalityTraitsSchema.default(PERSONALITY_TRAITS_DEFAULTS),
|
||||
personalityTraits: schemaOr(PersonalityTraitsSchema, PERSONALITY_TRAITS_DEFAULTS),
|
||||
preferredLanguage: stringOrEmpty,
|
||||
createdAt: stringOrEmpty,
|
||||
// 后端新游客返回 null("还没发过消息"),前端统一收敛为空字符串。
|
||||
|
||||
@@ -6,9 +6,49 @@ import { z } from "zod";
|
||||
import {
|
||||
booleanOrFalse,
|
||||
numberOrZero,
|
||||
schemaOr,
|
||||
stringOrEmpty,
|
||||
stringOrNull,
|
||||
} from "../nullable-defaults";
|
||||
|
||||
const POLICY_DEFAULTS = {
|
||||
membershipState: "",
|
||||
nonVipEntitlementsShared: false,
|
||||
guestSameAsLoggedInNonVip: false,
|
||||
refreshAfterPayment: "",
|
||||
} as const;
|
||||
|
||||
const COSTS_DEFAULTS = {
|
||||
normal_message: 0,
|
||||
private_message: 0,
|
||||
voice_message: 0,
|
||||
photo: 0,
|
||||
voice_call_minute: 0,
|
||||
private_album_10: 0,
|
||||
private_album_20: 0,
|
||||
} as const;
|
||||
|
||||
const QUOTAS_DEFAULTS = {
|
||||
normalChatFreeDaily: 0,
|
||||
privateUnlockFreeDaily: 0,
|
||||
voiceMessageFreeDaily: 0,
|
||||
photoFreeDaily: 0,
|
||||
} as const;
|
||||
|
||||
const HISTORY_UNLOCK_COSTS_DEFAULTS = {
|
||||
private_message: 0,
|
||||
voice_message: 0,
|
||||
photo: 0,
|
||||
} as const;
|
||||
|
||||
const HISTORY_UNLOCK_DEFAULTS = {
|
||||
enabled: false,
|
||||
order: "",
|
||||
chargeMode: "",
|
||||
insufficientBalanceBehavior: "",
|
||||
costs: HISTORY_UNLOCK_COSTS_DEFAULTS,
|
||||
} as const;
|
||||
|
||||
export const UserEntitlementsPolicySchema = z
|
||||
.object({
|
||||
membershipState: stringOrEmpty,
|
||||
@@ -39,24 +79,24 @@ export const UserEntitlementsQuotasSchema = z
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const UserEntitlementsHistoryUnlockCostsSchema = z
|
||||
.object({
|
||||
private_message: numberOrZero,
|
||||
voice_message: numberOrZero,
|
||||
photo: numberOrZero,
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const UserEntitlementsHistoryUnlockSchema = z
|
||||
.object({
|
||||
enabled: booleanOrFalse,
|
||||
order: stringOrEmpty,
|
||||
chargeMode: stringOrEmpty,
|
||||
insufficientBalanceBehavior: stringOrEmpty,
|
||||
costs: z
|
||||
.object({
|
||||
private_message: numberOrZero,
|
||||
voice_message: numberOrZero,
|
||||
photo: numberOrZero,
|
||||
})
|
||||
.passthrough()
|
||||
.default(() => ({
|
||||
private_message: 0,
|
||||
voice_message: 0,
|
||||
photo: 0,
|
||||
})),
|
||||
costs: schemaOr(
|
||||
UserEntitlementsHistoryUnlockCostsSchema,
|
||||
HISTORY_UNLOCK_COSTS_DEFAULTS,
|
||||
),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
@@ -64,41 +104,16 @@ export const UserEntitlementsSchema = z.object({
|
||||
userId: stringOrEmpty,
|
||||
isGuest: booleanOrFalse,
|
||||
isVip: booleanOrFalse,
|
||||
vipExpiresAt: z.string().nullable().default(null),
|
||||
vipExpiresAt: stringOrNull,
|
||||
creditBalance: numberOrZero,
|
||||
dolBalance: numberOrZero,
|
||||
policy: UserEntitlementsPolicySchema.default(() => ({
|
||||
membershipState: "",
|
||||
nonVipEntitlementsShared: false,
|
||||
guestSameAsLoggedInNonVip: false,
|
||||
refreshAfterPayment: "",
|
||||
})),
|
||||
costs: UserEntitlementsCostsSchema.default(() => ({
|
||||
normal_message: 0,
|
||||
private_message: 0,
|
||||
voice_message: 0,
|
||||
photo: 0,
|
||||
voice_call_minute: 0,
|
||||
private_album_10: 0,
|
||||
private_album_20: 0,
|
||||
})),
|
||||
quotas: UserEntitlementsQuotasSchema.default(() => ({
|
||||
normalChatFreeDaily: 0,
|
||||
privateUnlockFreeDaily: 0,
|
||||
voiceMessageFreeDaily: 0,
|
||||
photoFreeDaily: 0,
|
||||
})),
|
||||
historyUnlock: UserEntitlementsHistoryUnlockSchema.default(() => ({
|
||||
enabled: false,
|
||||
order: "",
|
||||
chargeMode: "",
|
||||
insufficientBalanceBehavior: "",
|
||||
costs: {
|
||||
private_message: 0,
|
||||
voice_message: 0,
|
||||
photo: 0,
|
||||
},
|
||||
})),
|
||||
policy: schemaOr(UserEntitlementsPolicySchema, POLICY_DEFAULTS),
|
||||
costs: schemaOr(UserEntitlementsCostsSchema, COSTS_DEFAULTS),
|
||||
quotas: schemaOr(UserEntitlementsQuotasSchema, QUOTAS_DEFAULTS),
|
||||
historyUnlock: schemaOr(
|
||||
UserEntitlementsHistoryUnlockSchema,
|
||||
HISTORY_UNLOCK_DEFAULTS,
|
||||
),
|
||||
});
|
||||
|
||||
export type UserEntitlementsInput = z.input<typeof UserEntitlementsSchema>;
|
||||
|
||||
@@ -3,26 +3,33 @@
|
||||
*
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
arrayOrEmpty,
|
||||
numberOrZero,
|
||||
schemaOr,
|
||||
stringOrEmpty,
|
||||
} from "../nullable-defaults";
|
||||
import { PersonalityTraitsSchema, PERSONALITY_TRAITS_DEFAULTS } from "./personality_traits";
|
||||
import { RecentMemorySchema } from "./recent_memory";
|
||||
|
||||
export const UserStatsResponseSchema = z.object({
|
||||
userId: z.string(),
|
||||
username: z.string(),
|
||||
platform: z.string(),
|
||||
intimacy: z.number(),
|
||||
relationshipStage: z.string(),
|
||||
dolBalance: z.number(),
|
||||
totalMessages: z.number(),
|
||||
lastMessageAt: z.string().default(""),
|
||||
accountCreatedAt: z.string().default(""),
|
||||
currentMood: z.string().default(""),
|
||||
personalityTraits: PersonalityTraitsSchema.default(PERSONALITY_TRAITS_DEFAULTS),
|
||||
recentMemories: z.array(RecentMemorySchema).default([]),
|
||||
promptTokensTotal: z.number().default(0),
|
||||
completionTokensTotal: z.number().default(0),
|
||||
cacheHitTokensTotal: z.number().default(0),
|
||||
tokensTotal: z.number().default(0),
|
||||
userId: stringOrEmpty,
|
||||
username: stringOrEmpty,
|
||||
platform: stringOrEmpty,
|
||||
intimacy: numberOrZero,
|
||||
relationshipStage: stringOrEmpty,
|
||||
dolBalance: numberOrZero,
|
||||
totalMessages: numberOrZero,
|
||||
lastMessageAt: stringOrEmpty,
|
||||
accountCreatedAt: stringOrEmpty,
|
||||
currentMood: stringOrEmpty,
|
||||
personalityTraits: schemaOr(PersonalityTraitsSchema, PERSONALITY_TRAITS_DEFAULTS),
|
||||
recentMemories: arrayOrEmpty(RecentMemorySchema),
|
||||
promptTokensTotal: numberOrZero,
|
||||
completionTokensTotal: numberOrZero,
|
||||
cacheHitTokensTotal: numberOrZero,
|
||||
tokensTotal: numberOrZero,
|
||||
});
|
||||
|
||||
export type UserStatsResponseInput = z.input<typeof UserStatsResponseSchema>;
|
||||
|
||||
Reference in New Issue
Block a user