feat(contracts): ad.updated/ad.deleted 事件类型与 ad 实体映射
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -6,9 +6,11 @@
|
||||
import { ErrorCodes, type ErrorCode } from '../errors.js';
|
||||
import type { components } from '../generated/openapi.js';
|
||||
import {
|
||||
isTombstoneOutboxType,
|
||||
OUTBOX_EVENT_TYPES,
|
||||
type OutboxEvent,
|
||||
type PhotoPublishedPayload,
|
||||
syncEntityTypeOfOutboxType,
|
||||
} from '../events.js';
|
||||
|
||||
// 类型恒等辅助:仅当 X 与 Y 是完全相同的类型(双向兼容,多/少一个成员都为 false)。
|
||||
@@ -60,7 +62,7 @@ describe('OutboxEvent', () => {
|
||||
});
|
||||
|
||||
describe('OUTBOX_EVENT_TYPES', () => {
|
||||
it('恰好包含全部 9 个事件类型字符串', () => {
|
||||
it('恰好包含全部 11 个事件类型字符串', () => {
|
||||
expect([...OUTBOX_EVENT_TYPES]).toEqual([
|
||||
'photo.published',
|
||||
'photo.hidden',
|
||||
@@ -71,6 +73,8 @@ describe('OUTBOX_EVENT_TYPES', () => {
|
||||
'category.deleted',
|
||||
'asset.updated',
|
||||
'slot.updated',
|
||||
'ad.updated',
|
||||
'ad.deleted',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -83,6 +87,16 @@ describe('OUTBOX_EVENT_TYPES', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('syncEntityTypeOfOutboxType / isTombstoneOutboxType', () => {
|
||||
// ad.* 映射与墓碑(广告位轮播 2026-09-28)
|
||||
it('ad.updated/ad.deleted → entityType ad;ad.deleted 为墓碑', () => {
|
||||
expect(syncEntityTypeOfOutboxType('ad.updated')).toBe('ad');
|
||||
expect(syncEntityTypeOfOutboxType('ad.deleted')).toBe('ad');
|
||||
expect(isTombstoneOutboxType('ad.deleted')).toBe(true);
|
||||
expect(isTombstoneOutboxType('ad.updated')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openapi Error.code enum ↔ errors.ts ErrorCodes 同步', () => {
|
||||
it('两侧 code 联合类型双向恒等(1:1)', () => {
|
||||
const apiEqualsErrors: Equal<ApiErrorCode, ErrorCode> = true;
|
||||
|
||||
@@ -13,7 +13,9 @@ export interface OutboxEvent<T = unknown> {
|
||||
| 'category.updated'
|
||||
| 'category.deleted'
|
||||
| 'asset.updated'
|
||||
| 'slot.updated';
|
||||
| 'slot.updated'
|
||||
| 'ad.updated'
|
||||
| 'ad.deleted';
|
||||
albumId: string;
|
||||
photoId?: string;
|
||||
entityVersion: number; // 单调递增,P2 inbox 消费依据
|
||||
@@ -162,6 +164,27 @@ export interface SlotSettingsPayload {
|
||||
reactionsEnabled?: boolean; // 默认 true(UI-04;false 仅隐藏气泡)
|
||||
}
|
||||
|
||||
// —— 广告位(2026-09-28 设计 §4)——
|
||||
// ad.updated 载荷:ads_ov 投影重建所需的**全量快照**——每次管理面变更整行替换
|
||||
// (slot.updated 同款);adId 供发送器推导 entityId(outbox 表无此列)。
|
||||
// albumIds null=全局投放;enabled=false 消费侧按「删投影行」处理(无墓碑下线)。
|
||||
export interface AdUpdatedPayload {
|
||||
adId: string;
|
||||
imageObjectKey: string;
|
||||
linkUrl: string;
|
||||
enabled: boolean;
|
||||
sortOrder: number;
|
||||
albumIds: string[] | null;
|
||||
createdAt: string; // ISO UTC;ov 排序 tie-break(设计 §5.2)
|
||||
entityVersion: number;
|
||||
}
|
||||
// ad.deleted 载荷:墓碑(ov 删 ads_ov 行且不被旧事件复活);cn 行已删,
|
||||
// entityVersion 为删除时刻终值(末次版本+1)。
|
||||
export interface AdDeletedPayload {
|
||||
adId: string;
|
||||
entityVersion: number;
|
||||
}
|
||||
|
||||
// 运行时校验用:全部合法事件类型字符串。satisfies 保证 数组 ⊆ 接口 union;
|
||||
// __tests__/events.test.ts 的 Equal 断言反向锁定 union ⊆ 数组,两侧永远 1:1。
|
||||
export const OUTBOX_EVENT_TYPES = [
|
||||
@@ -174,6 +197,8 @@ export const OUTBOX_EVENT_TYPES = [
|
||||
'category.deleted',
|
||||
'asset.updated',
|
||||
'slot.updated',
|
||||
'ad.updated',
|
||||
'ad.deleted',
|
||||
] as const satisfies readonly OutboxEvent['type'][];
|
||||
|
||||
// ── outbox 类型 → 内部同步线格式(openapi SyncEvent.entityType)的映射 ──
|
||||
@@ -186,7 +211,8 @@ export type SyncEntityType =
|
||||
| 'category'
|
||||
| 'photo'
|
||||
| 'asset'
|
||||
| 'slot-state';
|
||||
| 'slot-state'
|
||||
| 'ad';
|
||||
|
||||
export function syncEntityTypeOfOutboxType(
|
||||
type: OutboxEvent['type'],
|
||||
@@ -201,6 +227,9 @@ export function syncEntityTypeOfOutboxType(
|
||||
return 'asset';
|
||||
case 'slot.updated':
|
||||
return 'slot-state';
|
||||
case 'ad.updated':
|
||||
case 'ad.deleted':
|
||||
return 'ad';
|
||||
case 'photo.published':
|
||||
case 'photo.hidden':
|
||||
case 'photo.deleted':
|
||||
@@ -211,5 +240,9 @@ export function syncEntityTypeOfOutboxType(
|
||||
|
||||
/** 墓碑类事件(副本删除且不被旧事件复活,A26)。 */
|
||||
export function isTombstoneOutboxType(type: OutboxEvent['type']): boolean {
|
||||
return type === 'category.deleted' || type === 'photo.deleted';
|
||||
return (
|
||||
type === 'category.deleted' ||
|
||||
type === 'photo.deleted' ||
|
||||
type === 'ad.deleted'
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user