feat(sync): 对账清单 ad 实体当前态清扫
Task 8(ads-carousel):cn reconcile 增 entityType='ad' 分支——纯 SELECT 当前态映射(sortOrder/createdAt 确定性排序,albumId 恒空串信封约定), 载荷复用 ads/ad-payload.ts 单一出处(与业务事务内即时事件逐字节同形, 纯函数模块无依赖环);DTO SYNC_ENTITY_TYPES 追加 'ad'(对齐契约 enum)。 随附质量审查两项: - ads.service list() 行→AdminAdItem 映射复用 toItem(删 ~14 行内联重复, ⑮ 补 admin list 200 形状断言覆盖重构路径); - 新增 test/ads-extensions.spec.ts 守护 AD_IMAGE_EXTENSIONS === UPLOAD_CONTENT_TYPES 派生归一扩展名集合(jpeg→jpg),防三处白名单漂移。 测试基建:reset.ts RESET_TABLES 补 'ads'(Task 6 建表遗漏,固定 id 直种 依赖跨 spec 清场);reconcile.e2e-spec 追加 ⑤(红→绿,5/5)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -68,8 +68,12 @@ export type AdUploadInitResponse = components['schemas']['AdUploadInitResponse']
|
||||
export type AdminAdItem = components['schemas']['AdminAdItem'];
|
||||
export type AdminAdListResponse = components['schemas']['AdminAdListResponse'];
|
||||
|
||||
/** staging/终键扩展名合法集(init 扩展名白名单的归一值域)。 */
|
||||
const AD_IMAGE_EXTENSIONS = new Set(['jpg', 'png', 'heic']);
|
||||
/**
|
||||
* staging/终键扩展名合法集(init 扩展名白名单的归一值域)。导出供
|
||||
* test/ads-extensions.spec.ts 与上传域白名单做 1:1 一致性守护(三处同源:
|
||||
* UPLOAD_CONTENT_TYPES → EXTENSION_WHITELIST 归一值 → 本集合)。
|
||||
*/
|
||||
export const AD_IMAGE_EXTENSIONS = new Set(['jpg', 'png', 'heic']);
|
||||
|
||||
/**
|
||||
* updateAd 审计 metadata.fields 的字段名序列(契约 AdUpdateRequest 属性序,
|
||||
@@ -166,21 +170,9 @@ export class AdsService {
|
||||
const rows = await this.prisma.ads.findMany({
|
||||
orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }],
|
||||
});
|
||||
const ttl = signedUrlTtlSeconds();
|
||||
const items = await Promise.all(
|
||||
rows.map(async (row) => ({
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
imageObjectKey: row.imageObjectKey,
|
||||
imageUrl: await this.storage.presignGet(row.imageObjectKey, ttl),
|
||||
linkUrl: row.linkUrl,
|
||||
enabled: row.enabled,
|
||||
sortOrder: row.sortOrder,
|
||||
albumIds: (row.albumIds as string[] | null) ?? null,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
})),
|
||||
);
|
||||
// 行→AdminAdItem 映射复用 toItem(loadItem 同一出口;TTL 惰性读取在
|
||||
// toItem 内逐行进行——与 create/update 的单品响应口径一致)。
|
||||
const items = await Promise.all(rows.map((row) => this.toItem(row)));
|
||||
return { items };
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,14 @@ import {
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
/** 契约 SyncEntityType(slot-state 预留——与 openapi enum 同步)。 */
|
||||
/** 契约 SyncEntityType(slot-state 预留;ad 自 ads-carousel Task 8 落地——与 openapi enum 同步)。 */
|
||||
export const SYNC_ENTITY_TYPES = [
|
||||
'album',
|
||||
'category',
|
||||
'photo',
|
||||
'asset',
|
||||
'slot-state',
|
||||
'ad',
|
||||
] as const;
|
||||
|
||||
/** 契约 BootstrapRequest:albumId 缺省 = 全部相册。 */
|
||||
|
||||
@@ -30,6 +30,11 @@ import { Injectable } from '@nestjs/common';
|
||||
import type { components } from '@photo/contracts';
|
||||
import type { Prisma } from '@photo/db-cn';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
// ads 域载荷构建器(ads-carousel Task 8):纯函数模块(仅依赖 @photo/contracts
|
||||
// 与 @photo/db-cn 类型),与 ads.service 业务事务内即时事件共用同一出处——
|
||||
// 「对账快照事件」与「即时事件」载荷逐字节同形(ad-payload.ts 头注约定),
|
||||
// 无模块环(对照 ./payloads 的既有跨域消费方式)。
|
||||
import { buildAdUpdatedPayload } from '../ads/ad-payload';
|
||||
import type { StatSnapshotDto } from './dto';
|
||||
import {
|
||||
type AlbumCategoryInput,
|
||||
@@ -424,6 +429,29 @@ export class SyncService {
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if (query.entityType === 'ad') {
|
||||
// 广告当前态清扫(ads-carousel Task 8):全量拉取,sortOrder/createdAt
|
||||
// 确定性排序(与 ads list 同口径;运营量级 <20,不分页)。版本/limit 过滤
|
||||
// 不适用:ov 侧按 eventId 幂等 + 版本单调自行判重(reconciler 语义,见
|
||||
// reconciler.ts 阶段一说明)。广告无相册归属:albumId 恒空串(与 outbox
|
||||
// 写入口径一致,设计 §4);无墓碑——enabled=false 的全量快照由消费侧按
|
||||
// 「删投影行」处理(Task 5 语义),硬删行不再出现在清单(category 同一
|
||||
// 已知边界)。
|
||||
const rows = await this.prisma.ads.findMany({
|
||||
orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }],
|
||||
});
|
||||
events.push(
|
||||
...rows.map((row) =>
|
||||
this.toWireEvent(
|
||||
'ad',
|
||||
row.id,
|
||||
'',
|
||||
row.entityVersion,
|
||||
false,
|
||||
buildAdUpdatedPayload(row),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// slot-state 预留:恒空清单。
|
||||
return { events };
|
||||
|
||||
Reference in New Issue
Block a user