From eac3d8f0a777d901b006f24778298a4d66fbf00d Mon Sep 17 00:00:00 2001 From: chenhang Date: Fri, 17 Jul 2026 12:47:18 +0800 Subject: [PATCH] refactor(data): merge DTO models into schemas --- .agents/skills/sync-backend-api/SKILL.md | 90 +++++++------------ barrelsby.json | 7 -- docs/backend/api-contract-check.md | 2 +- .../use-payment-plan-analytics.test.tsx | 2 +- src/app/_hooks/use-payment-plan-analytics.ts | 2 +- src/app/_hooks/use-payment-route-flow.ts | 2 +- .../chat/__tests__/chat-area.helpers.test.ts | 2 +- src/app/chat/chat-render-items.ts | 2 +- src/app/chat/chat-screen.helpers.ts | 2 +- .../__tests__/chat-area-scroll.test.tsx | 2 +- src/app/chat/components/chat-area.tsx | 2 +- .../hooks/use-first-recharge-offer-banner.ts | 2 +- .../hooks/use-splash-latest-message-sync.ts | 4 +- src/app/feedback/feedback-context.ts | 2 +- src/app/feedback/feedback-screen.tsx | 2 +- src/app/feedback/use-feedback-submission.ts | 2 +- .../use-private-room-flow-navigation.test.tsx | 2 +- .../__tests__/private-album-card.test.tsx | 2 +- .../components/private-album-card.tsx | 2 +- .../components/private-album-gallery.tsx | 2 +- .../components/unlock-confirm-dialog.tsx | 2 +- src/app/private-room/use-private-room-flow.ts | 2 +- .../__tests__/sidebar-view-model.test.ts | 2 +- src/app/sidebar/sidebar-view-model.ts | 4 +- src/app/sidebar/use-sidebar-user-bootstrap.ts | 2 +- .../splash/hooks/use-splash-latest-message.ts | 2 +- .../subscription-screen.helpers.test.ts | 2 +- .../subscription-payment-method.tsx | 2 +- .../subscription-screen.helpers.ts | 2 +- src/app/subscription/subscription-screen.tsx | 2 +- .../use-subscription-payment-flow.ts | 2 +- .../tip/__tests__/tip-screen.helpers.test.ts | 2 +- src/app/tip/tip-screen.helpers.ts | 4 +- src/app/tip/tip-screen.tsx | 2 +- src/data/dto/__tests__/schema_dto.test.ts | 39 -------- src/data/dto/auth/facebook_user_data.ts | 35 -------- src/data/dto/auth/index.ts | 9 -- .../auth/request/facebook_identity_request.ts | 17 ---- .../auth/request/facebook_login_request.ts | 33 ------- .../request/facebook_psid_login_request.ts | 17 ---- .../dto/auth/request/fb_id_login_request.ts | 32 ------- .../dto/auth/request/google_login_request.ts | 33 ------- .../dto/auth/request/guest_login_request.ts | 31 ------- src/data/dto/auth/request/index.ts | 9 -- src/data/dto/auth/request/login_request.ts | 35 -------- .../dto/auth/request/refresh_token_request.ts | 19 ---- src/data/dto/auth/request/register_request.ts | 35 -------- .../response/facebook_identity_response.ts | 17 ---- .../response/facebook_psid_login_response.ts | 32 ------- .../dto/auth/response/guest_login_response.ts | 37 -------- src/data/dto/auth/response/index.ts | 6 -- src/data/dto/auth/response/login_response.ts | 36 -------- src/data/dto/auth/response/logout_response.ts | 19 ---- .../auth/response/refresh_token_response.ts | 31 ------- src/data/dto/character/character.ts | 29 ------ src/data/dto/character/characters_response.ts | 33 ------- src/data/dto/character/index.ts | 2 - src/data/dto/chat/chat_list_item.ts | 28 ------ src/data/dto/chat/chat_message.ts | 39 -------- src/data/dto/chat/index.ts | 10 --- src/data/dto/chat/request/index.ts | 3 - .../dto/chat/request/send_message_request.ts | 38 -------- .../chat/request/unlock_history_request.ts | 22 ----- .../chat/request/unlock_private_request.ts | 33 ------- .../chat/response/chat_history_response.ts | 36 -------- .../dto/chat/response/chat_send_response.ts | 42 --------- src/data/dto/chat/response/index.ts | 4 - .../chat/response/unlock_history_response.ts | 33 ------- .../chat/response/unlock_private_response.ts | 41 --------- src/data/dto/feedback/index.ts | 2 - .../response/feedback_submit_response.ts | 16 ---- src/data/dto/feedback/response/index.ts | 1 - src/data/dto/metrics/index.ts | 5 -- src/data/dto/metrics/request/app_event.ts | 17 ---- src/data/dto/metrics/request/index.ts | 2 - src/data/dto/metrics/request/pwa_event.ts | 22 ----- src/data/dto/payment/index.ts | 8 -- src/data/dto/payment/payment_plan.ts | 42 --------- .../request/create_payment_order_request.ts | 39 -------- src/data/dto/payment/request/index.ts | 1 - .../response/create_payment_order_response.ts | 35 -------- src/data/dto/payment/response/index.ts | 4 - .../response/payment_order_status_response.ts | 37 -------- .../response/payment_plans_response.ts | 45 ---------- .../response/tip_payment_plans_response.ts | 35 -------- src/data/dto/payment/tip_payment_plan.ts | 30 ------- src/data/dto/private-room/index.ts | 3 - src/data/dto/private-room/private_album.ts | 37 -------- src/data/dto/private-room/request/index.ts | 1 - .../request/unlock_private_album_request.ts | 21 ----- src/data/dto/private-room/response/index.ts | 2 - .../response/private_album_unlock_response.ts | 38 -------- .../response/private_albums_response.ts | 36 -------- src/data/dto/schema_dto.ts | 59 ------------ src/data/dto/user/avatar_data.ts | 31 ------- src/data/dto/user/index.ts | 10 --- src/data/dto/user/personality_traits.ts | 34 ------- src/data/dto/user/recent_memory.ts | 33 ------- src/data/dto/user/user.ts | 64 ------------- src/data/dto/user/user_entitlements.ts | 39 -------- .../auth_repository.facebook_identity.test.ts | 2 +- .../__tests__/auth_repository.session.test.ts | 2 +- .../__tests__/chat_cache_identity.test.ts | 2 +- .../chat_local_message_store.test.ts | 2 +- .../chat_media_cache_helpers.test.ts | 2 +- .../__tests__/payment_repository.test.ts | 2 +- src/data/repositories/auth_repository.ts | 4 +- src/data/repositories/character_repository.ts | 2 +- src/data/repositories/chat_cache_identity.ts | 2 +- .../repositories/chat_local_message_store.ts | 2 +- .../chat_media_cache_coordinator.ts | 2 +- .../repositories/chat_media_cache_helpers.ts | 2 +- .../repositories/chat_remote_data_source.ts | 2 +- src/data/repositories/chat_repository.ts | 2 +- src/data/repositories/feedback_repository.ts | 2 +- .../interfaces/iauth_repository.ts | 4 +- .../interfaces/icharacter_repository.ts | 2 +- .../interfaces/ichat_repository.ts | 2 +- .../interfaces/ifeedback_repository.ts | 2 +- .../interfaces/ipayment_repository.ts | 2 +- .../interfaces/iprivate_room_repository.ts | 2 +- .../interfaces/iuser_repository.ts | 2 +- src/data/repositories/metrics_repository.ts | 2 +- src/data/repositories/payment_repository.ts | 2 +- .../repositories/private_room_repository.ts | 2 +- src/data/repositories/user_repository.ts | 2 +- .../schemas/__tests__/schema_model.test.ts | 42 +++++++++ .../auth/__tests__/facebook_identity.test.ts | 6 +- src/data/schemas/auth/facebook_user_data.ts | 25 ++++++ src/data/schemas/auth/index.ts | 1 + .../{dto => schemas}/auth/login_status.ts | 9 +- .../auth/request/facebook_identity_request.ts | 12 +++ .../auth/request/facebook_login_request.ts | 25 ++++++ .../request/facebook_psid_login_request.ts | 12 +++ .../auth/request/fb_id_login_request.ts | 24 +++++ .../auth/request/google_login_request.ts | 25 ++++++ .../auth/request/guest_login_request.ts | 23 +++++ .../schemas/auth/request/login_request.ts | 27 ++++++ .../auth/request/refresh_token_request.ts | 14 +++ .../schemas/auth/request/register_request.ts | 27 ++++++ .../response/facebook_identity_response.ts | 12 +++ .../response/facebook_psid_login_response.ts | 24 +++++ .../auth/response/guest_login_response.ts | 30 ++++++- .../schemas/auth/response/login_response.ts | 29 +++++- .../schemas/auth/response/logout_response.ts | 14 +++ .../auth/response/refresh_token_response.ts | 23 +++++ .../character/__tests__/character.test.ts | 2 +- src/data/schemas/character/character.ts | 24 +++++ .../schemas/character/characters_response.ts | 28 +++++- src/data/schemas/character/index.ts | 4 + .../__tests__/chat_history_response.test.ts | 2 +- .../__tests__/unlock_private_request.test.ts | 2 +- .../__tests__/unlock_private_response.test.ts | 2 +- src/data/{dto => schemas}/chat/chat_media.ts | 0 src/data/schemas/chat/chat_message.ts | 36 +++++++- src/data/schemas/chat/index.ts | 2 + .../chat/request/send_message_request.ts | 30 +++++++ .../chat/request/unlock_history_request.ts | 17 ++++ .../chat/request/unlock_private_request.ts | 25 ++++++ .../chat/response/chat_history_response.ts | 29 +++++- .../chat/response/chat_send_response.ts | 39 +++++++- .../chat/response/unlock_history_response.ts | 24 +++++ .../chat/response/unlock_private_response.ts | 36 +++++++- .../feedback/feedback_submission.ts | 0 src/data/schemas/feedback/index.ts | 5 ++ .../response/feedback_submit_response.ts | 14 +++ src/data/schemas/feedback/response/index.ts | 4 + src/data/schemas/metrics/request/app_event.ts | 12 +++ src/data/schemas/metrics/request/pwa_event.ts | 17 ++++ .../payment/__tests__/payment_plan.test.ts | 2 +- src/data/schemas/payment/payment_plan.ts | 34 +++++++ .../request/create_payment_order_request.ts | 28 ++++++ .../response/create_payment_order_response.ts | 27 ++++++ .../response/payment_order_status_response.ts | 26 ++++++ .../response/payment_plans_response.ts | 36 +++++++- .../response/tip_payment_plans_response.ts | 30 ++++++- src/data/schemas/payment/tip_payment_plan.ts | 25 ++++++ .../__tests__/private_album.test.ts | 4 +- .../schemas/private-room/private_album.ts | 32 +++++++ .../request/unlock_private_album_request.ts | 16 ++++ .../response/private_album_unlock_response.ts | 32 +++++++ .../response/private_albums_response.ts | 31 ++++++- src/data/schemas/schema_model.ts | 53 +++++++++++ .../user/__tests__/user.test.ts | 4 +- src/data/schemas/user/avatar_data.ts | 23 +++++ src/data/schemas/user/personality_traits.ts | 26 ++++++ src/data/schemas/user/recent_memory.ts | 25 ++++++ src/data/schemas/user/user.ts | 61 ++++++++++++- src/data/schemas/user/user_entitlements.ts | 31 +++++++ .../api/__tests__/multi_character_api.test.ts | 2 +- src/data/services/api/auth_api.ts | 4 +- src/data/services/api/character_api.ts | 2 +- src/data/services/api/chat_api.ts | 2 +- src/data/services/api/feedback_api.ts | 2 +- src/data/services/api/metrics_api.ts | 2 +- src/data/services/api/payment_api.ts | 2 +- src/data/services/api/private_room_api.ts | 2 +- src/data/services/api/user_api.ts | 2 +- src/data/services/facebook-graph.ts | 4 +- src/data/services/index.ts | 27 ------ src/data/storage/auth/auth_storage.ts | 2 +- src/data/storage/auth/iauth_storage.ts | 2 +- src/data/storage/chat/local_chat_db.ts | 2 +- .../storage/chat/local_chat_media_storage.ts | 2 +- src/data/storage/user/iuser_storage.ts | 2 +- src/hooks/use-guest-login-bootstrap.ts | 2 +- .../__tests__/behavior_analytics.test.ts | 2 +- src/lib/analytics/behavior_analytics.ts | 2 +- src/lib/auth/__tests__/auth_session.test.ts | 2 +- src/lib/chat/chat_media_cache_client.ts | 2 +- src/lib/chat/use_cached_chat_media_url.ts | 2 +- src/lib/feedback/submit_feedback.ts | 2 +- src/lib/navigation/external_entry.ts | 2 +- src/lib/payment/default_pay_channel.ts | 2 +- src/lib/payment/payment_search_params.ts | 2 +- src/lib/private-room/private_album.ts | 2 +- src/router/navigation-resolver.ts | 2 +- src/router/navigation-types.ts | 2 +- src/router/routes.ts | 2 +- .../__tests__/auth-context-hydration.test.tsx | 3 +- .../auth/__tests__/auth-machine.test-utils.ts | 2 +- src/stores/auth/auth-events.ts | 2 +- .../auth/auth-panel-mode.ts} | 5 -- src/stores/auth/auth-state.ts | 4 +- src/stores/auth/index.ts | 1 + src/stores/auth/machine/actors/credentials.ts | 2 +- src/stores/auth/machine/actors/oauth.ts | 2 +- src/stores/auth/machine/actors/session.ts | 2 +- src/stores/auth/machine/credentials-flow.ts | 2 +- src/stores/auth/machine/oauth-flow.ts | 2 +- src/stores/auth/machine/session-flow.ts | 2 +- .../chat/__tests__/chat-helpers.test.ts | 2 +- .../chat/__tests__/chat-history-flow.test.ts | 2 +- .../chat/__tests__/chat-machine.test-utils.ts | 4 +- .../chat/__tests__/chat-promotion.test.ts | 2 +- .../chat/__tests__/chat-unlock-flow.test.ts | 2 +- src/stores/chat/chat-history-sync.ts | 2 +- src/stores/chat/chat-state.ts | 2 +- src/stores/chat/helper/history.ts | 2 +- src/stores/chat/helper/message-mappers.ts | 7 +- src/stores/chat/helper/promotion.ts | 2 +- src/stores/chat/helper/send-state.ts | 3 +- src/stores/chat/helper/unlock.ts | 7 +- src/stores/chat/index.ts | 1 + src/stores/chat/machine/actors/history.ts | 2 +- src/stores/chat/machine/actors/send.ts | 4 +- src/stores/chat/machine/actors/unlock.ts | 2 +- .../chat/ui-message.ts} | 13 --- .../__tests__/payment-catalog-flow.test.ts | 2 +- .../__tests__/payment-machine.test-utils.ts | 2 +- src/stores/payment/helper/catalog.ts | 2 +- src/stores/payment/helper/order.ts | 2 +- src/stores/payment/machine/actors/order.ts | 2 +- src/stores/payment/machine/actors/plans.ts | 2 +- src/stores/payment/machine/catalog-flow.ts | 2 +- src/stores/payment/machine/order-flow.ts | 2 +- src/stores/payment/payment-events.ts | 2 +- src/stores/payment/payment-state.ts | 2 +- .../private-room-machine.test-utils.ts | 2 +- src/stores/private-room/helper/albums.ts | 2 +- src/stores/private-room/helper/unlock.ts | 2 +- .../private-room/machine/actors/albums.ts | 2 +- src/stores/private-room/machine/album-flow.ts | 2 +- .../private-room/machine/unlock-flow.ts | 2 +- src/stores/private-room/private-room-state.ts | 2 +- src/stores/sync/user-auth-sync.tsx | 2 +- .../user/__tests__/user-machine.test-utils.ts | 2 +- src/stores/user/helper/view.ts | 2 +- src/stores/user/index.ts | 1 + src/stores/user/machine/actors/profile.ts | 2 +- src/stores/user/machine/actors/session.ts | 2 +- src/stores/user/user-events.ts | 2 +- src/stores/user/user-state.ts | 2 +- .../user_view.ts => stores/user/user-view.ts} | 5 -- 274 files changed, 1466 insertions(+), 1956 deletions(-) delete mode 100644 src/data/dto/__tests__/schema_dto.test.ts delete mode 100644 src/data/dto/auth/facebook_user_data.ts delete mode 100644 src/data/dto/auth/index.ts delete mode 100644 src/data/dto/auth/request/facebook_identity_request.ts delete mode 100644 src/data/dto/auth/request/facebook_login_request.ts delete mode 100644 src/data/dto/auth/request/facebook_psid_login_request.ts delete mode 100644 src/data/dto/auth/request/fb_id_login_request.ts delete mode 100644 src/data/dto/auth/request/google_login_request.ts delete mode 100644 src/data/dto/auth/request/guest_login_request.ts delete mode 100644 src/data/dto/auth/request/index.ts delete mode 100644 src/data/dto/auth/request/login_request.ts delete mode 100644 src/data/dto/auth/request/refresh_token_request.ts delete mode 100644 src/data/dto/auth/request/register_request.ts delete mode 100644 src/data/dto/auth/response/facebook_identity_response.ts delete mode 100644 src/data/dto/auth/response/facebook_psid_login_response.ts delete mode 100644 src/data/dto/auth/response/guest_login_response.ts delete mode 100644 src/data/dto/auth/response/index.ts delete mode 100644 src/data/dto/auth/response/login_response.ts delete mode 100644 src/data/dto/auth/response/logout_response.ts delete mode 100644 src/data/dto/auth/response/refresh_token_response.ts delete mode 100644 src/data/dto/character/character.ts delete mode 100644 src/data/dto/character/characters_response.ts delete mode 100644 src/data/dto/character/index.ts delete mode 100644 src/data/dto/chat/chat_list_item.ts delete mode 100644 src/data/dto/chat/chat_message.ts delete mode 100644 src/data/dto/chat/index.ts delete mode 100644 src/data/dto/chat/request/index.ts delete mode 100644 src/data/dto/chat/request/send_message_request.ts delete mode 100644 src/data/dto/chat/request/unlock_history_request.ts delete mode 100644 src/data/dto/chat/request/unlock_private_request.ts delete mode 100644 src/data/dto/chat/response/chat_history_response.ts delete mode 100644 src/data/dto/chat/response/chat_send_response.ts delete mode 100644 src/data/dto/chat/response/index.ts delete mode 100644 src/data/dto/chat/response/unlock_history_response.ts delete mode 100644 src/data/dto/chat/response/unlock_private_response.ts delete mode 100644 src/data/dto/feedback/index.ts delete mode 100644 src/data/dto/feedback/response/feedback_submit_response.ts delete mode 100644 src/data/dto/feedback/response/index.ts delete mode 100644 src/data/dto/metrics/index.ts delete mode 100644 src/data/dto/metrics/request/app_event.ts delete mode 100644 src/data/dto/metrics/request/index.ts delete mode 100644 src/data/dto/metrics/request/pwa_event.ts delete mode 100644 src/data/dto/payment/index.ts delete mode 100644 src/data/dto/payment/payment_plan.ts delete mode 100644 src/data/dto/payment/request/create_payment_order_request.ts delete mode 100644 src/data/dto/payment/request/index.ts delete mode 100644 src/data/dto/payment/response/create_payment_order_response.ts delete mode 100644 src/data/dto/payment/response/index.ts delete mode 100644 src/data/dto/payment/response/payment_order_status_response.ts delete mode 100644 src/data/dto/payment/response/payment_plans_response.ts delete mode 100644 src/data/dto/payment/response/tip_payment_plans_response.ts delete mode 100644 src/data/dto/payment/tip_payment_plan.ts delete mode 100644 src/data/dto/private-room/index.ts delete mode 100644 src/data/dto/private-room/private_album.ts delete mode 100644 src/data/dto/private-room/request/index.ts delete mode 100644 src/data/dto/private-room/request/unlock_private_album_request.ts delete mode 100644 src/data/dto/private-room/response/index.ts delete mode 100644 src/data/dto/private-room/response/private_album_unlock_response.ts delete mode 100644 src/data/dto/private-room/response/private_albums_response.ts delete mode 100644 src/data/dto/schema_dto.ts delete mode 100644 src/data/dto/user/avatar_data.ts delete mode 100644 src/data/dto/user/index.ts delete mode 100644 src/data/dto/user/personality_traits.ts delete mode 100644 src/data/dto/user/recent_memory.ts delete mode 100644 src/data/dto/user/user.ts delete mode 100644 src/data/dto/user/user_entitlements.ts create mode 100644 src/data/schemas/__tests__/schema_model.test.ts rename src/data/{dto => schemas}/auth/__tests__/facebook_identity.test.ts (92%) rename src/data/{dto => schemas}/auth/login_status.ts (55%) rename src/data/{dto => schemas}/character/__tests__/character.test.ts (95%) rename src/data/{dto => schemas}/chat/__tests__/chat_history_response.test.ts (86%) rename src/data/{dto => schemas}/chat/__tests__/unlock_private_request.test.ts (93%) rename src/data/{dto => schemas}/chat/__tests__/unlock_private_response.test.ts (98%) rename src/data/{dto => schemas}/chat/chat_media.ts (100%) rename src/data/{dto => schemas}/feedback/feedback_submission.ts (100%) rename src/data/{dto => schemas}/payment/__tests__/payment_plan.test.ts (99%) rename src/data/{dto => schemas}/private-room/__tests__/private_album.test.ts (97%) create mode 100644 src/data/schemas/schema_model.ts rename src/data/{dto => schemas}/user/__tests__/user.test.ts (97%) rename src/{data/dto/auth/auth_panel_mode.ts => stores/auth/auth-panel-mode.ts} (74%) rename src/{data/dto/chat/ui_message.ts => stores/chat/ui-message.ts} (65%) rename src/{data/dto/user/user_view.ts => stores/user/user-view.ts} (78%) diff --git a/.agents/skills/sync-backend-api/SKILL.md b/.agents/skills/sync-backend-api/SKILL.md index f86187bb..d841065e 100644 --- a/.agents/skills/sync-backend-api/SKILL.md +++ b/.agents/skills/sync-backend-api/SKILL.md @@ -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//*.ts` | Zod schema,负责防御性解析 | -| DTO | `src/data/dto//*.ts` | DTO class,封装 `fromJson/toJson` | +| Schema Model | `src/data/schemas//*.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//*` | XState 状态机、actors、helpers、sync | | UI | `src/app/**` | Next.js app router UI | | Mock | `src/data/mock//**` | 模拟请求 / 响应数据 | -| 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//*.ts - 输入类型用 `z.input`。 - 对后端可能返回 `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; export type XxxResponseData = z.output; -``` - -### 5. 更新 DTO - -在对应模块下新增或修改: - -```text -src/data/dto//*.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); - 不要在 API 层处理 UI 行为。 - multipart 仍使用 `FormData`。 -### 7. 更新 Repository +### 6. 更新 Repository 修改: @@ -229,14 +207,14 @@ async getXxx(): Promise> { Repository 可以做轻量编排,例如: -- request DTO 构造 +- request 数据类构造 - 多接口组合 - 本地 storage 同步 -- local model 与 DTO 转换 +- 本地模型与接口模型转换 不要把 React / UI 逻辑放进 repository。 -### 8. 更新 Storage +### 7. 更新 Storage 如果接口影响本地持久化,修改: @@ -262,7 +240,7 @@ getEntitlementSnapshot(): Promise> { } ``` -### 9. 更新 Store / 状态机 +### 8. 更新 Store / 状态机 如果接口影响状态流,修改: @@ -284,7 +262,7 @@ src/stores//*-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//index.ts -src/data/dto//index.ts src/app/**/components/index.ts ``` 如果项目使用 barrelsby 生成,不要手动破坏现有导出格式。 -### 12. 更新 Mock 数据 +### 11. 更新 Mock 数据 如果新增或调整数据结构,按一个结构一个文件的原则放到: @@ -322,11 +299,11 @@ src/data/mock//websocket-events 不要把多个结构塞进一个大 JSON。 -### 13. 更新测试 +### 12. 更新测试 优先补充: -- DTO / schema parse 测试 +- schema model 解析与序列化测试 - helper 纯函数测试 - XState transition 测试 - repository 编排测试(如已有测试基础) @@ -334,11 +311,11 @@ src/data/mock//websocket-events 常见位置: ```text -src/data/dto//__tests__/*.test.ts +src/data/schemas//__tests__/*.test.ts src/stores//__tests__/*.test.ts ``` -### 14. 验证 +### 13. 验证 至少运行: @@ -356,18 +333,17 @@ pnpm exec vitest run 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 1. 搜索所有引用。 2. 删除 API path / API method / repository method。 -3. 删除 DTO/schema/mock/test。 +3. 删除 schema model、mock 和测试。 4. 更新状态机和 UI 调用。 5. 运行 `rg` 确认无残留引用。 6. 运行验证。 diff --git a/barrelsby.json b/barrelsby.json index e78fef4d..9d9d0e06 100644 --- a/barrelsby.json +++ b/barrelsby.json @@ -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", diff --git a/docs/backend/api-contract-check.md b/docs/backend/api-contract-check.md index 23043761..f93703c4 100644 --- a/docs/backend/api-contract-check.md +++ b/docs/backend/api-contract-check.md @@ -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 配置 diff --git a/src/app/_hooks/__tests__/use-payment-plan-analytics.test.tsx b/src/app/_hooks/__tests__/use-payment-plan-analytics.test.tsx index f2a7246c..a31939b7 100644 --- a/src/app/_hooks/__tests__/use-payment-plan-analytics.test.tsx +++ b/src/app/_hooks/__tests__/use-payment-plan-analytics.test.tsx @@ -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"; diff --git a/src/app/_hooks/use-payment-plan-analytics.ts b/src/app/_hooks/use-payment-plan-analytics.ts index 84105c94..ce933d61 100644 --- a/src/app/_hooks/use-payment-plan-analytics.ts +++ b/src/app/_hooks/use-payment-plan-analytics.ts @@ -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, diff --git a/src/app/_hooks/use-payment-route-flow.ts b/src/app/_hooks/use-payment-route-flow.ts index 64107fab..274cc5da 100644 --- a/src/app/_hooks/use-payment-route-flow.ts +++ b/src/app/_hooks/use-payment-route-flow.ts @@ -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, diff --git a/src/app/chat/__tests__/chat-area.helpers.test.ts b/src/app/chat/__tests__/chat-area.helpers.test.ts index bcc56a9f..04d44b81 100644 --- a/src/app/chat/__tests__/chat-area.helpers.test.ts +++ b/src/app/chat/__tests__/chat-area.helpers.test.ts @@ -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, diff --git a/src/app/chat/chat-render-items.ts b/src/app/chat/chat-render-items.ts index 1edc93ea..4dd4f9d9 100644 --- a/src/app/chat/chat-render-items.ts +++ b/src/app/chat/chat-render-items.ts @@ -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; diff --git a/src/app/chat/chat-screen.helpers.ts b/src/app/chat/chat-screen.helpers.ts index 3da5447d..17387a1e 100644 --- a/src/app/chat/chat-screen.helpers.ts +++ b/src/app/chat/chat-screen.helpers.ts @@ -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"; diff --git a/src/app/chat/components/__tests__/chat-area-scroll.test.tsx b/src/app/chat/components/__tests__/chat-area-scroll.test.tsx index 43ef36a8..36a92982 100644 --- a/src/app/chat/components/__tests__/chat-area-scroll.test.tsx +++ b/src/app/chat/components/__tests__/chat-area-scroll.test.tsx @@ -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"; diff --git a/src/app/chat/components/chat-area.tsx b/src/app/chat/components/chat-area.tsx index 9fadd1a7..be6c5bed 100644 --- a/src/app/chat/components/chat-area.tsx +++ b/src/app/chat/components/chat-area.tsx @@ -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"; diff --git a/src/app/chat/hooks/use-first-recharge-offer-banner.ts b/src/app/chat/hooks/use-first-recharge-offer-banner.ts index 46ae0c33..6447db9a 100644 --- a/src/app/chat/hooks/use-first-recharge-offer-banner.ts +++ b/src/app/chat/hooks/use-first-recharge-offer-banner.ts @@ -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, diff --git a/src/app/chat/hooks/use-splash-latest-message-sync.ts b/src/app/chat/hooks/use-splash-latest-message-sync.ts index ce0fb766..d96166f0 100644 --- a/src/app/chat/hooks/use-splash-latest-message-sync.ts +++ b/src/app/chat/hooks/use-splash-latest-message-sync.ts @@ -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"; diff --git a/src/app/feedback/feedback-context.ts b/src/app/feedback/feedback-context.ts index 041561f0..de3dbb07 100644 --- a/src/app/feedback/feedback-context.ts +++ b/src/app/feedback/feedback-context.ts @@ -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"; diff --git a/src/app/feedback/feedback-screen.tsx b/src/app/feedback/feedback-screen.tsx index c2852906..d75ec919 100644 --- a/src/app/feedback/feedback-screen.tsx +++ b/src/app/feedback/feedback-screen.tsx @@ -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"; diff --git a/src/app/feedback/use-feedback-submission.ts b/src/app/feedback/use-feedback-submission.ts index 282c6bb6..fdfed032 100644 --- a/src/app/feedback/use-feedback-submission.ts +++ b/src/app/feedback/use-feedback-submission.ts @@ -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"; diff --git a/src/app/private-room/__tests__/use-private-room-flow-navigation.test.tsx b/src/app/private-room/__tests__/use-private-room-flow-navigation.test.tsx index 9b4338a9..824adc6e 100644 --- a/src/app/private-room/__tests__/use-private-room-flow-navigation.test.tsx +++ b/src/app/private-room/__tests__/use-private-room-flow-navigation.test.tsx @@ -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"; diff --git a/src/app/private-room/components/__tests__/private-album-card.test.tsx b/src/app/private-room/components/__tests__/private-album-card.test.tsx index 915ba8d6..348995f4 100644 --- a/src/app/private-room/components/__tests__/private-album-card.test.tsx +++ b/src/app/private-room/components/__tests__/private-album-card.test.tsx @@ -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"; diff --git a/src/app/private-room/components/private-album-card.tsx b/src/app/private-room/components/private-album-card.tsx index 257c7831..23929865 100644 --- a/src/app/private-room/components/private-album-card.tsx +++ b/src/app/private-room/components/private-album-card.tsx @@ -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"; diff --git a/src/app/private-room/components/private-album-gallery.tsx b/src/app/private-room/components/private-album-gallery.tsx index 860b24b8..fc1ce7dd 100644 --- a/src/app/private-room/components/private-album-gallery.tsx +++ b/src/app/private-room/components/private-album-gallery.tsx @@ -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"; diff --git a/src/app/private-room/components/unlock-confirm-dialog.tsx b/src/app/private-room/components/unlock-confirm-dialog.tsx index d5efc7a9..566dbe5a 100644 --- a/src/app/private-room/components/unlock-confirm-dialog.tsx +++ b/src/app/private-room/components/unlock-confirm-dialog.tsx @@ -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"; diff --git a/src/app/private-room/use-private-room-flow.ts b/src/app/private-room/use-private-room-flow.ts index f752197b..103d8b1b 100644 --- a/src/app/private-room/use-private-room-flow.ts +++ b/src/app/private-room/use-private-room-flow.ts @@ -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"; diff --git a/src/app/sidebar/__tests__/sidebar-view-model.test.ts b/src/app/sidebar/__tests__/sidebar-view-model.test.ts index fc8796bd..1cb24990 100644 --- a/src/app/sidebar/__tests__/sidebar-view-model.test.ts +++ b/src/app/sidebar/__tests__/sidebar-view-model.test.ts @@ -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, diff --git a/src/app/sidebar/sidebar-view-model.ts b/src/app/sidebar/sidebar-view-model.ts index 963d4a04..f0c2f606 100644 --- a/src/app/sidebar/sidebar-view-model.ts +++ b/src/app/sidebar/sidebar-view-model.ts @@ -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"; diff --git a/src/app/sidebar/use-sidebar-user-bootstrap.ts b/src/app/sidebar/use-sidebar-user-bootstrap.ts index 6965b802..7e1895bf 100644 --- a/src/app/sidebar/use-sidebar-user-bootstrap.ts +++ b/src/app/sidebar/use-sidebar-user-bootstrap.ts @@ -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; diff --git a/src/app/splash/hooks/use-splash-latest-message.ts b/src/app/splash/hooks/use-splash-latest-message.ts index 426ed294..4b7e5227 100644 --- a/src/app/splash/hooks/use-splash-latest-message.ts +++ b/src/app/splash/hooks/use-splash-latest-message.ts @@ -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"; diff --git a/src/app/subscription/__tests__/subscription-screen.helpers.test.ts b/src/app/subscription/__tests__/subscription-screen.helpers.test.ts index 92bc0fbf..0cecc65f 100644 --- a/src/app/subscription/__tests__/subscription-screen.helpers.test.ts +++ b/src/app/subscription/__tests__/subscription-screen.helpers.test.ts @@ -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, diff --git a/src/app/subscription/components/subscription-payment-method.tsx b/src/app/subscription/components/subscription-payment-method.tsx index 5481210b..0d4c51ee 100644 --- a/src/app/subscription/components/subscription-payment-method.tsx +++ b/src/app/subscription/components/subscription-payment-method.tsx @@ -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; diff --git a/src/app/subscription/subscription-screen.helpers.ts b/src/app/subscription/subscription-screen.helpers.ts index b7cb1b91..1a2f1894 100644 --- a/src/app/subscription/subscription-screen.helpers.ts +++ b/src/app/subscription/subscription-screen.helpers.ts @@ -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"; diff --git a/src/app/subscription/subscription-screen.tsx b/src/app/subscription/subscription-screen.tsx index e7385bb6..572e1d1e 100644 --- a/src/app/subscription/subscription-screen.tsx +++ b/src/app/subscription/subscription-screen.tsx @@ -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, diff --git a/src/app/subscription/use-subscription-payment-flow.ts b/src/app/subscription/use-subscription-payment-flow.ts index b86e0b2c..589a56a3 100644 --- a/src/app/subscription/use-subscription-payment-flow.ts +++ b/src/app/subscription/use-subscription-payment-flow.ts @@ -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"; diff --git a/src/app/tip/__tests__/tip-screen.helpers.test.ts b/src/app/tip/__tests__/tip-screen.helpers.test.ts index 32b691bf..5ff67e9f 100644 --- a/src/app/tip/__tests__/tip-screen.helpers.test.ts +++ b/src/app/tip/__tests__/tip-screen.helpers.test.ts @@ -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 { diff --git a/src/app/tip/tip-screen.helpers.ts b/src/app/tip/tip-screen.helpers.ts index f0dd2ed5..b816b218 100644 --- a/src/app/tip/tip-screen.helpers.ts +++ b/src/app/tip/tip-screen.helpers.ts @@ -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, diff --git a/src/app/tip/tip-screen.tsx b/src/app/tip/tip-screen.tsx index 03276ada..61e46c23 100644 --- a/src/app/tip/tip-screen.tsx +++ b/src/app/tip/tip-screen.tsx @@ -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, diff --git a/src/data/dto/__tests__/schema_dto.test.ts b/src/data/dto/__tests__/schema_dto.test.ts deleted file mode 100644 index 6eabee39..00000000 --- a/src/data/dto/__tests__/schema_dto.test.ts +++ /dev/null @@ -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; -const ExampleDto = createSchemaDto( - 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); - }); -}); diff --git a/src/data/dto/auth/facebook_user_data.ts b/src/data/dto/auth/facebook_user_data.ts deleted file mode 100644 index f04fa071..00000000 --- a/src/data/dto/auth/facebook_user_data.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/auth/index.ts b/src/data/dto/auth/index.ts deleted file mode 100644 index 8b2b519f..00000000 --- a/src/data/dto/auth/index.ts +++ /dev/null @@ -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"; diff --git a/src/data/dto/auth/request/facebook_identity_request.ts b/src/data/dto/auth/request/facebook_identity_request.ts deleted file mode 100644 index 2e61a682..00000000 --- a/src/data/dto/auth/request/facebook_identity_request.ts +++ /dev/null @@ -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, -); diff --git a/src/data/dto/auth/request/facebook_login_request.ts b/src/data/dto/auth/request/facebook_login_request.ts deleted file mode 100644 index 4c36afde..00000000 --- a/src/data/dto/auth/request/facebook_login_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/auth/request/facebook_psid_login_request.ts b/src/data/dto/auth/request/facebook_psid_login_request.ts deleted file mode 100644 index 998e7652..00000000 --- a/src/data/dto/auth/request/facebook_psid_login_request.ts +++ /dev/null @@ -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, -); diff --git a/src/data/dto/auth/request/fb_id_login_request.ts b/src/data/dto/auth/request/fb_id_login_request.ts deleted file mode 100644 index 296a4156..00000000 --- a/src/data/dto/auth/request/fb_id_login_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/auth/request/google_login_request.ts b/src/data/dto/auth/request/google_login_request.ts deleted file mode 100644 index 8b3ff87a..00000000 --- a/src/data/dto/auth/request/google_login_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/auth/request/guest_login_request.ts b/src/data/dto/auth/request/guest_login_request.ts deleted file mode 100644 index f42b45a8..00000000 --- a/src/data/dto/auth/request/guest_login_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/auth/request/index.ts b/src/data/dto/auth/request/index.ts deleted file mode 100644 index f664f27d..00000000 --- a/src/data/dto/auth/request/index.ts +++ /dev/null @@ -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"; diff --git a/src/data/dto/auth/request/login_request.ts b/src/data/dto/auth/request/login_request.ts deleted file mode 100644 index 734097c0..00000000 --- a/src/data/dto/auth/request/login_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/auth/request/refresh_token_request.ts b/src/data/dto/auth/request/refresh_token_request.ts deleted file mode 100644 index ace009f3..00000000 --- a/src/data/dto/auth/request/refresh_token_request.ts +++ /dev/null @@ -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); diff --git a/src/data/dto/auth/request/register_request.ts b/src/data/dto/auth/request/register_request.ts deleted file mode 100644 index 5c9e2d34..00000000 --- a/src/data/dto/auth/request/register_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/auth/response/facebook_identity_response.ts b/src/data/dto/auth/response/facebook_identity_response.ts deleted file mode 100644 index b5b4e338..00000000 --- a/src/data/dto/auth/response/facebook_identity_response.ts +++ /dev/null @@ -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, -); diff --git a/src/data/dto/auth/response/facebook_psid_login_response.ts b/src/data/dto/auth/response/facebook_psid_login_response.ts deleted file mode 100644 index 437115a0..00000000 --- a/src/data/dto/auth/response/facebook_psid_login_response.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/auth/response/guest_login_response.ts b/src/data/dto/auth/response/guest_login_response.ts deleted file mode 100644 index c636d810..00000000 --- a/src/data/dto/auth/response/guest_login_response.ts +++ /dev/null @@ -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; - } -} diff --git a/src/data/dto/auth/response/index.ts b/src/data/dto/auth/response/index.ts deleted file mode 100644 index da8cc91e..00000000 --- a/src/data/dto/auth/response/index.ts +++ /dev/null @@ -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"; diff --git a/src/data/dto/auth/response/login_response.ts b/src/data/dto/auth/response/login_response.ts deleted file mode 100644 index 6fef62aa..00000000 --- a/src/data/dto/auth/response/login_response.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/auth/response/logout_response.ts b/src/data/dto/auth/response/logout_response.ts deleted file mode 100644 index c0ed4e16..00000000 --- a/src/data/dto/auth/response/logout_response.ts +++ /dev/null @@ -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); diff --git a/src/data/dto/auth/response/refresh_token_response.ts b/src/data/dto/auth/response/refresh_token_response.ts deleted file mode 100644 index 31f42fc6..00000000 --- a/src/data/dto/auth/response/refresh_token_response.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/character/character.ts b/src/data/dto/character/character.ts deleted file mode 100644 index 1a438512..00000000 --- a/src/data/dto/character/character.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/character/characters_response.ts b/src/data/dto/character/characters_response.ts deleted file mode 100644 index 9437c314..00000000 --- a/src/data/dto/character/characters_response.ts +++ /dev/null @@ -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()), - }); - } -} diff --git a/src/data/dto/character/index.ts b/src/data/dto/character/index.ts deleted file mode 100644 index 1b7d7a90..00000000 --- a/src/data/dto/character/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./character"; -export * from "./characters_response"; diff --git a/src/data/dto/chat/chat_list_item.ts b/src/data/dto/chat/chat_list_item.ts deleted file mode 100644 index c1b42f0d..00000000 --- a/src/data/dto/chat/chat_list_item.ts +++ /dev/null @@ -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]; diff --git a/src/data/dto/chat/chat_message.ts b/src/data/dto/chat/chat_message.ts deleted file mode 100644 index 57a1fbe6..00000000 --- a/src/data/dto/chat/chat_message.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/chat/index.ts b/src/data/dto/chat/index.ts deleted file mode 100644 index 6af70d90..00000000 --- a/src/data/dto/chat/index.ts +++ /dev/null @@ -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"; diff --git a/src/data/dto/chat/request/index.ts b/src/data/dto/chat/request/index.ts deleted file mode 100644 index 45f1de84..00000000 --- a/src/data/dto/chat/request/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./send_message_request"; -export * from "./unlock_private_request"; -export * from "./unlock_history_request"; diff --git a/src/data/dto/chat/request/send_message_request.ts b/src/data/dto/chat/request/send_message_request.ts deleted file mode 100644 index 98f8dff5..00000000 --- a/src/data/dto/chat/request/send_message_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/chat/request/unlock_history_request.ts b/src/data/dto/chat/request/unlock_history_request.ts deleted file mode 100644 index d763ddd8..00000000 --- a/src/data/dto/chat/request/unlock_history_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/chat/request/unlock_private_request.ts b/src/data/dto/chat/request/unlock_private_request.ts deleted file mode 100644 index adcd9c77..00000000 --- a/src/data/dto/chat/request/unlock_private_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/chat/response/chat_history_response.ts b/src/data/dto/chat/response/chat_history_response.ts deleted file mode 100644 index b563f84b..00000000 --- a/src/data/dto/chat/response/chat_history_response.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/chat/response/chat_send_response.ts b/src/data/dto/chat/response/chat_send_response.ts deleted file mode 100644 index 2f560cfe..00000000 --- a/src/data/dto/chat/response/chat_send_response.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/chat/response/index.ts b/src/data/dto/chat/response/index.ts deleted file mode 100644 index 81bd1dfb..00000000 --- a/src/data/dto/chat/response/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./chat_history_response"; -export * from "./chat_send_response"; -export * from "./unlock_history_response"; -export * from "./unlock_private_response"; diff --git a/src/data/dto/chat/response/unlock_history_response.ts b/src/data/dto/chat/response/unlock_history_response.ts deleted file mode 100644 index 804e0183..00000000 --- a/src/data/dto/chat/response/unlock_history_response.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/chat/response/unlock_private_response.ts b/src/data/dto/chat/response/unlock_private_response.ts deleted file mode 100644 index 732f341d..00000000 --- a/src/data/dto/chat/response/unlock_private_response.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/feedback/index.ts b/src/data/dto/feedback/index.ts deleted file mode 100644 index 8392624c..00000000 --- a/src/data/dto/feedback/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./feedback_submission"; -export * from "./response"; diff --git a/src/data/dto/feedback/response/feedback_submit_response.ts b/src/data/dto/feedback/response/feedback_submit_response.ts deleted file mode 100644 index 784c01a7..00000000 --- a/src/data/dto/feedback/response/feedback_submit_response.ts +++ /dev/null @@ -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); diff --git a/src/data/dto/feedback/response/index.ts b/src/data/dto/feedback/response/index.ts deleted file mode 100644 index 338edb5d..00000000 --- a/src/data/dto/feedback/response/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./feedback_submit_response"; diff --git a/src/data/dto/metrics/index.ts b/src/data/dto/metrics/index.ts deleted file mode 100644 index 1ae97ec0..00000000 --- a/src/data/dto/metrics/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * @file Automatically generated by barrelsby. - */ - -export * from "./request"; diff --git a/src/data/dto/metrics/request/app_event.ts b/src/data/dto/metrics/request/app_event.ts deleted file mode 100644 index 2184ef0c..00000000 --- a/src/data/dto/metrics/request/app_event.ts +++ /dev/null @@ -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; -export const AppEvent = createSchemaDto< - typeof AppEventSchema, - AppEventPublicKey ->(AppEventSchema); diff --git a/src/data/dto/metrics/request/index.ts b/src/data/dto/metrics/request/index.ts deleted file mode 100644 index ae041a3d..00000000 --- a/src/data/dto/metrics/request/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./app_event"; -export * from "./pwa_event"; diff --git a/src/data/dto/metrics/request/pwa_event.ts b/src/data/dto/metrics/request/pwa_event.ts deleted file mode 100644 index 6cfff0d6..00000000 --- a/src/data/dto/metrics/request/pwa_event.ts +++ /dev/null @@ -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; -export const PwaEvent = createSchemaDto< - typeof PwaEventSchema, - PwaEventPublicKey ->(PwaEventSchema); diff --git a/src/data/dto/payment/index.ts b/src/data/dto/payment/index.ts deleted file mode 100644 index cdfe6219..00000000 --- a/src/data/dto/payment/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @file Payment DTO barrel. - */ - -export * from "./payment_plan"; -export * from "./tip_payment_plan"; -export * from "./request"; -export * from "./response"; diff --git a/src/data/dto/payment/payment_plan.ts b/src/data/dto/payment/payment_plan.ts deleted file mode 100644 index 49356e70..00000000 --- a/src/data/dto/payment/payment_plan.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/payment/request/create_payment_order_request.ts b/src/data/dto/payment/request/create_payment_order_request.ts deleted file mode 100644 index 1ceef2c0..00000000 --- a/src/data/dto/payment/request/create_payment_order_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/payment/request/index.ts b/src/data/dto/payment/request/index.ts deleted file mode 100644 index 978d26ec..00000000 --- a/src/data/dto/payment/request/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./create_payment_order_request"; diff --git a/src/data/dto/payment/response/create_payment_order_response.ts b/src/data/dto/payment/response/create_payment_order_response.ts deleted file mode 100644 index 790ccec5..00000000 --- a/src/data/dto/payment/response/create_payment_order_response.ts +++ /dev/null @@ -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; - - 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); - } -} diff --git a/src/data/dto/payment/response/index.ts b/src/data/dto/payment/response/index.ts deleted file mode 100644 index 5dc8d242..00000000 --- a/src/data/dto/payment/response/index.ts +++ /dev/null @@ -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"; diff --git a/src/data/dto/payment/response/payment_order_status_response.ts b/src/data/dto/payment/response/payment_order_status_response.ts deleted file mode 100644 index 9211ad3c..00000000 --- a/src/data/dto/payment/response/payment_order_status_response.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/payment/response/payment_plans_response.ts b/src/data/dto/payment/response/payment_plans_response.ts deleted file mode 100644 index dfe88910..00000000 --- a/src/data/dto/payment/response/payment_plans_response.ts +++ /dev/null @@ -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; diff --git a/src/data/dto/payment/response/tip_payment_plans_response.ts b/src/data/dto/payment/response/tip_payment_plans_response.ts deleted file mode 100644 index 5f8289f3..00000000 --- a/src/data/dto/payment/response/tip_payment_plans_response.ts +++ /dev/null @@ -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()), - }; - } -} diff --git a/src/data/dto/payment/tip_payment_plan.ts b/src/data/dto/payment/tip_payment_plan.ts deleted file mode 100644 index 53814b83..00000000 --- a/src/data/dto/payment/tip_payment_plan.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/private-room/index.ts b/src/data/dto/private-room/index.ts deleted file mode 100644 index 8ea5042e..00000000 --- a/src/data/dto/private-room/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./private_album"; -export * from "./request"; -export * from "./response"; diff --git a/src/data/dto/private-room/private_album.ts b/src/data/dto/private-room/private_album.ts deleted file mode 100644 index 16cbae97..00000000 --- a/src/data/dto/private-room/private_album.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/private-room/request/index.ts b/src/data/dto/private-room/request/index.ts deleted file mode 100644 index 70348de0..00000000 --- a/src/data/dto/private-room/request/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./unlock_private_album_request"; diff --git a/src/data/dto/private-room/request/unlock_private_album_request.ts b/src/data/dto/private-room/request/unlock_private_album_request.ts deleted file mode 100644 index 772da0a4..00000000 --- a/src/data/dto/private-room/request/unlock_private_album_request.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/private-room/response/index.ts b/src/data/dto/private-room/response/index.ts deleted file mode 100644 index e3ff32f4..00000000 --- a/src/data/dto/private-room/response/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./private_albums_response"; -export * from "./private_album_unlock_response"; diff --git a/src/data/dto/private-room/response/private_album_unlock_response.ts b/src/data/dto/private-room/response/private_album_unlock_response.ts deleted file mode 100644 index d696f220..00000000 --- a/src/data/dto/private-room/response/private_album_unlock_response.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/private-room/response/private_albums_response.ts b/src/data/dto/private-room/response/private_albums_response.ts deleted file mode 100644 index bf3d4ad3..00000000 --- a/src/data/dto/private-room/response/private_albums_response.ts +++ /dev/null @@ -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, - }; - } -} diff --git a/src/data/dto/schema_dto.ts b/src/data/dto/schema_dto.ts deleted file mode 100644 index c8f3f613..00000000 --- a/src/data/dto/schema_dto.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { z } from "zod"; - -export type SchemaDto< - TSchema extends z.ZodType, - TPublicKey extends keyof z.output = never, -> = Readonly, TPublicKey>> & { - toJson(): z.output; -}; - -export interface SchemaDtoFactory< - TSchema extends z.ZodType, - TPublicKey extends keyof z.output = never, -> { - readonly prototype: SchemaDto; - [Symbol.hasInstance](value: unknown): boolean; - from(input: z.input): SchemaDto; - fromJson(json: unknown): SchemaDto; -} - -/** - * 为没有自定义映射逻辑的对象 Schema 创建不可变 DTO。 - * - * `TPublicKey` 只暴露业务代码实际读取的字段;完整解析结果仍会保留在实例中, - * 并由 `toJson` 按原 Schema 输出。包含嵌套 DTO 转换或领域方法的模型继续使用 - * 显式 class,避免把业务逻辑隐藏进通用工厂。 - */ -export function createSchemaDto< - TSchema extends z.ZodType, - TPublicKey extends keyof z.output = never, ->(schema: TSchema): SchemaDtoFactory { - class GeneratedSchemaDto { - private constructor(input: z.input) { - Object.assign(this, schema.parse(input) as object); - Object.freeze(this); - } - - static from( - input: z.input, - ): SchemaDto { - return new GeneratedSchemaDto(input) as SchemaDto< - TSchema, - TPublicKey - >; - } - - static fromJson(json: unknown): SchemaDto { - return GeneratedSchemaDto.from(json as z.input); - } - - toJson(): z.output { - return schema.parse(this); - } - } - - return GeneratedSchemaDto as unknown as SchemaDtoFactory< - TSchema, - TPublicKey - >; -} diff --git a/src/data/dto/user/avatar_data.ts b/src/data/dto/user/avatar_data.ts deleted file mode 100644 index edb2dcc8..00000000 --- a/src/data/dto/user/avatar_data.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/user/index.ts b/src/data/dto/user/index.ts deleted file mode 100644 index 7aecee25..00000000 --- a/src/data/dto/user/index.ts +++ /dev/null @@ -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"; diff --git a/src/data/dto/user/personality_traits.ts b/src/data/dto/user/personality_traits.ts deleted file mode 100644 index 8802a2c7..00000000 --- a/src/data/dto/user/personality_traits.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/user/recent_memory.ts b/src/data/dto/user/recent_memory.ts deleted file mode 100644 index 8d363810..00000000 --- a/src/data/dto/user/recent_memory.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/dto/user/user.ts b/src/data/dto/user/user.ts deleted file mode 100644 index bd386cdb..00000000 --- a/src/data/dto/user/user.ts +++ /dev/null @@ -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(), - }); - } -} diff --git a/src/data/dto/user/user_entitlements.ts b/src/data/dto/user/user_entitlements.ts deleted file mode 100644 index e17e3878..00000000 --- a/src/data/dto/user/user_entitlements.ts +++ /dev/null @@ -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); - } -} diff --git a/src/data/repositories/__tests__/auth_repository.facebook_identity.test.ts b/src/data/repositories/__tests__/auth_repository.facebook_identity.test.ts index cd34f9f8..b570654c 100644 --- a/src/data/repositories/__tests__/auth_repository.facebook_identity.test.ts +++ b/src/data/repositories/__tests__/auth_repository.facebook_identity.test.ts @@ -4,7 +4,7 @@ import { FacebookIdentityResponse, FacebookPsidLoginResponse, LoginStatus, -} from "@/data/dto/auth"; +} from "@/data/schemas/auth"; import { AuthRepository } from "@/data/repositories/auth_repository"; import type { AuthApi } from "@/data/services/api"; import type { IAuthStorage } from "@/data/storage/auth"; diff --git a/src/data/repositories/__tests__/auth_repository.session.test.ts b/src/data/repositories/__tests__/auth_repository.session.test.ts index 1c36f23f..c71b2976 100644 --- a/src/data/repositories/__tests__/auth_repository.session.test.ts +++ b/src/data/repositories/__tests__/auth_repository.session.test.ts @@ -5,7 +5,7 @@ import { LoginResponse, LoginStatus, RefreshTokenResponse, -} from "@/data/dto/auth"; +} from "@/data/schemas/auth"; import { AuthRepository } from "@/data/repositories/auth_repository"; import type { AuthApi } from "@/data/services/api"; import type { IAuthStorage } from "@/data/storage/auth"; diff --git a/src/data/repositories/__tests__/chat_cache_identity.test.ts b/src/data/repositories/__tests__/chat_cache_identity.test.ts index ae5b2784..624a1fce 100644 --- a/src/data/repositories/__tests__/chat_cache_identity.test.ts +++ b/src/data/repositories/__tests__/chat_cache_identity.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; -import { LoginStatus } from "@/data/dto/auth"; +import { LoginStatus } from "@/data/schemas/auth"; import { Result } from "@/utils/result"; import { diff --git a/src/data/repositories/__tests__/chat_local_message_store.test.ts b/src/data/repositories/__tests__/chat_local_message_store.test.ts index 9e9bbf43..685a969b 100644 --- a/src/data/repositories/__tests__/chat_local_message_store.test.ts +++ b/src/data/repositories/__tests__/chat_local_message_store.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; -import { ChatMessage } from "@/data/dto/chat"; +import { ChatMessage } from "@/data/schemas/chat"; import { LocalMessage } from "@/data/storage/chat"; import { Result } from "@/utils/result"; diff --git a/src/data/repositories/__tests__/chat_media_cache_helpers.test.ts b/src/data/repositories/__tests__/chat_media_cache_helpers.test.ts index 0dcd2308..9ce5fad1 100644 --- a/src/data/repositories/__tests__/chat_media_cache_helpers.test.ts +++ b/src/data/repositories/__tests__/chat_media_cache_helpers.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { DEFAULT_CHARACTER_ID } from "@/data/constants/character"; -import { ChatMessage, ChatSendResponse } from "@/data/dto/chat"; +import { ChatMessage, ChatSendResponse } from "@/data/schemas/chat"; import { buildChatMediaCacheKey, diff --git a/src/data/repositories/__tests__/payment_repository.test.ts b/src/data/repositories/__tests__/payment_repository.test.ts index ba193e7f..e079ed90 100644 --- a/src/data/repositories/__tests__/payment_repository.test.ts +++ b/src/data/repositories/__tests__/payment_repository.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; -import { TipPaymentPlansResponse } from "@/data/dto/payment"; +import { TipPaymentPlansResponse } from "@/data/schemas/payment"; import { PaymentRepository } from "@/data/repositories/payment_repository"; import type { PaymentApi } from "@/data/services/api"; import { Result } from "@/utils/result"; diff --git a/src/data/repositories/auth_repository.ts b/src/data/repositories/auth_repository.ts index 52f8e3ff..79abe756 100644 --- a/src/data/repositories/auth_repository.ts +++ b/src/data/repositories/auth_repository.ts @@ -15,8 +15,8 @@ import { RefreshTokenRequest, RefreshTokenResponse, RegisterRequest, -} from "@/data/dto/auth"; -import { User } from "@/data/dto/user"; +} from "@/data/schemas/auth"; +import { User } from "@/data/schemas/user"; import { AppEnvUtil } from "@/utils/app-env"; import { PlatformDetector } from "@/utils/platform-detect"; import { Result } from "@/utils/result"; diff --git a/src/data/repositories/character_repository.ts b/src/data/repositories/character_repository.ts index 11a77995..d1fba538 100644 --- a/src/data/repositories/character_repository.ts +++ b/src/data/repositories/character_repository.ts @@ -1,4 +1,4 @@ -import type { CharactersResponse } from "@/data/dto/character"; +import type { CharactersResponse } from "@/data/schemas/character"; import type { ICharacterRepository } from "@/data/repositories/interfaces"; import { CharacterApi, characterApi } from "@/data/services/api/character_api"; import { Result } from "@/utils/result"; diff --git a/src/data/repositories/chat_cache_identity.ts b/src/data/repositories/chat_cache_identity.ts index 786b9325..52796fb8 100644 --- a/src/data/repositories/chat_cache_identity.ts +++ b/src/data/repositories/chat_cache_identity.ts @@ -1,4 +1,4 @@ -import { LoginStatus } from "@/data/dto/auth"; +import { LoginStatus } from "@/data/schemas/auth"; import { AuthStorage, type IAuthStorage } from "@/data/storage/auth"; import { UserStorage, type IUserStorage } from "@/data/storage/user"; import { Result, type Result as ResultT } from "@/utils/result"; diff --git a/src/data/repositories/chat_local_message_store.ts b/src/data/repositories/chat_local_message_store.ts index c9859c25..89408fbd 100644 --- a/src/data/repositories/chat_local_message_store.ts +++ b/src/data/repositories/chat_local_message_store.ts @@ -1,4 +1,4 @@ -import { ChatMessage } from "@/data/dto/chat"; +import { ChatMessage } from "@/data/schemas/chat"; import type { UnlockedPrivateMessageLocalPatch } from "@/data/repositories/interfaces"; import { LocalChatStorage, LocalMessage } from "@/data/storage/chat"; import { Result } from "@/utils/result"; diff --git a/src/data/repositories/chat_media_cache_coordinator.ts b/src/data/repositories/chat_media_cache_coordinator.ts index a3f3650b..175da7b0 100644 --- a/src/data/repositories/chat_media_cache_coordinator.ts +++ b/src/data/repositories/chat_media_cache_coordinator.ts @@ -1,4 +1,4 @@ -import type { ChatMessage, ChatSendResponse } from "@/data/dto/chat"; +import type { ChatMessage, ChatSendResponse } from "@/data/schemas/chat"; import { LocalChatMediaStorage, type LocalChatMediaRow, diff --git a/src/data/repositories/chat_media_cache_helpers.ts b/src/data/repositories/chat_media_cache_helpers.ts index dd17e0c3..71ff7e15 100644 --- a/src/data/repositories/chat_media_cache_helpers.ts +++ b/src/data/repositories/chat_media_cache_helpers.ts @@ -2,7 +2,7 @@ import type { ChatMediaKind, ChatMessage, ChatSendResponse, -} from "@/data/dto/chat"; +} from "@/data/schemas/chat"; import type { CacheRemoteChatMediaInput } from "@/data/repositories/interfaces"; import { isCacheableRemoteChatMediaUrl } from "@/lib/chat/chat_media_url"; diff --git a/src/data/repositories/chat_remote_data_source.ts b/src/data/repositories/chat_remote_data_source.ts index 0a96dfc2..706d998c 100644 --- a/src/data/repositories/chat_remote_data_source.ts +++ b/src/data/repositories/chat_remote_data_source.ts @@ -6,7 +6,7 @@ import { UnlockHistoryResponse, UnlockPrivateRequest, UnlockPrivateResponse, -} from "@/data/dto/chat"; +} from "@/data/schemas/chat"; import type { ChatApi } from "@/data/services/api"; import type { UnlockPrivateMessageInput } from "@/data/repositories/interfaces"; import { Result } from "@/utils/result"; diff --git a/src/data/repositories/chat_repository.ts b/src/data/repositories/chat_repository.ts index bbbaf0d4..998d6e9f 100644 --- a/src/data/repositories/chat_repository.ts +++ b/src/data/repositories/chat_repository.ts @@ -4,7 +4,7 @@ import type { ChatSendResponse, UnlockHistoryResponse, UnlockPrivateResponse, -} from "@/data/dto/chat"; +} from "@/data/schemas/chat"; import { chatApi } from "@/data/services/api"; import { LocalChatMediaStorage, diff --git a/src/data/repositories/feedback_repository.ts b/src/data/repositories/feedback_repository.ts index bea54888..88674ad2 100644 --- a/src/data/repositories/feedback_repository.ts +++ b/src/data/repositories/feedback_repository.ts @@ -1,7 +1,7 @@ import type { FeedbackSubmitResponse, SubmitFeedbackInput, -} from "@/data/dto/feedback"; +} from "@/data/schemas/feedback"; import type { IFeedbackRepository } from "@/data/repositories/interfaces"; import { FeedbackApi, feedbackApi } from "@/data/services/api/feedback_api"; import { Result } from "@/utils/result"; diff --git a/src/data/repositories/interfaces/iauth_repository.ts b/src/data/repositories/interfaces/iauth_repository.ts index 7d443d43..79f90b7c 100644 --- a/src/data/repositories/interfaces/iauth_repository.ts +++ b/src/data/repositories/interfaces/iauth_repository.ts @@ -17,8 +17,8 @@ import type { LoginStatus, LoginResponse, RefreshTokenResponse, -} from "@/data/dto/auth"; -import type { User } from "@/data/dto/user"; +} from "@/data/schemas/auth"; +import type { User } from "@/data/schemas/user"; export interface IAuthRepository { /** 用户注册。注册成功不会自动登录(与 Dart 行为一致)。 */ diff --git a/src/data/repositories/interfaces/icharacter_repository.ts b/src/data/repositories/interfaces/icharacter_repository.ts index 333d7a64..f75f93a6 100644 --- a/src/data/repositories/interfaces/icharacter_repository.ts +++ b/src/data/repositories/interfaces/icharacter_repository.ts @@ -1,4 +1,4 @@ -import type { CharactersResponse } from "@/data/dto/character"; +import type { CharactersResponse } from "@/data/schemas/character"; import type { Result } from "@/utils/result"; export interface ICharacterRepository { diff --git a/src/data/repositories/interfaces/ichat_repository.ts b/src/data/repositories/interfaces/ichat_repository.ts index fbd553ad..a80ee0bc 100644 --- a/src/data/repositories/interfaces/ichat_repository.ts +++ b/src/data/repositories/interfaces/ichat_repository.ts @@ -12,7 +12,7 @@ import type { ChatSendResponse, UnlockHistoryResponse, UnlockPrivateResponse, -} from "@/data/dto/chat"; +} from "@/data/schemas/chat"; import type { ChatLockDetailData } from "@/data/schemas/chat"; import type { ChatImageData, ChatLockType } from "@/data/schemas/chat"; import type { LocalChatMediaRow } from "@/data/storage/chat"; diff --git a/src/data/repositories/interfaces/ifeedback_repository.ts b/src/data/repositories/interfaces/ifeedback_repository.ts index 323952ae..97fe70fa 100644 --- a/src/data/repositories/interfaces/ifeedback_repository.ts +++ b/src/data/repositories/interfaces/ifeedback_repository.ts @@ -1,7 +1,7 @@ import type { FeedbackSubmitResponse, SubmitFeedbackInput, -} from "@/data/dto/feedback"; +} from "@/data/schemas/feedback"; import type { Result } from "@/utils/result"; export interface IFeedbackRepository { diff --git a/src/data/repositories/interfaces/ipayment_repository.ts b/src/data/repositories/interfaces/ipayment_repository.ts index 96fce23e..7494a5fc 100644 --- a/src/data/repositories/interfaces/ipayment_repository.ts +++ b/src/data/repositories/interfaces/ipayment_repository.ts @@ -6,7 +6,7 @@ import type { PayChannel, PaymentOrderStatusResponse, PaymentPlansResponse, -} from "@/data/dto/payment"; +} from "@/data/schemas/payment"; import type { Result } from "@/utils/result"; export interface IPaymentRepository { diff --git a/src/data/repositories/interfaces/iprivate_room_repository.ts b/src/data/repositories/interfaces/iprivate_room_repository.ts index 91bcbc11..e929db89 100644 --- a/src/data/repositories/interfaces/iprivate_room_repository.ts +++ b/src/data/repositories/interfaces/iprivate_room_repository.ts @@ -1,7 +1,7 @@ import type { PrivateAlbumsResponse, PrivateAlbumUnlockResponse, -} from "@/data/dto/private-room"; +} from "@/data/schemas/private-room"; import type { Result } from "@/utils/result"; export interface GetPrivateAlbumsInput { diff --git a/src/data/repositories/interfaces/iuser_repository.ts b/src/data/repositories/interfaces/iuser_repository.ts index 38bb269a..81352dac 100644 --- a/src/data/repositories/interfaces/iuser_repository.ts +++ b/src/data/repositories/interfaces/iuser_repository.ts @@ -6,7 +6,7 @@ import type { Result } from "@/utils/result"; import type { User, UserEntitlements, -} from "@/data/dto/user"; +} from "@/data/schemas/user"; export interface IUserRepository { /** 获取当前登录用户信息。 */ diff --git a/src/data/repositories/metrics_repository.ts b/src/data/repositories/metrics_repository.ts index 1221f827..2e4941dd 100644 --- a/src/data/repositories/metrics_repository.ts +++ b/src/data/repositories/metrics_repository.ts @@ -4,7 +4,7 @@ * 指标/埋点仓库:纯远程 fire-and-forget 上报,无本地状态。 */ import { MetricsApi, metricsApi } from "@/data/services/api"; -import { AppEvent, PwaEvent } from "@/data/dto/metrics"; +import { AppEvent, PwaEvent } from "@/data/schemas/metrics"; import { Result } from "@/utils/result"; import type { IMetricsRepository } from "@/data/repositories/interfaces"; import { createLazySingleton } from "./lazy_singleton"; diff --git a/src/data/repositories/payment_repository.ts b/src/data/repositories/payment_repository.ts index 7f47c0b8..be5e5dbd 100644 --- a/src/data/repositories/payment_repository.ts +++ b/src/data/repositories/payment_repository.ts @@ -9,7 +9,7 @@ import { PayChannel, PaymentOrderStatusResponse, PaymentPlansResponse, -} from "@/data/dto/payment"; +} from "@/data/schemas/payment"; import { PaymentApi, paymentApi } from "@/data/services/api"; import { PaymentPlansStorage } from "@/data/storage/payment"; import type { IPaymentRepository } from "@/data/repositories/interfaces"; diff --git a/src/data/repositories/private_room_repository.ts b/src/data/repositories/private_room_repository.ts index 4461e20c..ec577862 100644 --- a/src/data/repositories/private_room_repository.ts +++ b/src/data/repositories/private_room_repository.ts @@ -2,7 +2,7 @@ import { UnlockPrivateAlbumRequest, type PrivateAlbumsResponse, type PrivateAlbumUnlockResponse, -} from "@/data/dto/private-room"; +} from "@/data/schemas/private-room"; import { PrivateRoomApi, privateRoomApi, diff --git a/src/data/repositories/user_repository.ts b/src/data/repositories/user_repository.ts index 4eb2f267..2c1c387c 100644 --- a/src/data/repositories/user_repository.ts +++ b/src/data/repositories/user_repository.ts @@ -2,7 +2,7 @@ import { UserApi, userApi } from "@/data/services/api"; import { User, UserEntitlements, -} from "@/data/dto/user"; +} from "@/data/schemas/user"; import { Result } from "@/utils/result"; import type { IUserRepository } from "@/data/repositories/interfaces"; import { createLazySingleton } from "./lazy_singleton"; diff --git a/src/data/schemas/__tests__/schema_model.test.ts b/src/data/schemas/__tests__/schema_model.test.ts new file mode 100644 index 00000000..04a9195b --- /dev/null +++ b/src/data/schemas/__tests__/schema_model.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; +import { z } from "zod"; + +import { + createSchemaModel, + type SchemaModel, +} from "../schema_model"; + +const ExampleSchema = z.object({ + id: z.string(), + traceId: z.string().default("generated-trace"), +}); + +type ExampleModel = SchemaModel; +const ExampleModel = createSchemaModel( + ExampleSchema, +); + +describe("createSchemaModel", () => { + it("parses, freezes and serializes through the source schema", () => { + const model: ExampleModel = ExampleModel.from({ id: "item-1" }); + + expect(model).toBeInstanceOf(ExampleModel); + expect(Object.isFrozen(model)).toBe(true); + expect(model.id).toBe("item-1"); + expect(model.toJson()).toEqual({ + id: "item-1", + traceId: "generated-trace", + }); + }); + + it("keeps non-public schema fields out of the declared model interface", () => { + const model = ExampleModel.fromJson({ id: "item-1", traceId: "trace-1" }); + + // @ts-expect-error traceId is serialized but not part of the public API. + expect(model.traceId).toBe("trace-1"); + }); + + it("preserves schema validation errors", () => { + expect(() => ExampleModel.fromJson({ id: 1 })).toThrow(z.ZodError); + }); +}); diff --git a/src/data/dto/auth/__tests__/facebook_identity.test.ts b/src/data/schemas/auth/__tests__/facebook_identity.test.ts similarity index 92% rename from src/data/dto/auth/__tests__/facebook_identity.test.ts rename to src/data/schemas/auth/__tests__/facebook_identity.test.ts index 9faf251c..d0f2b117 100644 --- a/src/data/dto/auth/__tests__/facebook_identity.test.ts +++ b/src/data/schemas/auth/__tests__/facebook_identity.test.ts @@ -4,10 +4,10 @@ import { FacebookIdentityResponse, FacebookPsidLoginResponse, LoginRequest, -} from "@/data/dto/auth"; +} from "@/data/schemas/auth"; import { UserSchema } from "@/data/schemas/user/user"; -describe("facebook identity DTO/schema", () => { +describe("facebook identity schema models", () => { it("parses facebook identity binding responses", () => { const response = FacebookIdentityResponse.fromJson({ fbAsid: "asid-1", @@ -63,7 +63,7 @@ describe("facebook identity DTO/schema", () => { }); }); - it("keeps psid in request serialization without requiring DTO field declarations", () => { + it("keeps psid in request serialization without a public field declaration", () => { const request = LoginRequest.from({ email: "user@example.com", password: "password", diff --git a/src/data/schemas/auth/facebook_user_data.ts b/src/data/schemas/auth/facebook_user_data.ts index 1672a849..52f8a52e 100644 --- a/src/data/schemas/auth/facebook_user_data.ts +++ b/src/data/schemas/auth/facebook_user_data.ts @@ -17,3 +17,28 @@ export const FacebookUserDataSchema = z.object({ export type FacebookUserDataInput = z.input; export type FacebookUserDataData = z.output; + +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); + } +} diff --git a/src/data/schemas/auth/index.ts b/src/data/schemas/auth/index.ts index 4d862a88..d9e3eb62 100644 --- a/src/data/schemas/auth/index.ts +++ b/src/data/schemas/auth/index.ts @@ -3,6 +3,7 @@ */ export * from "./facebook_user_data"; +export * from "./login_status"; export * from "./request/facebook_identity_request"; export * from "./request/facebook_login_request"; export * from "./request/facebook_psid_login_request"; diff --git a/src/data/dto/auth/login_status.ts b/src/data/schemas/auth/login_status.ts similarity index 55% rename from src/data/dto/auth/login_status.ts rename to src/data/schemas/auth/login_status.ts index c9a54ca4..5800214c 100644 --- a/src/data/dto/auth/login_status.ts +++ b/src/data/schemas/auth/login_status.ts @@ -1,16 +1,9 @@ -/** - * 登录状态枚举 - */ +/** Login status persisted by the auth data layer. */ export const LoginStatus = { - /** 未登录(默认初值) */ NotLoggedIn: "notLoggedIn", - /** 游客登录 */ Guest: "guest", - /** Facebook OAuth 登录 */ Facebook: "facebook", - /** Google OAuth 登录 */ Google: "google", - /** 邮箱 + 密码登录 */ Email: "email", } as const; diff --git a/src/data/schemas/auth/request/facebook_identity_request.ts b/src/data/schemas/auth/request/facebook_identity_request.ts index 661a3992..b56658f0 100644 --- a/src/data/schemas/auth/request/facebook_identity_request.ts +++ b/src/data/schemas/auth/request/facebook_identity_request.ts @@ -3,6 +3,11 @@ */ import { z } from "zod"; +import { + createSchemaModel, + type SchemaModel, +} from "../../schema_model"; + import { stringOrEmpty } from "../../nullable-defaults"; export const FacebookIdentityRequestSchema = z.object({ @@ -16,3 +21,10 @@ export type FacebookIdentityRequestInput = z.input< export type FacebookIdentityRequestData = z.output< typeof FacebookIdentityRequestSchema >; + +export type FacebookIdentityRequest = SchemaModel< + typeof FacebookIdentityRequestSchema +>; +export const FacebookIdentityRequest = createSchemaModel( + FacebookIdentityRequestSchema, +); diff --git a/src/data/schemas/auth/request/facebook_login_request.ts b/src/data/schemas/auth/request/facebook_login_request.ts index 3afa6aab..603ed73b 100644 --- a/src/data/schemas/auth/request/facebook_login_request.ts +++ b/src/data/schemas/auth/request/facebook_login_request.ts @@ -16,3 +16,28 @@ export const FacebookLoginRequestSchema = z.object({ export type FacebookLoginRequestInput = z.input; export type FacebookLoginRequestData = z.output; + +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); + } +} diff --git a/src/data/schemas/auth/request/facebook_psid_login_request.ts b/src/data/schemas/auth/request/facebook_psid_login_request.ts index 005bf912..0435b6d1 100644 --- a/src/data/schemas/auth/request/facebook_psid_login_request.ts +++ b/src/data/schemas/auth/request/facebook_psid_login_request.ts @@ -3,6 +3,11 @@ */ import { z } from "zod"; +import { + createSchemaModel, + type SchemaModel, +} from "../../schema_model"; + import { booleanOrTrue, stringOrEmpty } from "../../nullable-defaults"; export const FacebookPsidLoginRequestSchema = z.object({ @@ -17,3 +22,10 @@ export type FacebookPsidLoginRequestInput = z.input< export type FacebookPsidLoginRequestData = z.output< typeof FacebookPsidLoginRequestSchema >; + +export type FacebookPsidLoginRequest = SchemaModel< + typeof FacebookPsidLoginRequestSchema +>; +export const FacebookPsidLoginRequest = createSchemaModel( + FacebookPsidLoginRequestSchema, +); diff --git a/src/data/schemas/auth/request/fb_id_login_request.ts b/src/data/schemas/auth/request/fb_id_login_request.ts index 2975097b..d20121ce 100644 --- a/src/data/schemas/auth/request/fb_id_login_request.ts +++ b/src/data/schemas/auth/request/fb_id_login_request.ts @@ -15,3 +15,27 @@ export const FbIdLoginRequestSchema = z.object({ export type FbIdLoginRequestInput = z.input; export type FbIdLoginRequestData = z.output; + +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); + } +} diff --git a/src/data/schemas/auth/request/google_login_request.ts b/src/data/schemas/auth/request/google_login_request.ts index 03653fcd..6ed9975b 100644 --- a/src/data/schemas/auth/request/google_login_request.ts +++ b/src/data/schemas/auth/request/google_login_request.ts @@ -16,3 +16,28 @@ export const GoogleLoginRequestSchema = z.object({ export type GoogleLoginRequestInput = z.input; export type GoogleLoginRequestData = z.output; + +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); + } +} diff --git a/src/data/schemas/auth/request/guest_login_request.ts b/src/data/schemas/auth/request/guest_login_request.ts index c2fd5367..c836a272 100644 --- a/src/data/schemas/auth/request/guest_login_request.ts +++ b/src/data/schemas/auth/request/guest_login_request.ts @@ -13,3 +13,26 @@ export const GuestLoginRequestSchema = z.object({ export type GuestLoginRequestInput = z.input; export type GuestLoginRequestData = z.output; + +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); + } +} diff --git a/src/data/schemas/auth/request/login_request.ts b/src/data/schemas/auth/request/login_request.ts index a43a640a..0b7ed9ec 100644 --- a/src/data/schemas/auth/request/login_request.ts +++ b/src/data/schemas/auth/request/login_request.ts @@ -18,3 +18,30 @@ export const LoginRequestSchema = z.object({ export type LoginRequestInput = z.input; export type LoginRequestData = z.output; + +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); + } +} diff --git a/src/data/schemas/auth/request/refresh_token_request.ts b/src/data/schemas/auth/request/refresh_token_request.ts index a1cce575..64a81e00 100644 --- a/src/data/schemas/auth/request/refresh_token_request.ts +++ b/src/data/schemas/auth/request/refresh_token_request.ts @@ -4,9 +4,23 @@ */ import { z } from "zod"; +import { + createSchemaModel, + type SchemaModel, +} from "../../schema_model"; + export const RefreshTokenRequestSchema = z.object({ refreshToken: z.string(), }); export type RefreshTokenRequestInput = z.input; export type RefreshTokenRequestData = z.output; + +export type RefreshTokenRequest = SchemaModel< + typeof RefreshTokenRequestSchema, + "refreshToken" +>; +export const RefreshTokenRequest = createSchemaModel< + typeof RefreshTokenRequestSchema, + "refreshToken" +>(RefreshTokenRequestSchema); diff --git a/src/data/schemas/auth/request/register_request.ts b/src/data/schemas/auth/request/register_request.ts index 1c30728d..a2ad1a98 100644 --- a/src/data/schemas/auth/request/register_request.ts +++ b/src/data/schemas/auth/request/register_request.ts @@ -17,3 +17,30 @@ export const RegisterRequestSchema = z.object({ export type RegisterRequestInput = z.input; export type RegisterRequestData = z.output; + +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); + } +} diff --git a/src/data/schemas/auth/response/facebook_identity_response.ts b/src/data/schemas/auth/response/facebook_identity_response.ts index 3f2345bb..bfb6b1a9 100644 --- a/src/data/schemas/auth/response/facebook_identity_response.ts +++ b/src/data/schemas/auth/response/facebook_identity_response.ts @@ -3,6 +3,11 @@ */ import { z } from "zod"; +import { + createSchemaModel, + type SchemaModel, +} from "../../schema_model"; + import { arrayOrEmpty, stringOrEmpty } from "../../nullable-defaults"; export const FacebookIdentityBindingSchema = z.object({ @@ -25,3 +30,10 @@ export type FacebookIdentityResponseInput = z.input< export type FacebookIdentityResponseData = z.output< typeof FacebookIdentityResponseSchema >; + +export type FacebookIdentityResponse = SchemaModel< + typeof FacebookIdentityResponseSchema +>; +export const FacebookIdentityResponse = createSchemaModel( + FacebookIdentityResponseSchema, +); diff --git a/src/data/schemas/auth/response/facebook_psid_login_response.ts b/src/data/schemas/auth/response/facebook_psid_login_response.ts index ea49e81b..24ce71b5 100644 --- a/src/data/schemas/auth/response/facebook_psid_login_response.ts +++ b/src/data/schemas/auth/response/facebook_psid_login_response.ts @@ -28,3 +28,27 @@ export type FacebookPsidLoginResponseInput = z.input< export type FacebookPsidLoginResponseData = z.output< typeof FacebookPsidLoginResponseSchema >; + +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); + } +} diff --git a/src/data/schemas/auth/response/guest_login_response.ts b/src/data/schemas/auth/response/guest_login_response.ts index 81da27eb..fa25bd8b 100644 --- a/src/data/schemas/auth/response/guest_login_response.ts +++ b/src/data/schemas/auth/response/guest_login_response.ts @@ -5,7 +5,7 @@ import { z } from "zod"; import { booleanOrTrue, numberOr, schemaOrNull } from "../../nullable-defaults"; -import { UserSchema } from "../../user/user"; +import { User, UserSchema } from "../../user/user"; export const GuestLoginResponseSchema = z.object({ token: z.string(), @@ -18,3 +18,31 @@ export const GuestLoginResponseSchema = z.object({ export type GuestLoginResponseInput = z.input; export type GuestLoginResponseData = z.output; + +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; + } +} diff --git a/src/data/schemas/auth/response/login_response.ts b/src/data/schemas/auth/response/login_response.ts index dec0c3c6..23423b36 100644 --- a/src/data/schemas/auth/response/login_response.ts +++ b/src/data/schemas/auth/response/login_response.ts @@ -5,7 +5,7 @@ import { z } from "zod"; import { stringOrEmpty } from "../../nullable-defaults"; -import { UserSchema } from "../../user/user"; +import { User, UserSchema } from "../../user/user"; export const LoginResponseSchema = z.object({ user: UserSchema, @@ -15,3 +15,30 @@ export const LoginResponseSchema = z.object({ export type LoginResponseInput = z.input; export type LoginResponseData = z.output; + +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); + } +} diff --git a/src/data/schemas/auth/response/logout_response.ts b/src/data/schemas/auth/response/logout_response.ts index 50c9526f..9b1d738d 100644 --- a/src/data/schemas/auth/response/logout_response.ts +++ b/src/data/schemas/auth/response/logout_response.ts @@ -4,6 +4,11 @@ */ import { z } from "zod"; +import { + createSchemaModel, + type SchemaModel, +} from "../../schema_model"; + import { booleanOrTrue } from "../../nullable-defaults"; export const LogoutResponseSchema = z.object({ @@ -12,3 +17,12 @@ export const LogoutResponseSchema = z.object({ export type LogoutResponseInput = z.input; export type LogoutResponseData = z.output; + +export type LogoutResponse = SchemaModel< + typeof LogoutResponseSchema, + "success" +>; +export const LogoutResponse = createSchemaModel< + typeof LogoutResponseSchema, + "success" +>(LogoutResponseSchema); diff --git a/src/data/schemas/auth/response/refresh_token_response.ts b/src/data/schemas/auth/response/refresh_token_response.ts index e8f323b9..163a360b 100644 --- a/src/data/schemas/auth/response/refresh_token_response.ts +++ b/src/data/schemas/auth/response/refresh_token_response.ts @@ -12,3 +12,26 @@ export const RefreshTokenResponseSchema = z.object({ export type RefreshTokenResponseInput = z.input; export type RefreshTokenResponseData = z.output; + +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); + } +} diff --git a/src/data/dto/character/__tests__/character.test.ts b/src/data/schemas/character/__tests__/character.test.ts similarity index 95% rename from src/data/dto/character/__tests__/character.test.ts rename to src/data/schemas/character/__tests__/character.test.ts index 61a1ad69..1c51005c 100644 --- a/src/data/dto/character/__tests__/character.test.ts +++ b/src/data/schemas/character/__tests__/character.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { Character } from "@/data/dto/character"; +import { Character } from "@/data/schemas/character"; describe("Character", () => { it("keeps only the four public character fields", () => { diff --git a/src/data/schemas/character/character.ts b/src/data/schemas/character/character.ts index 566f3bd6..81555900 100644 --- a/src/data/schemas/character/character.ts +++ b/src/data/schemas/character/character.ts @@ -11,3 +11,27 @@ export const CharacterSchema = z.object({ export type CharacterInput = z.input; export type CharacterData = z.output; + +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); + } +} diff --git a/src/data/schemas/character/characters_response.ts b/src/data/schemas/character/characters_response.ts index 3395968b..c8975b33 100644 --- a/src/data/schemas/character/characters_response.ts +++ b/src/data/schemas/character/characters_response.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { CharacterSchema } from "./character"; +import { Character, CharacterSchema } from "./character"; export const CharactersResponseSchema = z.object({ items: z.array(CharacterSchema).default([]), @@ -8,3 +8,29 @@ export const CharactersResponseSchema = z.object({ export type CharactersResponseInput = z.input; export type CharactersResponseData = z.output; + +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()), + }); + } +} diff --git a/src/data/schemas/character/index.ts b/src/data/schemas/character/index.ts index 1b7d7a90..163c2bba 100644 --- a/src/data/schemas/character/index.ts +++ b/src/data/schemas/character/index.ts @@ -1,2 +1,6 @@ +/** + * @file Automatically generated by barrelsby. + */ + export * from "./character"; export * from "./characters_response"; diff --git a/src/data/dto/chat/__tests__/chat_history_response.test.ts b/src/data/schemas/chat/__tests__/chat_history_response.test.ts similarity index 86% rename from src/data/dto/chat/__tests__/chat_history_response.test.ts rename to src/data/schemas/chat/__tests__/chat_history_response.test.ts index 9b08cd37..c81529f4 100644 --- a/src/data/dto/chat/__tests__/chat_history_response.test.ts +++ b/src/data/schemas/chat/__tests__/chat_history_response.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { ChatHistoryResponse } from "@/data/dto/chat"; +import { ChatHistoryResponse } from "@/data/schemas/chat"; describe("ChatHistoryResponse", () => { it("exposes the pagination total and limit used by chat history", () => { diff --git a/src/data/dto/chat/__tests__/unlock_private_request.test.ts b/src/data/schemas/chat/__tests__/unlock_private_request.test.ts similarity index 93% rename from src/data/dto/chat/__tests__/unlock_private_request.test.ts rename to src/data/schemas/chat/__tests__/unlock_private_request.test.ts index f326150f..b0e9361a 100644 --- a/src/data/dto/chat/__tests__/unlock_private_request.test.ts +++ b/src/data/schemas/chat/__tests__/unlock_private_request.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { DEFAULT_CHARACTER_ID } from "@/data/constants/character"; -import { UnlockPrivateRequest } from "@/data/dto/chat"; +import { UnlockPrivateRequest } from "@/data/schemas/chat"; describe("UnlockPrivateRequest", () => { it("serializes an existing backend message unlock", () => { diff --git a/src/data/dto/chat/__tests__/unlock_private_response.test.ts b/src/data/schemas/chat/__tests__/unlock_private_response.test.ts similarity index 98% rename from src/data/dto/chat/__tests__/unlock_private_response.test.ts rename to src/data/schemas/chat/__tests__/unlock_private_response.test.ts index 64b4089e..b8295495 100644 --- a/src/data/dto/chat/__tests__/unlock_private_response.test.ts +++ b/src/data/schemas/chat/__tests__/unlock_private_response.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { UnlockPrivateResponse } from "@/data/dto/chat"; +import { UnlockPrivateResponse } from "@/data/schemas/chat"; describe("UnlockPrivateResponse", () => { it("parses a successful single-message unlock response", () => { diff --git a/src/data/dto/chat/chat_media.ts b/src/data/schemas/chat/chat_media.ts similarity index 100% rename from src/data/dto/chat/chat_media.ts rename to src/data/schemas/chat/chat_media.ts diff --git a/src/data/schemas/chat/chat_message.ts b/src/data/schemas/chat/chat_message.ts index 0d6dbb18..c409d1b0 100644 --- a/src/data/schemas/chat/chat_message.ts +++ b/src/data/schemas/chat/chat_message.ts @@ -5,7 +5,12 @@ import { z } from "zod"; import { stringOr, stringOrEmpty, stringOrNull } from "../nullable-defaults"; -import { ChatImageSchema, ChatLockDetailSchema } from "./chat_payloads"; +import { + ChatImageSchema, + ChatLockDetailSchema, + type ChatImageData, + type ChatLockDetailData, +} from "./chat_payloads"; export const ChatMessageSchema = z .object({ @@ -26,3 +31,32 @@ export const ChatMessageSchema = z export type ChatMessageInput = z.input; export type ChatMessageData = z.output; + +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); + } +} diff --git a/src/data/schemas/chat/index.ts b/src/data/schemas/chat/index.ts index 042d450f..729d569a 100644 --- a/src/data/schemas/chat/index.ts +++ b/src/data/schemas/chat/index.ts @@ -3,9 +3,11 @@ */ export * from "./chat_lock_type"; +export * from "./chat_media"; export * from "./chat_message"; export * from "./chat_payloads"; export * from "./request/send_message_request"; +export * from "./request/unlock_history_request"; export * from "./request/unlock_private_request"; export * from "./response/chat_history_response"; export * from "./response/chat_send_response"; diff --git a/src/data/schemas/chat/request/send_message_request.ts b/src/data/schemas/chat/request/send_message_request.ts index c666154e..dd359fed 100644 --- a/src/data/schemas/chat/request/send_message_request.ts +++ b/src/data/schemas/chat/request/send_message_request.ts @@ -25,3 +25,33 @@ export const SendMessageRequestSchema = z.object({ export type SendMessageRequestInput = z.input; export type SendMessageRequestData = z.output; + +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); + } +} diff --git a/src/data/schemas/chat/request/unlock_history_request.ts b/src/data/schemas/chat/request/unlock_history_request.ts index db93d97c..a28eed8b 100644 --- a/src/data/schemas/chat/request/unlock_history_request.ts +++ b/src/data/schemas/chat/request/unlock_history_request.ts @@ -10,3 +10,20 @@ export type UnlockHistoryRequestInput = z.input< export type UnlockHistoryRequestData = z.output< typeof UnlockHistoryRequestSchema >; + +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); + } +} diff --git a/src/data/schemas/chat/request/unlock_private_request.ts b/src/data/schemas/chat/request/unlock_private_request.ts index 5ccb0ee4..90ce5659 100644 --- a/src/data/schemas/chat/request/unlock_private_request.ts +++ b/src/data/schemas/chat/request/unlock_private_request.ts @@ -22,3 +22,28 @@ export type UnlockPrivateRequestInput = z.input< export type UnlockPrivateRequestData = z.output< typeof UnlockPrivateRequestSchema >; + +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); + } +} diff --git a/src/data/schemas/chat/response/chat_history_response.ts b/src/data/schemas/chat/response/chat_history_response.ts index 39c03fa0..c24f31ab 100644 --- a/src/data/schemas/chat/response/chat_history_response.ts +++ b/src/data/schemas/chat/response/chat_history_response.ts @@ -9,7 +9,7 @@ import { booleanOrFalse, numberOrZero, } from "../../nullable-defaults"; -import { ChatMessageSchema } from "../chat_message"; +import { ChatMessage, ChatMessageSchema } from "../chat_message"; export const ChatHistoryResponseSchema = z.object({ messages: arrayOrEmpty(ChatMessageSchema), @@ -24,3 +24,30 @@ export const ChatHistoryResponseSchema = z.object({ export type ChatHistoryResponseInput = z.input; export type ChatHistoryResponseData = z.output; + +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); + } +} diff --git a/src/data/schemas/chat/response/chat_send_response.ts b/src/data/schemas/chat/response/chat_send_response.ts index 22c9539d..0107d272 100644 --- a/src/data/schemas/chat/response/chat_send_response.ts +++ b/src/data/schemas/chat/response/chat_send_response.ts @@ -10,7 +10,12 @@ import { numberOrZero, stringOrEmpty, } from "../../nullable-defaults"; -import { ChatImageSchema, ChatLockDetailSchema } from "../chat_payloads"; +import { + ChatImageSchema, + ChatLockDetailSchema, + type ChatImageData, + type ChatLockDetailData, +} from "../chat_payloads"; export const ChatSendResponseSchema = z.object({ reply: stringOrEmpty, @@ -30,3 +35,35 @@ export const ChatSendResponseSchema = z.object({ export type ChatSendResponseInput = z.input; export type ChatSendResponseData = z.output; + +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); + } +} diff --git a/src/data/schemas/chat/response/unlock_history_response.ts b/src/data/schemas/chat/response/unlock_history_response.ts index 5aafe7cd..69f57885 100644 --- a/src/data/schemas/chat/response/unlock_history_response.ts +++ b/src/data/schemas/chat/response/unlock_history_response.ts @@ -41,3 +41,27 @@ export type UnlockHistoryResponseInput = z.input< export type UnlockHistoryResponseData = z.output< typeof UnlockHistoryResponseSchema >; + +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); + } +} diff --git a/src/data/schemas/chat/response/unlock_private_response.ts b/src/data/schemas/chat/response/unlock_private_response.ts index e3d907be..38ee1b31 100644 --- a/src/data/schemas/chat/response/unlock_private_response.ts +++ b/src/data/schemas/chat/response/unlock_private_response.ts @@ -6,7 +6,7 @@ import { stringOrEmpty, stringOrNull, } from "../../nullable-defaults"; -import { ChatImageSchema } from "../chat_payloads"; +import { ChatImageSchema, type ChatImageData } from "../chat_payloads"; import { ChatLockTypeSchema } from "../chat_lock_type"; /** @@ -36,3 +36,37 @@ export type UnlockPrivateResponseInput = z.input< export type UnlockPrivateResponseData = z.output< typeof UnlockPrivateResponseSchema >; + +/** + * 单条历史付费 / 私密消息解锁响应数据类。 + */ +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); + } +} diff --git a/src/data/dto/feedback/feedback_submission.ts b/src/data/schemas/feedback/feedback_submission.ts similarity index 100% rename from src/data/dto/feedback/feedback_submission.ts rename to src/data/schemas/feedback/feedback_submission.ts diff --git a/src/data/schemas/feedback/index.ts b/src/data/schemas/feedback/index.ts index 9d2cfb1f..69aa1dfc 100644 --- a/src/data/schemas/feedback/index.ts +++ b/src/data/schemas/feedback/index.ts @@ -1 +1,6 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from "./feedback_submission"; export * from "./response"; diff --git a/src/data/schemas/feedback/response/feedback_submit_response.ts b/src/data/schemas/feedback/response/feedback_submit_response.ts index 8fd7ca95..76a6d87c 100644 --- a/src/data/schemas/feedback/response/feedback_submit_response.ts +++ b/src/data/schemas/feedback/response/feedback_submit_response.ts @@ -1,5 +1,10 @@ import { z } from "zod"; +import { + createSchemaModel, + type SchemaModel, +} from "../../schema_model"; + export const FeedbackSubmitResponseSchema = z.object({ feedbackId: z.string().min(1), }); @@ -10,3 +15,12 @@ export type FeedbackSubmitResponseInput = z.input< export type FeedbackSubmitResponseData = z.output< typeof FeedbackSubmitResponseSchema >; + +export type FeedbackSubmitResponse = SchemaModel< + typeof FeedbackSubmitResponseSchema, + "feedbackId" +>; +export const FeedbackSubmitResponse = createSchemaModel< + typeof FeedbackSubmitResponseSchema, + "feedbackId" +>(FeedbackSubmitResponseSchema); diff --git a/src/data/schemas/feedback/response/index.ts b/src/data/schemas/feedback/response/index.ts index 338edb5d..d58543e2 100644 --- a/src/data/schemas/feedback/response/index.ts +++ b/src/data/schemas/feedback/response/index.ts @@ -1 +1,5 @@ +/** + * @file Automatically generated by barrelsby. + */ + export * from "./feedback_submit_response"; diff --git a/src/data/schemas/metrics/request/app_event.ts b/src/data/schemas/metrics/request/app_event.ts index 293e149d..eed49773 100644 --- a/src/data/schemas/metrics/request/app_event.ts +++ b/src/data/schemas/metrics/request/app_event.ts @@ -4,6 +4,11 @@ */ import { z } from "zod"; +import { + createSchemaModel, + type SchemaModel, +} from "../../schema_model"; + import { stringOrEmpty } from "../../nullable-defaults"; export const AppEventSchema = z.object({ @@ -14,3 +19,10 @@ export const AppEventSchema = z.object({ export type AppEventInput = z.input; export type AppEventData = z.output; + +type AppEventPublicKey = "userId" | "browser" | "userAgent"; +export type AppEvent = SchemaModel; +export const AppEvent = createSchemaModel< + typeof AppEventSchema, + AppEventPublicKey +>(AppEventSchema); diff --git a/src/data/schemas/metrics/request/pwa_event.ts b/src/data/schemas/metrics/request/pwa_event.ts index c67eb714..b92994c1 100644 --- a/src/data/schemas/metrics/request/pwa_event.ts +++ b/src/data/schemas/metrics/request/pwa_event.ts @@ -4,6 +4,11 @@ */ import { z } from "zod"; +import { + createSchemaModel, + type SchemaModel, +} from "../../schema_model"; + import { booleanOrFalse, numberOrZero, @@ -20,3 +25,15 @@ export const PwaEventSchema = z.object({ export type PwaEventInput = z.input; export type PwaEventData = z.output; + +type PwaEventPublicKey = + | "deviceId" + | "deviceType" + | "timestamp" + | "pwaInstalled" + | "pwaSupported"; +export type PwaEvent = SchemaModel; +export const PwaEvent = createSchemaModel< + typeof PwaEventSchema, + PwaEventPublicKey +>(PwaEventSchema); diff --git a/src/data/dto/payment/__tests__/payment_plan.test.ts b/src/data/schemas/payment/__tests__/payment_plan.test.ts similarity index 99% rename from src/data/dto/payment/__tests__/payment_plan.test.ts rename to src/data/schemas/payment/__tests__/payment_plan.test.ts index 9e8a8ec7..90cc735e 100644 --- a/src/data/dto/payment/__tests__/payment_plan.test.ts +++ b/src/data/schemas/payment/__tests__/payment_plan.test.ts @@ -5,7 +5,7 @@ import { PaymentPlan, PaymentPlansResponse, TipPaymentPlansResponse, -} from "@/data/dto/payment"; +} from "@/data/schemas/payment"; describe("PaymentPlan", () => { it("parses the camelCase payment plan shape", () => { diff --git a/src/data/schemas/payment/payment_plan.ts b/src/data/schemas/payment/payment_plan.ts index 472621b5..6c5e63ea 100644 --- a/src/data/schemas/payment/payment_plan.ts +++ b/src/data/schemas/payment/payment_plan.ts @@ -28,3 +28,37 @@ export const PaymentPlanSchema = z.object({ export type PaymentPlanInput = z.input; export type PaymentPlanData = z.output; + +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); + } +} diff --git a/src/data/schemas/payment/request/create_payment_order_request.ts b/src/data/schemas/payment/request/create_payment_order_request.ts index 396d1e0c..38793281 100644 --- a/src/data/schemas/payment/request/create_payment_order_request.ts +++ b/src/data/schemas/payment/request/create_payment_order_request.ts @@ -18,3 +18,31 @@ export type CreatePaymentOrderRequestInput = z.input< export type CreatePaymentOrderRequestData = z.output< typeof CreatePaymentOrderRequestSchema >; + +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); + } +} diff --git a/src/data/schemas/payment/response/create_payment_order_response.ts b/src/data/schemas/payment/response/create_payment_order_response.ts index 2c31aa7a..dccd397a 100644 --- a/src/data/schemas/payment/response/create_payment_order_response.ts +++ b/src/data/schemas/payment/response/create_payment_order_response.ts @@ -55,3 +55,30 @@ export type CreatePaymentOrderResponseInput = z.input< export type CreatePaymentOrderResponseData = z.output< typeof CreatePaymentOrderResponseSchema >; + +export class CreatePaymentOrderResponse { + declare readonly orderId: string; + declare readonly payParams: Record; + + 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); + } +} diff --git a/src/data/schemas/payment/response/payment_order_status_response.ts b/src/data/schemas/payment/response/payment_order_status_response.ts index 2cf4fbfc..d35f1686 100644 --- a/src/data/schemas/payment/response/payment_order_status_response.ts +++ b/src/data/schemas/payment/response/payment_order_status_response.ts @@ -19,3 +19,29 @@ export type PaymentOrderStatusResponseInput = z.input< export type PaymentOrderStatusResponseData = z.output< typeof PaymentOrderStatusResponseSchema >; + +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); + } +} diff --git a/src/data/schemas/payment/response/payment_plans_response.ts b/src/data/schemas/payment/response/payment_plans_response.ts index 269ff4e5..2aee10a4 100644 --- a/src/data/schemas/payment/response/payment_plans_response.ts +++ b/src/data/schemas/payment/response/payment_plans_response.ts @@ -10,7 +10,7 @@ import { schemaOrNull, stringOrEmpty, } from "../../nullable-defaults"; -import { PaymentPlanSchema } from "../payment_plan"; +import { PaymentPlan, PaymentPlanSchema } from "../payment_plan"; export const FirstRechargeOfferSchema = z.object({ enabled: booleanOrFalse, @@ -31,3 +31,37 @@ export type PaymentPlansResponseData = z.output< typeof PaymentPlansResponseSchema >; export type FirstRechargeOfferData = z.output; + +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; diff --git a/src/data/schemas/payment/response/tip_payment_plans_response.ts b/src/data/schemas/payment/response/tip_payment_plans_response.ts index 0eeeef19..b1b2b47d 100644 --- a/src/data/schemas/payment/response/tip_payment_plans_response.ts +++ b/src/data/schemas/payment/response/tip_payment_plans_response.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { arrayOrEmpty } from "../../nullable-defaults"; -import { TipPaymentPlanSchema } from "../tip_payment_plan"; +import { TipPaymentPlan, TipPaymentPlanSchema } from "../tip_payment_plan"; export const TipPaymentPlansResponseSchema = z.object({ plans: arrayOrEmpty(TipPaymentPlanSchema), @@ -13,3 +13,31 @@ export type TipPaymentPlansResponseInput = z.input< export type TipPaymentPlansResponseData = z.output< typeof TipPaymentPlansResponseSchema >; + +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()), + }; + } +} diff --git a/src/data/schemas/payment/tip_payment_plan.ts b/src/data/schemas/payment/tip_payment_plan.ts index 7f3a7bc8..a3a8955e 100644 --- a/src/data/schemas/payment/tip_payment_plan.ts +++ b/src/data/schemas/payment/tip_payment_plan.ts @@ -9,3 +9,28 @@ export const TipPaymentPlanSchema = z.object({ export type TipPaymentPlanInput = z.input; export type TipPaymentPlanData = z.output; + +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); + } +} diff --git a/src/data/dto/private-room/__tests__/private_album.test.ts b/src/data/schemas/private-room/__tests__/private_album.test.ts similarity index 97% rename from src/data/dto/private-room/__tests__/private_album.test.ts rename to src/data/schemas/private-room/__tests__/private_album.test.ts index 9e2cd676..629ae5f8 100644 --- a/src/data/dto/private-room/__tests__/private_album.test.ts +++ b/src/data/schemas/private-room/__tests__/private_album.test.ts @@ -4,7 +4,7 @@ import { PrivateAlbumsResponse, PrivateAlbumUnlockResponse, UnlockPrivateAlbumRequest, -} from "@/data/dto/private-room"; +} from "@/data/schemas/private-room"; import { ApiPath } from "@/data/services/api"; const ALBUM_ID = "a1b2c3d4-0000-0000-0000-000000000000"; @@ -38,7 +38,7 @@ const lockedAlbum = { }, }; -describe("Private Album DTOs", () => { +describe("Private Album schema models", () => { it("keeps a locked album cover URL while preserving the lock state", () => { const response = PrivateAlbumsResponse.fromJson({ items: [lockedAlbum], diff --git a/src/data/schemas/private-room/private_album.ts b/src/data/schemas/private-room/private_album.ts index ceb88498..e57887fa 100644 --- a/src/data/schemas/private-room/private_album.ts +++ b/src/data/schemas/private-room/private_album.ts @@ -36,3 +36,35 @@ export const PrivateAlbumSchema = z.object({ export type PrivateAlbumImageData = z.output; export type PrivateAlbumInput = z.input; export type PrivateAlbumData = z.output; + +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); + } +} diff --git a/src/data/schemas/private-room/request/unlock_private_album_request.ts b/src/data/schemas/private-room/request/unlock_private_album_request.ts index 0c74e7ec..9ae44cbf 100644 --- a/src/data/schemas/private-room/request/unlock_private_album_request.ts +++ b/src/data/schemas/private-room/request/unlock_private_album_request.ts @@ -12,3 +12,19 @@ export type UnlockPrivateAlbumRequestInput = z.input< export type UnlockPrivateAlbumRequestData = z.output< typeof UnlockPrivateAlbumRequestSchema >; + +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); + } +} diff --git a/src/data/schemas/private-room/response/private_album_unlock_response.ts b/src/data/schemas/private-room/response/private_album_unlock_response.ts index b7ad94fb..ee76e2aa 100644 --- a/src/data/schemas/private-room/response/private_album_unlock_response.ts +++ b/src/data/schemas/private-room/response/private_album_unlock_response.ts @@ -40,3 +40,35 @@ export type PrivateAlbumUnlockResponseInput = z.input< export type PrivateAlbumUnlockResponseData = z.output< typeof PrivateAlbumUnlockResponseSchema >; + +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); + } +} diff --git a/src/data/schemas/private-room/response/private_albums_response.ts b/src/data/schemas/private-room/response/private_albums_response.ts index 633171bd..afe590d1 100644 --- a/src/data/schemas/private-room/response/private_albums_response.ts +++ b/src/data/schemas/private-room/response/private_albums_response.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { arrayOrEmpty, numberOrZero } from "../../nullable-defaults"; -import { PrivateAlbumSchema } from "../private_album"; +import { PrivateAlbum, PrivateAlbumSchema } from "../private_album"; export const PrivateAlbumsResponseSchema = z.object({ items: arrayOrEmpty(PrivateAlbumSchema), @@ -14,3 +14,32 @@ export type PrivateAlbumsResponseInput = z.input< export type PrivateAlbumsResponseData = z.output< typeof PrivateAlbumsResponseSchema >; + +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, + }; + } +} diff --git a/src/data/schemas/schema_model.ts b/src/data/schemas/schema_model.ts new file mode 100644 index 00000000..1cb42b9b --- /dev/null +++ b/src/data/schemas/schema_model.ts @@ -0,0 +1,53 @@ +import type { z } from "zod"; + +export type SchemaModel< + TSchema extends z.ZodType, + TPublicKey extends keyof z.output = never, +> = Readonly, TPublicKey>> & { + toJson(): z.output; +}; + +export interface SchemaModelFactory< + TSchema extends z.ZodType, + TPublicKey extends keyof z.output = never, +> { + readonly prototype: SchemaModel; + [Symbol.hasInstance](value: unknown): boolean; + from(input: z.input): SchemaModel; + fromJson(json: unknown): SchemaModel; +} + +/** Creates a shallow-frozen data class backed by a Zod schema. */ +export function createSchemaModel< + TSchema extends z.ZodType, + TPublicKey extends keyof z.output = never, +>(schema: TSchema): SchemaModelFactory { + class GeneratedSchemaModel { + private constructor(input: z.input) { + Object.assign(this, schema.parse(input) as object); + Object.freeze(this); + } + + static from( + input: z.input, + ): SchemaModel { + return new GeneratedSchemaModel(input) as SchemaModel< + TSchema, + TPublicKey + >; + } + + static fromJson(json: unknown): SchemaModel { + return GeneratedSchemaModel.from(json as z.input); + } + + toJson(): z.output { + return schema.parse(this); + } + } + + return GeneratedSchemaModel as unknown as SchemaModelFactory< + TSchema, + TPublicKey + >; +} diff --git a/src/data/dto/user/__tests__/user.test.ts b/src/data/schemas/user/__tests__/user.test.ts similarity index 97% rename from src/data/dto/user/__tests__/user.test.ts rename to src/data/schemas/user/__tests__/user.test.ts index 5166b678..a6d6ae92 100644 --- a/src/data/dto/user/__tests__/user.test.ts +++ b/src/data/schemas/user/__tests__/user.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; -import { UserEntitlements } from "@/data/dto/user"; -import { User } from "@/data/dto/user/user"; +import { UserEntitlements } from "@/data/schemas/user"; +import { User } from "@/data/schemas/user/user"; describe("User", () => { it("parses the latest backend user payload", () => { diff --git a/src/data/schemas/user/avatar_data.ts b/src/data/schemas/user/avatar_data.ts index 65f18c85..20b4da54 100644 --- a/src/data/schemas/user/avatar_data.ts +++ b/src/data/schemas/user/avatar_data.ts @@ -13,3 +13,26 @@ export const AvatarDataSchema = z.object({ export type AvatarDataInput = z.input; export type AvatarDataData = z.output; + +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); + } +} diff --git a/src/data/schemas/user/personality_traits.ts b/src/data/schemas/user/personality_traits.ts index 488bdee7..cccd01fa 100644 --- a/src/data/schemas/user/personality_traits.ts +++ b/src/data/schemas/user/personality_traits.ts @@ -24,3 +24,29 @@ export const PersonalityTraitsSchema = z.object({ export type PersonalityTraitsInput = z.input; export type PersonalityTraitsData = z.output; + +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); + } +} diff --git a/src/data/schemas/user/recent_memory.ts b/src/data/schemas/user/recent_memory.ts index 0a2eebb1..22e52a8d 100644 --- a/src/data/schemas/user/recent_memory.ts +++ b/src/data/schemas/user/recent_memory.ts @@ -15,3 +15,28 @@ export const RecentMemorySchema = z.object({ export type RecentMemoryInput = z.input; export type RecentMemoryData = z.output; + +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); + } +} diff --git a/src/data/schemas/user/user.ts b/src/data/schemas/user/user.ts index 0dfffeea..98b4885d 100644 --- a/src/data/schemas/user/user.ts +++ b/src/data/schemas/user/user.ts @@ -3,7 +3,11 @@ * */ import { z } from "zod"; -import { PersonalityTraitsSchema, PERSONALITY_TRAITS_DEFAULTS } from "./personality_traits"; +import { + PersonalityTraits, + PersonalityTraitsSchema, + PERSONALITY_TRAITS_DEFAULTS, +} from "./personality_traits"; import { numberOrZero, schemaOr, @@ -37,3 +41,58 @@ export const UserSchema = z.object({ export type UserInput = z.input; export type UserData = z.output; + +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(), + }); + } +} diff --git a/src/data/schemas/user/user_entitlements.ts b/src/data/schemas/user/user_entitlements.ts index caaf855b..09f8fe94 100644 --- a/src/data/schemas/user/user_entitlements.ts +++ b/src/data/schemas/user/user_entitlements.ts @@ -118,3 +118,34 @@ export const UserEntitlementsSchema = z.object({ export type UserEntitlementsInput = z.input; export type UserEntitlementsData = z.output; + +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); + } +} diff --git a/src/data/services/api/__tests__/multi_character_api.test.ts b/src/data/services/api/__tests__/multi_character_api.test.ts index cd8d3fdd..6b3b7c62 100644 --- a/src/data/services/api/__tests__/multi_character_api.test.ts +++ b/src/data/services/api/__tests__/multi_character_api.test.ts @@ -4,7 +4,7 @@ import { SendMessageRequest, UnlockHistoryRequest, UnlockPrivateRequest, -} from "@/data/dto/chat"; +} from "@/data/schemas/chat"; const httpClientMock = vi.hoisted(() => vi.fn()); diff --git a/src/data/services/api/auth_api.ts b/src/data/services/api/auth_api.ts index 0a43dcd2..c44af896 100644 --- a/src/data/services/api/auth_api.ts +++ b/src/data/services/api/auth_api.ts @@ -24,8 +24,8 @@ import { RefreshTokenRequest, RefreshTokenResponse, RegisterRequest, -} from "@/data/dto/auth"; -import { User } from "@/data/dto/user"; +} from "@/data/schemas/auth"; +import { User } from "@/data/schemas/user"; import type { UserData } from "@/data/schemas/user/user"; import { Logger } from "@/utils/logger"; diff --git a/src/data/services/api/character_api.ts b/src/data/services/api/character_api.ts index 4b7d1658..a714e5eb 100644 --- a/src/data/services/api/character_api.ts +++ b/src/data/services/api/character_api.ts @@ -1,4 +1,4 @@ -import { CharactersResponse } from "@/data/dto/character"; +import { CharactersResponse } from "@/data/schemas/character"; import { ApiPath } from "./api_path"; import { httpClient } from "./http_client"; diff --git a/src/data/services/api/chat_api.ts b/src/data/services/api/chat_api.ts index e0674dae..dbffc88f 100644 --- a/src/data/services/api/chat_api.ts +++ b/src/data/services/api/chat_api.ts @@ -15,7 +15,7 @@ import { UnlockHistoryResponse, UnlockPrivateRequest, UnlockPrivateResponse, -} from "@/data/dto/chat"; +} from "@/data/schemas/chat"; export class ChatApi { /** diff --git a/src/data/services/api/feedback_api.ts b/src/data/services/api/feedback_api.ts index 29a66083..d0555707 100644 --- a/src/data/services/api/feedback_api.ts +++ b/src/data/services/api/feedback_api.ts @@ -1,7 +1,7 @@ import { FeedbackSubmitResponse, type SubmitFeedbackInput, -} from "@/data/dto/feedback"; +} from "@/data/schemas/feedback"; import { ApiPath } from "./api_path"; import { httpClient } from "./http_client"; diff --git a/src/data/services/api/metrics_api.ts b/src/data/services/api/metrics_api.ts index 4311a039..b72f0cb3 100644 --- a/src/data/services/api/metrics_api.ts +++ b/src/data/services/api/metrics_api.ts @@ -7,7 +7,7 @@ import { httpClient } from "./http_client"; import { ApiPath } from "./api_path"; import { ApiEnvelope, unwrapOptional } from "./response_helper"; -import { AppEvent, PwaEvent } from "@/data/dto/metrics"; +import { AppEvent, PwaEvent } from "@/data/schemas/metrics"; export class MetricsApi { /** diff --git a/src/data/services/api/payment_api.ts b/src/data/services/api/payment_api.ts index c7550f37..e0c5c63f 100644 --- a/src/data/services/api/payment_api.ts +++ b/src/data/services/api/payment_api.ts @@ -9,7 +9,7 @@ import { PaymentOrderStatusResponse, PaymentPlansResponse, TipPaymentPlansResponse, -} from "@/data/dto/payment"; +} from "@/data/schemas/payment"; import { ApiPath } from "./api_path"; import { httpClient } from "./http_client"; diff --git a/src/data/services/api/private_room_api.ts b/src/data/services/api/private_room_api.ts index 5faeb7cf..ce2949c9 100644 --- a/src/data/services/api/private_room_api.ts +++ b/src/data/services/api/private_room_api.ts @@ -2,7 +2,7 @@ import { PrivateAlbumsResponse, PrivateAlbumUnlockResponse, UnlockPrivateAlbumRequest, -} from "@/data/dto/private-room"; +} from "@/data/schemas/private-room"; import { DEFAULT_CHARACTER_ID } from "@/data/constants/character"; import { ApiPath } from "./api_path"; diff --git a/src/data/services/api/user_api.ts b/src/data/services/api/user_api.ts index 8b68d008..80711518 100644 --- a/src/data/services/api/user_api.ts +++ b/src/data/services/api/user_api.ts @@ -10,7 +10,7 @@ import { ApiEnvelope, unwrap } from "./response_helper"; import { User, UserEntitlements, -} from "@/data/dto/user"; +} from "@/data/schemas/user"; import type { UserData } from "@/data/schemas/user/user"; export class UserApi { diff --git a/src/data/services/facebook-graph.ts b/src/data/services/facebook-graph.ts index c51a8752..d8072e47 100644 --- a/src/data/services/facebook-graph.ts +++ b/src/data/services/facebook-graph.ts @@ -11,7 +11,7 @@ * - 不指定 version 走最新稳定版 * - 不需要 appId / appSecret(accessToken 自身就是凭证) */ -import { FacebookUserData } from "@/data/dto/auth"; +import { FacebookUserData } from "@/data/schemas/auth"; import { Logger } from "@/utils/logger"; import { Result, toError } from "@/utils/result"; @@ -22,7 +22,7 @@ const GRAPH_ME_URL = "https://graph.facebook.com/me"; /** * 拉取字段集合: - * - id / name / email —— 给本地 FacebookUserData DTO + * - id / name / email —— 给本地 FacebookUserData 数据类 * - picture.type(large) —— 头像 URL(默认 square 50px 太小) */ const FIELDS = "id,name,email,picture.type(large)"; diff --git a/src/data/services/index.ts b/src/data/services/index.ts index 5aa4c3e9..a6c99f48 100644 --- a/src/data/services/index.ts +++ b/src/data/services/index.ts @@ -15,33 +15,6 @@ export * from "../../core/net/config/api_config"; export * from "../../core/net/interceptor/auth_refresh_interceptor"; export * from "../../core/net/interceptor/logging_interceptor"; export * from "../../core/net/interceptor/token_interceptor"; -// 注:原 ./auth/* 平台适配层已删除(社交登录改由 NextAuth 处理) -export * from "../dto/auth/facebook_user_data"; -export * from "../dto/auth/request/facebook_identity_request"; -export * from "../dto/auth/request/facebook_login_request"; -export * from "../dto/auth/request/facebook_psid_login_request"; -export * from "../dto/auth/request/fb_id_login_request"; -export * from "../dto/auth/request/google_login_request"; -export * from "../dto/auth/request/guest_login_request"; -export * from "../dto/auth/request/login_request"; -export * from "../dto/auth/request/refresh_token_request"; -export * from "../dto/auth/request/register_request"; -export * from "../dto/auth/response/facebook_identity_response"; -export * from "../dto/auth/response/facebook_psid_login_response"; -export * from "../dto/auth/response/guest_login_response"; -export * from "../dto/auth/response/login_response"; -export * from "../dto/auth/response/logout_response"; -export * from "../dto/auth/response/refresh_token_response"; -export * from "../dto/chat/chat_message"; -export * from "../dto/chat/request/send_message_request"; -export * from "../dto/chat/response/chat_history_response"; -export * from "../dto/chat/response/chat_send_response"; -export * from "../dto/metrics/request/app_event"; -export * from "../dto/metrics/request/pwa_event"; -export * from "../dto/user/avatar_data"; -export * from "../dto/user/personality_traits"; -export * from "../dto/user/recent_memory"; -export * from "../dto/user/user"; export * from "../schemas/auth/request/facebook_login_request"; export * from "../schemas/auth/request/facebook_identity_request"; export * from "../schemas/auth/response/facebook_identity_response"; diff --git a/src/data/storage/auth/auth_storage.ts b/src/data/storage/auth/auth_storage.ts index ab5cb3b0..df53eec8 100644 --- a/src/data/storage/auth/auth_storage.ts +++ b/src/data/storage/auth/auth_storage.ts @@ -1,6 +1,6 @@ "use client"; -import { LoginStatus, type LoginStatus as LoginStatusT } from "@/data/dto/auth"; +import { LoginStatus, type LoginStatus as LoginStatusT } from "@/data/schemas/auth"; import { Result, type Result as ResultT } from "@/utils/result"; import { SpAsyncUtil } from "@/utils/storage"; import { StorageKeys } from "../storage_keys"; diff --git a/src/data/storage/auth/iauth_storage.ts b/src/data/storage/auth/iauth_storage.ts index fef0e043..bf69882a 100644 --- a/src/data/storage/auth/iauth_storage.ts +++ b/src/data/storage/auth/iauth_storage.ts @@ -9,7 +9,7 @@ */ import type { Result } from "@/utils/result"; -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; export interface IAuthStorage { getLoginToken(): Promise>; diff --git a/src/data/storage/chat/local_chat_db.ts b/src/data/storage/chat/local_chat_db.ts index ce6a2ef0..3da6e2d5 100644 --- a/src/data/storage/chat/local_chat_db.ts +++ b/src/data/storage/chat/local_chat_db.ts @@ -10,7 +10,7 @@ */ import Dexie, { type Table } from "dexie"; -import type { ChatMediaKind } from "@/data/dto/chat"; +import type { ChatMediaKind } from "@/data/schemas/chat"; import type { ChatImageData, ChatLockDetailData, diff --git a/src/data/storage/chat/local_chat_media_storage.ts b/src/data/storage/chat/local_chat_media_storage.ts index 5723c2c6..bafe9530 100644 --- a/src/data/storage/chat/local_chat_media_storage.ts +++ b/src/data/storage/chat/local_chat_media_storage.ts @@ -1,7 +1,7 @@ "use client"; import { Result, type Result as ResultT } from "@/utils/result"; -import type { ChatMediaKind } from "@/data/dto/chat"; +import type { ChatMediaKind } from "@/data/schemas/chat"; import { LocalChatDB, diff --git a/src/data/storage/user/iuser_storage.ts b/src/data/storage/user/iuser_storage.ts index 8a2b0007..0c70a68c 100644 --- a/src/data/storage/user/iuser_storage.ts +++ b/src/data/storage/user/iuser_storage.ts @@ -5,7 +5,7 @@ * - User 对象 / userId / avatarUrl 三个独立字段的 CRUD * - `clearUserData()` 批量清空 * - * 注:完整 User 模型见 `src/data/dto/user/user.ts`。本地缓存只持久化 UI + * 注:完整 User 模型见 `src/data/schemas/user/user.ts`。本地缓存只持久化 UI * 当前需要的最小字段,避免把完整后端 profile 长期写入 localStorage。 */ diff --git a/src/hooks/use-guest-login-bootstrap.ts b/src/hooks/use-guest-login-bootstrap.ts index 0b0a9266..30a8e63b 100644 --- a/src/hooks/use-guest-login-bootstrap.ts +++ b/src/hooks/use-guest-login-bootstrap.ts @@ -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 type { AuthEvent } from "@/stores/auth/auth-events"; export interface GuestLoginBootstrapState { diff --git a/src/lib/analytics/__tests__/behavior_analytics.test.ts b/src/lib/analytics/__tests__/behavior_analytics.test.ts index 14e74a47..ea097274 100644 --- a/src/lib/analytics/__tests__/behavior_analytics.test.ts +++ b/src/lib/analytics/__tests__/behavior_analytics.test.ts @@ -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 { behaviorAnalytics, diff --git a/src/lib/analytics/behavior_analytics.ts b/src/lib/analytics/behavior_analytics.ts index ebbabaa9..db315b72 100644 --- a/src/lib/analytics/behavior_analytics.ts +++ b/src/lib/analytics/behavior_analytics.ts @@ -1,6 +1,6 @@ "use client"; -import type { PayChannel, PaymentPlan } from "@/data/dto/payment"; +import type { PayChannel, PaymentPlan } from "@/data/schemas/payment"; import type { PaymentAnalyticsContext, diff --git a/src/lib/auth/__tests__/auth_session.test.ts b/src/lib/auth/__tests__/auth_session.test.ts index 093ae780..aa09b21a 100644 --- a/src/lib/auth/__tests__/auth_session.test.ts +++ b/src/lib/auth/__tests__/auth_session.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from "vitest"; import type { IAuthStorage } from "@/data/storage/auth"; -import { LoginStatus } from "@/data/dto/auth"; +import { LoginStatus } from "@/data/schemas/auth"; import { Result } from "@/utils/result"; import { hasCompleteBusinessAuthSession } from "../auth_session"; diff --git a/src/lib/chat/chat_media_cache_client.ts b/src/lib/chat/chat_media_cache_client.ts index ab4ae006..4731ba16 100644 --- a/src/lib/chat/chat_media_cache_client.ts +++ b/src/lib/chat/chat_media_cache_client.ts @@ -1,6 +1,6 @@ "use client"; -import type { ChatMediaKind } from "@/data/dto/chat"; +import type { ChatMediaKind } from "@/data/schemas/chat"; import { loadChatRepository } from "@/data/repositories/chat_repository_loader"; import { Result, type Result as ResultT } from "@/utils/result"; diff --git a/src/lib/chat/use_cached_chat_media_url.ts b/src/lib/chat/use_cached_chat_media_url.ts index 7db482ec..c260100e 100644 --- a/src/lib/chat/use_cached_chat_media_url.ts +++ b/src/lib/chat/use_cached_chat_media_url.ts @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react"; -import type { ChatMediaKind } from "@/data/dto/chat"; +import type { ChatMediaKind } from "@/data/schemas/chat"; import { BrowserDetector } from "@/utils/browser-detect"; import { Logger } from "@/utils/logger"; import { PlatformDetector } from "@/utils/platform-detect"; diff --git a/src/lib/feedback/submit_feedback.ts b/src/lib/feedback/submit_feedback.ts index e4a5cddc..dfd0ea7b 100644 --- a/src/lib/feedback/submit_feedback.ts +++ b/src/lib/feedback/submit_feedback.ts @@ -1,7 +1,7 @@ import type { FeedbackSubmitResponse, SubmitFeedbackInput, -} from "@/data/dto/feedback"; +} from "@/data/schemas/feedback"; import { getFeedbackRepository } from "@/data/repositories/feedback_repository"; import type { Result } from "@/utils/result"; diff --git a/src/lib/navigation/external_entry.ts b/src/lib/navigation/external_entry.ts index 13862b7b..03ed3b09 100644 --- a/src/lib/navigation/external_entry.ts +++ b/src/lib/navigation/external_entry.ts @@ -3,7 +3,7 @@ import { AuthStorage } from "@/data/storage/auth/auth_storage"; import { UserStorage } from "@/data/storage/user/user_storage"; import type { PendingChatPromotionType } from "@/data/storage/navigation"; -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; import { ROUTES } from "@/router/routes"; export type ExternalEntryTarget = diff --git a/src/lib/payment/default_pay_channel.ts b/src/lib/payment/default_pay_channel.ts index 093a5cdf..b332a40a 100644 --- a/src/lib/payment/default_pay_channel.ts +++ b/src/lib/payment/default_pay_channel.ts @@ -1,4 +1,4 @@ -import type { PayChannel } from "@/data/dto/payment"; +import type { PayChannel } from "@/data/schemas/payment"; export function getDefaultPayChannelForCountryCode( countryCode: string | null | undefined, diff --git a/src/lib/payment/payment_search_params.ts b/src/lib/payment/payment_search_params.ts index 2fd5f219..f6c58fc9 100644 --- a/src/lib/payment/payment_search_params.ts +++ b/src/lib/payment/payment_search_params.ts @@ -1,4 +1,4 @@ -import type { PayChannel } from "@/data/dto/payment"; +import type { PayChannel } from "@/data/schemas/payment"; import type { AppSubscriptionReturnTo } from "@/router/navigation-types"; export type PaymentSearchParamValue = string | string[] | undefined; diff --git a/src/lib/private-room/private_album.ts b/src/lib/private-room/private_album.ts index d810f0b1..94d073e6 100644 --- a/src/lib/private-room/private_album.ts +++ b/src/lib/private-room/private_album.ts @@ -1,4 +1,4 @@ -import type { PrivateAlbum } from "@/data/dto/private-room"; +import type { PrivateAlbum } from "@/data/schemas/private-room"; export function isPrivateAlbumLocked(album: PrivateAlbum): boolean { return album.locked || !album.unlocked || album.lockDetail.locked; diff --git a/src/router/navigation-resolver.ts b/src/router/navigation-resolver.ts index 3bc65d52..9e149185 100644 --- a/src/router/navigation-resolver.ts +++ b/src/router/navigation-resolver.ts @@ -1,4 +1,4 @@ -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; import { getRouteAccess } from "./route-meta"; import { ROUTE_BUILDERS, ROUTES } from "./routes"; diff --git a/src/router/navigation-types.ts b/src/router/navigation-types.ts index 8f5aea51..e51e2e1d 100644 --- a/src/router/navigation-types.ts +++ b/src/router/navigation-types.ts @@ -1,4 +1,4 @@ -import type { PayChannel } from "@/data/dto/payment"; +import type { PayChannel } from "@/data/schemas/payment"; import type { PaymentAnalyticsContext } from "@/lib/analytics/payment_analytics_context"; import type { PendingChatUnlockKind, diff --git a/src/router/routes.ts b/src/router/routes.ts index ee7fa660..9ef494d0 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -7,7 +7,7 @@ * - 动态查询路由通过 `ROUTE_BUILDERS` 函数生成,避免手拼字符串。 */ -import type { PayChannel } from "@/data/dto/payment"; +import type { PayChannel } from "@/data/schemas/payment"; import type { AppSubscriptionReturnTo } from "@/router/navigation-types"; import { PAYMENT_ANALYTICS_ENTRY_PARAM, diff --git a/src/stores/auth/__tests__/auth-context-hydration.test.tsx b/src/stores/auth/__tests__/auth-context-hydration.test.tsx index 7eb484bc..96240bb3 100644 --- a/src/stores/auth/__tests__/auth-context-hydration.test.tsx +++ b/src/stores/auth/__tests__/auth-context-hydration.test.tsx @@ -5,7 +5,8 @@ import { hydrateRoot, type Root } from "react-dom/client"; import { renderToString } from "react-dom/server"; import { afterEach, describe, expect, it, vi } from "vitest"; -import type { AuthPanelMode, LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; +import type { AuthPanelMode } from "@/stores/auth/auth-panel-mode"; vi.mock("@/stores/auth/auth-machine", async () => { const { setup } = await import("xstate"); diff --git a/src/stores/auth/__tests__/auth-machine.test-utils.ts b/src/stores/auth/__tests__/auth-machine.test-utils.ts index e7536df3..a964a95b 100644 --- a/src/stores/auth/__tests__/auth-machine.test-utils.ts +++ b/src/stores/auth/__tests__/auth-machine.test-utils.ts @@ -1,7 +1,7 @@ import { vi } from "vitest"; import { fromPromise } from "xstate"; -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; import type { AuthProvider } from "@/lib/auth/auth_platform"; import { authMachine } from "@/stores/auth/auth-machine"; import type { BindFacebookIdentityInput } from "@/stores/auth/auth-events"; diff --git a/src/stores/auth/auth-events.ts b/src/stores/auth/auth-events.ts index 6c4f76fe..980b96fc 100644 --- a/src/stores/auth/auth-events.ts +++ b/src/stores/auth/auth-events.ts @@ -4,7 +4,7 @@ * 事件名与原 Dart AuthEvent 对齐。 */ import type { AuthProvider } from "@/lib/auth/auth_platform"; -import type { AuthPanelMode } from "@/data/dto/auth"; +import type { AuthPanelMode } from "@/stores/auth/auth-panel-mode"; export interface BindFacebookIdentityInput { asid?: string; diff --git a/src/data/dto/auth/auth_panel_mode.ts b/src/stores/auth/auth-panel-mode.ts similarity index 74% rename from src/data/dto/auth/auth_panel_mode.ts rename to src/stores/auth/auth-panel-mode.ts index c84cce62..b4476e39 100644 --- a/src/data/dto/auth/auth_panel_mode.ts +++ b/src/stores/auth/auth-panel-mode.ts @@ -1,8 +1,3 @@ -/** - * 认证面板模式(Facebook / Email) - * - * - */ export const AuthPanelMode = { Facebook: "facebook", Email: "email", diff --git a/src/stores/auth/auth-state.ts b/src/stores/auth/auth-state.ts index 957f66db..cc670b10 100644 --- a/src/stores/auth/auth-state.ts +++ b/src/stores/auth/auth-state.ts @@ -1,7 +1,9 @@ /** * Auth 状态机:State 形状 + 初始值 */ -import type { AuthPanelMode, LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; + +import type { AuthPanelMode } from "./auth-panel-mode"; export interface AuthState { /** 当前面板模式(Facebook / Email) */ diff --git a/src/stores/auth/index.ts b/src/stores/auth/index.ts index 6f8285a0..1cde3f8f 100644 --- a/src/stores/auth/index.ts +++ b/src/stores/auth/index.ts @@ -2,6 +2,7 @@ export * from "./auth-context"; export * from "./auth-events"; +export * from "./auth-panel-mode"; export * from "./helper"; export * from "./auth-machine"; export * from "./auth-state"; diff --git a/src/stores/auth/machine/actors/credentials.ts b/src/stores/auth/machine/actors/credentials.ts index 00d2cbc4..df382a2f 100644 --- a/src/stores/auth/machine/actors/credentials.ts +++ b/src/stores/auth/machine/actors/credentials.ts @@ -1,6 +1,6 @@ import { fromPromise } from "xstate"; -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; import { getAuthRepository } from "@/data/repositories/auth_repository"; import { Logger } from "@/utils/logger"; import { Result } from "@/utils/result"; diff --git a/src/stores/auth/machine/actors/oauth.ts b/src/stores/auth/machine/actors/oauth.ts index f6be22b0..56d71e47 100644 --- a/src/stores/auth/machine/actors/oauth.ts +++ b/src/stores/auth/machine/actors/oauth.ts @@ -1,6 +1,6 @@ import { fromPromise } from "xstate"; -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; import { getAuthRepository } from "@/data/repositories/auth_repository"; import { fetchFacebookUserData } from "@/data/services"; import { AuthStorage } from "@/data/storage/auth/auth_storage"; diff --git a/src/stores/auth/machine/actors/session.ts b/src/stores/auth/machine/actors/session.ts index aace5389..f3b03017 100644 --- a/src/stores/auth/machine/actors/session.ts +++ b/src/stores/auth/machine/actors/session.ts @@ -1,6 +1,6 @@ import { fromPromise } from "xstate"; -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; import { getAuthRepository } from "@/data/repositories/auth_repository"; import { AuthStorage } from "@/data/storage/auth/auth_storage"; import { UserStorage } from "@/data/storage/user/user_storage"; diff --git a/src/stores/auth/machine/credentials-flow.ts b/src/stores/auth/machine/credentials-flow.ts index c2c449a5..f241da02 100644 --- a/src/stores/auth/machine/credentials-flow.ts +++ b/src/stores/auth/machine/credentials-flow.ts @@ -1,6 +1,6 @@ import type { DoneActorEvent, ErrorActorEvent } from "xstate"; -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; import { toAuthErrorMessage } from "../helper/error"; import { diff --git a/src/stores/auth/machine/oauth-flow.ts b/src/stores/auth/machine/oauth-flow.ts index 61177fdf..d017dc2a 100644 --- a/src/stores/auth/machine/oauth-flow.ts +++ b/src/stores/auth/machine/oauth-flow.ts @@ -1,6 +1,6 @@ import type { DoneActorEvent, ErrorActorEvent } from "xstate"; -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; import type { AuthProvider } from "@/lib/auth/auth_platform"; import { toAuthErrorMessage } from "../helper/error"; diff --git a/src/stores/auth/machine/session-flow.ts b/src/stores/auth/machine/session-flow.ts index d400f9b1..6fe7d2b5 100644 --- a/src/stores/auth/machine/session-flow.ts +++ b/src/stores/auth/machine/session-flow.ts @@ -1,6 +1,6 @@ import type { DoneActorEvent, ErrorActorEvent } from "xstate"; -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; import { toAuthErrorMessage } from "../helper/error"; import { diff --git a/src/stores/chat/__tests__/chat-helpers.test.ts b/src/stores/chat/__tests__/chat-helpers.test.ts index dd5a6c74..fd5557b7 100644 --- a/src/stores/chat/__tests__/chat-helpers.test.ts +++ b/src/stores/chat/__tests__/chat-helpers.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { DEFAULT_CHARACTER_ID } from "@/data/constants/character"; -import { ChatSendResponse } from "@/data/dto/chat"; +import { ChatSendResponse } from "@/data/schemas/chat"; import type { ChatState } from "@/stores/chat/chat-state"; import { applyNetworkHistoryLoadedOutput, diff --git a/src/stores/chat/__tests__/chat-history-flow.test.ts b/src/stores/chat/__tests__/chat-history-flow.test.ts index d218c60d..02e85fb6 100644 --- a/src/stores/chat/__tests__/chat-history-flow.test.ts +++ b/src/stores/chat/__tests__/chat-history-flow.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { createActor, fromCallback, waitFor } from "xstate"; -import type { UiMessage } from "@/data/dto/chat"; +import type { UiMessage } from "@/stores/chat/ui-message"; import type { ChatEvent } from "@/stores/chat/chat-events"; import { chatMachine } from "@/stores/chat/chat-machine"; import type { LoadMoreHistoryActorEvent } from "@/stores/chat/machine/actors/history"; diff --git a/src/stores/chat/__tests__/chat-machine.test-utils.ts b/src/stores/chat/__tests__/chat-machine.test-utils.ts index 18712dc2..c5c65c47 100644 --- a/src/stores/chat/__tests__/chat-machine.test-utils.ts +++ b/src/stores/chat/__tests__/chat-machine.test-utils.ts @@ -3,8 +3,7 @@ import { fromCallback, fromPromise } from "xstate"; import { ChatSendResponse, UnlockPrivateResponse, - type UiMessage, -} from "@/data/dto/chat"; +} from "@/data/schemas/chat"; import { DEFAULT_CHARACTER_ID } from "@/data/constants/character"; import { chatMachine } from "@/stores/chat/chat-machine"; import type { ChatEvent } from "@/stores/chat/chat-events"; @@ -13,6 +12,7 @@ import type { UnlockMessageRequest, } from "@/stores/chat/helper/unlock"; import type { ChatHistoryActorInput } from "@/stores/chat/machine/actors/history"; +import type { UiMessage } from "@/stores/chat/ui-message"; export interface SendMessageHttpOutput { response: ChatSendResponse; diff --git a/src/stores/chat/__tests__/chat-promotion.test.ts b/src/stores/chat/__tests__/chat-promotion.test.ts index 352a9f0e..904ab6f1 100644 --- a/src/stores/chat/__tests__/chat-promotion.test.ts +++ b/src/stores/chat/__tests__/chat-promotion.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { UnlockPrivateResponse } from "@/data/dto/chat"; +import { UnlockPrivateResponse } from "@/data/schemas/chat"; import type { PendingChatPromotion } from "@/data/storage/navigation"; import { appendPromotionMessage, diff --git a/src/stores/chat/__tests__/chat-unlock-flow.test.ts b/src/stores/chat/__tests__/chat-unlock-flow.test.ts index c97401e8..039b95d2 100644 --- a/src/stores/chat/__tests__/chat-unlock-flow.test.ts +++ b/src/stores/chat/__tests__/chat-unlock-flow.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { createActor, fromPromise, waitFor } from "xstate"; -import { ChatSendResponse } from "@/data/dto/chat"; +import { ChatSendResponse } from "@/data/schemas/chat"; import type { UnlockMessageOutput, UnlockMessageRequest, diff --git a/src/stores/chat/chat-history-sync.ts b/src/stores/chat/chat-history-sync.ts index b2aef36b..013ee11b 100644 --- a/src/stores/chat/chat-history-sync.ts +++ b/src/stores/chat/chat-history-sync.ts @@ -1,4 +1,4 @@ -import type { UiMessage } from "@/data/dto/chat"; +import type { UiMessage } from "@/stores/chat/ui-message"; import { resolveChatConversationKey } from "@/data/repositories/chat_cache_identity"; import { loadChatRepository } from "@/data/repositories/chat_repository_loader"; import { Logger } from "@/utils/logger"; diff --git a/src/stores/chat/chat-state.ts b/src/stores/chat/chat-state.ts index f5ca8b73..96e8a74c 100644 --- a/src/stores/chat/chat-state.ts +++ b/src/stores/chat/chat-state.ts @@ -1,4 +1,4 @@ -import type { UiMessage } from "@/data/dto/chat"; +import type { UiMessage } from "@/stores/chat/ui-message"; import type { PendingChatUnlockKind } from "@/lib/navigation/chat_unlock_session"; import type { ChatLockType } from "@/data/schemas/chat"; import type { PendingChatPromotion } from "@/data/storage/navigation"; diff --git a/src/stores/chat/helper/history.ts b/src/stores/chat/helper/history.ts index 860bb1b0..dd2f0c1f 100644 --- a/src/stores/chat/helper/history.ts +++ b/src/stores/chat/helper/history.ts @@ -1,4 +1,4 @@ -import type { UiMessage } from "@/data/dto/chat"; +import type { UiMessage } from "@/stores/chat/ui-message"; import type { ChatState } from "../chat-state"; diff --git a/src/stores/chat/helper/message-mappers.ts b/src/stores/chat/helper/message-mappers.ts index ea372156..fea786b7 100644 --- a/src/stores/chat/helper/message-mappers.ts +++ b/src/stores/chat/helper/message-mappers.ts @@ -1,5 +1,8 @@ -import { type ChatSendResponse, type UiMessage } from "@/data/dto/chat"; -import type { ChatLockDetailData } from "@/data/schemas/chat"; +import type { + ChatLockDetailData, + ChatSendResponse, +} from "@/data/schemas/chat"; +import type { UiMessage } from "@/stores/chat/ui-message"; import { todayString } from "@/utils/date"; /** diff --git a/src/stores/chat/helper/promotion.ts b/src/stores/chat/helper/promotion.ts index b1d7ffb6..48c9277c 100644 --- a/src/stores/chat/helper/promotion.ts +++ b/src/stores/chat/helper/promotion.ts @@ -1,4 +1,4 @@ -import type { UiMessage } from "@/data/dto/chat"; +import type { UiMessage } from "@/stores/chat/ui-message"; import type { PendingChatPromotion } from "@/data/storage/navigation"; import { todayString } from "@/utils/date"; diff --git a/src/stores/chat/helper/send-state.ts b/src/stores/chat/helper/send-state.ts index 310d870b..c12d75ac 100644 --- a/src/stores/chat/helper/send-state.ts +++ b/src/stores/chat/helper/send-state.ts @@ -1,4 +1,5 @@ -import { type ChatSendResponse, type UiMessage } from "@/data/dto/chat"; +import type { ChatSendResponse } from "@/data/schemas/chat"; +import type { UiMessage } from "@/stores/chat/ui-message"; import type { ChatState, ChatUpgradeReason } from "../chat-state"; diff --git a/src/stores/chat/helper/unlock.ts b/src/stores/chat/helper/unlock.ts index 1909b16b..9b9ec67a 100644 --- a/src/stores/chat/helper/unlock.ts +++ b/src/stores/chat/helper/unlock.ts @@ -1,5 +1,8 @@ -import type { UiMessage, UnlockPrivateResponse } from "@/data/dto/chat"; -import type { ChatLockType } from "@/data/schemas/chat"; +import type { + ChatLockType, + UnlockPrivateResponse, +} from "@/data/schemas/chat"; +import type { UiMessage } from "@/stores/chat/ui-message"; export interface UnlockMessageRequest { displayMessageId: string; diff --git a/src/stores/chat/index.ts b/src/stores/chat/index.ts index fa67d6bf..f2e6a808 100644 --- a/src/stores/chat/index.ts +++ b/src/stores/chat/index.ts @@ -5,3 +5,4 @@ export * from "./chat-events"; export * from "./helper"; export * from "./chat-machine"; export * from "./chat-state"; +export * from "./ui-message"; diff --git a/src/stores/chat/machine/actors/history.ts b/src/stores/chat/machine/actors/history.ts index 5e888c24..4207e90d 100644 --- a/src/stores/chat/machine/actors/history.ts +++ b/src/stores/chat/machine/actors/history.ts @@ -1,6 +1,6 @@ import { fromCallback } from "xstate"; -import type { UiMessage } from "@/data/dto/chat"; +import type { UiMessage } from "@/stores/chat/ui-message"; import { loadChatRepository } from "@/data/repositories/chat_repository_loader"; import { Logger } from "@/utils/logger"; import { Result } from "@/utils/result"; diff --git a/src/stores/chat/machine/actors/send.ts b/src/stores/chat/machine/actors/send.ts index 3b97e6b2..4b0730b4 100644 --- a/src/stores/chat/machine/actors/send.ts +++ b/src/stores/chat/machine/actors/send.ts @@ -2,7 +2,7 @@ import { fromCallback, fromPromise } from "xstate"; import { ExceptionHandler } from "@/core/errors"; import { MessageQueue } from "@/core/net/message-queue"; -import type { ChatSendResponse } from "@/data/dto/chat"; +import type { ChatSendResponse } from "@/data/schemas/chat"; import { loadChatRepository } from "@/data/repositories/chat_repository_loader"; import { resolveChatConversationKey } from "@/data/repositories/chat_cache_identity"; import { Logger } from "@/utils/logger"; @@ -13,7 +13,7 @@ import { sendResponseToUiMessage } from "../../helper/message-mappers"; const log = new Logger("StoresChatChatSendFlow"); -type UiMessage = import("@/data/dto/chat").UiMessage; +type UiMessage = import("@/stores/chat/ui-message").UiMessage; export const sendMessageHttpActor = fromPromise< { response: ChatSendResponse; reply: UiMessage | null }, diff --git a/src/stores/chat/machine/actors/unlock.ts b/src/stores/chat/machine/actors/unlock.ts index 7d1b1d33..087e2b18 100644 --- a/src/stores/chat/machine/actors/unlock.ts +++ b/src/stores/chat/machine/actors/unlock.ts @@ -13,7 +13,7 @@ import { const log = new Logger("StoresChatChatUnlockFlow"); -type UiMessage = import("@/data/dto/chat").UiMessage; +type UiMessage = import("@/stores/chat/ui-message").UiMessage; export interface UnlockHistoryOutput { unlocked: boolean; diff --git a/src/data/dto/chat/ui_message.ts b/src/stores/chat/ui-message.ts similarity index 65% rename from src/data/dto/chat/ui_message.ts rename to src/stores/chat/ui-message.ts index 76715b5f..7f95c714 100644 --- a/src/data/dto/chat/ui_message.ts +++ b/src/stores/chat/ui-message.ts @@ -1,25 +1,13 @@ -/** - * 聊天 UI 消息模型 - * - * - * - * 字段:content、isFromAI、date、imageUrl(可选)、audioUrl(可选) - */ import { z } from "zod"; export const UiMessageSchema = z.object({ id: z.string().optional(), content: z.string(), isFromAI: z.boolean(), - /** 日期分隔条使用的本地日期(YYYY-MM-DD) */ date: z.string(), - /** 前端生成的辅助消息,不应作为服务端聊天预览。 */ isSynthetic: z.boolean().optional(), - /** 图片 URL(base64 data URL 或 http URL) */ imageUrl: z.string().optional(), - /** true = 图片可在列表展示,但全屏查看时需要会员解锁 */ imagePaywalled: z.boolean().optional(), - /** 语音 URL */ audioUrl: z.string().optional(), locked: z.boolean().nullable().optional(), lockReason: z.string().nullable().optional(), @@ -30,7 +18,6 @@ export const UiMessageSchema = z.object({ export type UiMessage = z.infer; -/** 工厂函数(替代 Dart `UiMessage(content:..., isFromAI:...)` 构造)。 */ export const UiMessage = { create(input: Omit & { date?: string }): UiMessage { return UiMessageSchema.parse({ diff --git a/src/stores/payment/__tests__/payment-catalog-flow.test.ts b/src/stores/payment/__tests__/payment-catalog-flow.test.ts index 4a976965..6b1f9c78 100644 --- a/src/stores/payment/__tests__/payment-catalog-flow.test.ts +++ b/src/stores/payment/__tests__/payment-catalog-flow.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { createActor, waitFor } from "xstate"; -import { PaymentPlansResponse } from "@/data/dto/payment"; +import { PaymentPlansResponse } from "@/data/schemas/payment"; import { createTestPaymentMachine, diff --git a/src/stores/payment/__tests__/payment-machine.test-utils.ts b/src/stores/payment/__tests__/payment-machine.test-utils.ts index affe25fb..92650889 100644 --- a/src/stores/payment/__tests__/payment-machine.test-utils.ts +++ b/src/stores/payment/__tests__/payment-machine.test-utils.ts @@ -6,7 +6,7 @@ import { type PayChannel, PaymentOrderStatusResponse, PaymentPlansResponse, -} from "@/data/dto/payment"; +} from "@/data/schemas/payment"; import { paymentMachine } from "@/stores/payment/payment-machine"; import type { PaymentPlanCatalog } from "@/stores/payment/payment-state"; diff --git a/src/stores/payment/helper/catalog.ts b/src/stores/payment/helper/catalog.ts index f5659b62..aec07d6f 100644 --- a/src/stores/payment/helper/catalog.ts +++ b/src/stores/payment/helper/catalog.ts @@ -1,4 +1,4 @@ -import { PaymentPlan, type PaymentPlansResponse } from "@/data/dto/payment"; +import { PaymentPlan, type PaymentPlansResponse } from "@/data/schemas/payment"; import type { PaymentState } from "../payment-state"; import { resetOrderState } from "./order"; diff --git a/src/stores/payment/helper/order.ts b/src/stores/payment/helper/order.ts index 8ec2e43b..781b7236 100644 --- a/src/stores/payment/helper/order.ts +++ b/src/stores/payment/helper/order.ts @@ -1,5 +1,5 @@ import { ExceptionHandler } from "@/core/errors"; -import type { PaymentPlan } from "@/data/dto/payment"; +import type { PaymentPlan } from "@/data/schemas/payment"; import type { PaymentState } from "../payment-state"; diff --git a/src/stores/payment/machine/actors/order.ts b/src/stores/payment/machine/actors/order.ts index 9e204876..1b3b5c2b 100644 --- a/src/stores/payment/machine/actors/order.ts +++ b/src/stores/payment/machine/actors/order.ts @@ -4,7 +4,7 @@ import { CreatePaymentOrderResponse, PayChannel, PaymentOrderStatusResponse, -} from "@/data/dto/payment"; +} from "@/data/schemas/payment"; import { getPaymentRepository } from "@/data/repositories/payment_repository"; import { Result } from "@/utils/result"; diff --git a/src/stores/payment/machine/actors/plans.ts b/src/stores/payment/machine/actors/plans.ts index 27be0868..fc2e8200 100644 --- a/src/stores/payment/machine/actors/plans.ts +++ b/src/stores/payment/machine/actors/plans.ts @@ -1,6 +1,6 @@ import { fromPromise } from "xstate"; -import { PaymentPlansResponse } from "@/data/dto/payment"; +import { PaymentPlansResponse } from "@/data/schemas/payment"; import { getPaymentRepository } from "@/data/repositories/payment_repository"; import { Result } from "@/utils/result"; diff --git a/src/stores/payment/machine/catalog-flow.ts b/src/stores/payment/machine/catalog-flow.ts index d2cf324f..146921fc 100644 --- a/src/stores/payment/machine/catalog-flow.ts +++ b/src/stores/payment/machine/catalog-flow.ts @@ -1,6 +1,6 @@ import type { DoneActorEvent, ErrorActorEvent } from "xstate"; -import type { PaymentPlansResponse } from "@/data/dto/payment"; +import type { PaymentPlansResponse } from "@/data/schemas/payment"; import { consumeFirstRechargeState, diff --git a/src/stores/payment/machine/order-flow.ts b/src/stores/payment/machine/order-flow.ts index 49c2076f..6926f136 100644 --- a/src/stores/payment/machine/order-flow.ts +++ b/src/stores/payment/machine/order-flow.ts @@ -3,7 +3,7 @@ import type { DoneActorEvent, ErrorActorEvent } from "xstate"; import type { CreatePaymentOrderResponse, PaymentOrderStatusResponse, -} from "@/data/dto/payment"; +} from "@/data/schemas/payment"; import { behaviorAnalytics } from "@/lib/analytics"; import { diff --git a/src/stores/payment/payment-events.ts b/src/stores/payment/payment-events.ts index 45d63d14..103316a4 100644 --- a/src/stores/payment/payment-events.ts +++ b/src/stores/payment/payment-events.ts @@ -1,7 +1,7 @@ /** * Payment 状态机:事件联合 */ -import type { PayChannel } from "@/data/dto/payment"; +import type { PayChannel } from "@/data/schemas/payment"; import type { PaymentPlanCatalog } from "./payment-state"; export type PaymentEvent = diff --git a/src/stores/payment/payment-state.ts b/src/stores/payment/payment-state.ts index 40876b93..e6620a7a 100644 --- a/src/stores/payment/payment-state.ts +++ b/src/stores/payment/payment-state.ts @@ -5,7 +5,7 @@ import type { PayChannel, PaymentOrderStatus, PaymentPlan, -} from "@/data/dto/payment"; +} from "@/data/schemas/payment"; export type PaymentPlanCatalog = "default" | "tip"; diff --git a/src/stores/private-room/__tests__/private-room-machine.test-utils.ts b/src/stores/private-room/__tests__/private-room-machine.test-utils.ts index fd5e66c0..b2144fd8 100644 --- a/src/stores/private-room/__tests__/private-room-machine.test-utils.ts +++ b/src/stores/private-room/__tests__/private-room-machine.test-utils.ts @@ -3,7 +3,7 @@ import { createActor, fromPromise, waitFor } from "xstate"; import { PrivateAlbumsResponse, PrivateAlbumUnlockResponse, -} from "@/data/dto/private-room"; +} from "@/data/schemas/private-room"; import { DEFAULT_CHARACTER_ID } from "@/data/constants/character"; import { privateRoomMachine } from "@/stores/private-room/private-room-machine"; diff --git a/src/stores/private-room/helper/albums.ts b/src/stores/private-room/helper/albums.ts index 8a8b7ed6..0582355c 100644 --- a/src/stores/private-room/helper/albums.ts +++ b/src/stores/private-room/helper/albums.ts @@ -2,7 +2,7 @@ import { PrivateAlbum, type PrivateAlbumsResponse, type PrivateAlbumUnlockResponse, -} from "@/data/dto/private-room"; +} from "@/data/schemas/private-room"; import type { PrivateRoomState } from "../private-room-state"; diff --git a/src/stores/private-room/helper/unlock.ts b/src/stores/private-room/helper/unlock.ts index 3cd216a7..597b289f 100644 --- a/src/stores/private-room/helper/unlock.ts +++ b/src/stores/private-room/helper/unlock.ts @@ -1,4 +1,4 @@ -import type { PrivateAlbumUnlockResponse } from "@/data/dto/private-room"; +import type { PrivateAlbumUnlockResponse } from "@/data/schemas/private-room"; import type { PrivateRoomUnlockPaywallRequest } from "../private-room-state"; diff --git a/src/stores/private-room/machine/actors/albums.ts b/src/stores/private-room/machine/actors/albums.ts index 66ebb6ce..6e08b6f6 100644 --- a/src/stores/private-room/machine/actors/albums.ts +++ b/src/stores/private-room/machine/actors/albums.ts @@ -3,7 +3,7 @@ import { fromPromise } from "xstate"; import type { PrivateAlbumsResponse, PrivateAlbumUnlockResponse, -} from "@/data/dto/private-room"; +} from "@/data/schemas/private-room"; import { getPrivateRoomRepository } from "@/data/repositories/private_room_repository"; import { Result } from "@/utils/result"; diff --git a/src/stores/private-room/machine/album-flow.ts b/src/stores/private-room/machine/album-flow.ts index 8c7c7b08..6828ee3c 100644 --- a/src/stores/private-room/machine/album-flow.ts +++ b/src/stores/private-room/machine/album-flow.ts @@ -1,6 +1,6 @@ import type { DoneActorEvent, ErrorActorEvent } from "xstate"; -import type { PrivateAlbumsResponse } from "@/data/dto/private-room"; +import type { PrivateAlbumsResponse } from "@/data/schemas/private-room"; import { applyAlbumsResponse } from "../helper/albums"; import { toPrivateRoomErrorMessage } from "../helper/unlock"; diff --git a/src/stores/private-room/machine/unlock-flow.ts b/src/stores/private-room/machine/unlock-flow.ts index ca58fd5d..fc57dc2c 100644 --- a/src/stores/private-room/machine/unlock-flow.ts +++ b/src/stores/private-room/machine/unlock-flow.ts @@ -1,6 +1,6 @@ import type { DoneActorEvent, ErrorActorEvent } from "xstate"; -import type { PrivateAlbumUnlockResponse } from "@/data/dto/private-room"; +import type { PrivateAlbumUnlockResponse } from "@/data/schemas/private-room"; import { getPendingAlbum, diff --git a/src/stores/private-room/private-room-state.ts b/src/stores/private-room/private-room-state.ts index 299cf7ec..ad7167e3 100644 --- a/src/stores/private-room/private-room-state.ts +++ b/src/stores/private-room/private-room-state.ts @@ -1,4 +1,4 @@ -import type { PrivateAlbum } from "@/data/dto/private-room"; +import type { PrivateAlbum } from "@/data/schemas/private-room"; import { DEFAULT_CHARACTER_ID } from "@/data/constants/character"; export interface PrivateRoomUnlockPaywallRequest { diff --git a/src/stores/sync/user-auth-sync.tsx b/src/stores/sync/user-auth-sync.tsx index 400f66c3..4134a9c3 100644 --- a/src/stores/sync/user-auth-sync.tsx +++ b/src/stores/sync/user-auth-sync.tsx @@ -11,7 +11,7 @@ import { useEffect, useRef } from "react"; import { shallowEqual } from "@xstate/react"; -import type { LoginStatus } from "@/data/dto/auth"; +import type { LoginStatus } from "@/data/schemas/auth"; import { selectAuthIsLoading, useAuthSelector, diff --git a/src/stores/user/__tests__/user-machine.test-utils.ts b/src/stores/user/__tests__/user-machine.test-utils.ts index 02586324..9b66a288 100644 --- a/src/stores/user/__tests__/user-machine.test-utils.ts +++ b/src/stores/user/__tests__/user-machine.test-utils.ts @@ -1,7 +1,7 @@ import { vi } from "vitest"; import { fromPromise } from "xstate"; -import type { UserView } from "@/data/dto/user"; +import type { UserView } from "@/stores/user/user-view"; import { userMachine } from "@/stores/user/user-machine"; import type { UserFetchData } from "@/stores/user/machine/actors/profile"; import type { InitData } from "@/stores/user/machine/actors/session"; diff --git a/src/stores/user/helper/view.ts b/src/stores/user/helper/view.ts index 816ac987..8d31bd14 100644 --- a/src/stores/user/helper/view.ts +++ b/src/stores/user/helper/view.ts @@ -1,4 +1,4 @@ -import type { UserView } from "@/data/dto/user"; +import type { UserView } from "@/stores/user/user-view"; import type { UserEntitlementSnapshotData } from "@/data/schemas/user"; export function toView(u: { diff --git a/src/stores/user/index.ts b/src/stores/user/index.ts index fd2af279..d8281e27 100644 --- a/src/stores/user/index.ts +++ b/src/stores/user/index.ts @@ -5,3 +5,4 @@ export * from "./user-events"; export * from "./helper"; export * from "./user-machine"; export * from "./user-state"; +export * from "./user-view"; diff --git a/src/stores/user/machine/actors/profile.ts b/src/stores/user/machine/actors/profile.ts index c99fb534..b3da0165 100644 --- a/src/stores/user/machine/actors/profile.ts +++ b/src/stores/user/machine/actors/profile.ts @@ -1,6 +1,6 @@ import { fromPromise } from "xstate"; -import type { UserView } from "@/data/dto/user"; +import type { UserView } from "@/stores/user/user-view"; import { getUserRepository } from "@/data/repositories/user_repository"; import type { UserEntitlementSnapshotData } from "@/data/schemas/user"; import { UserStorage } from "@/data/storage/user/user_storage"; diff --git a/src/stores/user/machine/actors/session.ts b/src/stores/user/machine/actors/session.ts index 15c1bacd..a8a56e36 100644 --- a/src/stores/user/machine/actors/session.ts +++ b/src/stores/user/machine/actors/session.ts @@ -1,6 +1,6 @@ import { fromPromise } from "xstate"; -import type { UserView } from "@/data/dto/user"; +import type { UserView } from "@/stores/user/user-view"; import { getAuthRepository } from "@/data/repositories/auth_repository"; import type { UserEntitlementSnapshotData } from "@/data/schemas/user"; import { UserStorage } from "@/data/storage/user/user_storage"; diff --git a/src/stores/user/user-events.ts b/src/stores/user/user-events.ts index 8f0c1f60..1703e56a 100644 --- a/src/stores/user/user-events.ts +++ b/src/stores/user/user-events.ts @@ -1,7 +1,7 @@ /** * User 状态机:事件联合 */ -import type { UserView } from "@/data/dto/user"; +import type { UserView } from "@/stores/user/user-view"; export type UserEvent = | { type: "UserInit" } diff --git a/src/stores/user/user-state.ts b/src/stores/user/user-state.ts index f43cbca5..e9a0b5e5 100644 --- a/src/stores/user/user-state.ts +++ b/src/stores/user/user-state.ts @@ -1,7 +1,7 @@ /** * User 状态机:State 形状 + 初始值 */ -import type { UserView } from "@/data/dto/user"; +import type { UserView } from "@/stores/user/user-view"; export interface UserState { currentUser: UserView | null; diff --git a/src/data/dto/user/user_view.ts b/src/stores/user/user-view.ts similarity index 78% rename from src/data/dto/user/user_view.ts rename to src/stores/user/user-view.ts index f47325b3..6b03b3df 100644 --- a/src/data/dto/user/user_view.ts +++ b/src/stores/user/user-view.ts @@ -1,8 +1,3 @@ -/** - * UI 侧用户模型(基于 data/dto/user/user + user_storage 重新导出 view-model) - * - * - */ import { z } from "zod"; export const UserViewSchema = z.object({