refactor(data): replace schema classes with readonly models
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { AuthRepository } from "@/data/repositories/auth_repository";
|
||||
import {
|
||||
FacebookIdentityResponse,
|
||||
FacebookPsidLoginResponse,
|
||||
FacebookIdentityResponseSchema,
|
||||
FacebookPsidLoginResponseSchema,
|
||||
LoginStatus,
|
||||
} 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";
|
||||
import type { IUserStorage } from "@/data/storage/user";
|
||||
@@ -36,7 +36,7 @@ function createRepository(overrides: Partial<AuthApi>) {
|
||||
describe("AuthRepository facebook identity", () => {
|
||||
it("binds facebook identity and stores returned ids", async () => {
|
||||
const bindFacebookIdentity = vi.fn(async () =>
|
||||
FacebookIdentityResponse.fromJson({
|
||||
FacebookIdentityResponseSchema.parse({
|
||||
fbAsid: "asid-1",
|
||||
fbPsid: "psid-1",
|
||||
facebookBinding: { conflicts: [], bound: [] },
|
||||
@@ -57,7 +57,7 @@ describe("AuthRepository facebook identity", () => {
|
||||
|
||||
it("saves real login data from complete psid login responses", async () => {
|
||||
const facebookPsidLogin = vi.fn(async () =>
|
||||
FacebookPsidLoginResponse.fromJson({
|
||||
FacebookPsidLoginResponseSchema.parse({
|
||||
token: "login-token",
|
||||
refreshToken: "refresh-token",
|
||||
matchedBy: "psid",
|
||||
@@ -88,7 +88,7 @@ describe("AuthRepository facebook identity", () => {
|
||||
|
||||
it("saves guest data from incomplete psid login responses", async () => {
|
||||
const facebookPsidLogin = vi.fn(async () =>
|
||||
FacebookPsidLoginResponse.fromJson({
|
||||
FacebookPsidLoginResponseSchema.parse({
|
||||
token: "guest-token",
|
||||
matchedBy: "psid_incomplete",
|
||||
fbPsid: "psid-1",
|
||||
@@ -116,7 +116,7 @@ describe("AuthRepository facebook identity", () => {
|
||||
it("does not report guest psid login success when session persistence fails", async () => {
|
||||
const storageError = new Error("guest token write failed");
|
||||
const facebookPsidLogin = vi.fn(async () =>
|
||||
FacebookPsidLoginResponse.fromJson({
|
||||
FacebookPsidLoginResponseSchema.parse({
|
||||
token: "guest-token",
|
||||
matchedBy: "psid_incomplete",
|
||||
fbPsid: "psid-1",
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
GuestLoginResponse,
|
||||
LoginResponse,
|
||||
LoginStatus,
|
||||
RefreshTokenResponse,
|
||||
} from "@/data/schemas/auth";
|
||||
import { AuthRepository } from "@/data/repositories/auth_repository";
|
||||
import {
|
||||
GuestLoginResponseSchema,
|
||||
LoginResponseSchema,
|
||||
LoginStatus,
|
||||
RefreshTokenResponseSchema,
|
||||
} from "@/data/schemas/auth";
|
||||
import type { AuthApi } from "@/data/services/api";
|
||||
import type { IAuthStorage } from "@/data/storage/auth";
|
||||
import type { IUserStorage } from "@/data/storage/user";
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
function createRepository(input: {
|
||||
api?: Partial<AuthApi>;
|
||||
storage?: Partial<IAuthStorage>;
|
||||
userStorage?: Partial<IUserStorage>;
|
||||
} = {}) {
|
||||
function createRepository(
|
||||
input: {
|
||||
api?: Partial<AuthApi>;
|
||||
storage?: Partial<IAuthStorage>;
|
||||
userStorage?: Partial<IUserStorage>;
|
||||
} = {},
|
||||
) {
|
||||
const storage = {
|
||||
getRefreshToken: vi.fn(async () => Result.ok("refresh-token")),
|
||||
setLoginToken: vi.fn(async () => Result.ok(undefined)),
|
||||
@@ -52,7 +54,7 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("AuthRepository session Result handling", () => {
|
||||
const loginResponse = LoginResponse.from({
|
||||
const loginResponse = LoginResponseSchema.parse({
|
||||
token: "login-token",
|
||||
refreshToken: "refresh-token",
|
||||
user: { id: "user-1", username: "Elio" },
|
||||
@@ -112,7 +114,7 @@ describe("AuthRepository session Result handling", () => {
|
||||
|
||||
it("does not report refresh success when a new token cannot be stored", async () => {
|
||||
const storageError = new Error("login token write failed");
|
||||
const response = RefreshTokenResponse.from({
|
||||
const response = RefreshTokenResponseSchema.parse({
|
||||
token: "new-login-token",
|
||||
refreshToken: "new-refresh-token",
|
||||
userId: "user-1",
|
||||
@@ -135,7 +137,7 @@ describe("AuthRepository session Result handling", () => {
|
||||
|
||||
it("does not report guest login success when session persistence fails", async () => {
|
||||
const storageError = new Error("guest token write failed");
|
||||
const response = GuestLoginResponse.from({
|
||||
const response = GuestLoginResponseSchema.parse({
|
||||
token: "guest-token",
|
||||
deviceId: "device-1",
|
||||
userId: "guest-1",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { ChatMessage } from "@/data/schemas/chat";
|
||||
import { ChatMessage, ChatMessageSchema } from "@/data/schemas/chat";
|
||||
import { LocalMessage } from "@/data/storage/chat";
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
import { ChatLocalMessageStore } from "../chat_local_message_store";
|
||||
|
||||
function makeMessage(id: string, content: string): ChatMessage {
|
||||
return ChatMessage.from({
|
||||
return ChatMessageSchema.parse({
|
||||
id,
|
||||
role: "assistant",
|
||||
type: "text",
|
||||
@@ -53,9 +53,8 @@ describe("ChatLocalMessageStore identity isolation", () => {
|
||||
it("reads, replaces, and clears only the current identity", async () => {
|
||||
const memory = createMemoryStorage();
|
||||
let identity = "user:account-a";
|
||||
const store = new ChatLocalMessageStore(
|
||||
memory.storage,
|
||||
async () => Result.ok(identity),
|
||||
const store = new ChatLocalMessageStore(memory.storage, async () =>
|
||||
Result.ok(identity),
|
||||
);
|
||||
|
||||
await store.saveMessages([makeMessage("a-1", "account A")]);
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { DEFAULT_CHARACTER_ID } from "@/data/constants/character";
|
||||
import { ChatMessage, ChatSendResponse } from "@/data/schemas/chat";
|
||||
import {
|
||||
ChatMessageSchema,
|
||||
ChatSendResponseSchema,
|
||||
type ChatMessage,
|
||||
type ChatMessageInput,
|
||||
type ChatSendResponse,
|
||||
type ChatSendResponseInput,
|
||||
} from "@/data/schemas/chat";
|
||||
|
||||
import {
|
||||
buildChatMediaCacheKey,
|
||||
@@ -11,10 +18,8 @@ import {
|
||||
uniqueMediaTargets,
|
||||
} from "../chat_media_cache_helpers";
|
||||
|
||||
function makeMessage(
|
||||
overrides: Partial<Parameters<typeof ChatMessage.from>[0]> = {},
|
||||
): ChatMessage {
|
||||
return ChatMessage.from({
|
||||
function makeMessage(overrides: Partial<ChatMessageInput> = {}): ChatMessage {
|
||||
return ChatMessageSchema.parse({
|
||||
id: "msg-1",
|
||||
role: "assistant",
|
||||
type: "image",
|
||||
@@ -31,9 +36,9 @@ function makeMessage(
|
||||
}
|
||||
|
||||
function makeResponse(
|
||||
overrides: Partial<Parameters<typeof ChatSendResponse.from>[0]> = {},
|
||||
overrides: Partial<ChatSendResponseInput> = {},
|
||||
): ChatSendResponse {
|
||||
return ChatSendResponse.from({
|
||||
return ChatSendResponseSchema.parse({
|
||||
reply: "",
|
||||
messageId: "msg-1",
|
||||
isGuest: false,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { TipPaymentPlansResponse } from "@/data/schemas/payment";
|
||||
import { PaymentRepository } from "@/data/repositories/payment_repository";
|
||||
import { TipPaymentPlansResponseSchema } from "@/data/schemas/payment";
|
||||
import type { PaymentApi } from "@/data/services/api";
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
describe("PaymentRepository", () => {
|
||||
it("adapts tip plans without caching them as subscription plans", async () => {
|
||||
const getTipPlans = vi.fn().mockResolvedValue(
|
||||
TipPaymentPlansResponse.from({
|
||||
TipPaymentPlansResponseSchema.parse({
|
||||
plans: [
|
||||
{
|
||||
planId: "tip_coffee_usd_9_99",
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
import { ExceptionHandler } from "@/core/errors";
|
||||
import { ApiError, AuthApi, authApi, ErrorCode } from "@/data/services/api";
|
||||
import type { IAuthRepository } from "@/data/repositories/interfaces";
|
||||
import {
|
||||
FacebookIdentityRequest,
|
||||
FacebookPsidLoginRequest,
|
||||
FacebookLoginRequest,
|
||||
FbIdLoginRequest,
|
||||
GoogleLoginRequest,
|
||||
GuestLoginRequest,
|
||||
FacebookIdentityRequestSchema,
|
||||
FacebookLoginRequestSchema,
|
||||
FacebookPsidLoginRequestSchema,
|
||||
FbIdLoginRequestSchema,
|
||||
GoogleLoginRequestSchema,
|
||||
GuestLoginRequestSchema,
|
||||
GuestLoginResponse,
|
||||
LoginRequest,
|
||||
LoginRequestSchema,
|
||||
LoginResponse,
|
||||
LoginResponseSchema,
|
||||
LoginStatus,
|
||||
type LoginStatus as LoginStatusT,
|
||||
RefreshTokenRequest,
|
||||
RefreshTokenRequestSchema,
|
||||
RefreshTokenResponse,
|
||||
RegisterRequest,
|
||||
RegisterRequestSchema,
|
||||
type LoginStatus as LoginStatusT,
|
||||
} 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";
|
||||
import { Logger } from "@/utils/logger";
|
||||
import { deviceIdentifier } from "@/utils/device_identifier";
|
||||
import type { IAuthRepository } from "@/data/repositories/interfaces";
|
||||
import { ApiError, AuthApi, authApi, ErrorCode } from "@/data/services/api";
|
||||
import { AuthStorage, type IAuthStorage } from "@/data/storage/auth";
|
||||
import { UserStorage, type IUserStorage } from "@/data/storage/user";
|
||||
import { AppEnvUtil } from "@/utils/app-env";
|
||||
import { deviceIdentifier } from "@/utils/device_identifier";
|
||||
import { Logger } from "@/utils/logger";
|
||||
import { PlatformDetector } from "@/utils/platform-detect";
|
||||
import { Result } from "@/utils/result";
|
||||
import { createLazySingleton } from "./lazy_singleton";
|
||||
|
||||
const log = new Logger("DataRepositoriesAuthRepository");
|
||||
@@ -52,7 +53,7 @@ export class AuthRepository implements IAuthRepository {
|
||||
}): Promise<Result<void>> {
|
||||
return Result.wrap(async () => {
|
||||
await this.api.register(
|
||||
RegisterRequest.from(
|
||||
RegisterRequestSchema.parse(
|
||||
withTestAccountFlag({
|
||||
username: input.username,
|
||||
email: input.email,
|
||||
@@ -76,7 +77,7 @@ export class AuthRepository implements IAuthRepository {
|
||||
return Result.wrap(async () => {
|
||||
const email = input.email?.trim() ?? "";
|
||||
const response = await this.api.emailLogin(
|
||||
LoginRequest.from(
|
||||
LoginRequestSchema.parse(
|
||||
withTestAccountFlag({
|
||||
email,
|
||||
username: input.username,
|
||||
@@ -152,7 +153,7 @@ export class AuthRepository implements IAuthRepository {
|
||||
});
|
||||
return Result.wrap(async () => {
|
||||
const response = await this.api.guestLogin(
|
||||
GuestLoginRequest.from(withTestAccountFlag({ deviceId })),
|
||||
GuestLoginRequestSchema.parse(withTestAccountFlag({ deviceId })),
|
||||
);
|
||||
log.debug("[AuthRepository.guestLogin] API call SUCCESS (Zod parsed)", {
|
||||
hasToken: !!response.token,
|
||||
@@ -190,7 +191,7 @@ export class AuthRepository implements IAuthRepository {
|
||||
}): Promise<Result<LoginResponse>> {
|
||||
return this._socialLogin(LoginStatus.Google, () =>
|
||||
this.api.googleLogin(
|
||||
GoogleLoginRequest.from(
|
||||
GoogleLoginRequestSchema.parse(
|
||||
withTestAccountFlag({
|
||||
idToken: input.idToken,
|
||||
platform: getAuthPlatform(),
|
||||
@@ -210,7 +211,7 @@ export class AuthRepository implements IAuthRepository {
|
||||
}): Promise<Result<LoginResponse>> {
|
||||
return this._socialLogin(LoginStatus.Facebook, () =>
|
||||
this.api.facebookLogin(
|
||||
FacebookLoginRequest.from(
|
||||
FacebookLoginRequestSchema.parse(
|
||||
withTestAccountFlag({
|
||||
accessToken: input.accessToken,
|
||||
platform: getAuthPlatform(),
|
||||
@@ -230,7 +231,7 @@ export class AuthRepository implements IAuthRepository {
|
||||
}): Promise<Result<LoginResponse>> {
|
||||
return this._socialLogin(LoginStatus.Facebook, () =>
|
||||
this.api.facebookIdLogin(
|
||||
FbIdLoginRequest.from(
|
||||
FbIdLoginRequestSchema.parse(
|
||||
withTestAccountFlag({
|
||||
fbId: input.asid,
|
||||
avatarUrl: input.avatarUrl ?? "",
|
||||
@@ -250,12 +251,12 @@ export class AuthRepository implements IAuthRepository {
|
||||
|
||||
return Result.wrap(async () => {
|
||||
const response = await this.api.bindFacebookIdentity(
|
||||
FacebookIdentityRequest.from({
|
||||
FacebookIdentityRequestSchema.parse({
|
||||
asid: input.asid ?? "",
|
||||
psid: input.psid ?? "",
|
||||
}),
|
||||
);
|
||||
const data = response.toJson();
|
||||
const data = response;
|
||||
await this._saveFacebookIdentity({
|
||||
asid: data.fbAsid,
|
||||
psid: data.fbPsid,
|
||||
@@ -271,13 +272,13 @@ export class AuthRepository implements IAuthRepository {
|
||||
}): Promise<Result<LoginStatusT>> {
|
||||
return Result.wrap(async () => {
|
||||
const response = await this.api.facebookPsidLogin(
|
||||
FacebookPsidLoginRequest.from({
|
||||
FacebookPsidLoginRequestSchema.parse({
|
||||
psid: input.psid,
|
||||
deviceId: input.deviceId ?? "",
|
||||
bindToGuest: input.bindToGuest ?? true,
|
||||
}),
|
||||
);
|
||||
const data = response.toJson();
|
||||
const data = response;
|
||||
await this._saveFacebookIdentity({
|
||||
asid: data.fbAsid,
|
||||
psid: data.fbPsid,
|
||||
@@ -303,7 +304,7 @@ export class AuthRepository implements IAuthRepository {
|
||||
}
|
||||
|
||||
await this._saveLoginData(
|
||||
LoginResponse.from({
|
||||
LoginResponseSchema.parse({
|
||||
token: data.token,
|
||||
refreshToken: data.refreshToken,
|
||||
user: data.user ?? { id: data.userId },
|
||||
@@ -330,7 +331,7 @@ export class AuthRepository implements IAuthRepository {
|
||||
);
|
||||
}
|
||||
const response = await this.api.refreshToken(
|
||||
RefreshTokenRequest.from({ refreshToken: existing.data }),
|
||||
RefreshTokenRequestSchema.parse({ refreshToken: existing.data }),
|
||||
);
|
||||
const tokenResult = await this.storage.setLoginToken(response.token);
|
||||
if (Result.isErr(tokenResult)) throw tokenResult.error;
|
||||
@@ -404,7 +405,7 @@ export class AuthRepository implements IAuthRepository {
|
||||
|
||||
private async _cacheUser(user: User): Promise<void> {
|
||||
try {
|
||||
const userResult = await this.userStorage.setUser(user.toJson());
|
||||
const userResult = await this.userStorage.setUser(user);
|
||||
if (Result.isErr(userResult)) {
|
||||
log.warn("[AuthRepository] setUser failed", userResult.error);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChatMessage } from "@/data/schemas/chat";
|
||||
import type { UnlockedPrivateMessageLocalPatch } from "@/data/repositories/interfaces";
|
||||
import { ChatMessage, ChatMessageSchema } from "@/data/schemas/chat";
|
||||
import { LocalChatStorage, LocalMessage } from "@/data/storage/chat";
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
@@ -20,8 +20,7 @@ type LocalMessageStorage = Pick<
|
||||
export class ChatLocalMessageStore {
|
||||
constructor(
|
||||
private readonly localStorage: LocalMessageStorage,
|
||||
private readonly resolveIdentity: ChatCacheIdentityResolver =
|
||||
resolveChatCacheOwnerKey,
|
||||
private readonly resolveIdentity: ChatCacheIdentityResolver = resolveChatCacheOwnerKey,
|
||||
) {}
|
||||
|
||||
async markMessageUnlocked(
|
||||
@@ -40,8 +39,8 @@ export class ChatLocalMessageStore {
|
||||
const updatedMessages = localResult.data.map((message) => {
|
||||
if (!shouldMarkMessageUnlocked(message, messageId)) return message;
|
||||
changed = true;
|
||||
return ChatMessage.from({
|
||||
...message.toJson(),
|
||||
return ChatMessageSchema.parse({
|
||||
...message,
|
||||
content: normalizeUnlockedContent(patch.content, message.content),
|
||||
audioUrl: normalizeUnlockedAudioUrl(patch.audioUrl, message.audioUrl),
|
||||
image: patch.image ?? message.image,
|
||||
@@ -85,7 +84,9 @@ export class ChatLocalMessageStore {
|
||||
});
|
||||
}
|
||||
|
||||
async getMessages(identity?: string): Promise<Result<ChatMessage[]>> {
|
||||
async getMessages(
|
||||
identity?: string,
|
||||
): Promise<Result<readonly ChatMessage[]>> {
|
||||
return Result.wrap(async () => {
|
||||
const ownerKey = await this._requireIdentity(identity);
|
||||
const result = await this._getMessages(ownerKey);
|
||||
@@ -128,12 +129,10 @@ export class ChatLocalMessageStore {
|
||||
|
||||
private async _getMessages(
|
||||
identity: string,
|
||||
): Promise<Result<ChatMessage[]>> {
|
||||
): Promise<Result<readonly ChatMessage[]>> {
|
||||
const result = await this.localStorage.getAllMessagesBySession(identity);
|
||||
if (Result.isErr(result)) return result;
|
||||
return Result.ok(
|
||||
result.data.map((message) => this._localToChat(message)),
|
||||
);
|
||||
return Result.ok(result.data.map((message) => this._localToChat(message)));
|
||||
}
|
||||
|
||||
private _saveMessages(
|
||||
@@ -161,7 +160,7 @@ export class ChatLocalMessageStore {
|
||||
}
|
||||
|
||||
private _localToChat(local: LocalMessage): ChatMessage {
|
||||
return ChatMessage.from({
|
||||
return ChatMessageSchema.parse({
|
||||
id: local.id,
|
||||
role: local.role,
|
||||
type: local.type,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { UnlockPrivateMessageInput } from "@/data/repositories/interfaces";
|
||||
import {
|
||||
ChatHistoryResponse,
|
||||
ChatSendResponse,
|
||||
SendMessageRequest,
|
||||
UnlockHistoryRequest,
|
||||
SendMessageRequestSchema,
|
||||
UnlockHistoryRequestSchema,
|
||||
UnlockHistoryResponse,
|
||||
UnlockPrivateRequest,
|
||||
UnlockPrivateRequestSchema,
|
||||
UnlockPrivateResponse,
|
||||
} from "@/data/schemas/chat";
|
||||
import type { ChatApi } from "@/data/services/api";
|
||||
import type { UnlockPrivateMessageInput } from "@/data/repositories/interfaces";
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
export class ChatRemoteDataSource {
|
||||
@@ -20,7 +20,7 @@ export class ChatRemoteDataSource {
|
||||
options?: { image?: string; useWebSocket?: boolean },
|
||||
): Promise<Result<ChatSendResponse>> {
|
||||
return Result.wrap(async () => {
|
||||
const request = SendMessageRequest.from({
|
||||
const request = SendMessageRequestSchema.parse({
|
||||
characterId,
|
||||
message,
|
||||
image: options?.image ?? "",
|
||||
@@ -42,7 +42,7 @@ export class ChatRemoteDataSource {
|
||||
input: UnlockPrivateMessageInput,
|
||||
): Promise<Result<UnlockPrivateResponse>> {
|
||||
return Result.wrap(() =>
|
||||
this.api.unlockPrivateMessage(UnlockPrivateRequest.from(input)),
|
||||
this.api.unlockPrivateMessage(UnlockPrivateRequestSchema.parse(input)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export class ChatRemoteDataSource {
|
||||
characterId: string,
|
||||
): Promise<Result<UnlockHistoryResponse>> {
|
||||
return Result.wrap(() =>
|
||||
this.api.unlockHistory(UnlockHistoryRequest.from({ characterId })),
|
||||
this.api.unlockHistory(UnlockHistoryRequestSchema.parse({ characterId })),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import type {
|
||||
CacheRemoteChatMediaInput,
|
||||
ChatMediaLookupInput,
|
||||
IChatRepository,
|
||||
UnlockPrivateMessageInput,
|
||||
UnlockedPrivateMessageLocalPatch,
|
||||
} from "@/data/repositories/interfaces";
|
||||
import type {
|
||||
ChatHistoryResponse,
|
||||
ChatMessage,
|
||||
@@ -11,13 +18,6 @@ import {
|
||||
LocalChatStorage,
|
||||
type LocalChatMediaRow,
|
||||
} from "@/data/storage/chat";
|
||||
import type {
|
||||
CacheRemoteChatMediaInput,
|
||||
ChatMediaLookupInput,
|
||||
IChatRepository,
|
||||
UnlockPrivateMessageInput,
|
||||
UnlockedPrivateMessageLocalPatch,
|
||||
} from "@/data/repositories/interfaces";
|
||||
import type { Result } from "@/utils/result";
|
||||
|
||||
import { ChatLocalMessageStore } from "./chat_local_message_store";
|
||||
@@ -60,7 +60,9 @@ export class ChatRepository implements IChatRepository {
|
||||
}
|
||||
|
||||
/** 一键解锁历史锁定消息。 */
|
||||
async unlockHistory(characterId: string): Promise<Result<UnlockHistoryResponse>> {
|
||||
async unlockHistory(
|
||||
characterId: string,
|
||||
): Promise<Result<UnlockHistoryResponse>> {
|
||||
return this.remote.unlockHistory(characterId);
|
||||
}
|
||||
|
||||
@@ -101,7 +103,7 @@ export class ChatRepository implements IChatRepository {
|
||||
/** 读取所有本地消息,按 dbId 升序。 */
|
||||
async getLocalMessages(
|
||||
cacheIdentity?: string,
|
||||
): Promise<Result<ChatMessage[]>> {
|
||||
): Promise<Result<readonly ChatMessage[]>> {
|
||||
return this.localMessages.getMessages(cacheIdentity);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,18 +4,19 @@
|
||||
* 聚合聊天远程操作、身份隔离的本地历史,以及媒体缓存能力。
|
||||
*/
|
||||
|
||||
import type { Result } from "@/utils/result";
|
||||
import type {
|
||||
ChatHistoryResponse,
|
||||
ChatImageData,
|
||||
ChatLockDetailData,
|
||||
ChatLockType,
|
||||
ChatMediaKind,
|
||||
ChatMessage,
|
||||
ChatSendResponse,
|
||||
UnlockHistoryResponse,
|
||||
UnlockPrivateResponse,
|
||||
} 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";
|
||||
import type { Result } from "@/utils/result";
|
||||
|
||||
export interface ChatMediaLookupInput {
|
||||
characterId: string;
|
||||
@@ -84,7 +85,9 @@ export interface IChatRepository {
|
||||
): Promise<Result<void>>;
|
||||
|
||||
/** 读取所有本地消息,按 dbId 升序。 */
|
||||
getLocalMessages(cacheIdentity?: string): Promise<Result<ChatMessage[]>>;
|
||||
getLocalMessages(
|
||||
cacheIdentity?: string,
|
||||
): Promise<Result<readonly ChatMessage[]>>;
|
||||
|
||||
/** 清空本地消息。 */
|
||||
clearLocalMessages(cacheIdentity?: string): Promise<Result<void>>;
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*
|
||||
* 指标/埋点仓库:纯远程 fire-and-forget 上报,无本地状态。
|
||||
*/
|
||||
import { MetricsApi, metricsApi } from "@/data/services/api";
|
||||
import { AppEvent, PwaEvent } from "@/data/schemas/metrics";
|
||||
import { Result } from "@/utils/result";
|
||||
import type { IMetricsRepository } from "@/data/repositories/interfaces";
|
||||
import { AppEventSchema, PwaEventSchema } from "@/data/schemas/metrics";
|
||||
import { MetricsApi, metricsApi } from "@/data/services/api";
|
||||
import { Result } from "@/utils/result";
|
||||
import { createLazySingleton } from "./lazy_singleton";
|
||||
|
||||
export class MetricsRepository implements IMetricsRepository {
|
||||
@@ -24,7 +24,7 @@ export class MetricsRepository implements IMetricsRepository {
|
||||
}): Promise<Result<void>> {
|
||||
return Result.wrap(async () => {
|
||||
await this.api.reportPwaEvent(
|
||||
PwaEvent.from({
|
||||
PwaEventSchema.parse({
|
||||
deviceId: input.deviceId,
|
||||
deviceType: input.deviceType,
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
@@ -45,7 +45,7 @@ export class MetricsRepository implements IMetricsRepository {
|
||||
}): Promise<Result<void>> {
|
||||
return Result.wrap(async () => {
|
||||
await this.api.reportUserInfo(
|
||||
AppEvent.from({
|
||||
AppEventSchema.parse({
|
||||
userId: input.userId,
|
||||
browser: input.browser,
|
||||
userAgent: input.userAgent,
|
||||
|
||||
@@ -3,16 +3,17 @@
|
||||
*
|
||||
* 支付 / 付费墙相关远程调用。
|
||||
*/
|
||||
import type { IPaymentRepository } from "@/data/repositories/interfaces";
|
||||
import {
|
||||
CreatePaymentOrderRequest,
|
||||
CreatePaymentOrderRequestSchema,
|
||||
CreatePaymentOrderResponse,
|
||||
PayChannel,
|
||||
PaymentOrderStatusResponse,
|
||||
PaymentPlansResponse,
|
||||
PaymentPlansResponseSchema,
|
||||
} from "@/data/schemas/payment";
|
||||
import { PaymentApi, paymentApi } from "@/data/services/api";
|
||||
import { PaymentPlansStorage } from "@/data/storage/payment";
|
||||
import type { IPaymentRepository } from "@/data/repositories/interfaces";
|
||||
import { Result } from "@/utils/result";
|
||||
import { createLazySingleton } from "./lazy_singleton";
|
||||
|
||||
@@ -23,7 +24,7 @@ export class PaymentRepository implements IPaymentRepository {
|
||||
async getPlans(): Promise<Result<PaymentPlansResponse>> {
|
||||
return Result.wrap(async () => {
|
||||
const response = await this.api.getPlans();
|
||||
await PaymentPlansStorage.setPlans(response.toJson());
|
||||
await PaymentPlansStorage.setPlans(response);
|
||||
return response;
|
||||
});
|
||||
}
|
||||
@@ -33,7 +34,7 @@ export class PaymentRepository implements IPaymentRepository {
|
||||
return Result.wrap(async () => {
|
||||
const result = await PaymentPlansStorage.getPlans();
|
||||
if (Result.isErr(result)) throw result.error;
|
||||
return result.data ? PaymentPlansResponse.from(result.data) : null;
|
||||
return result.data ? PaymentPlansResponseSchema.parse(result.data) : null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,9 +42,9 @@ export class PaymentRepository implements IPaymentRepository {
|
||||
async getTipPlans(): Promise<Result<PaymentPlansResponse>> {
|
||||
return Result.wrap(async () => {
|
||||
const response = await this.api.getTipPlans();
|
||||
return PaymentPlansResponse.from({
|
||||
return PaymentPlansResponseSchema.parse({
|
||||
plans: response.plans.map((plan) => ({
|
||||
...plan.toJson(),
|
||||
...plan,
|
||||
orderType: "tip",
|
||||
vipDays: null,
|
||||
dolAmount: null,
|
||||
@@ -72,7 +73,7 @@ export class PaymentRepository implements IPaymentRepository {
|
||||
payChannel: PayChannel,
|
||||
autoRenew: boolean,
|
||||
): Promise<Result<CreatePaymentOrderResponse>> {
|
||||
const request = CreatePaymentOrderRequest.from({
|
||||
const request = CreatePaymentOrderRequestSchema.parse({
|
||||
planId,
|
||||
payChannel,
|
||||
autoRenew,
|
||||
@@ -86,7 +87,6 @@ export class PaymentRepository implements IPaymentRepository {
|
||||
): Promise<Result<PaymentOrderStatusResponse>> {
|
||||
return Result.wrap(() => this.api.getOrderStatus(orderId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** 全局懒单例。 */
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import type {
|
||||
GetPrivateAlbumsInput,
|
||||
IPrivateRoomRepository,
|
||||
} from "@/data/repositories/interfaces";
|
||||
import {
|
||||
UnlockPrivateAlbumRequest,
|
||||
type PrivateAlbumsResponse,
|
||||
type PrivateAlbumUnlockResponse,
|
||||
UnlockPrivateAlbumRequestSchema,
|
||||
} from "@/data/schemas/private-room";
|
||||
import {
|
||||
PrivateRoomApi,
|
||||
privateRoomApi,
|
||||
} from "@/data/services/api/private_room_api";
|
||||
import type {
|
||||
GetPrivateAlbumsInput,
|
||||
IPrivateRoomRepository,
|
||||
} from "@/data/repositories/interfaces";
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
import { createLazySingleton } from "./lazy_singleton";
|
||||
@@ -31,7 +31,7 @@ export class PrivateRoomRepository implements IPrivateRoomRepository {
|
||||
return Result.wrap(() =>
|
||||
this.api.unlockAlbum(
|
||||
albumId,
|
||||
UnlockPrivateAlbumRequest.from({ expectedCost }),
|
||||
UnlockPrivateAlbumRequestSchema.parse({ expectedCost }),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user