refactor(data): merge DTO models into schemas
This commit is contained in:
@@ -12,7 +12,7 @@ description: Sync this Next.js frontend with backend API documentation
|
||||
当后端 API 新增、删除、字段结构调整、响应语义变化时,使用本 skill 更新:
|
||||
|
||||
- 网络层
|
||||
- schema / DTO
|
||||
- schema / 不可变数据类
|
||||
- repository
|
||||
- storage
|
||||
- XState store
|
||||
@@ -26,15 +26,14 @@ description: Sync this Next.js frontend with backend API documentation
|
||||
| --- | --- | --- |
|
||||
| API Path | `src/data/services/api/api_path.ts` | 统一维护接口路径 |
|
||||
| API Service | `src/data/services/api/*_api.ts` | 调用 `httpClient` 并解析响应 |
|
||||
| Schema | `src/data/schemas/<module>/*.ts` | Zod schema,负责防御性解析 |
|
||||
| DTO | `src/data/dto/<module>/*.ts` | DTO class,封装 `fromJson/toJson` |
|
||||
| Schema Model | `src/data/schemas/<module>/*.ts` | Zod schema 与不可变数据类,负责解析和序列化 |
|
||||
| Repository Interface | `src/data/repositories/interfaces/i*_repository.ts` | 仓库接口 |
|
||||
| Repository | `src/data/repositories/*_repository.ts` | 业务仓库实现 |
|
||||
| Storage | `src/data/storage/*` | 本地持久化 |
|
||||
| Store | `src/stores/<module>/*` | XState 状态机、actors、helpers、sync |
|
||||
| UI | `src/app/**` | Next.js app router UI |
|
||||
| Mock | `src/data/mock/<module>/**` | 模拟请求 / 响应数据 |
|
||||
| Tests | `**/__tests__/*.test.ts` | DTO、helper、machine transition 测试 |
|
||||
| Tests | `**/__tests__/*.test.ts` | schema model、helper、machine transition 测试 |
|
||||
|
||||
## 后端文档来源
|
||||
|
||||
@@ -108,7 +107,7 @@ src/data/services/api/api_path.ts
|
||||
static readonly userEntitlements = `${ApiPath._user}/entitlements`;
|
||||
```
|
||||
|
||||
### 4. 更新 Schema
|
||||
### 4. 更新 Schema Model
|
||||
|
||||
在对应模块下新增或修改:
|
||||
|
||||
@@ -123,6 +122,13 @@ src/data/schemas/<module>/*.ts
|
||||
- 输入类型用 `z.input<typeof Schema>`。
|
||||
- 对后端可能返回 `null` 的字段做防御性处理。
|
||||
- 不要在 schema 中写 UI 逻辑。
|
||||
- Schema 与对应不可变数据类必须定义在同一个文件中。
|
||||
- 数据类的 `declare readonly` 只声明业务代码直接访问的字段。
|
||||
- 后端返回但前端既不读取、也不透传的字段,不要加入 Schema。
|
||||
- 仅用于请求序列化、响应兼容或内部透传的字段保留在 Schema 中,不声明 class 属性。
|
||||
- 数据类统一使用 `from()`、`fromJson()`、`toJson()` 与 `Object.freeze(this)`。
|
||||
- 没有嵌套转换或自定义方法时,使用 `src/data/schemas/schema_model.ts` 的 `createSchemaModel()`。
|
||||
- 不要再创建独立的数据传输对象层。
|
||||
|
||||
示例:
|
||||
|
||||
@@ -134,36 +140,8 @@ export const XxxResponseSchema = z.object({
|
||||
|
||||
export type XxxResponseInput = z.input<typeof XxxResponseSchema>;
|
||||
export type XxxResponseData = z.output<typeof XxxResponseSchema>;
|
||||
```
|
||||
|
||||
### 5. 更新 DTO
|
||||
|
||||
在对应模块下新增或修改:
|
||||
|
||||
```text
|
||||
src/data/dto/<module>/*.ts
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- DTO class 的 `declare readonly` 只声明前端业务代码会直接访问的字段。
|
||||
- 不要因为后端响应包含某字段,就机械地为它增加 class 属性、嵌套类型或类型别名。
|
||||
- 后端返回但前端既不读取、也不透传的字段,可以不加入 schema 和 DTO。
|
||||
- 仅用于请求序列化、响应兼容或内部透传的字段保留在 schema 中,不添加 class 属性声明。
|
||||
- `Object.assign(this, data)` 会保留 schema 解析后的字段;即使 class 未声明对应属性,`toJson()` 仍可按 schema 正常序列化。
|
||||
- 新增 DTO 字段前先搜索实际调用点;没有直接读取方时默认不声明。
|
||||
|
||||
DTO 统一模式:
|
||||
|
||||
```ts
|
||||
export const XxxResponseSchema = z.object({
|
||||
id: z.string(),
|
||||
// 仅用于内部透传,不需要在 DTO class 中声明。
|
||||
traceId: z.string(),
|
||||
});
|
||||
|
||||
export class XxxResponse {
|
||||
// 业务代码直接读取的字段才公开声明。
|
||||
declare readonly id: string;
|
||||
|
||||
private constructor(input: XxxResponseInput) {
|
||||
@@ -186,7 +164,7 @@ export class XxxResponse {
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 更新 API Service
|
||||
### 5. 更新 API Service
|
||||
|
||||
修改:
|
||||
|
||||
@@ -210,7 +188,7 @@ return XxxResponse.fromJson(unwrap(env) as Record<string, unknown>);
|
||||
- 不要在 API 层处理 UI 行为。
|
||||
- multipart 仍使用 `FormData`。
|
||||
|
||||
### 7. 更新 Repository
|
||||
### 6. 更新 Repository
|
||||
|
||||
修改:
|
||||
|
||||
@@ -229,14 +207,14 @@ async getXxx(): Promise<Result<XxxResponse>> {
|
||||
|
||||
Repository 可以做轻量编排,例如:
|
||||
|
||||
- request DTO 构造
|
||||
- request 数据类构造
|
||||
- 多接口组合
|
||||
- 本地 storage 同步
|
||||
- local model 与 DTO 转换
|
||||
- 本地模型与接口模型转换
|
||||
|
||||
不要把 React / UI 逻辑放进 repository。
|
||||
|
||||
### 8. 更新 Storage
|
||||
### 7. 更新 Storage
|
||||
|
||||
如果接口影响本地持久化,修改:
|
||||
|
||||
@@ -262,7 +240,7 @@ getEntitlementSnapshot(): Promise<Result<UserEntitlementSnapshotData | null>> {
|
||||
}
|
||||
```
|
||||
|
||||
### 9. 更新 Store / 状态机
|
||||
### 8. 更新 Store / 状态机
|
||||
|
||||
如果接口影响状态流,修改:
|
||||
|
||||
@@ -284,7 +262,7 @@ src/stores/<module>/*-sync.tsx
|
||||
- 页面组件不承担全局监听职责。
|
||||
- 状态机事件应表达业务事实,而不是 UI 操作细节。
|
||||
|
||||
### 10. 更新 UI
|
||||
### 9. 更新 UI
|
||||
|
||||
只有在接口变更影响展示或用户明确要求时,才修改:
|
||||
|
||||
@@ -298,19 +276,18 @@ src/app/**
|
||||
- 不在页面组件里直接写复杂业务编排。
|
||||
- 全局监听逻辑优先放到 `stores/*/*-sync.tsx`。
|
||||
|
||||
### 11. 更新桶文件
|
||||
### 10. 更新桶文件
|
||||
|
||||
如果新增 schema / DTO / component,更新对应 `index.ts`:
|
||||
如果新增 schema model 或 component,更新对应 `index.ts`:
|
||||
|
||||
```text
|
||||
src/data/schemas/<module>/index.ts
|
||||
src/data/dto/<module>/index.ts
|
||||
src/app/**/components/index.ts
|
||||
```
|
||||
|
||||
如果项目使用 barrelsby 生成,不要手动破坏现有导出格式。
|
||||
|
||||
### 12. 更新 Mock 数据
|
||||
### 11. 更新 Mock 数据
|
||||
|
||||
如果新增或调整数据结构,按一个结构一个文件的原则放到:
|
||||
|
||||
@@ -322,11 +299,11 @@ src/data/mock/<module>/websocket-events
|
||||
|
||||
不要把多个结构塞进一个大 JSON。
|
||||
|
||||
### 13. 更新测试
|
||||
### 12. 更新测试
|
||||
|
||||
优先补充:
|
||||
|
||||
- DTO / schema parse 测试
|
||||
- schema model 解析与序列化测试
|
||||
- helper 纯函数测试
|
||||
- XState transition 测试
|
||||
- repository 编排测试(如已有测试基础)
|
||||
@@ -334,11 +311,11 @@ src/data/mock/<module>/websocket-events
|
||||
常见位置:
|
||||
|
||||
```text
|
||||
src/data/dto/<module>/__tests__/*.test.ts
|
||||
src/data/schemas/<module>/__tests__/*.test.ts
|
||||
src/stores/<module>/__tests__/*.test.ts
|
||||
```
|
||||
|
||||
### 14. 验证
|
||||
### 13. 验证
|
||||
|
||||
至少运行:
|
||||
|
||||
@@ -356,18 +333,17 @@ pnpm exec vitest run <related tests>
|
||||
|
||||
1. 文档确认 path / method / payload。
|
||||
2. `api_path.ts` 新增路径。
|
||||
3. 新增 schema。
|
||||
4. 新增 DTO。
|
||||
5. API service 新增方法。
|
||||
6. repository interface + implementation 新增方法。
|
||||
7. 如需要,接入 store actor / event。
|
||||
8. 补 mock 和测试。
|
||||
9. 运行验证。
|
||||
3. 在同一文件新增 schema 与不可变数据类。
|
||||
4. API service 新增方法。
|
||||
5. repository interface + implementation 新增方法。
|
||||
6. 如需要,接入 store actor / event。
|
||||
7. 补 mock 和测试。
|
||||
8. 运行验证。
|
||||
|
||||
### 修改响应字段
|
||||
|
||||
1. 更新 schema。
|
||||
2. 更新 DTO declare 字段和 `toJson`。
|
||||
2. 更新同文件数据类的公开字段和 `toJson`。
|
||||
3. 更新 mapper/helper。
|
||||
4. 更新 UI 或状态机依赖字段。
|
||||
5. 更新 mock 和测试。
|
||||
@@ -377,7 +353,7 @@ pnpm exec vitest run <related tests>
|
||||
|
||||
1. 搜索所有引用。
|
||||
2. 删除 API path / API method / repository method。
|
||||
3. 删除 DTO/schema/mock/test。
|
||||
3. 删除 schema model、mock 和测试。
|
||||
4. 更新状态机和 UI 调用。
|
||||
5. 运行 `rg` 确认无残留引用。
|
||||
6. 运行验证。
|
||||
|
||||
@@ -12,13 +12,6 @@
|
||||
"./src/data/repositories",
|
||||
"./src/data/repositories/interfaces",
|
||||
"./src/data/services/api",
|
||||
"./src/data/dto/auth",
|
||||
"./src/data/dto/chat",
|
||||
"./src/data/dto/character",
|
||||
"./src/data/dto/feedback",
|
||||
"./src/data/dto/feedback/response",
|
||||
"./src/data/dto/metrics",
|
||||
"./src/data/dto/user",
|
||||
"./src/data/schemas/auth",
|
||||
"./src/data/schemas/auth/request",
|
||||
"./src/data/schemas/auth/response",
|
||||
|
||||
@@ -21,7 +21,7 @@ BACKEND_OPENAPI_SOURCE=https://backend.example.com/openapi.json pnpm contract:ch
|
||||
|
||||
检查会验证前端依赖的每个 method/path 是否仍由后端公开。路径参数名称不同
|
||||
(例如 `{albumId}` 与 `{album_id}`)不会产生误报。字段级兼容仍由前端 Zod
|
||||
Schema 和 DTO 测试负责,避免仅凭 OpenAPI 生成代码覆盖现有防御性解析规则。
|
||||
Schema Model 测试负责,避免仅凭 OpenAPI 生成代码覆盖现有防御性解析规则。
|
||||
|
||||
## CI 配置
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { PaymentPlan } from "@/data/dto/payment";
|
||||
import { PaymentPlan } from "@/data/schemas/payment";
|
||||
import { behaviorAnalytics } from "@/lib/analytics";
|
||||
|
||||
import { usePaymentPlanAnalytics } from "../use-payment-plan-analytics";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import type { PaymentPlan } from "@/data/dto/payment";
|
||||
import type { PaymentPlan } from "@/data/schemas/payment";
|
||||
import {
|
||||
behaviorAnalytics,
|
||||
type PaymentAnalyticsContext,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { type Dispatch, useEffect, useRef } from "react";
|
||||
|
||||
import type { PayChannel } from "@/data/dto/payment";
|
||||
import type { PayChannel } from "@/data/schemas/payment";
|
||||
import type { PendingPaymentSubscriptionType } from "@/lib/payment/pending_payment_order";
|
||||
import {
|
||||
usePaymentDispatch,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { UiMessage } from "@/data/dto/chat";
|
||||
import type { UiMessage } from "@/stores/chat/ui-message";
|
||||
|
||||
import {
|
||||
buildChatRenderItems,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UiMessage } from "@/data/dto/chat";
|
||||
import type { UiMessage } from "@/stores/chat/ui-message";
|
||||
|
||||
export type ChatMessageKeyResolver = (message: UiMessage) => string;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { LoginStatus } from "@/data/schemas/auth";
|
||||
import type { ChatUpgradeReason } from "@/stores/chat/chat-state";
|
||||
import { AppEnvUtil } from "@/utils/app-env";
|
||||
import { BrowserDetector } from "@/utils/browser-detect";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { UiMessage } from "@/data/dto/chat";
|
||||
import type { UiMessage } from "@/stores/chat/ui-message";
|
||||
|
||||
import { ChatArea } from "../chat-area";
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
} from "react";
|
||||
import { LoaderCircle } from "lucide-react";
|
||||
|
||||
import type { UiMessage } from "@/data/dto/chat";
|
||||
import type { UiMessage } from "@/stores/chat/ui-message";
|
||||
import { DEFAULT_CHARACTER_ID } from "@/data/constants/character";
|
||||
import { usePullToRefresh } from "@/hooks/use-pull-to-refresh";
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { shallowEqual } from "@xstate/react";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { LoginStatus } from "@/data/schemas/auth";
|
||||
import { useAppNavigator } from "@/router/use-app-navigator";
|
||||
import {
|
||||
usePaymentDispatch,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { UiMessage } from "@/data/dto/chat";
|
||||
import type { LoginStatus } from "@/data/schemas/auth";
|
||||
import type { UiMessage } from "@/stores/chat/ui-message";
|
||||
import { resolveSplashLatestMessageCacheIdentity } from "@/lib/chat/splash_latest_message";
|
||||
import { getLatestSplashMessagePreview } from "@/lib/chat/splash_latest_message_preview";
|
||||
import { useSplashLatestMessageCache } from "@/providers/splash-latest-message-provider";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import packageInfo from "../../../package.json";
|
||||
|
||||
import type { FeedbackContext } from "@/data/dto/feedback";
|
||||
import type { FeedbackContext } from "@/data/schemas/feedback";
|
||||
import { BrowserDetector } from "@/utils/browser-detect";
|
||||
import { PlatformDetector } from "@/utils/platform-detect";
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
|
||||
import { BackButton } from "@/app/_components";
|
||||
import { MobileShell } from "@/app/_components/core";
|
||||
import type { FeedbackCategory } from "@/data/dto/feedback";
|
||||
import type { FeedbackCategory } from "@/data/schemas/feedback";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import { useAppNavigator } from "@/router/use-app-navigator";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { type FormEvent, useEffect, useRef, useState } from "react";
|
||||
|
||||
import type { FeedbackCategory } from "@/data/dto/feedback";
|
||||
import type { FeedbackCategory } from "@/data/schemas/feedback";
|
||||
import { submitFeedback } from "@/lib/feedback";
|
||||
|
||||
import { createFeedbackContext } from "./feedback-context";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { act, type Dispatch } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { LoginStatus } from "@/data/schemas/auth";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import type { PrivateRoomEvent } from "@/stores/private-room";
|
||||
import type { PrivateRoomUnlockPaywallRequest } from "@/stores/private-room/private-room-state";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { PrivateAlbum } from "@/data/dto/private-room";
|
||||
import { PrivateAlbum } from "@/data/schemas/private-room";
|
||||
|
||||
import { PrivateAlbumCard } from "../private-album-card";
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import Image from "next/image";
|
||||
import { ImageIcon, LockKeyhole } from "lucide-react";
|
||||
|
||||
import { CharacterAvatar } from "@/app/_components";
|
||||
import type { PrivateAlbum } from "@/data/dto/private-room";
|
||||
import type { PrivateAlbum } from "@/data/schemas/private-room";
|
||||
import { isPrivateAlbumLocked } from "@/lib/private-room/private_album";
|
||||
|
||||
import styles from "../private-room-screen.module.css";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useRef } from "react";
|
||||
import Image from "next/image";
|
||||
import { ChevronLeft, ChevronRight, X } from "lucide-react";
|
||||
|
||||
import type { PrivateAlbum } from "@/data/dto/private-room";
|
||||
import type { PrivateAlbum } from "@/data/schemas/private-room";
|
||||
|
||||
import styles from "../private-room-screen.module.css";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PrivateAlbum } from "@/data/dto/private-room";
|
||||
import type { PrivateAlbum } from "@/data/schemas/private-room";
|
||||
|
||||
import styles from "../private-room-screen.module.css";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { type Dispatch, useEffect, useRef } from "react";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { LoginStatus } from "@/data/schemas/auth";
|
||||
import { useGuestLoginBootstrap } from "@/hooks/use-guest-login-bootstrap";
|
||||
import { behaviorAnalytics } from "@/lib/analytics";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { UserView } from "@/data/dto/user";
|
||||
import type { UserView } from "@/stores/user/user-view";
|
||||
|
||||
import {
|
||||
getSidebarViewModel,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { UserView } from "@/data/dto/user";
|
||||
import type { LoginStatus } from "@/data/schemas/auth";
|
||||
import type { UserView } from "@/stores/user/user-view";
|
||||
|
||||
export type SidebarUserState = "guest" | "member" | "vip";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { type Dispatch, useEffect } from "react";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { LoginStatus } from "@/data/schemas/auth";
|
||||
|
||||
interface SidebarAuthBootstrapState {
|
||||
hasInitialized: boolean;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { LoginStatus } from "@/data/schemas/auth";
|
||||
import { useHasHydrated } from "@/hooks/use-has-hydrated";
|
||||
import { loadSplashLatestMessagePreview } from "@/lib/chat/splash_latest_message";
|
||||
import { useSplashLatestMessageCache } from "@/providers/splash-latest-message-provider";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { PaymentPlan } from "@/data/dto/payment";
|
||||
import { PaymentPlan } from "@/data/schemas/payment";
|
||||
|
||||
import {
|
||||
canChooseSubscriptionPayChannel,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Image from "next/image";
|
||||
|
||||
import type { PayChannel } from "@/data/dto/payment";
|
||||
import type { PayChannel } from "@/data/schemas/payment";
|
||||
|
||||
const PAYMENT_METHODS: {
|
||||
channel: PayChannel;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PayChannel, PaymentPlan } from "@/data/dto/payment";
|
||||
import type { PayChannel, PaymentPlan } from "@/data/schemas/payment";
|
||||
import { getDefaultPayChannelForCountryCode } from "@/lib/payment/default_pay_channel";
|
||||
import { AppEnvUtil } from "@/utils/app-env";
|
||||
export { getDefaultPayChannelForCountryCode } from "@/lib/payment/default_pay_channel";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { BackButton } from "@/app/_components";
|
||||
import { Checkbox, MobileShell } from "@/app/_components/core";
|
||||
import { usePaymentPlanAnalytics } from "@/app/_hooks/use-payment-plan-analytics";
|
||||
import { AppConstants } from "@/core/app_constants";
|
||||
import type { PayChannel } from "@/data/dto/payment";
|
||||
import type { PayChannel } from "@/data/schemas/payment";
|
||||
import type { SubscriptionReturnTo } from "@/lib/navigation/subscription_exit";
|
||||
import {
|
||||
behaviorAnalytics,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { usePaymentRouteFlow } from "@/app/_hooks/use-payment-route-flow";
|
||||
import { useAppNavigator } from "@/router/use-app-navigator";
|
||||
import type { PayChannel } from "@/data/dto/payment";
|
||||
import type { PayChannel } from "@/data/schemas/payment";
|
||||
import type { SubscriptionReturnTo } from "@/lib/navigation/subscription_exit";
|
||||
|
||||
import type { SubscriptionType } from "./subscription-screen.helpers";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { PaymentPlan } from "@/data/dto/payment";
|
||||
import { PaymentPlan } from "@/data/schemas/payment";
|
||||
import type { PaymentPlanInput } from "@/data/schemas/payment/payment_plan";
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { PaymentPlan } from "@/data/dto/payment";
|
||||
import type { LoginStatus } from "@/data/schemas/auth";
|
||||
import type { PaymentPlan } from "@/data/schemas/payment";
|
||||
import {
|
||||
getTipCoffeeOption,
|
||||
type TipCoffeeType,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { CharacterAvatar } from "@/app/_components";
|
||||
import { MobileShell } from "@/app/_components/core";
|
||||
import { usePaymentPlanAnalytics } from "@/app/_hooks/use-payment-plan-analytics";
|
||||
import { usePaymentRouteFlow } from "@/app/_hooks/use-payment-route-flow";
|
||||
import type { PayChannel } from "@/data/dto/payment";
|
||||
import type { PayChannel } from "@/data/schemas/payment";
|
||||
import {
|
||||
behaviorAnalytics,
|
||||
type PaymentAnalyticsContext,
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createSchemaDto, type SchemaDto } from "../schema_dto";
|
||||
|
||||
const ExampleSchema = z.object({
|
||||
id: z.string(),
|
||||
traceId: z.string().default("generated-trace"),
|
||||
});
|
||||
|
||||
type ExampleDto = SchemaDto<typeof ExampleSchema, "id">;
|
||||
const ExampleDto = createSchemaDto<typeof ExampleSchema, "id">(
|
||||
ExampleSchema,
|
||||
);
|
||||
|
||||
describe("createSchemaDto", () => {
|
||||
it("parses, freezes and serializes through the source schema", () => {
|
||||
const dto: ExampleDto = ExampleDto.from({ id: "item-1" });
|
||||
|
||||
expect(dto).toBeInstanceOf(ExampleDto);
|
||||
expect(Object.isFrozen(dto)).toBe(true);
|
||||
expect(dto.id).toBe("item-1");
|
||||
expect(dto.toJson()).toEqual({
|
||||
id: "item-1",
|
||||
traceId: "generated-trace",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps non-public schema fields out of the declared DTO interface", () => {
|
||||
const dto = ExampleDto.fromJson({ id: "item-1", traceId: "trace-1" });
|
||||
|
||||
// @ts-expect-error traceId is serialized but not part of the public DTO API.
|
||||
expect(dto.traceId).toBe("trace-1");
|
||||
});
|
||||
|
||||
it("preserves schema validation errors", () => {
|
||||
expect(() => ExampleDto.fromJson({ id: 1 })).toThrow(z.ZodError);
|
||||
});
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Facebook 用户数据 DTO
|
||||
*
|
||||
*
|
||||
*/
|
||||
import {
|
||||
FacebookUserDataSchema,
|
||||
type FacebookUserDataInput,
|
||||
type FacebookUserDataData,
|
||||
} from "@/data/schemas/auth/facebook_user_data";
|
||||
|
||||
export class FacebookUserData {
|
||||
declare readonly id: string | null;
|
||||
declare readonly name: string | null;
|
||||
declare readonly email: string | null;
|
||||
declare readonly pictureUrl: string | null;
|
||||
|
||||
private constructor(input: FacebookUserDataInput) {
|
||||
const data = FacebookUserDataSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: FacebookUserDataInput): FacebookUserData {
|
||||
return new FacebookUserData(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): FacebookUserData {
|
||||
return FacebookUserData.from(json as FacebookUserDataInput);
|
||||
}
|
||||
|
||||
toJson(): FacebookUserDataData {
|
||||
return FacebookUserDataSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @file Automatically generated by barrelsby.
|
||||
*/
|
||||
|
||||
export * from "./auth_panel_mode";
|
||||
export * from "./facebook_user_data";
|
||||
export * from "./login_status";
|
||||
export * from "./request";
|
||||
export * from "./response";
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Facebook identity 绑定请求 DTO
|
||||
*/
|
||||
import {
|
||||
FacebookIdentityRequestSchema,
|
||||
} from "@/data/schemas/auth/request/facebook_identity_request";
|
||||
import {
|
||||
createSchemaDto,
|
||||
type SchemaDto,
|
||||
} from "@/data/dto/schema_dto";
|
||||
|
||||
export type FacebookIdentityRequest = SchemaDto<
|
||||
typeof FacebookIdentityRequestSchema
|
||||
>;
|
||||
export const FacebookIdentityRequest = createSchemaDto(
|
||||
FacebookIdentityRequestSchema,
|
||||
);
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Facebook 登录请求 DTO
|
||||
*/
|
||||
import {
|
||||
FacebookLoginRequestSchema,
|
||||
type FacebookLoginRequestInput,
|
||||
type FacebookLoginRequestData,
|
||||
} from "@/data/schemas/auth/request/facebook_login_request";
|
||||
|
||||
export class FacebookLoginRequest {
|
||||
declare readonly accessToken: string;
|
||||
declare readonly platform: string;
|
||||
declare readonly guestId: string;
|
||||
declare readonly isTestAccount: boolean;
|
||||
|
||||
private constructor(input: FacebookLoginRequestInput) {
|
||||
const data = FacebookLoginRequestSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: FacebookLoginRequestInput): FacebookLoginRequest {
|
||||
return new FacebookLoginRequest(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): FacebookLoginRequest {
|
||||
return FacebookLoginRequest.from(json as FacebookLoginRequestInput);
|
||||
}
|
||||
|
||||
toJson(): FacebookLoginRequestData {
|
||||
return FacebookLoginRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Facebook PSID 登录请求 DTO
|
||||
*/
|
||||
import {
|
||||
FacebookPsidLoginRequestSchema,
|
||||
} from "@/data/schemas/auth/request/facebook_psid_login_request";
|
||||
import {
|
||||
createSchemaDto,
|
||||
type SchemaDto,
|
||||
} from "@/data/dto/schema_dto";
|
||||
|
||||
export type FacebookPsidLoginRequest = SchemaDto<
|
||||
typeof FacebookPsidLoginRequestSchema
|
||||
>;
|
||||
export const FacebookPsidLoginRequest = createSchemaDto(
|
||||
FacebookPsidLoginRequestSchema,
|
||||
);
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Facebook ID 登录请求 DTO
|
||||
*/
|
||||
import {
|
||||
FbIdLoginRequestSchema,
|
||||
type FbIdLoginRequestInput,
|
||||
type FbIdLoginRequestData,
|
||||
} from "@/data/schemas/auth/request/fb_id_login_request";
|
||||
|
||||
export class FbIdLoginRequest {
|
||||
declare readonly fbId: string;
|
||||
declare readonly avatarUrl: string;
|
||||
declare readonly isTestAccount: boolean;
|
||||
|
||||
private constructor(input: FbIdLoginRequestInput) {
|
||||
const data = FbIdLoginRequestSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: FbIdLoginRequestInput): FbIdLoginRequest {
|
||||
return new FbIdLoginRequest(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): FbIdLoginRequest {
|
||||
return FbIdLoginRequest.from(json as FbIdLoginRequestInput);
|
||||
}
|
||||
|
||||
toJson(): FbIdLoginRequestData {
|
||||
return FbIdLoginRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Google 登录请求 DTO
|
||||
*/
|
||||
import {
|
||||
GoogleLoginRequestSchema,
|
||||
type GoogleLoginRequestInput,
|
||||
type GoogleLoginRequestData,
|
||||
} from "@/data/schemas/auth/request/google_login_request";
|
||||
|
||||
export class GoogleLoginRequest {
|
||||
declare readonly idToken: string;
|
||||
declare readonly platform: string;
|
||||
declare readonly guestId: string;
|
||||
declare readonly isTestAccount: boolean;
|
||||
|
||||
private constructor(input: GoogleLoginRequestInput) {
|
||||
const data = GoogleLoginRequestSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: GoogleLoginRequestInput): GoogleLoginRequest {
|
||||
return new GoogleLoginRequest(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): GoogleLoginRequest {
|
||||
return GoogleLoginRequest.from(json as GoogleLoginRequestInput);
|
||||
}
|
||||
|
||||
toJson(): GoogleLoginRequestData {
|
||||
return GoogleLoginRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* 游客登录请求 DTO
|
||||
*/
|
||||
import {
|
||||
GuestLoginRequestSchema,
|
||||
type GuestLoginRequestInput,
|
||||
type GuestLoginRequestData,
|
||||
} from "@/data/schemas/auth/request/guest_login_request";
|
||||
|
||||
export class GuestLoginRequest {
|
||||
declare readonly deviceId: string;
|
||||
declare readonly isTestAccount: boolean;
|
||||
|
||||
private constructor(input: GuestLoginRequestInput) {
|
||||
const data = GuestLoginRequestSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: GuestLoginRequestInput): GuestLoginRequest {
|
||||
return new GuestLoginRequest(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): GuestLoginRequest {
|
||||
return GuestLoginRequest.from(json as GuestLoginRequestInput);
|
||||
}
|
||||
|
||||
toJson(): GuestLoginRequestData {
|
||||
return GuestLoginRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export * from "./facebook_identity_request";
|
||||
export * from "./facebook_login_request";
|
||||
export * from "./facebook_psid_login_request";
|
||||
export * from "./fb_id_login_request";
|
||||
export * from "./google_login_request";
|
||||
export * from "./guest_login_request";
|
||||
export * from "./login_request";
|
||||
export * from "./refresh_token_request";
|
||||
export * from "./register_request";
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* 登录请求 DTO
|
||||
*/
|
||||
import {
|
||||
LoginRequestSchema,
|
||||
type LoginRequestInput,
|
||||
type LoginRequestData,
|
||||
} from "@/data/schemas/auth/request/login_request";
|
||||
|
||||
export class LoginRequest {
|
||||
declare readonly email: string;
|
||||
declare readonly username: string;
|
||||
declare readonly password: string;
|
||||
declare readonly platform: string;
|
||||
declare readonly guestId: string;
|
||||
declare readonly isTestAccount: boolean;
|
||||
|
||||
private constructor(input: LoginRequestInput) {
|
||||
const data = LoginRequestSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: LoginRequestInput): LoginRequest {
|
||||
return new LoginRequest(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): LoginRequest {
|
||||
return LoginRequest.from(json as LoginRequestInput);
|
||||
}
|
||||
|
||||
toJson(): LoginRequestData {
|
||||
return LoginRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* 刷新 Token 请求 DTO
|
||||
*/
|
||||
import {
|
||||
RefreshTokenRequestSchema,
|
||||
} from "@/data/schemas/auth/request/refresh_token_request";
|
||||
import {
|
||||
createSchemaDto,
|
||||
type SchemaDto,
|
||||
} from "@/data/dto/schema_dto";
|
||||
|
||||
export type RefreshTokenRequest = SchemaDto<
|
||||
typeof RefreshTokenRequestSchema,
|
||||
"refreshToken"
|
||||
>;
|
||||
export const RefreshTokenRequest = createSchemaDto<
|
||||
typeof RefreshTokenRequestSchema,
|
||||
"refreshToken"
|
||||
>(RefreshTokenRequestSchema);
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* 注册请求 DTO
|
||||
*/
|
||||
import {
|
||||
RegisterRequestSchema,
|
||||
type RegisterRequestInput,
|
||||
type RegisterRequestData,
|
||||
} from "@/data/schemas/auth/request/register_request";
|
||||
|
||||
export class RegisterRequest {
|
||||
declare readonly username: string;
|
||||
declare readonly email: string;
|
||||
declare readonly password: string;
|
||||
declare readonly platform: string;
|
||||
declare readonly guestId: string;
|
||||
declare readonly isTestAccount: boolean;
|
||||
|
||||
private constructor(input: RegisterRequestInput) {
|
||||
const data = RegisterRequestSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: RegisterRequestInput): RegisterRequest {
|
||||
return new RegisterRequest(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): RegisterRequest {
|
||||
return RegisterRequest.from(json as RegisterRequestInput);
|
||||
}
|
||||
|
||||
toJson(): RegisterRequestData {
|
||||
return RegisterRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Facebook identity 绑定响应 DTO
|
||||
*/
|
||||
import {
|
||||
FacebookIdentityResponseSchema,
|
||||
} from "@/data/schemas/auth/response/facebook_identity_response";
|
||||
import {
|
||||
createSchemaDto,
|
||||
type SchemaDto,
|
||||
} from "@/data/dto/schema_dto";
|
||||
|
||||
export type FacebookIdentityResponse = SchemaDto<
|
||||
typeof FacebookIdentityResponseSchema
|
||||
>;
|
||||
export const FacebookIdentityResponse = createSchemaDto(
|
||||
FacebookIdentityResponseSchema,
|
||||
);
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Facebook PSID 登录响应 DTO
|
||||
*/
|
||||
import {
|
||||
FacebookPsidLoginResponseSchema,
|
||||
type FacebookPsidLoginResponseInput,
|
||||
type FacebookPsidLoginResponseData,
|
||||
} from "@/data/schemas/auth/response/facebook_psid_login_response";
|
||||
|
||||
export class FacebookPsidLoginResponse {
|
||||
private constructor(input: FacebookPsidLoginResponseInput) {
|
||||
const data = FacebookPsidLoginResponseSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(
|
||||
input: FacebookPsidLoginResponseInput,
|
||||
): FacebookPsidLoginResponse {
|
||||
return new FacebookPsidLoginResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): FacebookPsidLoginResponse {
|
||||
return FacebookPsidLoginResponse.from(
|
||||
json as FacebookPsidLoginResponseInput,
|
||||
);
|
||||
}
|
||||
|
||||
toJson(): FacebookPsidLoginResponseData {
|
||||
return FacebookPsidLoginResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* 游客登录响应 DTO
|
||||
*/
|
||||
import {
|
||||
GuestLoginResponseSchema,
|
||||
type GuestLoginResponseInput,
|
||||
type GuestLoginResponseData,
|
||||
} from "@/data/schemas/auth/response/guest_login_response";
|
||||
import { User } from "@/data/dto/user/user";
|
||||
|
||||
export class GuestLoginResponse {
|
||||
declare readonly token: string;
|
||||
declare readonly userId: string;
|
||||
declare readonly user: User;
|
||||
|
||||
private constructor(input: GuestLoginResponseInput) {
|
||||
const parsed = GuestLoginResponseSchema.parse(input);
|
||||
Object.assign(this, {
|
||||
...parsed,
|
||||
user: parsed.user ? User.fromJson(parsed.user) : User.empty(),
|
||||
});
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: GuestLoginResponseInput): GuestLoginResponse {
|
||||
return new GuestLoginResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): GuestLoginResponse {
|
||||
return GuestLoginResponse.from(json as GuestLoginResponseInput);
|
||||
}
|
||||
|
||||
toJson(): GuestLoginResponseData {
|
||||
const data = GuestLoginResponseSchema.parse(this);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export * from "./facebook_identity_response";
|
||||
export * from "./facebook_psid_login_response";
|
||||
export * from "./guest_login_response";
|
||||
export * from "./login_response";
|
||||
export * from "./logout_response";
|
||||
export * from "./refresh_token_response";
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* 登录响应 DTO
|
||||
*/
|
||||
import {
|
||||
LoginResponseSchema,
|
||||
type LoginResponseInput,
|
||||
type LoginResponseData,
|
||||
} from "@/data/schemas/auth/response/login_response";
|
||||
import { User } from "@/data/dto/user/user";
|
||||
|
||||
export class LoginResponse {
|
||||
declare readonly user: User;
|
||||
declare readonly token: string;
|
||||
declare readonly refreshToken: string;
|
||||
|
||||
private constructor(input: LoginResponseInput) {
|
||||
const data = LoginResponseSchema.parse(input);
|
||||
Object.assign(this, {
|
||||
...data,
|
||||
user: User.fromJson(data.user),
|
||||
});
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: LoginResponseInput): LoginResponse {
|
||||
return new LoginResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): LoginResponse {
|
||||
return LoginResponse.from(json as LoginResponseInput);
|
||||
}
|
||||
|
||||
toJson(): LoginResponseData {
|
||||
return LoginResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* 退出登录响应 DTO
|
||||
*/
|
||||
import {
|
||||
LogoutResponseSchema,
|
||||
} from "@/data/schemas/auth/response/logout_response";
|
||||
import {
|
||||
createSchemaDto,
|
||||
type SchemaDto,
|
||||
} from "@/data/dto/schema_dto";
|
||||
|
||||
export type LogoutResponse = SchemaDto<
|
||||
typeof LogoutResponseSchema,
|
||||
"success"
|
||||
>;
|
||||
export const LogoutResponse = createSchemaDto<
|
||||
typeof LogoutResponseSchema,
|
||||
"success"
|
||||
>(LogoutResponseSchema);
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* 刷新 Token 响应 DTO
|
||||
*/
|
||||
import {
|
||||
RefreshTokenResponseSchema,
|
||||
type RefreshTokenResponseInput,
|
||||
type RefreshTokenResponseData,
|
||||
} from "@/data/schemas/auth/response/refresh_token_response";
|
||||
|
||||
export class RefreshTokenResponse {
|
||||
declare readonly token: string;
|
||||
declare readonly refreshToken: string;
|
||||
|
||||
private constructor(input: RefreshTokenResponseInput) {
|
||||
const data = RefreshTokenResponseSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: RefreshTokenResponseInput): RefreshTokenResponse {
|
||||
return new RefreshTokenResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): RefreshTokenResponse {
|
||||
return RefreshTokenResponse.from(json as RefreshTokenResponseInput);
|
||||
}
|
||||
|
||||
toJson(): RefreshTokenResponseData {
|
||||
return RefreshTokenResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import {
|
||||
CharacterSchema,
|
||||
type CharacterData,
|
||||
type CharacterInput,
|
||||
} from "@/data/schemas/character";
|
||||
|
||||
export class Character {
|
||||
declare readonly id: string;
|
||||
declare readonly slug: string;
|
||||
declare readonly displayName: string;
|
||||
declare readonly avatarUrl: string;
|
||||
|
||||
private constructor(input: CharacterInput) {
|
||||
Object.assign(this, CharacterSchema.parse(input));
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: CharacterInput): Character {
|
||||
return new Character(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): Character {
|
||||
return Character.from(json as CharacterInput);
|
||||
}
|
||||
|
||||
toJson(): CharacterData {
|
||||
return CharacterSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import {
|
||||
CharactersResponseSchema,
|
||||
type CharactersResponseData,
|
||||
type CharactersResponseInput,
|
||||
} from "@/data/schemas/character";
|
||||
|
||||
import { Character } from "./character";
|
||||
|
||||
export class CharactersResponse {
|
||||
declare readonly items: Character[];
|
||||
|
||||
private constructor(input: CharactersResponseInput) {
|
||||
const data = CharactersResponseSchema.parse(input);
|
||||
Object.assign(this, {
|
||||
items: data.items.map((item) => Character.from(item)),
|
||||
});
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: CharactersResponseInput): CharactersResponse {
|
||||
return new CharactersResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): CharactersResponse {
|
||||
return CharactersResponse.from(json as CharactersResponseInput);
|
||||
}
|
||||
|
||||
toJson(): CharactersResponseData {
|
||||
return CharactersResponseSchema.parse({
|
||||
items: this.items.map((item) => item.toJson()),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./character";
|
||||
export * from "./characters_response";
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* 聊天列表项 discriminated union
|
||||
*
|
||||
*
|
||||
*
|
||||
* 4 种类型:
|
||||
* - ai-disclosure:顶部 AI 披露横幅
|
||||
* - date-separator:日期分隔条
|
||||
* - loading-animation:AI 正在输入的动画
|
||||
* - message:实际消息
|
||||
*/
|
||||
import type { UiMessage } from "./ui_message";
|
||||
|
||||
export type ChatListItem =
|
||||
| { type: "ai-disclosure" }
|
||||
| { type: "date-separator"; date: string }
|
||||
| { type: "loading-animation" }
|
||||
| { type: "message"; message: UiMessage; key: string };
|
||||
|
||||
export const ChatListItemType = {
|
||||
AiDisclosure: "ai-disclosure",
|
||||
DateSeparator: "date-separator",
|
||||
LoadingAnimation: "loading-animation",
|
||||
Message: "message",
|
||||
} as const;
|
||||
|
||||
export type ChatListItemType =
|
||||
(typeof ChatListItemType)[keyof typeof ChatListItemType];
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* 聊天历史中的单条消息 DTO
|
||||
*/
|
||||
import {
|
||||
ChatMessageSchema,
|
||||
type ChatMessageInput,
|
||||
type ChatMessageData,
|
||||
type ChatImageData,
|
||||
type ChatLockDetailData,
|
||||
} from "@/data/schemas/chat";
|
||||
|
||||
export class ChatMessage {
|
||||
declare readonly role: string;
|
||||
declare readonly type: string;
|
||||
declare readonly content: string;
|
||||
declare readonly id: string;
|
||||
declare readonly createdAt: string;
|
||||
declare readonly audioUrl: string | null;
|
||||
declare readonly image: ChatImageData;
|
||||
declare readonly lockDetail: ChatLockDetailData;
|
||||
|
||||
private constructor(input: ChatMessageInput) {
|
||||
const data = ChatMessageSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: ChatMessageInput): ChatMessage {
|
||||
return new ChatMessage(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): ChatMessage {
|
||||
return ChatMessage.from(json as ChatMessageInput);
|
||||
}
|
||||
|
||||
toJson(): ChatMessageData {
|
||||
return ChatMessageSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* @file Automatically generated by barrelsby.
|
||||
*/
|
||||
|
||||
export * from "./chat_list_item";
|
||||
export * from "./chat_media";
|
||||
export * from "./chat_message";
|
||||
export * from "./request";
|
||||
export * from "./response";
|
||||
export * from "./ui_message";
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from "./send_message_request";
|
||||
export * from "./unlock_private_request";
|
||||
export * from "./unlock_history_request";
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* 发送消息请求 DTO
|
||||
*/
|
||||
import {
|
||||
SendMessageRequestSchema,
|
||||
type SendMessageRequestInput,
|
||||
type SendMessageRequestData,
|
||||
} from "@/data/schemas/chat/request/send_message_request";
|
||||
|
||||
export class SendMessageRequest {
|
||||
declare readonly characterId: string;
|
||||
declare readonly message: string;
|
||||
declare readonly image: string;
|
||||
declare readonly imageId: string;
|
||||
declare readonly imageThumbUrl: string;
|
||||
declare readonly imageMediumUrl: string;
|
||||
declare readonly imageOriginalUrl: string;
|
||||
declare readonly imageWidth: number;
|
||||
declare readonly imageHeight: number;
|
||||
|
||||
private constructor(input: SendMessageRequestInput) {
|
||||
const data = SendMessageRequestSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: SendMessageRequestInput): SendMessageRequest {
|
||||
return new SendMessageRequest(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): SendMessageRequest {
|
||||
return SendMessageRequest.from(json as SendMessageRequestInput);
|
||||
}
|
||||
|
||||
toJson(): SendMessageRequestData {
|
||||
return SendMessageRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import {
|
||||
UnlockHistoryRequestSchema,
|
||||
type UnlockHistoryRequestData,
|
||||
type UnlockHistoryRequestInput,
|
||||
} from "@/data/schemas/chat/request/unlock_history_request";
|
||||
|
||||
export class UnlockHistoryRequest {
|
||||
declare readonly characterId: string;
|
||||
|
||||
private constructor(input: UnlockHistoryRequestInput) {
|
||||
Object.assign(this, UnlockHistoryRequestSchema.parse(input));
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: UnlockHistoryRequestInput): UnlockHistoryRequest {
|
||||
return new UnlockHistoryRequest(input);
|
||||
}
|
||||
|
||||
toJson(): UnlockHistoryRequestData {
|
||||
return UnlockHistoryRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* 私密消息解锁请求 DTO
|
||||
*/
|
||||
import {
|
||||
UnlockPrivateRequestSchema,
|
||||
type UnlockPrivateRequestData,
|
||||
type UnlockPrivateRequestInput,
|
||||
} from "@/data/schemas/chat/request/unlock_private_request";
|
||||
|
||||
export class UnlockPrivateRequest {
|
||||
declare readonly characterId: string;
|
||||
declare readonly messageId?: string;
|
||||
declare readonly lockType?: UnlockPrivateRequestData["lockType"];
|
||||
declare readonly clientLockId?: string;
|
||||
|
||||
private constructor(input: UnlockPrivateRequestInput) {
|
||||
const data = UnlockPrivateRequestSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: UnlockPrivateRequestInput): UnlockPrivateRequest {
|
||||
return new UnlockPrivateRequest(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): UnlockPrivateRequest {
|
||||
return UnlockPrivateRequest.from(json as UnlockPrivateRequestInput);
|
||||
}
|
||||
|
||||
toJson(): UnlockPrivateRequestData {
|
||||
return UnlockPrivateRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* 聊天历史响应 DTO
|
||||
*/
|
||||
import {
|
||||
ChatHistoryResponseSchema,
|
||||
type ChatHistoryResponseInput,
|
||||
type ChatHistoryResponseData,
|
||||
} from "@/data/schemas/chat/response/chat_history_response";
|
||||
import { ChatMessage } from "../chat_message";
|
||||
|
||||
export class ChatHistoryResponse {
|
||||
declare readonly messages: ChatMessage[];
|
||||
declare readonly total: number;
|
||||
declare readonly limit: number;
|
||||
|
||||
private constructor(input: ChatHistoryResponseInput) {
|
||||
const data = ChatHistoryResponseSchema.parse(input);
|
||||
Object.assign(this, {
|
||||
...data,
|
||||
messages: data.messages.map((m) => ChatMessage.from(m)),
|
||||
});
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: ChatHistoryResponseInput): ChatHistoryResponse {
|
||||
return new ChatHistoryResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): ChatHistoryResponse {
|
||||
return ChatHistoryResponse.from(json as ChatHistoryResponseInput);
|
||||
}
|
||||
|
||||
toJson(): ChatHistoryResponseData {
|
||||
return ChatHistoryResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* 发送消息响应 DTO
|
||||
*/
|
||||
import {
|
||||
ChatSendResponseSchema,
|
||||
type ChatSendResponseInput,
|
||||
type ChatSendResponseData,
|
||||
type ChatImageData,
|
||||
type ChatLockDetailData,
|
||||
} from "@/data/schemas/chat";
|
||||
|
||||
export class ChatSendResponse {
|
||||
declare readonly reply: string;
|
||||
declare readonly audioUrl: string;
|
||||
declare readonly messageId: string;
|
||||
declare readonly timestamp: number;
|
||||
declare readonly image: ChatImageData;
|
||||
declare readonly lockDetail: ChatLockDetailData;
|
||||
declare readonly canSendMessage: boolean;
|
||||
declare readonly creditBalance: number;
|
||||
declare readonly creditsCharged: number;
|
||||
declare readonly requiredCredits: number;
|
||||
declare readonly shortfallCredits: number;
|
||||
|
||||
private constructor(input: ChatSendResponseInput) {
|
||||
const data = ChatSendResponseSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: ChatSendResponseInput): ChatSendResponse {
|
||||
return new ChatSendResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): ChatSendResponse {
|
||||
return ChatSendResponse.from(json as ChatSendResponseInput);
|
||||
}
|
||||
|
||||
toJson(): ChatSendResponseData {
|
||||
return ChatSendResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from "./chat_history_response";
|
||||
export * from "./chat_send_response";
|
||||
export * from "./unlock_history_response";
|
||||
export * from "./unlock_private_response";
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* 一键解锁历史锁定消息响应 DTO
|
||||
*/
|
||||
import {
|
||||
UnlockHistoryResponseSchema,
|
||||
type UnlockHistoryReason,
|
||||
type UnlockHistoryResponseData,
|
||||
type UnlockHistoryResponseInput,
|
||||
} from "@/data/schemas/chat/response/unlock_history_response";
|
||||
|
||||
export class UnlockHistoryResponse {
|
||||
declare readonly unlocked: boolean;
|
||||
declare readonly reason: UnlockHistoryReason;
|
||||
declare readonly shortfallCredits: number;
|
||||
|
||||
private constructor(input: UnlockHistoryResponseInput) {
|
||||
const data = UnlockHistoryResponseSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: UnlockHistoryResponseInput): UnlockHistoryResponse {
|
||||
return new UnlockHistoryResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): UnlockHistoryResponse {
|
||||
return UnlockHistoryResponse.from(json as UnlockHistoryResponseInput);
|
||||
}
|
||||
|
||||
toJson(): UnlockHistoryResponseData {
|
||||
return UnlockHistoryResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import {
|
||||
UnlockPrivateResponseSchema,
|
||||
type UnlockPrivateReason,
|
||||
type UnlockPrivateResponseData,
|
||||
type UnlockPrivateResponseInput,
|
||||
} from "@/data/schemas/chat/response/unlock_private_response";
|
||||
import type { ChatImageData } from "@/data/schemas/chat";
|
||||
|
||||
/**
|
||||
* 单条历史付费 / 私密消息解锁响应 DTO。
|
||||
*/
|
||||
export class UnlockPrivateResponse {
|
||||
declare readonly unlocked: boolean;
|
||||
declare readonly messageId: string;
|
||||
declare readonly content: string;
|
||||
declare readonly audioUrl: string;
|
||||
declare readonly image: ChatImageData;
|
||||
declare readonly reason: UnlockPrivateReason;
|
||||
declare readonly creditBalance: number;
|
||||
declare readonly creditsCharged: number;
|
||||
declare readonly requiredCredits: number;
|
||||
declare readonly shortfallCredits: number;
|
||||
|
||||
private constructor(input: UnlockPrivateResponseInput) {
|
||||
const data = UnlockPrivateResponseSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: UnlockPrivateResponseInput): UnlockPrivateResponse {
|
||||
return new UnlockPrivateResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): UnlockPrivateResponse {
|
||||
return UnlockPrivateResponse.from(json as UnlockPrivateResponseInput);
|
||||
}
|
||||
|
||||
toJson(): UnlockPrivateResponseData {
|
||||
return UnlockPrivateResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./feedback_submission";
|
||||
export * from "./response";
|
||||
@@ -1,16 +0,0 @@
|
||||
import {
|
||||
FeedbackSubmitResponseSchema,
|
||||
} from "@/data/schemas/feedback";
|
||||
import {
|
||||
createSchemaDto,
|
||||
type SchemaDto,
|
||||
} from "@/data/dto/schema_dto";
|
||||
|
||||
export type FeedbackSubmitResponse = SchemaDto<
|
||||
typeof FeedbackSubmitResponseSchema,
|
||||
"feedbackId"
|
||||
>;
|
||||
export const FeedbackSubmitResponse = createSchemaDto<
|
||||
typeof FeedbackSubmitResponseSchema,
|
||||
"feedbackId"
|
||||
>(FeedbackSubmitResponseSchema);
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./feedback_submit_response";
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* @file Automatically generated by barrelsby.
|
||||
*/
|
||||
|
||||
export * from "./request";
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* 应用事件上报请求 DTO
|
||||
*/
|
||||
import {
|
||||
AppEventSchema,
|
||||
} from "@/data/schemas/metrics/request/app_event";
|
||||
import {
|
||||
createSchemaDto,
|
||||
type SchemaDto,
|
||||
} from "@/data/dto/schema_dto";
|
||||
|
||||
type AppEventPublicKey = "userId" | "browser" | "userAgent";
|
||||
export type AppEvent = SchemaDto<typeof AppEventSchema, AppEventPublicKey>;
|
||||
export const AppEvent = createSchemaDto<
|
||||
typeof AppEventSchema,
|
||||
AppEventPublicKey
|
||||
>(AppEventSchema);
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./app_event";
|
||||
export * from "./pwa_event";
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* PWA 事件上报请求 DTO
|
||||
*/
|
||||
import {
|
||||
PwaEventSchema,
|
||||
} from "@/data/schemas/metrics/request/pwa_event";
|
||||
import {
|
||||
createSchemaDto,
|
||||
type SchemaDto,
|
||||
} from "@/data/dto/schema_dto";
|
||||
|
||||
type PwaEventPublicKey =
|
||||
| "deviceId"
|
||||
| "deviceType"
|
||||
| "timestamp"
|
||||
| "pwaInstalled"
|
||||
| "pwaSupported";
|
||||
export type PwaEvent = SchemaDto<typeof PwaEventSchema, PwaEventPublicKey>;
|
||||
export const PwaEvent = createSchemaDto<
|
||||
typeof PwaEventSchema,
|
||||
PwaEventPublicKey
|
||||
>(PwaEventSchema);
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* @file Payment DTO barrel.
|
||||
*/
|
||||
|
||||
export * from "./payment_plan";
|
||||
export * from "./tip_payment_plan";
|
||||
export * from "./request";
|
||||
export * from "./response";
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* 支付套餐 DTO
|
||||
*/
|
||||
import {
|
||||
PaymentPlanSchema,
|
||||
type PaymentPlanData,
|
||||
type PaymentPlanInput,
|
||||
} from "@/data/schemas/payment/payment_plan";
|
||||
|
||||
export class PaymentPlan {
|
||||
declare readonly planId: string;
|
||||
declare readonly planName: string;
|
||||
declare readonly orderType: string;
|
||||
declare readonly vipDays: number | null;
|
||||
declare readonly dolAmount: number | null;
|
||||
declare readonly creditBalance: number;
|
||||
declare readonly amountCents: number;
|
||||
declare readonly originalAmountCents: number | null;
|
||||
declare readonly currency: string;
|
||||
declare readonly isFirstRechargeOffer: boolean;
|
||||
declare readonly mostPopular: boolean;
|
||||
declare readonly firstRechargeDiscountPercent: number | null;
|
||||
declare readonly promotionType: string | null;
|
||||
|
||||
private constructor(input: PaymentPlanInput) {
|
||||
const data = PaymentPlanSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: PaymentPlanInput): PaymentPlan {
|
||||
return new PaymentPlan(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): PaymentPlan {
|
||||
return PaymentPlan.from(json as PaymentPlanInput);
|
||||
}
|
||||
|
||||
toJson(): PaymentPlanData {
|
||||
return PaymentPlanSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* 创建支付订单请求 DTO
|
||||
*/
|
||||
import {
|
||||
CreatePaymentOrderRequestSchema,
|
||||
type CreatePaymentOrderRequestData,
|
||||
type CreatePaymentOrderRequestInput,
|
||||
type PayChannel,
|
||||
} from "@/data/schemas/payment/request/create_payment_order_request";
|
||||
|
||||
export type { PayChannel };
|
||||
|
||||
export class CreatePaymentOrderRequest {
|
||||
declare readonly planId: string;
|
||||
declare readonly payChannel: PayChannel;
|
||||
declare readonly autoRenew: boolean;
|
||||
|
||||
private constructor(input: CreatePaymentOrderRequestInput) {
|
||||
const data = CreatePaymentOrderRequestSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(
|
||||
input: CreatePaymentOrderRequestInput,
|
||||
): CreatePaymentOrderRequest {
|
||||
return new CreatePaymentOrderRequest(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): CreatePaymentOrderRequest {
|
||||
return CreatePaymentOrderRequest.from(
|
||||
json as CreatePaymentOrderRequestInput,
|
||||
);
|
||||
}
|
||||
|
||||
toJson(): CreatePaymentOrderRequestData {
|
||||
return CreatePaymentOrderRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./create_payment_order_request";
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* 创建支付订单响应 DTO
|
||||
*/
|
||||
import {
|
||||
CreatePaymentOrderResponseSchema,
|
||||
type CreatePaymentOrderResponseData,
|
||||
type CreatePaymentOrderResponseInput,
|
||||
} from "@/data/schemas/payment/response/create_payment_order_response";
|
||||
|
||||
export class CreatePaymentOrderResponse {
|
||||
declare readonly orderId: string;
|
||||
declare readonly payParams: Record<string, unknown>;
|
||||
|
||||
private constructor(input: CreatePaymentOrderResponseInput) {
|
||||
const data = CreatePaymentOrderResponseSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(
|
||||
input: CreatePaymentOrderResponseInput,
|
||||
): CreatePaymentOrderResponse {
|
||||
return new CreatePaymentOrderResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): CreatePaymentOrderResponse {
|
||||
return CreatePaymentOrderResponse.from(
|
||||
json as CreatePaymentOrderResponseInput,
|
||||
);
|
||||
}
|
||||
|
||||
toJson(): CreatePaymentOrderResponseData {
|
||||
return CreatePaymentOrderResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from "./create_payment_order_response";
|
||||
export * from "./payment_order_status_response";
|
||||
export * from "./payment_plans_response";
|
||||
export * from "./tip_payment_plans_response";
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* 支付订单状态响应 DTO
|
||||
*/
|
||||
import {
|
||||
PaymentOrderStatusResponseSchema,
|
||||
type PaymentOrderStatus,
|
||||
type PaymentOrderStatusResponseData,
|
||||
type PaymentOrderStatusResponseInput,
|
||||
} from "@/data/schemas/payment/response/payment_order_status_response";
|
||||
|
||||
export type { PaymentOrderStatus };
|
||||
|
||||
export class PaymentOrderStatusResponse {
|
||||
declare readonly status: PaymentOrderStatus;
|
||||
|
||||
private constructor(input: PaymentOrderStatusResponseInput) {
|
||||
const data = PaymentOrderStatusResponseSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(
|
||||
input: PaymentOrderStatusResponseInput,
|
||||
): PaymentOrderStatusResponse {
|
||||
return new PaymentOrderStatusResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): PaymentOrderStatusResponse {
|
||||
return PaymentOrderStatusResponse.from(
|
||||
json as PaymentOrderStatusResponseInput,
|
||||
);
|
||||
}
|
||||
|
||||
toJson(): PaymentOrderStatusResponseData {
|
||||
return PaymentOrderStatusResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* 支付套餐列表响应 DTO
|
||||
*/
|
||||
import {
|
||||
PaymentPlansResponseSchema,
|
||||
type FirstRechargeOfferData,
|
||||
type PaymentPlansResponseData,
|
||||
type PaymentPlansResponseInput,
|
||||
} from "@/data/schemas/payment/response/payment_plans_response";
|
||||
|
||||
import { PaymentPlan } from "../payment_plan";
|
||||
|
||||
export class PaymentPlansResponse {
|
||||
declare readonly isFirstRecharge: boolean;
|
||||
declare readonly firstRechargeOffer: FirstRechargeOfferData | null;
|
||||
declare readonly plans: PaymentPlan[];
|
||||
|
||||
private constructor(input: PaymentPlansResponseInput) {
|
||||
const data = PaymentPlansResponseSchema.parse(input);
|
||||
Object.assign(this, {
|
||||
isFirstRecharge: data.isFirstRecharge,
|
||||
firstRechargeOffer: data.firstRechargeOffer,
|
||||
plans: data.plans.map((plan) => PaymentPlan.from(plan)),
|
||||
});
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: PaymentPlansResponseInput): PaymentPlansResponse {
|
||||
return new PaymentPlansResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): PaymentPlansResponse {
|
||||
return PaymentPlansResponse.from(json as PaymentPlansResponseInput);
|
||||
}
|
||||
|
||||
toJson(): PaymentPlansResponseData {
|
||||
return {
|
||||
isFirstRecharge: this.isFirstRecharge,
|
||||
firstRechargeOffer: this.firstRechargeOffer,
|
||||
plans: this.plans.map((plan) => plan.toJson()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type FirstRechargeOffer = FirstRechargeOfferData;
|
||||
@@ -1,35 +0,0 @@
|
||||
import {
|
||||
TipPaymentPlansResponseSchema,
|
||||
type TipPaymentPlansResponseData,
|
||||
type TipPaymentPlansResponseInput,
|
||||
} from "@/data/schemas/payment/response/tip_payment_plans_response";
|
||||
|
||||
import { TipPaymentPlan } from "../tip_payment_plan";
|
||||
|
||||
export class TipPaymentPlansResponse {
|
||||
declare readonly plans: TipPaymentPlan[];
|
||||
|
||||
private constructor(input: TipPaymentPlansResponseInput) {
|
||||
const data = TipPaymentPlansResponseSchema.parse(input);
|
||||
Object.assign(this, {
|
||||
plans: data.plans.map((plan) => TipPaymentPlan.from(plan)),
|
||||
});
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: TipPaymentPlansResponseInput): TipPaymentPlansResponse {
|
||||
return new TipPaymentPlansResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): TipPaymentPlansResponse {
|
||||
return TipPaymentPlansResponse.from(
|
||||
json as TipPaymentPlansResponseInput,
|
||||
);
|
||||
}
|
||||
|
||||
toJson(): TipPaymentPlansResponseData {
|
||||
return {
|
||||
plans: this.plans.map((plan) => plan.toJson()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import {
|
||||
TipPaymentPlanSchema,
|
||||
type TipPaymentPlanData,
|
||||
type TipPaymentPlanInput,
|
||||
} from "@/data/schemas/payment/tip_payment_plan";
|
||||
|
||||
export class TipPaymentPlan {
|
||||
declare readonly planId: string;
|
||||
declare readonly planName: string;
|
||||
declare readonly amountCents: number;
|
||||
declare readonly currency: string;
|
||||
|
||||
private constructor(input: TipPaymentPlanInput) {
|
||||
const data = TipPaymentPlanSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: TipPaymentPlanInput): TipPaymentPlan {
|
||||
return new TipPaymentPlan(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): TipPaymentPlan {
|
||||
return TipPaymentPlan.from(json as TipPaymentPlanInput);
|
||||
}
|
||||
|
||||
toJson(): TipPaymentPlanData {
|
||||
return TipPaymentPlanSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from "./private_album";
|
||||
export * from "./request";
|
||||
export * from "./response";
|
||||
@@ -1,37 +0,0 @@
|
||||
import {
|
||||
PrivateAlbumSchema,
|
||||
type PrivateAlbumData,
|
||||
type PrivateAlbumInput,
|
||||
} from "@/data/schemas/private-room";
|
||||
|
||||
export class PrivateAlbum {
|
||||
declare readonly albumId: string;
|
||||
declare readonly title: string;
|
||||
declare readonly content: string | null;
|
||||
declare readonly previewText: string;
|
||||
declare readonly imageCount: number;
|
||||
declare readonly images: PrivateAlbumData["images"];
|
||||
declare readonly locked: boolean;
|
||||
declare readonly unlocked: boolean;
|
||||
declare readonly unlockCost: number;
|
||||
declare readonly publishedAt: string | null;
|
||||
declare readonly lockDetail: PrivateAlbumData["lockDetail"];
|
||||
|
||||
private constructor(input: PrivateAlbumInput) {
|
||||
const data = PrivateAlbumSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: PrivateAlbumInput): PrivateAlbum {
|
||||
return new PrivateAlbum(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): PrivateAlbum {
|
||||
return PrivateAlbum.from(json as PrivateAlbumInput);
|
||||
}
|
||||
|
||||
toJson(): PrivateAlbumData {
|
||||
return PrivateAlbumSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./unlock_private_album_request";
|
||||
@@ -1,21 +0,0 @@
|
||||
import {
|
||||
UnlockPrivateAlbumRequestSchema,
|
||||
type UnlockPrivateAlbumRequestData,
|
||||
type UnlockPrivateAlbumRequestInput,
|
||||
} from "@/data/schemas/private-room";
|
||||
|
||||
export class UnlockPrivateAlbumRequest {
|
||||
private constructor(input: UnlockPrivateAlbumRequestInput) {
|
||||
const data = UnlockPrivateAlbumRequestSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: UnlockPrivateAlbumRequestInput): UnlockPrivateAlbumRequest {
|
||||
return new UnlockPrivateAlbumRequest(input);
|
||||
}
|
||||
|
||||
toJson(): UnlockPrivateAlbumRequestData {
|
||||
return UnlockPrivateAlbumRequestSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./private_albums_response";
|
||||
export * from "./private_album_unlock_response";
|
||||
@@ -1,38 +0,0 @@
|
||||
import {
|
||||
PrivateAlbumUnlockResponseSchema,
|
||||
type PrivateAlbumUnlockReason,
|
||||
type PrivateAlbumUnlockResponseData,
|
||||
type PrivateAlbumUnlockResponseInput,
|
||||
} from "@/data/schemas/private-room";
|
||||
|
||||
export class PrivateAlbumUnlockResponse {
|
||||
declare readonly albumId: string;
|
||||
declare readonly locked: boolean;
|
||||
declare readonly unlocked: boolean;
|
||||
declare readonly reason: PrivateAlbumUnlockReason | string;
|
||||
declare readonly unlockCost: number;
|
||||
declare readonly requiredCredits: number;
|
||||
declare readonly creditBalance: number;
|
||||
declare readonly shortfallCredits: number;
|
||||
declare readonly images: PrivateAlbumUnlockResponseData["images"];
|
||||
|
||||
private constructor(input: PrivateAlbumUnlockResponseInput) {
|
||||
const data = PrivateAlbumUnlockResponseSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: PrivateAlbumUnlockResponseInput): PrivateAlbumUnlockResponse {
|
||||
return new PrivateAlbumUnlockResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): PrivateAlbumUnlockResponse {
|
||||
return PrivateAlbumUnlockResponse.from(
|
||||
json as PrivateAlbumUnlockResponseInput,
|
||||
);
|
||||
}
|
||||
|
||||
toJson(): PrivateAlbumUnlockResponseData {
|
||||
return PrivateAlbumUnlockResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import {
|
||||
PrivateAlbumsResponseSchema,
|
||||
type PrivateAlbumsResponseData,
|
||||
type PrivateAlbumsResponseInput,
|
||||
} from "@/data/schemas/private-room";
|
||||
|
||||
import { PrivateAlbum } from "../private_album";
|
||||
|
||||
export class PrivateAlbumsResponse {
|
||||
declare readonly items: PrivateAlbum[];
|
||||
declare readonly creditBalance: number;
|
||||
|
||||
private constructor(input: PrivateAlbumsResponseInput) {
|
||||
const data = PrivateAlbumsResponseSchema.parse(input);
|
||||
Object.assign(this, {
|
||||
items: data.items.map((item) => PrivateAlbum.from(item)),
|
||||
creditBalance: data.creditBalance,
|
||||
});
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: PrivateAlbumsResponseInput): PrivateAlbumsResponse {
|
||||
return new PrivateAlbumsResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): PrivateAlbumsResponse {
|
||||
return PrivateAlbumsResponse.from(json as PrivateAlbumsResponseInput);
|
||||
}
|
||||
|
||||
toJson(): PrivateAlbumsResponseData {
|
||||
return {
|
||||
items: this.items.map((item) => item.toJson()),
|
||||
creditBalance: this.creditBalance,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
export type SchemaDto<
|
||||
TSchema extends z.ZodType,
|
||||
TPublicKey extends keyof z.output<TSchema> = never,
|
||||
> = Readonly<Pick<z.output<TSchema>, TPublicKey>> & {
|
||||
toJson(): z.output<TSchema>;
|
||||
};
|
||||
|
||||
export interface SchemaDtoFactory<
|
||||
TSchema extends z.ZodType,
|
||||
TPublicKey extends keyof z.output<TSchema> = never,
|
||||
> {
|
||||
readonly prototype: SchemaDto<TSchema, TPublicKey>;
|
||||
[Symbol.hasInstance](value: unknown): boolean;
|
||||
from(input: z.input<TSchema>): SchemaDto<TSchema, TPublicKey>;
|
||||
fromJson(json: unknown): SchemaDto<TSchema, TPublicKey>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为没有自定义映射逻辑的对象 Schema 创建不可变 DTO。
|
||||
*
|
||||
* `TPublicKey` 只暴露业务代码实际读取的字段;完整解析结果仍会保留在实例中,
|
||||
* 并由 `toJson` 按原 Schema 输出。包含嵌套 DTO 转换或领域方法的模型继续使用
|
||||
* 显式 class,避免把业务逻辑隐藏进通用工厂。
|
||||
*/
|
||||
export function createSchemaDto<
|
||||
TSchema extends z.ZodType,
|
||||
TPublicKey extends keyof z.output<TSchema> = never,
|
||||
>(schema: TSchema): SchemaDtoFactory<TSchema, TPublicKey> {
|
||||
class GeneratedSchemaDto {
|
||||
private constructor(input: z.input<TSchema>) {
|
||||
Object.assign(this, schema.parse(input) as object);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(
|
||||
input: z.input<TSchema>,
|
||||
): SchemaDto<TSchema, TPublicKey> {
|
||||
return new GeneratedSchemaDto(input) as SchemaDto<
|
||||
TSchema,
|
||||
TPublicKey
|
||||
>;
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): SchemaDto<TSchema, TPublicKey> {
|
||||
return GeneratedSchemaDto.from(json as z.input<TSchema>);
|
||||
}
|
||||
|
||||
toJson(): z.output<TSchema> {
|
||||
return schema.parse(this);
|
||||
}
|
||||
}
|
||||
|
||||
return GeneratedSchemaDto as unknown as SchemaDtoFactory<
|
||||
TSchema,
|
||||
TPublicKey
|
||||
>;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* 头像数据 DTO
|
||||
*/
|
||||
import {
|
||||
AvatarDataSchema,
|
||||
type AvatarDataInput,
|
||||
type AvatarDataData,
|
||||
} from "@/data/schemas/user/avatar_data";
|
||||
|
||||
export class AvatarData {
|
||||
declare readonly avatarUrl: string;
|
||||
declare readonly userId: string;
|
||||
|
||||
private constructor(input: AvatarDataInput) {
|
||||
const data = AvatarDataSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: AvatarDataInput): AvatarData {
|
||||
return new AvatarData(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): AvatarData {
|
||||
return AvatarData.from(json as AvatarDataInput);
|
||||
}
|
||||
|
||||
toJson(): AvatarDataData {
|
||||
return AvatarDataSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* @file Automatically generated by barrelsby.
|
||||
*/
|
||||
|
||||
export * from "./avatar_data";
|
||||
export * from "./personality_traits";
|
||||
export * from "./recent_memory";
|
||||
export * from "./user";
|
||||
export * from "./user_entitlements";
|
||||
export * from "./user_view";
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* 个性特征 DTO
|
||||
*/
|
||||
import {
|
||||
PersonalityTraitsSchema,
|
||||
type PersonalityTraitsInput,
|
||||
type PersonalityTraitsData,
|
||||
} from "@/data/schemas/user/personality_traits";
|
||||
|
||||
export class PersonalityTraits {
|
||||
declare readonly cheerful: number;
|
||||
declare readonly caring: number;
|
||||
declare readonly playful: number;
|
||||
declare readonly serious: number;
|
||||
declare readonly romantic: number;
|
||||
|
||||
private constructor(input: PersonalityTraitsInput) {
|
||||
const data = PersonalityTraitsSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: PersonalityTraitsInput): PersonalityTraits {
|
||||
return new PersonalityTraits(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): PersonalityTraits {
|
||||
return PersonalityTraits.from(json as PersonalityTraitsInput);
|
||||
}
|
||||
|
||||
toJson(): PersonalityTraitsData {
|
||||
return PersonalityTraitsSchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* 最近记忆 DTO
|
||||
*/
|
||||
import {
|
||||
RecentMemorySchema,
|
||||
type RecentMemoryInput,
|
||||
type RecentMemoryData,
|
||||
} from "@/data/schemas/user/recent_memory";
|
||||
|
||||
export class RecentMemory {
|
||||
declare readonly type: string;
|
||||
declare readonly content: string;
|
||||
declare readonly importance: number;
|
||||
declare readonly createdAt: string;
|
||||
|
||||
private constructor(input: RecentMemoryInput) {
|
||||
const data = RecentMemorySchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: RecentMemoryInput): RecentMemory {
|
||||
return new RecentMemory(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): RecentMemory {
|
||||
return RecentMemory.from(json as RecentMemoryInput);
|
||||
}
|
||||
|
||||
toJson(): RecentMemoryData {
|
||||
return RecentMemorySchema.parse(this);
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* 用户 DTO
|
||||
*/
|
||||
import {
|
||||
UserSchema,
|
||||
type UserInput,
|
||||
type UserData,
|
||||
} from "@/data/schemas/user/user";
|
||||
import { PersonalityTraits } from "./personality_traits";
|
||||
|
||||
export class User {
|
||||
declare readonly id: string;
|
||||
declare readonly username: string;
|
||||
declare readonly email: string;
|
||||
declare readonly platform: string;
|
||||
declare readonly country: string;
|
||||
declare readonly countryCode: string;
|
||||
declare readonly fbAsid: string;
|
||||
declare readonly fbPsid: string;
|
||||
declare readonly intimacy: number;
|
||||
declare readonly dolBalance: number;
|
||||
declare readonly creditBalance: number;
|
||||
declare readonly dailyFreeChatLimit: number;
|
||||
declare readonly dailyFreeChatUsed: number;
|
||||
declare readonly dailyFreeChatRemaining: number;
|
||||
declare readonly dailyFreePrivateLimit: number;
|
||||
declare readonly dailyFreePrivateUsed: number;
|
||||
declare readonly dailyFreePrivateRemaining: number;
|
||||
declare readonly personalityTraits: PersonalityTraits;
|
||||
declare readonly preferredLanguage: string;
|
||||
declare readonly createdAt: string;
|
||||
declare readonly lastMessageAt: string;
|
||||
|
||||
private constructor(input: UserInput) {
|
||||
const data = UserSchema.parse(input);
|
||||
Object.assign(this, {
|
||||
...data,
|
||||
personalityTraits: PersonalityTraits.from(data.personalityTraits),
|
||||
});
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: UserInput): User {
|
||||
return new User(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): User {
|
||||
return User.from(json as UserInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个具有空 id/username 的 User 实例(用于可选 user 字段的默认占位)
|
||||
*/
|
||||
static empty(): User {
|
||||
return new User({ id: "", username: "" });
|
||||
}
|
||||
|
||||
toJson(): UserData {
|
||||
return UserSchema.parse({
|
||||
...this,
|
||||
personalityTraits: this.personalityTraits.toJson(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* 用户权益快照 DTO
|
||||
*/
|
||||
import {
|
||||
UserEntitlementsSchema,
|
||||
type UserEntitlementsData,
|
||||
type UserEntitlementsInput,
|
||||
} from "@/data/schemas/user/user_entitlements";
|
||||
|
||||
export class UserEntitlements {
|
||||
declare readonly userId: string;
|
||||
declare readonly isGuest: boolean;
|
||||
declare readonly isVip: boolean;
|
||||
declare readonly vipExpiresAt: string | null;
|
||||
declare readonly creditBalance: number;
|
||||
declare readonly dolBalance: number;
|
||||
declare readonly policy: UserEntitlementsData["policy"];
|
||||
declare readonly costs: UserEntitlementsData["costs"];
|
||||
declare readonly quotas: UserEntitlementsData["quotas"];
|
||||
declare readonly historyUnlock: UserEntitlementsData["historyUnlock"];
|
||||
|
||||
private constructor(input: UserEntitlementsInput) {
|
||||
const data = UserEntitlementsSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: UserEntitlementsInput): UserEntitlements {
|
||||
return new UserEntitlements(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): UserEntitlements {
|
||||
return UserEntitlements.from(json as UserEntitlementsInput);
|
||||
}
|
||||
|
||||
toJson(): UserEntitlementsData {
|
||||
return UserEntitlementsSchema.parse(this);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user