feat(admin): album management pages with members and categories
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
// e2e 数据种子夹具(Node 直跑,Playwright 用例内按需调用)。
|
||||
// 为什么不用 API 造数:目标照片需要 published/processing_failed 等终态,
|
||||
// 真实链路要 worker 处理流水线;e2e 只验证管理页展示与操作,直插 DB 行
|
||||
// (uploadedAt 置位即可入管理列表,见 services/api listAlbumPhotos 过滤条件)。
|
||||
// 依赖经 PHOTO_DB_CN 环境变量注入 @photo/db-cn 绝对路径(apps/admin 不依赖
|
||||
// 该包,node 从文件位置解析不到;services/api 的 node_modules 里有现成链接)。
|
||||
// 用法:
|
||||
// node seed-fixture.cjs staff <account> <displayName>
|
||||
// node seed-fixture.cjs photos <albumId> <uploaderAccount>
|
||||
// 幂等:账号存在即跳过;照片按 objectKey upsert(同相册重复种不重复建行)。
|
||||
const db_cn = process.env.PHOTO_DB_CN
|
||||
const databaseUrl = process.env.DATABASE_URL_CN
|
||||
if (!db_cn || !databaseUrl) {
|
||||
console.error('seed-fixture: PHOTO_DB_CN and DATABASE_URL_CN env vars are required')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// CommonJS(node 直跑,无构建步骤):动态 require 经环境变量注入的包路径
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { PrismaClient } = require(db_cn)
|
||||
const prisma = new PrismaClient({ datasources: { db: { url: databaseUrl } } })
|
||||
|
||||
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}`)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const [mode, argA, argB] = process.argv.slice(2)
|
||||
if (mode === 'staff') {
|
||||
await seedStaff(argA, argB)
|
||||
} else if (mode === 'photos') {
|
||||
await seedPhotos(argA, argB)
|
||||
} else {
|
||||
throw new Error(`seed-fixture: unknown mode "${mode}"`)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
.finally(() => prisma.$disconnect())
|
||||
Reference in New Issue
Block a user