feat(api): embed slots with ov replica and public feed
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -60,7 +60,7 @@ describe('OutboxEvent', () => {
|
||||
});
|
||||
|
||||
describe('OUTBOX_EVENT_TYPES', () => {
|
||||
it('恰好包含全部 8 个事件类型字符串', () => {
|
||||
it('恰好包含全部 9 个事件类型字符串', () => {
|
||||
expect([...OUTBOX_EVENT_TYPES]).toEqual([
|
||||
'photo.published',
|
||||
'photo.hidden',
|
||||
@@ -70,6 +70,7 @@ describe('OUTBOX_EVENT_TYPES', () => {
|
||||
'category.updated',
|
||||
'category.deleted',
|
||||
'asset.updated',
|
||||
'slot.updated',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -50,3 +50,7 @@ export const EMBED_POLL_INTERVAL_MS = 30000;
|
||||
export const EMBED_POLL_MAX_FAILURES = 5;
|
||||
/** 轮询失败退避倍数(连续失败达上限后拉长间隔,成功即恢复正常间隔)。 */
|
||||
export const EMBED_POLL_BACKOFF_MULTIPLIER = 5;
|
||||
/** 展示位公开端点基础限速:slotToken+IP 固定窗(EMBED_WINDOW_SECONDS)内请求数上限(含 304)。 */
|
||||
export const EMBED_WINDOW_REQUESTS = 120;
|
||||
/** 展示位限速固定窗秒数(轮询 30s/次 + 页面重载突发,窗口内余量充足)。 */
|
||||
export const EMBED_WINDOW_SECONDS = 60;
|
||||
|
||||
@@ -12,7 +12,8 @@ export interface OutboxEvent<T = unknown> {
|
||||
| 'album.updated'
|
||||
| 'category.updated'
|
||||
| 'category.deleted'
|
||||
| 'asset.updated';
|
||||
| 'asset.updated'
|
||||
| 'slot.updated';
|
||||
albumId: string;
|
||||
photoId?: string;
|
||||
entityVersion: number; // 单调递增,P2 inbox 消费依据
|
||||
@@ -122,6 +123,33 @@ export interface AssetUpdatedPayload {
|
||||
entityVersion: number;
|
||||
}
|
||||
|
||||
// —— 展示位(P4b Task 2)——
|
||||
// slot.updated 载荷:ov 副本(embed_slots_ov)重建展示位所需的**全量快照**——
|
||||
// 展示位无墓碑(无删除端点,enabled=false 即下线),每次管理面变更整行替换。
|
||||
// slotId 供发送器推导 entityId(同 categoryId/assetId 约定,outbox 表无此列);
|
||||
// albumId/albumPublicToken 为绑定相册双标识(未绑定时均 null):ov 副本外键
|
||||
// 落 albumPublicToken(公开端点以它关联 albums_ov),照片按副本实时查询、
|
||||
// 不随载荷携带(新照片发布走 photo.published,ETag 四元组含发布时间)。
|
||||
export interface SlotStateUpdatedPayload {
|
||||
slotId: string;
|
||||
publicToken: string;
|
||||
enabled: boolean;
|
||||
albumId: string | null;
|
||||
albumPublicToken: string | null;
|
||||
configVersion: number;
|
||||
settings: SlotSettingsPayload;
|
||||
entityVersion: number;
|
||||
}
|
||||
|
||||
/** 展示位设置载荷(契约 EmbedSlotSettings 的载荷形状;字段恒齐全)。 */
|
||||
export interface SlotSettingsPayload {
|
||||
position: string; // left|right
|
||||
size: string; // sm|md
|
||||
intervalMs: number;
|
||||
lang: string; // auto|zh-CN|en
|
||||
collapsedDefault: boolean;
|
||||
}
|
||||
|
||||
// 运行时校验用:全部合法事件类型字符串。satisfies 保证 数组 ⊆ 接口 union;
|
||||
// __tests__/events.test.ts 的 Equal 断言反向锁定 union ⊆ 数组,两侧永远 1:1。
|
||||
export const OUTBOX_EVENT_TYPES = [
|
||||
@@ -133,13 +161,14 @@ export const OUTBOX_EVENT_TYPES = [
|
||||
'category.updated',
|
||||
'category.deleted',
|
||||
'asset.updated',
|
||||
'slot.updated',
|
||||
] as const satisfies readonly OutboxEvent['type'][];
|
||||
|
||||
// ── outbox 类型 → 内部同步线格式(openapi SyncEvent.entityType)的映射 ──
|
||||
// 发送器(services/sync)与 cn reconcile 端点共用,单一实现防两侧漂移;
|
||||
// exhaustive switch:新增事件类型漏映射会在此编译报错。
|
||||
|
||||
/** 内部同步实体类型(对齐 openapi SyncEntityType;slot-state 为预留)。 */
|
||||
/** 内部同步实体类型(对齐 openapi SyncEntityType;slot-state 自 P4b Task 2 起 landing)。 */
|
||||
export type SyncEntityType =
|
||||
| 'album'
|
||||
| 'category'
|
||||
@@ -158,6 +187,8 @@ export function syncEntityTypeOfOutboxType(
|
||||
return 'category';
|
||||
case 'asset.updated':
|
||||
return 'asset';
|
||||
case 'slot.updated':
|
||||
return 'slot-state';
|
||||
case 'photo.published':
|
||||
case 'photo.hidden':
|
||||
case 'photo.deleted':
|
||||
|
||||
Reference in New Issue
Block a user