fix(chat): update unlock response contract
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { UnlockPrivateResponse } from "@/data/dto/chat";
|
||||
|
||||
describe("UnlockPrivateResponse", () => {
|
||||
it("parses a successful single-message unlock response", () => {
|
||||
const response = UnlockPrivateResponse.from({
|
||||
unlocked: true,
|
||||
content: "完整消息内容",
|
||||
reason: "ok",
|
||||
creditBalance: 9180,
|
||||
creditsCharged: 10,
|
||||
requiredCredits: 10,
|
||||
shortfallCredits: 0,
|
||||
lockDetail: {
|
||||
locked: false,
|
||||
showContent: true,
|
||||
showUpgrade: false,
|
||||
reason: null,
|
||||
hint: null,
|
||||
detail: null,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.unlocked).toBe(true);
|
||||
expect(response.content).toBe("完整消息内容");
|
||||
expect(response.reason).toBe("ok");
|
||||
expect(response.creditBalance).toBe(9180);
|
||||
expect(response.creditsCharged).toBe(10);
|
||||
expect(response.requiredCredits).toBe(10);
|
||||
expect(response.shortfallCredits).toBe(0);
|
||||
expect(response.lockDetail.locked).toBe(false);
|
||||
});
|
||||
|
||||
it("parses an insufficient-balance unlock response", () => {
|
||||
const response = UnlockPrivateResponse.from({
|
||||
unlocked: false,
|
||||
content: "",
|
||||
reason: "insufficient_balance",
|
||||
creditBalance: 5,
|
||||
creditsCharged: 0,
|
||||
requiredCredits: 10,
|
||||
shortfallCredits: 5,
|
||||
lockDetail: {
|
||||
locked: true,
|
||||
showContent: false,
|
||||
showUpgrade: true,
|
||||
reason: "insufficient_balance",
|
||||
hint: "Insufficient credits.",
|
||||
detail: {
|
||||
messageId: "msg_001",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.unlocked).toBe(false);
|
||||
expect(response.reason).toBe("insufficient_balance");
|
||||
expect(response.creditBalance).toBe(5);
|
||||
expect(response.creditsCharged).toBe(0);
|
||||
expect(response.requiredCredits).toBe(10);
|
||||
expect(response.shortfallCredits).toBe(5);
|
||||
expect(response.lockDetail.showUpgrade).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,21 +1,23 @@
|
||||
/**
|
||||
* 私密消息解锁响应 DTO
|
||||
*/
|
||||
import {
|
||||
UnlockPrivateResponseSchema,
|
||||
type UnlockPrivateReason,
|
||||
type UnlockPrivateResponseData,
|
||||
type UnlockPrivateResponseInput,
|
||||
} from "@/data/schemas/chat/unlock_private_response";
|
||||
import type { ChatLockDetailData } from "@/data/schemas/chat";
|
||||
|
||||
/**
|
||||
* 单条历史付费 / 私密消息解锁响应 DTO。
|
||||
*/
|
||||
export class UnlockPrivateResponse {
|
||||
declare readonly unlocked: boolean;
|
||||
declare readonly content: string | null;
|
||||
declare readonly showUpgrade: boolean;
|
||||
declare readonly paywallTriggered: boolean;
|
||||
declare readonly privateFreeLimit: number;
|
||||
declare readonly privateUsedToday: number;
|
||||
declare readonly content: string;
|
||||
declare readonly reason: UnlockPrivateReason;
|
||||
declare readonly creditBalance: number;
|
||||
declare readonly creditsCharged: number;
|
||||
declare readonly requiredCredits: number;
|
||||
declare readonly shortfallCredits: number;
|
||||
declare readonly lockDetail: ChatLockDetailData;
|
||||
|
||||
private constructor(input: UnlockPrivateResponseInput) {
|
||||
const data = UnlockPrivateResponseSchema.parse(input);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
UnlockPrivateRequest,
|
||||
UnlockPrivateResponse,
|
||||
} from "@/data/dto/chat";
|
||||
import type { ChatLockDetailData } from "@/data/schemas/chat";
|
||||
import { AuthStorage } from "@/data/storage/auth";
|
||||
import { UserStorage } from "@/data/storage/user/user_storage";
|
||||
import { Logger, Result } from "@/utils";
|
||||
@@ -69,7 +70,7 @@ export class ChatRepository implements IChatRepository {
|
||||
return Result.wrap(() => this.api.getHistory(limit, offset));
|
||||
}
|
||||
|
||||
/** 解锁私密消息。 */
|
||||
/** 解锁单条历史付费 / 私密消息。 */
|
||||
async unlockPrivateMessage(
|
||||
messageId: string,
|
||||
): Promise<Result<UnlockPrivateResponse>> {
|
||||
@@ -83,10 +84,11 @@ export class ChatRepository implements IChatRepository {
|
||||
return Result.wrap(() => this.api.unlockHistory());
|
||||
}
|
||||
|
||||
/** 把本地缓存中的私密消息标记为已解锁。 */
|
||||
/** 把本地缓存中的单条锁定消息标记为已解锁。 */
|
||||
async markPrivateMessageUnlockedInLocal(
|
||||
messageId: string,
|
||||
content: string,
|
||||
lockDetail?: ChatLockDetailData,
|
||||
): Promise<Result<void>> {
|
||||
return Result.wrap(async () => {
|
||||
const localResult = await this.getLocalMessages();
|
||||
@@ -101,12 +103,14 @@ export class ChatRepository implements IChatRepository {
|
||||
return ChatMessage.from({
|
||||
...message.toJson(),
|
||||
content,
|
||||
lockDetail: {
|
||||
lockDetail: lockDetail ?? {
|
||||
...message.lockDetail,
|
||||
locked: false,
|
||||
showContent: true,
|
||||
showUpgrade: false,
|
||||
reason: null,
|
||||
hint: null,
|
||||
detail: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
UnlockHistoryResponse,
|
||||
UnlockPrivateResponse,
|
||||
} from "@/data/dto/chat";
|
||||
import type { ChatLockDetailData } from "@/data/schemas/chat";
|
||||
import type { LocalChatMediaRow } from "@/data/storage/chat";
|
||||
|
||||
export interface ChatMediaLookupInput {
|
||||
@@ -41,18 +42,17 @@ export interface IChatRepository {
|
||||
/** 获取聊天历史,分页参数默认 limit=50, offset=0。 */
|
||||
getHistory(limit?: number, offset?: number): Promise<Result<ChatHistoryResponse>>;
|
||||
|
||||
//TODO remove
|
||||
/** 解锁私密消息。 */
|
||||
/** 解锁单条历史付费 / 私密消息。 */
|
||||
unlockPrivateMessage(messageId: string): Promise<Result<UnlockPrivateResponse>>;
|
||||
|
||||
/** 一键解锁历史锁定消息。 */
|
||||
unlockHistory(): Promise<Result<UnlockHistoryResponse>>;
|
||||
|
||||
//TODO remove
|
||||
/** 把本地缓存中的私密消息标记为已解锁。 */
|
||||
/** 把本地缓存中的单条锁定消息标记为已解锁。 */
|
||||
markPrivateMessageUnlockedInLocal(
|
||||
messageId: string,
|
||||
content: string,
|
||||
lockDetail?: ChatLockDetailData,
|
||||
): Promise<Result<void>>;
|
||||
|
||||
/** 把一条消息写入本地存储。 */
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
/**
|
||||
* 私密消息解锁响应
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
export const UnlockPrivateReasonSchema = z.enum([
|
||||
"ok",
|
||||
"quota_exceeded",
|
||||
"not_private",
|
||||
"not_found",
|
||||
]);
|
||||
import { ChatLockDetailSchema } from "./chat_payloads";
|
||||
|
||||
/**
|
||||
* 单条历史付费 / 私密消息解锁响应。
|
||||
*/
|
||||
export const UnlockPrivateReasonSchema = z.string().default("");
|
||||
|
||||
export const UnlockPrivateResponseSchema = z.object({
|
||||
unlocked: z.boolean(),
|
||||
content: z.string().nullable(),
|
||||
showUpgrade: z.boolean().default(false),
|
||||
paywallTriggered: z.boolean().default(false),
|
||||
privateFreeLimit: z.number().default(0),
|
||||
privateUsedToday: z.number().default(0),
|
||||
content: z.string().default(""),
|
||||
reason: UnlockPrivateReasonSchema,
|
||||
creditBalance: z.number().default(0),
|
||||
creditsCharged: z.number().default(0),
|
||||
requiredCredits: z.number().default(0),
|
||||
shortfallCredits: z.number().default(0),
|
||||
lockDetail: ChatLockDetailSchema,
|
||||
});
|
||||
|
||||
export type UnlockPrivateReason = z.output<typeof UnlockPrivateReasonSchema>;
|
||||
|
||||
@@ -86,7 +86,7 @@ export class ChatApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解锁私密消息
|
||||
* 解锁单条历史付费 / 私密消息
|
||||
*/
|
||||
async unlockPrivateMessage(
|
||||
body: UnlockPrivateRequest,
|
||||
|
||||
Reference in New Issue
Block a user