import type { ResultSetHeader, RowDataPacket } from 'mysql2/promise'; import type { MySqlPool } from '../db/mysql.js'; export const roleCodes = [ 'CUSTOMER', 'CLEANER', 'STAFF', 'STORE_ADMIN', 'TENANT_ADMIN', 'PLATFORM_ADMIN' ] as const; export interface AccessProfile { roles: string[]; capabilities: string[]; storeIds: string[]; } interface CodeRow extends RowDataPacket { code: string } interface StoreRow extends RowDataPacket { storeId: string } export class RbacRepository { constructor(private readonly pool: MySqlPool) {} async ensureCustomerRole(tenantId: string, userId: string): Promise { await this.pool.execute( `INSERT IGNORE INTO qipai_roles (tenant_id, code, name) VALUES (?, 'CUSTOMER', '顾客'), (?, 'CLEANER', '保洁员'), (?, 'STAFF', '门店员工'), (?, 'STORE_ADMIN', '门店管理员'), (?, 'TENANT_ADMIN', '租户管理员'), (?, 'PLATFORM_ADMIN', '平台管理员')`, Array(6).fill(tenantId) ); await this.pool.execute( `INSERT IGNORE INTO qipai_user_roles (tenant_id, user_id, role_id) SELECT ?, ?, id FROM qipai_roles WHERE tenant_id = ? AND code = 'CUSTOMER' AND status = 'ACTIVE'`, [tenantId, userId, tenantId] ); await this.pool.execute( `INSERT IGNORE INTO qipai_role_permissions (tenant_id, role_id, permission_id) SELECT ?, r.id, p.id FROM qipai_roles r INNER JOIN qipai_permissions p ON (r.code = 'CUSTOMER' AND p.code IN ('profile.read', 'order.self.read')) OR (r.code = 'STORE_ADMIN' AND p.code IN ('user.read', 'staff.manage', 'session.reset', 'store.operation.read', 'store.operation.write', 'device.read', 'device.write')) OR (r.code IN ('TENANT_ADMIN', 'PLATFORM_ADMIN') AND p.code IN ('user.read', 'staff.manage', 'session.reset', 'tenant.manage', 'device.read', 'device.write')) WHERE r.tenant_id = ?`, [tenantId, tenantId] ); } async getAccessProfile(tenantId: string, userId: string): Promise { const [roles] = await this.pool.execute( `SELECT DISTINCT r.code FROM qipai_user_roles ur INNER JOIN qipai_roles r ON r.id = ur.role_id AND r.tenant_id = ur.tenant_id AND r.status = 'ACTIVE' AND r.deleted_at IS NULL WHERE ur.tenant_id = ? AND ur.user_id = ? ORDER BY r.code`, [tenantId, userId] ); const [permissions] = await this.pool.execute( `SELECT DISTINCT p.code FROM qipai_user_roles ur INNER JOIN qipai_roles r ON r.id = ur.role_id AND r.tenant_id = ur.tenant_id AND r.status = 'ACTIVE' AND r.deleted_at IS NULL INNER JOIN qipai_role_permissions rp ON rp.tenant_id = ur.tenant_id AND rp.role_id = ur.role_id INNER JOIN qipai_permissions p ON p.id = rp.permission_id WHERE ur.tenant_id = ? AND ur.user_id = ? ORDER BY p.code`, [tenantId, userId] ); const [stores] = await this.pool.execute( `SELECT DISTINCT store_id AS storeId FROM qipai_user_store_scopes WHERE tenant_id = ? AND user_id = ? ORDER BY store_id`, [tenantId, userId] ); return { roles: roles.map((row) => row.code), capabilities: permissions.map((row) => row.code), storeIds: stores.map((row) => String(row.storeId)) }; } async grantStore(input: { tenantId: string; userId: string; storeId: string; scopeType: 'STAFF' | 'CLEANER'; }): Promise { const [result] = await this.pool.execute( `INSERT IGNORE INTO qipai_user_store_scopes (tenant_id, user_id, store_id, scope_type) SELECT ?, ?, s.id, ? FROM qipai_stores s INNER JOIN qipai_users u ON u.id = ? AND u.tenant_id = ? WHERE s.id = ? AND s.tenant_id = ? AND s.deleted_at IS NULL`, [ input.tenantId, input.userId, input.scopeType, input.userId, input.tenantId, input.storeId, input.tenantId ] ); return result.affectedRows === 1; } }