|
@@ -1,94 +1,133 @@
|
|
|
import { db } from './index';
|
|
import { db } from './index';
|
|
|
import { users, groups, roles, permissions, userGroups, groupRoles, rolePermissions, userRoles, resources, aclRules } from './schema/auth';
|
|
import { users, groups, roles, permissions, userGroups, groupRoles, rolePermissions, userRoles, resources, aclRules } from './schema/auth';
|
|
|
import { resources as resourceSchema, aclRules as aclRulesSchema } from './schema/resource';
|
|
import { resources as resourceSchema, aclRules as aclRulesSchema } from './schema/resource';
|
|
|
-import { eq } from 'drizzle-orm';
|
|
|
|
|
|
|
+import { eq, and } from 'drizzle-orm';
|
|
|
|
|
|
|
|
async function seed() {
|
|
async function seed() {
|
|
|
- console.log('🌱 开始执行 Seed 脚本...');
|
|
|
|
|
|
|
+ console.log('🌱 开始执行 Seed 脚本 (稳健 Upsert 模式)...');
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // 1. 清理旧数据 (为了保证多次运行 seed 的幂等性)
|
|
|
|
|
- // 注意:在生产环境严禁这样做,这里仅用于开发测试
|
|
|
|
|
- console.log('🧹 清理现有数据...');
|
|
|
|
|
- // 由于存在外键约束,需要按顺序删除或使用 TRUNCATE
|
|
|
|
|
- // 这里简单处理,直接尝试删除(实际开发中建议用更优雅的清理方式)
|
|
|
|
|
- // 为了演示方便,我们假设是干净的环境
|
|
|
|
|
-
|
|
|
|
|
- // 2. 创建基础角色和权限
|
|
|
|
|
- console.log('🔑 创建基础角色与权限...');
|
|
|
|
|
|
|
+ // --- 1. 创建基础角色与权限 ---
|
|
|
|
|
+ console.log('🔑 正在同步角色与权限...');
|
|
|
|
|
+
|
|
|
const [adminRole] = await db.insert(roles).values({
|
|
const [adminRole] = await db.insert(roles).values({
|
|
|
name: 'admin',
|
|
name: 'admin',
|
|
|
description: '系统管理员,拥有最高权限'
|
|
description: '系统管理员,拥有最高权限'
|
|
|
|
|
+ }).onConflictDoUpdate({
|
|
|
|
|
+ target: roles.name,
|
|
|
|
|
+ set: { description: '系统管理员,拥有最高权限' }
|
|
|
}).returning();
|
|
}).returning();
|
|
|
|
|
|
|
|
const [editorRole] = await db.insert(roles).values({
|
|
const [editorRole] = await db.insert(roles).values({
|
|
|
name: 'editor',
|
|
name: 'editor',
|
|
|
description: '内容编辑者,可以读写资源'
|
|
description: '内容编辑者,可以读写资源'
|
|
|
|
|
+ }).onConflictDoUpdate({
|
|
|
|
|
+ target: roles.name,
|
|
|
|
|
+ set: { description: '内容编辑者,可以读写资源' }
|
|
|
}).returning();
|
|
}).returning();
|
|
|
|
|
|
|
|
const [viewerRole] = await db.insert(roles).values({
|
|
const [viewerRole] = await db.insert(roles).values({
|
|
|
name: 'viewer',
|
|
name: 'viewer',
|
|
|
description: '普通查看者,仅限读取'
|
|
description: '普通查看者,仅限读取'
|
|
|
|
|
+ }).onConflictDoUpdate({
|
|
|
|
|
+ target: roles.name,
|
|
|
|
|
+ set: { description: '普通查看者,仅限读取' }
|
|
|
}).returning();
|
|
}).returning();
|
|
|
|
|
|
|
|
|
|
+ // 权限定义 (使用 upsert 确保唯一性)
|
|
|
const [readPerm] = await db.insert(permissions).values({
|
|
const [readPerm] = await db.insert(permissions).values({
|
|
|
action: 'read',
|
|
action: 'read',
|
|
|
resourceType: 'document'
|
|
resourceType: 'document'
|
|
|
- }).returning();
|
|
|
|
|
|
|
+ }).onConflictDoNothing().returning();
|
|
|
|
|
|
|
|
const [writePerm] = await db.insert(permissions).values({
|
|
const [writePerm] = await db.insert(permissions).values({
|
|
|
action: 'write',
|
|
action: 'write',
|
|
|
resourceType: 'document'
|
|
resourceType: 'document'
|
|
|
- }).returning();
|
|
|
|
|
|
|
+ }).onConflictDoNothing().returning();
|
|
|
|
|
|
|
|
- // 绑定权限到角色
|
|
|
|
|
|
|
+ // 绑定权限到角色 (使用 upsert 避免重复插入关联)
|
|
|
await db.insert(rolePermissions).values([
|
|
await db.insert(rolePermissions).values([
|
|
|
{ roleId: adminRole.id, permissionId: readPerm.id },
|
|
{ roleId: adminRole.id, permissionId: readPerm.id },
|
|
|
{ roleId: adminRole.id, permissionId: writePerm.id },
|
|
{ roleId: adminRole.id, permissionId: writePerm.id },
|
|
|
{ roleId: editorRole.id, permissionId: readPerm.id },
|
|
{ roleId: editorRole.id, permissionId: readPerm.id },
|
|
|
{ roleId: editorRole.id, permissionId: writePerm.id },
|
|
{ roleId: editorRole.id, permissionId: writePerm.id },
|
|
|
{ roleId: viewerRole.id, permissionId: readPerm.id },
|
|
{ roleId: viewerRole.id, permissionId: readPerm.id },
|
|
|
- ]);
|
|
|
|
|
|
|
+ ]).onConflictDoNothing();
|
|
|
|
|
+
|
|
|
|
|
+ // --- 2. 创建用户与组 ---
|
|
|
|
|
+ console.log('👥 正在同步测试用户与组织...');
|
|
|
|
|
|
|
|
- // 3. 创建用户和组
|
|
|
|
|
- console.log('👥 创建测试用户与组织...');
|
|
|
|
|
const [adminUser] = await db.insert(users).values({
|
|
const [adminUser] = await db.insert(users).values({
|
|
|
email: 'admin@ekb.com',
|
|
email: 'admin@ekb.com',
|
|
|
name: 'System Admin',
|
|
name: 'System Admin',
|
|
|
- passwordHash: 'hashed_password_here' // 实际应使用 bcrypt/argon2
|
|
|
|
|
|
|
+ passwordHash: 'hashed_password_here'
|
|
|
|
|
+ }).onConflictDoUpdate({
|
|
|
|
|
+ target: users.email,
|
|
|
|
|
+ set: { name: 'System Admin' }
|
|
|
}).returning();
|
|
}).returning();
|
|
|
|
|
|
|
|
const [testUser] = await db.insert(users).values({
|
|
const [testUser] = await db.insert(users).values({
|
|
|
email: 'tester@ekb.com',
|
|
email: 'tester@ekb.com',
|
|
|
name: 'Test User',
|
|
name: 'Test User',
|
|
|
passwordHash: 'hashed_password_here'
|
|
passwordHash: 'hashed_password_here'
|
|
|
|
|
+ }).onConflictDoUpdate({
|
|
|
|
|
+ target: users.email,
|
|
|
|
|
+ set: { name: 'Test User' }
|
|
|
}).returning();
|
|
}).returning();
|
|
|
|
|
|
|
|
const [engGroup] = await db.insert(groups).values({
|
|
const [engGroup] = await db.insert(groups).values({
|
|
|
name: 'Engineering Department'
|
|
name: 'Engineering Department'
|
|
|
- }).returning();
|
|
|
|
|
|
|
+ }).onConflictDoNothing().returning();
|
|
|
|
|
|
|
|
- // 将 admin 加入工程组,并赋予 editor 角色
|
|
|
|
|
- await db.insert(userGroups).values({ userId: adminUser.id, groupId: engGroup.id });
|
|
|
|
|
- await db.insert(groupRoles).values({ groupId: engGroup.id, roleId: editorRole.id });
|
|
|
|
|
-
|
|
|
|
|
- // 4. 创建资源与 ACL 测试 (核心:测试 Deny-Override)
|
|
|
|
|
- console.log('📂 创建测试资源与 ACL 规则...');
|
|
|
|
|
- const [publicFolder] = await db.insert(resourceSchema).values({
|
|
|
|
|
- name: 'Public Docs',
|
|
|
|
|
- path: '/public',
|
|
|
|
|
- type: 'folder',
|
|
|
|
|
- ownerId: adminUser.id
|
|
|
|
|
- }).returning();
|
|
|
|
|
|
|
+ // 关联用户到组和角色 (使用 upsert)
|
|
|
|
|
+ await db.insert(userGroups).values({ userId: adminUser.id, groupId: engGroup.id })
|
|
|
|
|
+ .onConflictDoNothing();
|
|
|
|
|
|
|
|
- const [secretFolder] = await db.insert(resourceSchema).values({
|
|
|
|
|
- name: 'Secret Projects',
|
|
|
|
|
- path: '/projects/secret',
|
|
|
|
|
- type: 'folder',
|
|
|
|
|
- ownerId: adminUser.id
|
|
|
|
|
- }).returning();
|
|
|
|
|
|
|
+ await db.insert(groupRoles).values({ groupId: engGroup.id, roleId: editorRole.id })
|
|
|
|
|
+ .onConflictDoNothing();
|
|
|
|
|
+
|
|
|
|
|
+ await db.insert(userRoles).values({ userId: adminUser.id, roleId: adminRole.id })
|
|
|
|
|
+ .onConflictDoNothing();
|
|
|
|
|
+
|
|
|
|
|
+ // --- 3. 创建资源与 ACL 测试 (核心:测试 Deny-Override) ---
|
|
|
|
|
+ console.log('📂 正在同步测试资源与 ACL 规则...');
|
|
|
|
|
+
|
|
|
|
|
+ // 获取或创建 Public Folder
|
|
|
|
|
+ let publicFolder = await db.query.resources.findFirst({
|
|
|
|
|
+ where: eq(resourceSchema.path, '/public')
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!publicFolder) {
|
|
|
|
|
+ [publicFolder] = await db.insert(resourceSchema).values({
|
|
|
|
|
+ name: 'Public Docs',
|
|
|
|
|
+ path: '/public',
|
|
|
|
|
+ type: 'folder',
|
|
|
|
|
+ ownerId: adminUser.id
|
|
|
|
|
+ }).returning();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取或创建 Secret Folder
|
|
|
|
|
+ let secretFolder = await db.query.resources.findFirst({
|
|
|
|
|
+ where: eq(resourceSchema.path, '/projects/secret')
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!secretFolder) {
|
|
|
|
|
+ [secretFolder] = await db.insert(resourceSchema).values({
|
|
|
|
|
+ name: 'Secret Projects',
|
|
|
|
|
+ path: '/projects/secret',
|
|
|
|
|
+ type: 'folder',
|
|
|
|
|
+ ownerId: adminUser.id
|
|
|
|
|
+ }).returning();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 为 secretFolder 设置一条针对 testUser 的显式 DENY 规则 (使用 upsert 逻辑)
|
|
|
|
|
+ // 先清理旧的该用户在该资源上的规则,确保测试环境干净
|
|
|
|
|
+ await db.delete(aclRulesSchema).where(
|
|
|
|
|
+ and(
|
|
|
|
|
+ eq(aclRulesSchema.resourceId, secretFolder.id),
|
|
|
|
|
+ eq(aclRulesSchema.subjectId, testUser.id),
|
|
|
|
|
+ eq(aclRulesSchema.subjectType, 'user')
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- // 为 secretFolder 设置一条针对 testUser 的显式 DENY 规则
|
|
|
|
|
await db.insert(aclRulesSchema).values({
|
|
await db.insert(aclRulesSchema).values({
|
|
|
resourceId: secretFolder.id,
|
|
resourceId: secretFolder.id,
|
|
|
subjectType: 'user',
|
|
subjectType: 'user',
|