feat(worker): watermark versioning with atomic history regeneration

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-20 17:53:44 +08:00
parent 6723a0ce88
commit 243b2f8721
34 changed files with 3057 additions and 78 deletions
+25 -43
View File
@@ -10,7 +10,7 @@
// bootstrap/reconcile 同一出口);worker publish.service 的首次发布载荷来自
// 流水线内存值(同构不同源),保持不动。
import { HttpStatus } from '@nestjs/common';
import { ErrorCodes } from '@photo/contracts';
import { ErrorCodes, buildAlbumUpdatedPayloadCore } from '@photo/contracts';
import type {
AlbumUpdatedPayload,
AssetUpdatedPayload,
@@ -31,6 +31,8 @@ type AlbumRow = Pick<
| 'publicEnabled'
| 'downloadEnabled'
| 'originalDownloadEnabled'
| 'watermarkEnabled'
| 'watermarkEffectiveVersion'
| 'defaultLocale'
| 'timezone'
| 'startAt'
@@ -52,6 +54,8 @@ type PhotoVariantRow = Prisma.photo_variantsGetPayload<
/**
* album.updated 载荷:albums_ov 展示所需标量 + 翻译全集(locale 升序)。
* P4a Task 3 起标量全集(含水印副本位)的构建收敛到 contracts 的共享实现
* (worker 完成事务同用,见 buildAlbumUpdatedPayloadCore);本包装仅叠加
* categories 可选参数:仅 bootstrap/reconcile 快照事件传入(ov 整体替换
* albums_ov.categories 的收敛原语,见 contracts events.ts 注释);业务事务内
* 增量事件不传(分类元素归 category.* 事件管)。输出按 (sortOrder, id) 升序
@@ -62,48 +66,26 @@ export function buildAlbumUpdatedPayload(
translations: AlbumTranslationRow[],
categories?: AlbumCategoryInput[],
): AlbumUpdatedPayload {
return {
albumId: album.id,
publicToken: album.publicToken,
status: album.status,
uploadEnabled: album.uploadEnabled,
publicEnabled: album.publicEnabled,
downloadEnabled: album.downloadEnabled,
originalDownloadEnabled: album.originalDownloadEnabled,
defaultLocale: album.defaultLocale,
timezone: album.timezone,
startAt: album.startAt.toISOString(),
endAt: album.endAt === null ? null : album.endAt.toISOString(),
translations: [...translations]
.sort((a, b) => (a.locale < b.locale ? -1 : 1))
.map((t) => ({
locale: t.locale,
title: t.title,
description: t.description,
venue: t.venue,
address: t.address,
})),
...(categories === undefined
? {}
: {
categories: [...categories]
.sort((a, b) =>
a.category.sortOrder !== b.category.sortOrder
? a.category.sortOrder - b.category.sortOrder
: a.category.id < b.category.id
? -1
: 1,
)
.map(({ category, translations: categoryTranslations }) => ({
id: category.id,
sortOrder: category.sortOrder,
translations: [...categoryTranslations]
.sort((a, b) => (a.locale < b.locale ? -1 : 1))
.map((t) => ({ locale: t.locale, name: t.name })),
})),
}),
entityVersion: album.entityVersion,
};
const payload = buildAlbumUpdatedPayloadCore(album, translations);
if (categories === undefined) {
return payload;
}
payload.categories = [...categories]
.sort((a, b) =>
a.category.sortOrder !== b.category.sortOrder
? a.category.sortOrder - b.category.sortOrder
: a.category.id < b.category.id
? -1
: 1,
)
.map(({ category, translations: categoryTranslations }) => ({
id: category.id,
sortOrder: category.sortOrder,
translations: [...categoryTranslations]
.sort((a, b) => (a.locale < b.locale ? -1 : 1))
.map((t) => ({ locale: t.locale, name: t.name })),
}));
return payload;
}
/** 快照 categories 参数形状:分类行 + 该分类的翻译行(见函数注释)。 */