feat: implement lazy singleton pattern for repository instances and update related imports
This commit is contained in:
@@ -16,7 +16,7 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
||||||
import { paymentRepository } from "@/data/repositories/payment_repository";
|
import { getPaymentRepository } from "@/data/repositories/payment_repository";
|
||||||
import { ROUTES } from "@/router/routes";
|
import { ROUTES } from "@/router/routes";
|
||||||
|
|
||||||
export default function SubscriptionSuccessPage() {
|
export default function SubscriptionSuccessPage() {
|
||||||
@@ -33,7 +33,7 @@ export default function SubscriptionSuccessPage() {
|
|||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const tryRefresh = async (attemptsLeft: number) => {
|
const tryRefresh = async (attemptsLeft: number) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
const r = await paymentRepository.getVipStatus();
|
const r = await getPaymentRepository().getVipStatus();
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
if (r.success) {
|
if (r.success) {
|
||||||
if (r.data.isVip) {
|
if (r.data.isVip) {
|
||||||
|
|||||||
@@ -1,17 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
/**
|
|
||||||
* AuthRepository
|
|
||||||
*
|
|
||||||
* 认证仓库:编排远程 API + 本地 AuthStorage + UserStorage。
|
|
||||||
* 行为对齐原 Dart `lib/data/repositories/auth_repository_impl.dart`:
|
|
||||||
* - 登录成功后调 `_saveLoginData` 持久化 token + User
|
|
||||||
* - 登出先调 API,再强制清本地态(无论 API 成败)
|
|
||||||
* - `getCurrentUser` 成功后尽力写本地 User 缓存(不因缓存失败影响主流程)
|
|
||||||
* - 写 storage 失败用 `Logger.warn` 记录,不让本地缓存失败影响主流程
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
import { AuthApi, authApi } from "@/data/services/api";
|
import { AuthApi, authApi } from "@/data/services/api";
|
||||||
import {
|
import {
|
||||||
AppleLoginRequest,
|
AppleLoginRequest,
|
||||||
@@ -32,6 +20,7 @@ import { AppEnvUtil, Result, Logger } from "@/utils";
|
|||||||
import type { IAuthRepository } from "@/data/repositories/interfaces";
|
import type { IAuthRepository } from "@/data/repositories/interfaces";
|
||||||
import { AuthStorage, type IAuthStorage } from "@/data/storage/auth";
|
import { AuthStorage, type IAuthStorage } from "@/data/storage/auth";
|
||||||
import { UserStorage, type IUserStorage } from "@/data/storage/user";
|
import { UserStorage, type IUserStorage } from "@/data/storage/user";
|
||||||
|
import { createLazySingleton } from "./lazy_singleton";
|
||||||
|
|
||||||
const log = new Logger("DataRepositoriesAuthRepository");
|
const log = new Logger("DataRepositoriesAuthRepository");
|
||||||
|
|
||||||
@@ -312,11 +301,14 @@ export class AuthRepository implements IAuthRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 全局单例。 */
|
/** 全局懒单例。 */
|
||||||
export const authRepository = new AuthRepository(
|
export const getAuthRepository = createLazySingleton<IAuthRepository>(
|
||||||
|
() =>
|
||||||
|
new AuthRepository(
|
||||||
authApi,
|
authApi,
|
||||||
AuthStorage.getInstance(),
|
AuthStorage.getInstance(),
|
||||||
UserStorage.getInstance(),
|
UserStorage.getInstance(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
function withTestAccountFlag<T extends Record<string, unknown>>(
|
function withTestAccountFlag<T extends Record<string, unknown>>(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { Result } from "@/utils";
|
import { Result } from "@/utils";
|
||||||
import type { IChatRepository } from "@/data/repositories/interfaces";
|
import type { IChatRepository } from "@/data/repositories/interfaces";
|
||||||
import { LocalChatStorage, LocalMessage } from "@/data/storage/chat";
|
import { LocalChatStorage, LocalMessage } from "@/data/storage/chat";
|
||||||
|
import { createLazySingleton } from "./lazy_singleton";
|
||||||
|
|
||||||
export class ChatRepository implements IChatRepository {
|
export class ChatRepository implements IChatRepository {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -186,8 +187,11 @@ export class ChatRepository implements IChatRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 全局单例。 */
|
/** 全局懒单例。 */
|
||||||
export const chatRepository = new ChatRepository(
|
export const getChatRepository = createLazySingleton<IChatRepository>(
|
||||||
|
() =>
|
||||||
|
new ChatRepository(
|
||||||
chatApi,
|
chatApi,
|
||||||
LocalChatStorage.getInstance(),
|
LocalChatStorage.getInstance(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export function createLazySingleton<T>(factory: () => T): () => T {
|
||||||
|
let instance: T | null = null;
|
||||||
|
return () => {
|
||||||
|
instance ??= factory();
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import { MetricsApi, metricsApi } from "@/data/services/api";
|
|||||||
import { AppEvent, PwaEvent } from "@/data/dto/metrics";
|
import { AppEvent, PwaEvent } from "@/data/dto/metrics";
|
||||||
import { Result } from "@/utils";
|
import { Result } from "@/utils";
|
||||||
import type { IMetricsRepository } from "@/data/repositories/interfaces";
|
import type { IMetricsRepository } from "@/data/repositories/interfaces";
|
||||||
|
import { createLazySingleton } from "./lazy_singleton";
|
||||||
|
|
||||||
export class MetricsRepository implements IMetricsRepository {
|
export class MetricsRepository implements IMetricsRepository {
|
||||||
constructor(private readonly api: MetricsApi) {}
|
constructor(private readonly api: MetricsApi) {}
|
||||||
@@ -56,5 +57,7 @@ export class MetricsRepository implements IMetricsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 全局单例。 */
|
/** 全局懒单例。 */
|
||||||
export const metricsRepository = new MetricsRepository(metricsApi);
|
export const getMetricsRepository = createLazySingleton<IMetricsRepository>(
|
||||||
|
() => new MetricsRepository(metricsApi),
|
||||||
|
);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { PaymentApi, paymentApi } from "@/data/services/api";
|
|||||||
import { PaymentPlansStorage } from "@/data/storage/payment";
|
import { PaymentPlansStorage } from "@/data/storage/payment";
|
||||||
import type { IPaymentRepository } from "@/data/repositories/interfaces";
|
import type { IPaymentRepository } from "@/data/repositories/interfaces";
|
||||||
import { Result } from "@/utils/result";
|
import { Result } from "@/utils/result";
|
||||||
|
import { createLazySingleton } from "./lazy_singleton";
|
||||||
|
|
||||||
export class PaymentRepository implements IPaymentRepository {
|
export class PaymentRepository implements IPaymentRepository {
|
||||||
constructor(private readonly api: PaymentApi) {}
|
constructor(private readonly api: PaymentApi) {}
|
||||||
@@ -83,8 +84,10 @@ export class PaymentRepository implements IPaymentRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 全局单例。 */
|
/** 全局懒单例。 */
|
||||||
export const paymentRepository = new PaymentRepository(paymentApi);
|
export const getPaymentRepository = createLazySingleton<IPaymentRepository>(
|
||||||
|
() => new PaymentRepository(paymentApi),
|
||||||
|
);
|
||||||
|
|
||||||
export function isVipPaymentPlan(plan: PaymentPlan): boolean {
|
export function isVipPaymentPlan(plan: PaymentPlan): boolean {
|
||||||
return plan.vipDays !== null;
|
return plan.vipDays !== null;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "@/data/dto/user";
|
} from "@/data/dto/user";
|
||||||
import { Result } from "@/utils";
|
import { Result } from "@/utils";
|
||||||
import type { IUserRepository } from "@/data/repositories/interfaces";
|
import type { IUserRepository } from "@/data/repositories/interfaces";
|
||||||
|
import { createLazySingleton } from "./lazy_singleton";
|
||||||
|
|
||||||
export class UserRepository implements IUserRepository {
|
export class UserRepository implements IUserRepository {
|
||||||
constructor(private readonly api: UserApi) {}
|
constructor(private readonly api: UserApi) {}
|
||||||
@@ -48,5 +49,7 @@ export class UserRepository implements IUserRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 全局单例。 */
|
/** 全局懒单例。 */
|
||||||
export const userRepository = new UserRepository(userApi);
|
export const getUserRepository = createLazySingleton<IUserRepository>(
|
||||||
|
() => new UserRepository(userApi),
|
||||||
|
);
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import { fromPromise } from "xstate";
|
|||||||
|
|
||||||
import { AuthPlatform, type AuthProvider } from "@/lib/auth/auth_platform";
|
import { AuthPlatform, type AuthProvider } from "@/lib/auth/auth_platform";
|
||||||
import type { LoginStatus } from "@/data/dto/auth";
|
import type { LoginStatus } from "@/data/dto/auth";
|
||||||
import { authRepository } from "@/data/repositories/auth_repository";
|
import { getAuthRepository } from "@/data/repositories/auth_repository";
|
||||||
import type { IAuthRepository } from "@/data/repositories/interfaces";
|
|
||||||
import { fetchFacebookUserData } from "@/data/services";
|
import { fetchFacebookUserData } from "@/data/services";
|
||||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||||
import { UserStorage } from "@/data/storage/user/user_storage";
|
import { UserStorage } from "@/data/storage/user/user_storage";
|
||||||
@@ -16,11 +15,9 @@ import { readGuestId } from "./auth-helpers";
|
|||||||
|
|
||||||
const log = new Logger("AuthActors");
|
const log = new Logger("AuthActors");
|
||||||
|
|
||||||
// 仓库以接口类型注入:调用面只看接口,运行时仍是同一单例
|
|
||||||
const authRepo: IAuthRepository = authRepository;
|
|
||||||
|
|
||||||
export const emailLoginActor = fromPromise<LoginStatus, { email: string; password: string }>(
|
export const emailLoginActor = fromPromise<LoginStatus, { email: string; password: string }>(
|
||||||
async ({ input }) => {
|
async ({ input }) => {
|
||||||
|
const authRepo = getAuthRepository();
|
||||||
log.debug("[auth-machine] emailLoginActor ENTRY", {
|
log.debug("[auth-machine] emailLoginActor ENTRY", {
|
||||||
emailLength: input.email.length,
|
emailLength: input.email.length,
|
||||||
emailPreview: input.email.slice(0, 8),
|
emailPreview: input.email.slice(0, 8),
|
||||||
@@ -46,6 +43,7 @@ export const emailRegisterThenLoginActor = fromPromise<
|
|||||||
LoginStatus,
|
LoginStatus,
|
||||||
{ email: string; password: string; username: string; confirmPassword: string }
|
{ email: string; password: string; username: string; confirmPassword: string }
|
||||||
>(async ({ input }) => {
|
>(async ({ input }) => {
|
||||||
|
const authRepo = getAuthRepository();
|
||||||
log.debug("[auth-machine] emailRegisterThenLoginActor ENTRY", {
|
log.debug("[auth-machine] emailRegisterThenLoginActor ENTRY", {
|
||||||
emailLength: input.email.length,
|
emailLength: input.email.length,
|
||||||
usernameLength: input.username.length,
|
usernameLength: input.username.length,
|
||||||
@@ -96,6 +94,7 @@ export const oauthSignInActor = fromPromise<void, AuthProvider>(async ({ input }
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const logoutActor = fromPromise<void>(async () => {
|
export const logoutActor = fromPromise<void>(async () => {
|
||||||
|
const authRepo = getAuthRepository();
|
||||||
const result = await authRepo.logout();
|
const result = await authRepo.logout();
|
||||||
if (Result.isErr(result)) throw result.error;
|
if (Result.isErr(result)) throw result.error;
|
||||||
});
|
});
|
||||||
@@ -103,10 +102,11 @@ export const logoutActor = fromPromise<void>(async () => {
|
|||||||
// ========== OAuth token → 后端业务 token sync actors ==========
|
// ========== OAuth token → 后端业务 token sync actors ==========
|
||||||
// 由 <OAuthSessionSync /> 在 NextAuth 回调后派发(监听 useSession())。
|
// 由 <OAuthSessionSync /> 在 NextAuth 回调后派发(监听 useSession())。
|
||||||
// 用 OAuth provider 的 idToken / accessToken 调后端换业务 token + user。
|
// 用 OAuth provider 的 idToken / accessToken 调后端换业务 token + user。
|
||||||
// 后端返回的 LoginResponse 已经被 authRepository._saveLoginData 持久化。
|
// 后端返回的 LoginResponse 已经被 AuthRepository._saveLoginData 持久化。
|
||||||
|
|
||||||
export const syncGoogleBackendActor = fromPromise<LoginStatus, { idToken: string }>(
|
export const syncGoogleBackendActor = fromPromise<LoginStatus, { idToken: string }>(
|
||||||
async ({ input }) => {
|
async ({ input }) => {
|
||||||
|
const authRepo = getAuthRepository();
|
||||||
const guestId = await readGuestId();
|
const guestId = await readGuestId();
|
||||||
const result = await authRepo.googleLogin({
|
const result = await authRepo.googleLogin({
|
||||||
idToken: input.idToken,
|
idToken: input.idToken,
|
||||||
@@ -121,6 +121,7 @@ export const syncFacebookBackendActor = fromPromise<
|
|||||||
LoginStatus,
|
LoginStatus,
|
||||||
{ accessToken: string }
|
{ accessToken: string }
|
||||||
>(async ({ input }) => {
|
>(async ({ input }) => {
|
||||||
|
const authRepo = getAuthRepository();
|
||||||
const guestId = await readGuestId();
|
const guestId = await readGuestId();
|
||||||
const result = await authRepo.facebookLogin({
|
const result = await authRepo.facebookLogin({
|
||||||
accessToken: input.accessToken,
|
accessToken: input.accessToken,
|
||||||
@@ -193,13 +194,6 @@ async function persistFacebookProfile(accessToken: string): Promise<void> {
|
|||||||
|
|
||||||
// ========== 显式游客登录 actor(opt-in) ==========
|
// ========== 显式游客登录 actor(opt-in) ==========
|
||||||
// 由 splash UI 派发 `AuthGuestLoginSubmitted` 触发,不自动跑。
|
// 由 splash UI 派发 `AuthGuestLoginSubmitted` 触发,不自动跑。
|
||||||
// 取代了之前 checkAuthStatusActor 的 auto-guest-login 行为 —— 避免每次
|
|
||||||
// 访 splash 都自动创建游客账号污染后端。
|
|
||||||
//
|
|
||||||
// 短路优化:本地已有 guestToken 时直接 return "guest",跳过 deviceId
|
|
||||||
// 探测 + 后端 round-trip(splash 每打开一次就可能点一次 Skip,命中率高)。
|
|
||||||
// 已知 trade-off:若后端已吊销该 token,actor 仍返回 "guest" —— 任何后续
|
|
||||||
// 受保护 API 401 会由 HTTP 层清掉 token 回到 splash,无需在此处主动校验。
|
|
||||||
|
|
||||||
export const guestLoginActor = fromPromise<LoginStatus, void>(async () => {
|
export const guestLoginActor = fromPromise<LoginStatus, void>(async () => {
|
||||||
log.debug("[auth-machine] guestLoginActor ENTRY");
|
log.debug("[auth-machine] guestLoginActor ENTRY");
|
||||||
@@ -223,9 +217,9 @@ export const guestLoginActor = fromPromise<LoginStatus, void>(async () => {
|
|||||||
prefix: deviceId.slice(0, 8),
|
prefix: deviceId.slice(0, 8),
|
||||||
});
|
});
|
||||||
|
|
||||||
log.debug("[auth-machine] guestLoginActor calling authRepository.guestLogin");
|
log.debug("[auth-machine] guestLoginActor calling authRepo.guestLogin");
|
||||||
const result = await authRepository.guestLogin(deviceId);
|
const result = await getAuthRepository().guestLogin(deviceId);
|
||||||
log.debug("[auth-machine] guestLoginActor authRepository.guestLogin DONE", {
|
log.debug("[auth-machine] guestLoginActor authRepo.guestLogin DONE", {
|
||||||
success: result.success,
|
success: result.success,
|
||||||
hasData: result.success ? !!result.data : null,
|
hasData: result.success ? !!result.data : null,
|
||||||
errorName: result.success ? null : result.error?.name,
|
errorName: result.success ? null : result.error?.name,
|
||||||
@@ -260,6 +254,7 @@ export const checkAuthStatusActor = fromPromise<LoginStatus, void>(async () => {
|
|||||||
// 3. 外部浏览器深链同步:仅 fbid 落地时,用它换业务 token。
|
// 3. 外部浏览器深链同步:仅 fbid 落地时,用它换业务 token。
|
||||||
const facebookIdR = await storage.getFacebookId();
|
const facebookIdR = await storage.getFacebookId();
|
||||||
if (facebookIdR.success && facebookIdR.data) {
|
if (facebookIdR.success && facebookIdR.data) {
|
||||||
|
const authRepo = getAuthRepository();
|
||||||
const avatarUrlR = await UserStorage.getInstance().getAvatarUrl();
|
const avatarUrlR = await UserStorage.getInstance().getAvatarUrl();
|
||||||
const result = await authRepo.facebookIdLogin({
|
const result = await authRepo.facebookIdLogin({
|
||||||
fbId: facebookIdR.data,
|
fbId: facebookIdR.data,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* - 有 NextAuth OAuth session(provider + token)
|
* - 有 NextAuth OAuth session(provider + token)
|
||||||
* - 且 auth machine 还没有该 provider 的业务 token
|
* - 且 auth machine 还没有该 provider 的业务 token
|
||||||
* → 派发 `AuthGoogleSyncSubmitted` / `AuthFacebookSyncSubmitted` 事件
|
* → 派发 `AuthGoogleSyncSubmitted` / `AuthFacebookSyncSubmitted` 事件
|
||||||
* → auth machine 调 `authRepository.googleLogin/facebookLogin` 换业务 token
|
* → auth machine 调 `AuthRepository.googleLogin/facebookLogin` 换业务 token
|
||||||
* → 业务 token 写入本地 storage
|
* → 业务 token 写入本地 storage
|
||||||
* → **清掉 NextAuth session**(`signOut({ redirect: false })`):
|
* → **清掉 NextAuth session**(`signOut({ redirect: false })`):
|
||||||
* idToken / accessToken 已交给后端换业务 token,浏览器侧不再需要保留 OAuth 凭证
|
* idToken / accessToken 已交给后端换业务 token,浏览器侧不再需要保留 OAuth 凭证
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import { fromPromise, fromCallback } from "xstate";
|
|||||||
|
|
||||||
import { MessageQueue } from "@/core/net/message-queue";
|
import { MessageQueue } from "@/core/net/message-queue";
|
||||||
import type { ChatSendResponse } from "@/data/dto/chat";
|
import type { ChatSendResponse } from "@/data/dto/chat";
|
||||||
|
import { getChatRepository } from "@/data/repositories/chat_repository";
|
||||||
import { Result, Logger } from "@/utils";
|
import { Result, Logger } from "@/utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PAGE_SIZE,
|
PAGE_SIZE,
|
||||||
chatRepo,
|
|
||||||
localMessagesToUi,
|
localMessagesToUi,
|
||||||
readAndSyncHistory,
|
readAndSyncHistory,
|
||||||
sendResponseToUiMessage,
|
sendResponseToUiMessage,
|
||||||
@@ -60,6 +60,7 @@ export const loadMoreHistoryActor = fromPromise<
|
|||||||
{ messages: UiMessage[]; hasMore: boolean; newOffset: number },
|
{ messages: UiMessage[]; hasMore: boolean; newOffset: number },
|
||||||
{ offset: number }
|
{ offset: number }
|
||||||
>(async ({ input }) => {
|
>(async ({ input }) => {
|
||||||
|
const chatRepo = getChatRepository();
|
||||||
const result = await chatRepo.getHistory(PAGE_SIZE, input.offset);
|
const result = await chatRepo.getHistory(PAGE_SIZE, input.offset);
|
||||||
if (Result.isErr(result)) {
|
if (Result.isErr(result)) {
|
||||||
log.error("[chat-machine] loadMoreHistoryActor failed", { error: result.error });
|
log.error("[chat-machine] loadMoreHistoryActor failed", { error: result.error });
|
||||||
@@ -81,6 +82,7 @@ export const unlockHistoryActor = fromPromise<{
|
|||||||
hasMore: boolean;
|
hasMore: boolean;
|
||||||
newOffset: number;
|
newOffset: number;
|
||||||
}>(async () => {
|
}>(async () => {
|
||||||
|
const chatRepo = getChatRepository();
|
||||||
const unlockResult = await chatRepo.unlockHistory();
|
const unlockResult = await chatRepo.unlockHistory();
|
||||||
if (Result.isErr(unlockResult)) {
|
if (Result.isErr(unlockResult)) {
|
||||||
log.error("[chat-machine] unlockHistoryActor failed", {
|
log.error("[chat-machine] unlockHistoryActor failed", {
|
||||||
@@ -148,6 +150,7 @@ async function sendMessageViaHttp(content: string): Promise<{
|
|||||||
response: ChatSendResponse;
|
response: ChatSendResponse;
|
||||||
reply: UiMessage | null;
|
reply: UiMessage | null;
|
||||||
}> {
|
}> {
|
||||||
|
const chatRepo = getChatRepository();
|
||||||
const result = await chatRepo.sendMessage(content);
|
const result = await chatRepo.sendMessage(content);
|
||||||
if (Result.isErr(result)) {
|
if (Result.isErr(result)) {
|
||||||
log.error("[chat-machine] sendMessageHttpActor failed", { error: result.error });
|
log.error("[chat-machine] sendMessageHttpActor failed", { error: result.error });
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { UiMessage } from "@/data/dto/chat";
|
import type { UiMessage } from "@/data/dto/chat";
|
||||||
import { chatRepository } from "@/data/repositories/chat_repository";
|
import { getChatRepository } from "@/data/repositories/chat_repository";
|
||||||
import type { IChatRepository } from "@/data/repositories/interfaces";
|
|
||||||
import { ChatSendResponse } from "@/data/dto/chat/chat_send_response";
|
import { ChatSendResponse } from "@/data/dto/chat/chat_send_response";
|
||||||
import { todayString, Result, Logger } from "@/utils";
|
import { todayString, Result, Logger } from "@/utils";
|
||||||
|
|
||||||
@@ -14,12 +13,6 @@ const log = new Logger("StoresChatChatMachineHelpers");
|
|||||||
/** 翻历史每页大小(仅 `loadMoreHistoryActor` 用) */
|
/** 翻历史每页大小(仅 `loadMoreHistoryActor` 用) */
|
||||||
export const PAGE_SIZE = 50;
|
export const PAGE_SIZE = 50;
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Repository injection
|
|
||||||
// ============================================================
|
|
||||||
// 仓库以接口类型注入:调用面只看接口,运行时仍是同一单例
|
|
||||||
export const chatRepo: IChatRepository = chatRepository;
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// DTO ↔ UiMessage 映射
|
// DTO ↔ UiMessage 映射
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -279,6 +272,7 @@ export async function readAndSyncHistory(): Promise<{
|
|||||||
* - 用户从未发过消息就关掉 app 再开,仍然没 network 消息 → 会再次显示本条
|
* - 用户从未发过消息就关掉 app 再开,仍然没 network 消息 → 会再次显示本条
|
||||||
* ("每次冷启动给个温暖的起点" —— 若要"只显示一次",需额外存 local)
|
* ("每次冷启动给个温暖的起点" —— 若要"只显示一次",需额外存 local)
|
||||||
*/
|
*/
|
||||||
|
const chatRepo = getChatRepository();
|
||||||
const greetingMessage: UiMessage = {
|
const greetingMessage: UiMessage = {
|
||||||
content:
|
content:
|
||||||
"You're here! Facebook is so restrictive, " +
|
"You're here! Facebook is so restrictive, " +
|
||||||
|
|||||||
@@ -9,14 +9,12 @@ import {
|
|||||||
PaymentOrderStatusResponse,
|
PaymentOrderStatusResponse,
|
||||||
PaymentPlansResponse,
|
PaymentPlansResponse,
|
||||||
} from "@/data/dto/payment";
|
} from "@/data/dto/payment";
|
||||||
import { paymentRepository } from "@/data/repositories/payment_repository";
|
import { getPaymentRepository } from "@/data/repositories/payment_repository";
|
||||||
import type { IPaymentRepository } from "@/data/repositories/interfaces";
|
|
||||||
import { Result } from "@/utils";
|
import { Result } from "@/utils";
|
||||||
|
|
||||||
const paymentRepo: IPaymentRepository = paymentRepository;
|
|
||||||
|
|
||||||
export const loadCachedPaymentPlansActor =
|
export const loadCachedPaymentPlansActor =
|
||||||
fromPromise<PaymentPlansResponse | null>(async () => {
|
fromPromise<PaymentPlansResponse | null>(async () => {
|
||||||
|
const paymentRepo = getPaymentRepository();
|
||||||
const result = await paymentRepo.getCachedPlans();
|
const result = await paymentRepo.getCachedPlans();
|
||||||
if (Result.isErr(result)) throw result.error;
|
if (Result.isErr(result)) throw result.error;
|
||||||
return result.data;
|
return result.data;
|
||||||
@@ -24,6 +22,7 @@ export const loadCachedPaymentPlansActor =
|
|||||||
|
|
||||||
export const refreshPaymentPlansActor = fromPromise<PaymentPlansResponse>(
|
export const refreshPaymentPlansActor = fromPromise<PaymentPlansResponse>(
|
||||||
async () => {
|
async () => {
|
||||||
|
const paymentRepo = getPaymentRepository();
|
||||||
const result = await paymentRepo.getPlans();
|
const result = await paymentRepo.getPlans();
|
||||||
if (Result.isErr(result)) throw result.error;
|
if (Result.isErr(result)) throw result.error;
|
||||||
return result.data;
|
return result.data;
|
||||||
@@ -34,6 +33,7 @@ export const createPaymentOrderActor = fromPromise<
|
|||||||
CreatePaymentOrderResponse,
|
CreatePaymentOrderResponse,
|
||||||
{ planId: string; payChannel: PayChannel; autoRenew: boolean }
|
{ planId: string; payChannel: PayChannel; autoRenew: boolean }
|
||||||
>(async ({ input }) => {
|
>(async ({ input }) => {
|
||||||
|
const paymentRepo = getPaymentRepository();
|
||||||
const result = await paymentRepo.createOrder(
|
const result = await paymentRepo.createOrder(
|
||||||
input.planId,
|
input.planId,
|
||||||
input.payChannel,
|
input.payChannel,
|
||||||
@@ -47,6 +47,7 @@ export const pollPaymentOrderStatusActor = fromPromise<
|
|||||||
PaymentOrderStatusResponse,
|
PaymentOrderStatusResponse,
|
||||||
{ orderId: string }
|
{ orderId: string }
|
||||||
>(async ({ input }) => {
|
>(async ({ input }) => {
|
||||||
|
const paymentRepo = getPaymentRepository();
|
||||||
const result = await paymentRepo.getOrderStatus(input.orderId);
|
const result = await paymentRepo.getOrderStatus(input.orderId);
|
||||||
if (Result.isErr(result)) throw result.error;
|
if (Result.isErr(result)) throw result.error;
|
||||||
return result.data;
|
return result.data;
|
||||||
|
|||||||
@@ -8,18 +8,14 @@
|
|||||||
*
|
*
|
||||||
* 设计:
|
* 设计:
|
||||||
* - 依赖 `user-machine.helpers.ts`(toView, readInitData, userStorage)
|
* - 依赖 `user-machine.helpers.ts`(toView, readInitData, userStorage)
|
||||||
* - 仓库以接口类型注入(调用面只看接口,运行时仍是同一单例)
|
* - 仓库使用懒单例 getter,actor 执行时才创建实例
|
||||||
* - 命名约定:`[user-machine]` 前缀日志(如未来加)—— 与 chat 模块对齐
|
* - 命名约定:`[user-machine]` 前缀日志(如未来加)—— 与 chat 模块对齐
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fromPromise } from "xstate";
|
import { fromPromise } from "xstate";
|
||||||
|
|
||||||
import { userRepository } from "@/data/repositories/user_repository";
|
import { getUserRepository } from "@/data/repositories/user_repository";
|
||||||
import { authRepository } from "@/data/repositories/auth_repository";
|
import { getAuthRepository } from "@/data/repositories/auth_repository";
|
||||||
import type {
|
|
||||||
IAuthRepository,
|
|
||||||
IUserRepository,
|
|
||||||
} from "@/data/repositories/interfaces";
|
|
||||||
import { Result } from "@/utils";
|
import { Result } from "@/utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -32,13 +28,6 @@ import {
|
|||||||
userStorage,
|
userStorage,
|
||||||
} from "./user-machine.helpers";
|
} from "./user-machine.helpers";
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 仓库注入
|
|
||||||
// ============================================================
|
|
||||||
// 仓库以接口类型注入:调用面只看接口,运行时仍是同一单例
|
|
||||||
const userRepo: IUserRepository = userRepository;
|
|
||||||
const authRepo: IAuthRepository = authRepository;
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Init actor:从 UserStorage 读 user + avatarUrl
|
// Init actor:从 UserStorage 读 user + avatarUrl
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -59,6 +48,7 @@ export const userInitActor = fromPromise<InitData>(async () => readInitData());
|
|||||||
* - 任一失败都不阻断另一方,machine 层负责保留已有 context
|
* - 任一失败都不阻断另一方,machine 层负责保留已有 context
|
||||||
*/
|
*/
|
||||||
export const userFetchActor = fromPromise<UserFetchData>(async () => {
|
export const userFetchActor = fromPromise<UserFetchData>(async () => {
|
||||||
|
const userRepo = getUserRepository();
|
||||||
const [userResult, entitlementsResult] = await Promise.all([
|
const [userResult, entitlementsResult] = await Promise.all([
|
||||||
userRepo.getCurrentUser(),
|
userRepo.getCurrentUser(),
|
||||||
userRepo.getEntitlements(),
|
userRepo.getEntitlements(),
|
||||||
@@ -97,5 +87,5 @@ export const userFetchActor = fromPromise<UserFetchData>(async () => {
|
|||||||
* - onDone / onError 都跑 `clearUser` action(machine 层)—— 失败也清本地
|
* - onDone / onError 都跑 `clearUser` action(machine 层)—— 失败也清本地
|
||||||
*/
|
*/
|
||||||
export const userLogoutActor = fromPromise<void>(async () => {
|
export const userLogoutActor = fromPromise<void>(async () => {
|
||||||
await authRepo.logout();
|
await getAuthRepository().logout();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user