// services/api/src/scripts/seed-it-users.ts —— P3 Task 11 集成测试专用 // 用户种子脚本(MuMu + 真实后端套件的预置步骤,由 scripts/run-mobile-it.sh // 调用)。运行方式:`IT_USERS="a:pw1,b:pw2" pnpm --filter @photo/api-cn seed:it` // (= tsc 构建后 node dist/scripts/seed-it-users.js)。 // // 与共享 dev 库的隔离约定(集成测试【不做 DB reset】,见脚本头注释): // - 每次运行由编排脚本生成【全新唯一账号】(时间戳后缀)传入本脚本; // - 本脚本只做「不存在则建」,存在即跳过且【绝不改写密码/角色】—— // 与 seed-admin 同幂等语义,重复调用安全; // - 角色固定 staff:相册创建/邀请查找在 cn 侧仅要求已认证(SessionGuard), // 测试账号无需 admin 角色。 import { existsSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { config } from 'dotenv'; import { PrismaClient } from '@photo/db-cn'; import { hashPassword } from '../auth/password'; /** 解析 `account:password,...` 列表(冒号冲突风险可接受:账号不含冒号)。 */ export function parseItUsers(raw: string): Array<{ account: string; password: string }> { return raw .split(',') .map((pair) => pair.trim()) .filter((pair) => pair !== '') .map((pair) => { const sep = pair.indexOf(':'); if (sep <= 0) { throw new Error(`seed-it-users: 非法条目 "${pair}"(应为 account:password)`); } const account = pair.slice(0, sep).trim(); const password = pair.slice(sep + 1); if (account === '' || password === '') { throw new Error(`seed-it-users: 空账号/密码(${pair})`); } return { account, password }; }); } /** 种子核心(可测):逐个建号,已存在跳过。返回账号 → 是否新建。 */ export async function seedItUsers( prisma: PrismaClient, users: Array<{ account: string; password: string }>, ): Promise> { const result = new Map(); for (const { account, password } of users) { const existing = await prisma.users.findUnique({ where: { account } }); if (existing !== null) { result.set(account, false); continue; } await prisma.users.create({ data: { account, passwordHash: await hashPassword(password), displayName: account, role: 'staff', credentialStatus: 'active', }, }); result.set(account, true); } return result; } /** 定位仓库根 .env(与 seed-admin 相同约定:不覆盖已有进程环境变量)。 */ function loadRootEnv(): void { let dir = __dirname; while (!existsSync(join(dir, 'pnpm-workspace.yaml'))) { const parent = dirname(dir); if (parent === dir) { throw new Error('seed-it-users: pnpm-workspace.yaml not found'); } dir = parent; } config({ path: join(dir, '.env') }); } async function main(): Promise { loadRootEnv(); const raw = process.env.IT_USERS; if (!raw) { throw new Error('seed-it-users: IT_USERS 环境变量缺失(格式 account:password,...)'); } const users = parseItUsers(raw); const prisma = new PrismaClient(); try { const created = await seedItUsers(prisma, users); for (const [account, wasCreated] of created) { console.log( `seed-it-users: ${account} ${wasCreated ? 'created (staff)' : 'exists, skipped'}`, ); } } finally { await prisma.$disconnect(); } } if (require.main === module) { void main().catch((err: unknown) => { console.error(err instanceof Error ? err.message : err); process.exitCode = 1; }); }