2026-09-18 12:33:46 +08:00
|
|
|
|
// e2e 数据种子夹具(Node 直跑,Playwright 用例内按需调用)。
|
|
|
|
|
|
// 为什么不用 API 造数:目标照片需要 published/processing_failed 等终态,
|
|
|
|
|
|
// 真实链路要 worker 处理流水线;e2e 只验证管理页展示与操作,直插 DB 行
|
|
|
|
|
|
// (uploadedAt 置位即可入管理列表,见 services/api listAlbumPhotos 过滤条件)。
|
2026-09-20 21:10:30 +08:00
|
|
|
|
// 依赖经环境变量注入包绝对路径(apps/admin 不依赖这些包,node 从文件位置
|
|
|
|
|
|
// 解析不到;services/api 的 node_modules 里有现成链接):
|
|
|
|
|
|
// PHOTO_DB_CN=@photo/db-cn(必填) PHOTO_STORAGE=@photo/storage(水印模式)
|
|
|
|
|
|
// 模式:
|
|
|
|
|
|
// staff <account> <displayName> 员工账号(不可登录)
|
|
|
|
|
|
// photos <albumId> <uploaderAccount> published/failed 各 1 张
|
|
|
|
|
|
// gov-photos <albumId> <uploaderAccount> <count> [bytes] 批量隐藏/用量旅程:
|
|
|
|
|
|
// N 张 published/ready
|
|
|
|
|
|
// DB 直种(含 thumb/browse
|
|
|
|
|
|
// 变体行——cn 公开列表计入;
|
|
|
|
|
|
// bytes 可调大以跨阈值)
|
|
|
|
|
|
// wm-photos <albumId> <uploaderAccount> <count> 水印旅程:N 张已发布
|
|
|
|
|
|
// 照片 + 真实 MinIO 对象
|
|
|
|
|
|
// (原图/browse/thumb,键
|
|
|
|
|
|
// 对齐 worker 发布后形态)
|
|
|
|
|
|
// watermark-run <albumId> 直驱 worker dist 的
|
|
|
|
|
|
// WatermarkRegenService 把
|
|
|
|
|
|
// 最新任务跑到终态(e2e 无
|
|
|
|
|
|
// 常驻 worker,同 services/
|
|
|
|
|
|
// api/test/watermark.e2e-
|
|
|
|
|
|
// spec.ts 直驱模式)
|
2026-09-20 23:25:59 +08:00
|
|
|
|
// sync-flush [albumId] cn→ov 种子同步(P4b Task 5
|
|
|
|
|
|
// 展示位 e2e):albumId 提供
|
|
|
|
|
|
// 时先打 cn bootstrap(当前
|
|
|
|
|
|
// 态快照写回 outbox,直种
|
|
|
|
|
|
// photos 行入公开 feed 的
|
|
|
|
|
|
// 必经通道),再经 services/
|
|
|
|
|
|
// sync dist 的
|
|
|
|
|
|
// deliverPendingOnce 排空
|
|
|
|
|
|
// outbox(真实 HMAC 发送
|
|
|
|
|
|
// 管线;slot.updated 事件不
|
|
|
|
|
|
// 随 bootstrap 走,须此排空;
|
|
|
|
|
|
// albumId 省略或 '-' = 仅排空)
|
2026-09-20 21:10:30 +08:00
|
|
|
|
// 幂等:账号存在即跳过;照片按确定性 photoId/objectKey upsert(同相册重复种
|
|
|
|
|
|
// 不重复建行);watermark-run 对已 done 的任务返回 skipped 视为成功。
|
|
|
|
|
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
|
|
|
|
const path = require('node:path')
|
|
|
|
|
|
const { execSync } = require('node:child_process')
|
|
|
|
|
|
const { existsSync, statSync } = require('node:fs')
|
2026-09-20 23:25:59 +08:00
|
|
|
|
const { createHash, createHmac } = require('node:crypto')
|
2026-09-20 21:10:30 +08:00
|
|
|
|
|
|
|
|
|
|
const dbCn = process.env.PHOTO_DB_CN
|
2026-09-18 12:33:46 +08:00
|
|
|
|
const databaseUrl = process.env.DATABASE_URL_CN
|
2026-09-20 21:10:30 +08:00
|
|
|
|
if (!dbCn || !databaseUrl) {
|
2026-09-18 12:33:46 +08:00
|
|
|
|
console.error('seed-fixture: PHOTO_DB_CN and DATABASE_URL_CN env vars are required')
|
|
|
|
|
|
process.exit(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CommonJS(node 直跑,无构建步骤):动态 require 经环境变量注入的包路径
|
2026-09-20 21:10:30 +08:00
|
|
|
|
|
|
|
|
|
|
const { PrismaClient } = require(dbCn)
|
2026-09-18 12:33:46 +08:00
|
|
|
|
const prisma = new PrismaClient({ datasources: { db: { url: databaseUrl } } })
|
|
|
|
|
|
|
2026-09-20 21:10:30 +08:00
|
|
|
|
/** worker 包根(sharp / dist 直驱均从这里解析)。 */
|
|
|
|
|
|
const workerRoot = path.resolve(__dirname, '..', '..', '..', 'services', 'worker')
|
|
|
|
|
|
|
2026-09-18 12:33:46 +08:00
|
|
|
|
async function seedStaff(account, displayName) {
|
|
|
|
|
|
const existing = await prisma.users.findUnique({ where: { account } })
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
|
console.log(`seed-fixture: staff "${account}" exists, skipping`)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
// passwordHash 占位(不可登录):e2e 只经账号查询邀请该用户,从不登录
|
|
|
|
|
|
await prisma.users.create({
|
|
|
|
|
|
data: {
|
|
|
|
|
|
account,
|
|
|
|
|
|
passwordHash: 'seeded-no-login',
|
|
|
|
|
|
displayName: displayName || account,
|
|
|
|
|
|
role: 'staff',
|
|
|
|
|
|
credentialStatus: 'active'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
console.log(`seed-fixture: staff "${account}" created`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function seedPhotos(albumId, uploaderAccount) {
|
|
|
|
|
|
const uploader = await prisma.users.findUnique({ where: { account: uploaderAccount } })
|
|
|
|
|
|
if (!uploader) {
|
|
|
|
|
|
throw new Error(`seed-fixture: uploader "${uploaderAccount}" not found`)
|
|
|
|
|
|
}
|
|
|
|
|
|
const now = new Date()
|
|
|
|
|
|
const rows = [
|
|
|
|
|
|
{
|
|
|
|
|
|
suffix: 'published',
|
|
|
|
|
|
visibility: 'published',
|
|
|
|
|
|
processingState: 'ready',
|
|
|
|
|
|
width: 1200,
|
|
|
|
|
|
height: 800,
|
|
|
|
|
|
publishedAt: now,
|
|
|
|
|
|
errorCode: null
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
suffix: 'failed',
|
|
|
|
|
|
visibility: 'unpublished',
|
|
|
|
|
|
processingState: 'processing_failed',
|
|
|
|
|
|
width: null,
|
|
|
|
|
|
height: null,
|
|
|
|
|
|
publishedAt: null,
|
|
|
|
|
|
errorCode: 'DECODE_FAILED'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
for (const row of rows) {
|
|
|
|
|
|
const objectKey = `e2e-seed/albums/${albumId}/${row.suffix}.jpg`
|
|
|
|
|
|
await prisma.photos.upsert({
|
|
|
|
|
|
where: { objectKey },
|
|
|
|
|
|
update: {},
|
|
|
|
|
|
create: {
|
|
|
|
|
|
albumId,
|
|
|
|
|
|
uploaderId: uploader.id,
|
|
|
|
|
|
objectKey,
|
|
|
|
|
|
bytes: 2048,
|
|
|
|
|
|
width: row.width,
|
|
|
|
|
|
height: row.height,
|
|
|
|
|
|
uploadedAt: now,
|
|
|
|
|
|
publishedAt: row.publishedAt,
|
|
|
|
|
|
processingState: row.processingState,
|
|
|
|
|
|
errorCode: row.errorCode,
|
|
|
|
|
|
visibility: row.visibility
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log(`seed-fixture: 2 photos seeded for album ${albumId}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-20 21:10:30 +08:00
|
|
|
|
/** 确定性 photoId(同相册同序号重复种不重复建行;长度/字符集适配 cuid 列)。 */
|
|
|
|
|
|
function deterministicPhotoId(scope, index) {
|
|
|
|
|
|
return `e2e${createHash('md5').update(`${scope}:${index}`).digest('hex')}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function findUploader(account) {
|
|
|
|
|
|
const uploader = await prisma.users.findUnique({ where: { account } })
|
|
|
|
|
|
if (!uploader) {
|
|
|
|
|
|
throw new Error(`seed-fixture: uploader "${account}" not found`)
|
|
|
|
|
|
}
|
|
|
|
|
|
return uploader
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* gov-photos:N 张 published/ready 照片 DB 直种。带 thumb/browse 变体行
|
|
|
|
|
|
* (cn 公开列表计入的必要条件——服务端只现签行上的键,不校验对象存在,
|
|
|
|
|
|
* 故无需真实对象);bytes 可调(用量旅程用大值跨 perAlbum 阈值)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function seedGovPhotos(albumId, uploaderAccount, count, bytesEach) {
|
|
|
|
|
|
const uploader = await findUploader(uploaderAccount)
|
|
|
|
|
|
const total = Math.max(1, Number(count) || 1)
|
|
|
|
|
|
const bytes = Math.max(1, Number(bytesEach) || 2048)
|
|
|
|
|
|
const now = new Date()
|
|
|
|
|
|
for (let i = 0; i < total; i++) {
|
|
|
|
|
|
const photoId = deterministicPhotoId(`${albumId}:gov`, i)
|
|
|
|
|
|
const objectKey = `e2e-gov/albums/${albumId}/originals/${photoId}.jpg`
|
|
|
|
|
|
await prisma.photos.upsert({
|
|
|
|
|
|
where: { objectKey },
|
|
|
|
|
|
update: { visibility: 'published', processingState: 'ready' },
|
|
|
|
|
|
create: {
|
|
|
|
|
|
id: photoId,
|
|
|
|
|
|
albumId,
|
|
|
|
|
|
uploaderId: uploader.id,
|
|
|
|
|
|
objectKey,
|
|
|
|
|
|
bytes,
|
|
|
|
|
|
width: 1200,
|
|
|
|
|
|
height: 800,
|
|
|
|
|
|
uploadedAt: now,
|
|
|
|
|
|
publishedAt: now,
|
|
|
|
|
|
processingState: 'ready',
|
|
|
|
|
|
visibility: 'published'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
// 变体行(thumb/browse):公开列表计入 + 现签 URL 可用;对象不落(e2e 不取图)
|
|
|
|
|
|
const variants = [
|
|
|
|
|
|
{ variant: 'thumb', width: 480, height: 320 },
|
|
|
|
|
|
{ variant: 'browse', width: 1200, height: 800 }
|
|
|
|
|
|
]
|
|
|
|
|
|
for (const v of variants) {
|
|
|
|
|
|
await prisma.photo_variants.upsert({
|
|
|
|
|
|
where: { photoId_variant: { photoId, variant: v.variant } },
|
|
|
|
|
|
update: {},
|
|
|
|
|
|
create: {
|
|
|
|
|
|
photoId,
|
|
|
|
|
|
variant: v.variant,
|
|
|
|
|
|
objectKey: `e2e-gov/albums/${albumId}/variants/${photoId}/1/${v.variant}.jpg`,
|
|
|
|
|
|
bytes: Math.round(bytes / 4),
|
|
|
|
|
|
version: 1,
|
|
|
|
|
|
width: v.width,
|
|
|
|
|
|
height: v.height
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log(`seed-fixture: ${total} gov photos seeded for album ${albumId} (${bytes} B each)`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 测试内生成真实 JPEG(带纹理;与 worker test makeJpeg 同法,水印差异可观测)。 */
|
|
|
|
|
|
function makeJpegBuffer(sharp, width, height) {
|
|
|
|
|
|
const svg = Buffer.from(
|
|
|
|
|
|
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">` +
|
|
|
|
|
|
Array.from(
|
|
|
|
|
|
{ length: 12 },
|
|
|
|
|
|
(_, i) =>
|
|
|
|
|
|
`<rect x="${(i * 83) % width}" y="${(i * 47) % height}" width="${50 + i * 9}" height="${35 + i * 7}" fill="#${(0x50 + i * 7).toString(16).padStart(2, '0')}6030" fill-opacity="0.85"/>`
|
|
|
|
|
|
).join('') +
|
|
|
|
|
|
'</svg>'
|
|
|
|
|
|
)
|
|
|
|
|
|
return sharp(svg).jpeg({ quality: 85 }).toBuffer()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* wm-photos:N 张已发布照片(原图 + browse/thumb 对象与行,形状对齐 worker
|
|
|
|
|
|
* 发布后形态——watermark regen 读原图、公开列表需 thumb/browse 行)。
|
|
|
|
|
|
* 键布局走 @photo/storage OBJECT_KEY_ROOT 约定(photo-live/dev)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function seedWmPhotos(albumId, uploaderAccount, count) {
|
|
|
|
|
|
const storagePath = process.env.PHOTO_STORAGE
|
|
|
|
|
|
if (!storagePath) {
|
|
|
|
|
|
throw new Error('seed-fixture: PHOTO_STORAGE env var is required for wm-photos')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { OBJECT_KEY_ROOT, createStorageFromEnv } = require(storagePath)
|
|
|
|
|
|
|
|
|
|
|
|
const sharp = require(path.join(workerRoot, 'node_modules', 'sharp'))
|
|
|
|
|
|
const uploader = await findUploader(uploaderAccount)
|
|
|
|
|
|
const storage = createStorageFromEnv()
|
|
|
|
|
|
const total = Math.max(1, Number(count) || 1)
|
|
|
|
|
|
const now = new Date()
|
|
|
|
|
|
for (let i = 0; i < total; i++) {
|
|
|
|
|
|
const photoId = deterministicPhotoId(`${albumId}:wm`, i)
|
|
|
|
|
|
const objectKey = `${OBJECT_KEY_ROOT}/albums/${albumId}/originals/${photoId}.jpg`
|
|
|
|
|
|
const browseKey = `${OBJECT_KEY_ROOT}/albums/${albumId}/variants/${photoId}/1/browse.jpg`
|
|
|
|
|
|
const thumbKey = `${OBJECT_KEY_ROOT}/albums/${albumId}/variants/${photoId}/1/thumb.jpg`
|
|
|
|
|
|
const original = await makeJpegBuffer(sharp, 900, 600)
|
|
|
|
|
|
// browse/thumb 与原图同缓冲即可(e2e 断言 URL 指向,不比对变体字节)
|
|
|
|
|
|
await storage.put(objectKey, original, 'image/jpeg')
|
|
|
|
|
|
await storage.put(browseKey, original, 'image/jpeg')
|
|
|
|
|
|
await storage.put(thumbKey, original, 'image/jpeg')
|
|
|
|
|
|
await prisma.photos.upsert({
|
|
|
|
|
|
where: { objectKey },
|
|
|
|
|
|
update: {},
|
|
|
|
|
|
create: {
|
|
|
|
|
|
id: photoId,
|
|
|
|
|
|
albumId,
|
|
|
|
|
|
uploaderId: uploader.id,
|
|
|
|
|
|
objectKey,
|
|
|
|
|
|
bytes: original.length,
|
|
|
|
|
|
width: 900,
|
|
|
|
|
|
height: 600,
|
|
|
|
|
|
uploadedAt: now,
|
|
|
|
|
|
publishedAt: now,
|
|
|
|
|
|
processingState: 'ready',
|
|
|
|
|
|
visibility: 'published'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
const variants = [
|
|
|
|
|
|
{ variant: 'thumb', key: thumbKey, width: 480, height: 320 },
|
|
|
|
|
|
{ variant: 'browse', key: browseKey, width: 900, height: 600 }
|
|
|
|
|
|
]
|
|
|
|
|
|
for (const v of variants) {
|
|
|
|
|
|
await prisma.photo_variants.upsert({
|
|
|
|
|
|
where: { photoId_variant: { photoId, variant: v.variant } },
|
|
|
|
|
|
update: {},
|
|
|
|
|
|
create: {
|
|
|
|
|
|
photoId,
|
|
|
|
|
|
variant: v.variant,
|
|
|
|
|
|
objectKey: v.key,
|
|
|
|
|
|
bytes: original.length,
|
|
|
|
|
|
version: 1,
|
|
|
|
|
|
width: v.width,
|
|
|
|
|
|
height: v.height
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log(`seed-fixture: ${total} wm photos (with real objects) seeded for album ${albumId}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* watermark-run:把相册最新水印任务直驱到终态。e2e 环境无常驻 worker
|
|
|
|
|
|
* (Playwright 只起 api + admin dev),借 worker dist 的服务核心直跑——与
|
|
|
|
|
|
* services/api/test/watermark.e2e-spec.ts 同一入口(processJob),API 侧
|
|
|
|
|
|
* POST 已落好 config/job 行。dist 过期(缺文件或源更新)则先 tsc 构建。
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function runWatermark(albumId) {
|
|
|
|
|
|
const distService = path.join(workerRoot, 'dist', 'watermark.service.js')
|
|
|
|
|
|
const srcService = path.join(workerRoot, 'src', 'watermark.service.ts')
|
|
|
|
|
|
const stale =
|
|
|
|
|
|
!existsSync(distService) ||
|
|
|
|
|
|
(existsSync(srcService) && statSync(srcService).mtimeMs > statSync(distService).mtimeMs)
|
|
|
|
|
|
if (stale) {
|
|
|
|
|
|
console.log('seed-fixture: building services/worker dist (stale or missing)...')
|
|
|
|
|
|
execSync('npx tsc -p tsconfig.build.json', { cwd: workerRoot, stdio: 'inherit' })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { WatermarkRegenService } = require(distService)
|
|
|
|
|
|
const storagePath = process.env.PHOTO_STORAGE
|
|
|
|
|
|
if (!storagePath) {
|
|
|
|
|
|
throw new Error('seed-fixture: PHOTO_STORAGE env var is required for watermark-run')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { createStorageFromEnv } = require(storagePath)
|
|
|
|
|
|
const storage = createStorageFromEnv()
|
|
|
|
|
|
const job = await prisma.watermark_jobs.findFirst({
|
|
|
|
|
|
where: { albumId },
|
|
|
|
|
|
orderBy: { createdAt: 'desc' }
|
|
|
|
|
|
})
|
|
|
|
|
|
if (!job) {
|
|
|
|
|
|
throw new Error(`seed-fixture: no watermark job found for album ${albumId}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
const service = new WatermarkRegenService(prisma, storage)
|
|
|
|
|
|
const outcome = await service.processJob(job.id)
|
|
|
|
|
|
console.log(`seed-fixture: watermark job ${job.id} → ${outcome}`)
|
|
|
|
|
|
if (outcome === 'failed') {
|
|
|
|
|
|
throw new Error('seed-fixture: watermark job failed (task-level abort)')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-20 23:25:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* sync-flush:cn→ov 种子同步(apps/viewer e2e helpers flushSync/flushOutbox
|
|
|
|
|
|
* 的 execSync 版——apps/admin 不依赖 @photo/db-cn/@photo/contracts,经
|
|
|
|
|
|
* services/{api,sync} 的 node_modules/dist 借道)。bootstrap 为 cn 内部
|
|
|
|
|
|
* HMAC 端点(签名 = HMAC-SHA256(key, rawBody).hex,同 contracts
|
|
|
|
|
|
* signSyncBody);发送器 dist 过期则先 tsc 构建(同 watermark-run 口径)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function syncFlush(albumId) {
|
|
|
|
|
|
const internalKey = process.env.SYNC_INTERNAL_KEY ?? 'dev-sync-secret-change-me'
|
|
|
|
|
|
const cnBase = (process.env.CN_API_URL ?? 'http://127.0.0.1:8080').replace(/\/+$/, '')
|
|
|
|
|
|
|
|
|
|
|
|
if (albumId && albumId !== '-') {
|
|
|
|
|
|
const rawBody = JSON.stringify({ albumId })
|
|
|
|
|
|
const bootstrap = await fetch(`${cnBase}/v1/internal/sync/bootstrap`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'content-type': 'application/json',
|
|
|
|
|
|
'x-sync-timestamp': String(Math.floor(Date.now() / 1000)),
|
|
|
|
|
|
'x-sync-signature': createHmac('sha256', internalKey).update(rawBody).digest('hex')
|
|
|
|
|
|
},
|
|
|
|
|
|
body: rawBody
|
|
|
|
|
|
})
|
|
|
|
|
|
if (!bootstrap.ok) {
|
|
|
|
|
|
throw new Error(
|
|
|
|
|
|
`seed-fixture: bootstrap 失败(${bootstrap.status}):${(await bootstrap.text()).slice(0, 300)}`
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log(`seed-fixture: bootstrap ok for album ${albumId}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const syncRoot = path.resolve(__dirname, '..', '..', '..', 'services', 'sync')
|
|
|
|
|
|
const distSender = path.join(syncRoot, 'dist', 'sender.js')
|
|
|
|
|
|
const srcSender = path.join(syncRoot, 'src', 'sender.ts')
|
|
|
|
|
|
const stale =
|
|
|
|
|
|
!existsSync(distSender) ||
|
|
|
|
|
|
(existsSync(srcSender) && statSync(srcSender).mtimeMs > statSync(distSender).mtimeMs)
|
|
|
|
|
|
if (stale) {
|
|
|
|
|
|
console.log('seed-fixture: building services/sync dist (stale or missing)...')
|
|
|
|
|
|
execSync('npx tsc -p tsconfig.build.json', { cwd: syncRoot, stdio: 'inherit' })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { deliverPendingOnce } = require(distSender)
|
|
|
|
|
|
await deliverPendingOnce({
|
|
|
|
|
|
prisma,
|
|
|
|
|
|
ovApiUrl: process.env.OV_API_URL ?? 'http://127.0.0.1:8081',
|
|
|
|
|
|
internalKey
|
|
|
|
|
|
})
|
|
|
|
|
|
console.log('seed-fixture: outbox drained to ov')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-18 12:33:46 +08:00
|
|
|
|
async function main() {
|
2026-09-20 21:10:30 +08:00
|
|
|
|
const [mode, argA, argB, argC, argD] = process.argv.slice(2)
|
2026-09-18 12:33:46 +08:00
|
|
|
|
if (mode === 'staff') {
|
|
|
|
|
|
await seedStaff(argA, argB)
|
|
|
|
|
|
} else if (mode === 'photos') {
|
|
|
|
|
|
await seedPhotos(argA, argB)
|
2026-09-20 21:10:30 +08:00
|
|
|
|
} else if (mode === 'gov-photos') {
|
|
|
|
|
|
await seedGovPhotos(argA, argB, argC, argD)
|
|
|
|
|
|
} else if (mode === 'wm-photos') {
|
|
|
|
|
|
await seedWmPhotos(argA, argB, argC)
|
|
|
|
|
|
} else if (mode === 'watermark-run') {
|
|
|
|
|
|
await runWatermark(argA)
|
2026-09-20 23:25:59 +08:00
|
|
|
|
} else if (mode === 'sync-flush') {
|
|
|
|
|
|
await syncFlush(argA)
|
2026-09-18 12:33:46 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error(`seed-fixture: unknown mode "${mode}"`)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main()
|
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
|
console.error(error)
|
|
|
|
|
|
process.exitCode = 1
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => prisma.$disconnect())
|