import { IFieldConfig, IUser, IRole, WithId, IRoleProto, IPageQuery, IPageResult, IShrimpOperator, IModuleInfo, ILabel, ShrimpError, } from "../IShrimpType" import { Shrimp } from "./Shrimp" import { ShrimpModule } from "./ShrimpModule" import { ShrimpTableModule } from "./ShrimpTableModule" import type { ShrimpCrud } from "./ShrimpCrud" /** 用户模块扩展 - 添加登录和获取用户信息功能 */ const userFields: IFieldConfig[] = [ { key: "username", label: { t: "user.username" }, type: "string", required: true, searchable: true, editable: "addOnly" }, { key: "password", label: { t: "user.password" }, type: "password", tableHide: true }, { key: "nickname", label: { t: "user.nickname" }, type: "string", searchable: true }, { key: "roleId", label: { t: "user.roleId" }, type: "relation", relation: { moduleKey: "role", labelKey: "name" }, filterable: true, multiple: true, defaultValue: [] }, { key: "active", label: { t: "user.active" }, type: "boolean", filterable: true, defaultValue: true }, { key: "createdAt", label: { t: "user.createdAt" }, type: "date", editable: "readonly", sortable: true, filterable: true }, { key: "updatedAt", label: { t: "user.updatedAt" }, type: "date", editable: "readonly", sortable: true }, ] export class UserModule extends ShrimpTableModule> { constructor(public parent: Shrimp | ShrimpModule, dataSource: ShrimpCrud>, fields?: IFieldConfig[]) { super(parent, "user", dataSource, fields || userFields, { t: "user.menuManage" }) this.initCrud() } protected async verifyPassword(storedPassword: string, plainPassword: string): Promise { return Shrimp.passwordHasher.verify(plainPassword, storedPassword) } protected getUserRoleIds(user: WithId): string[] { return Array.isArray(user.roleId) ? user.roleId.map(String) : user.roleId ? [String(user.roleId)] : [] } async getUser(username: string, password: string): Promise> { const user = await this.dataSource.findOne({ username, active: true }) if (!user || !(await this.verifyPassword(user.password, password))) { throw new ShrimpError({ t: 'shrimp.loginFailed' }) } return user } async getModule(userId: string, roleModule: RoleModule): Promise { const user = await this.dataSource.get(userId) if (!user) { throw new ShrimpError({ t: 'shrimp.userNotFound' }) } const roleIds = this.getUserRoleIds(user) const roles = await Promise.all(roleIds.map(id => roleModule.dataSource.get(id))) const isSuperAdmin = roles.some(r => r?.isSuperAdmin) const powerKeys = isSuperAdmin ? (await this.root.allPower()).map(p => p.key) : roles.flatMap(r => r?.powers || []) return this.root.getModuleInfo(new Set(powerKeys)) } async edit(id: string, item: Partial): Promise | null> { const op = this.root.operator if (op && String(op.userId) === String(id)) { if (item.active === false) { throw new ShrimpError({ t: 'shrimp.cannotDisableSelf' }) } if (item.roleId != null) { const newRoleIds = Array.isArray(item.roleId) ? item.roleId.map(String) : [String(item.roleId)] const oldUser = await this.dataSource.get(id) const oldRoleIds = oldUser ? this.getUserRoleIds(oldUser) : [] if (JSON.stringify([...newRoleIds].sort()) !== JSON.stringify([...oldRoleIds].sort())) { throw new ShrimpError({ t: 'shrimp.cannotChangeOwnRole' }) } } } return super.edit(id, item) } async delete(id: string): Promise { const op = this.root.operator if (op && String(op.userId) === String(id)) { throw new ShrimpError({ t: 'shrimp.cannotDeleteSelf' }) } return super.delete(id) } async clear(): Promise { if (this.root.operator) { throw new ShrimpError({ t: 'shrimp.cannotClearAllUsers' }) } return super.clear() } } /** 角色模块 */ const roleFields: IFieldConfig[] = [ { key: "name", label: { t: "role.name" }, type: "string", required: true, searchable: true }, { key: "description", label: { t: "role.description" }, type: "textarea" }, { key: "isSuperAdmin", label: { t: "role.isSuperAdmin" }, type: "boolean" }, { key: "powers", label: { t: "role.powers" }, type: "powers" }, { key: "createdAt", label: { t: "role.createdAt" }, type: "date", editable: "readonly", sortable: true }, { key: "updatedAt", label: { t: "role.updatedAt" }, type: "date", editable: "readonly", sortable: true }, ] export class RoleModule extends ShrimpTableModule, IRoleProto> { constructor(public parent: Shrimp | ShrimpModule, dataSource: ShrimpCrud>, fields?: IFieldConfig[]) { super(parent, "role", dataSource, fields || roleFields, { t: "role.menuManage" }) this.initCrud() this.addEvent("clear", { t: "power.clear" }) this.addEvent("export", { t: "power.export" }) this.addEvent("import", { t: "power.import" }) this.addEvent("allPower", this.power) } async allPower(): Promise<{ key: string; label: ILabel }[]> { return this.root.allPower() } protected getOperatorRoleIds(op: IShrimpOperator): string[] { return Array.isArray(op.roleId) ? op.roleId.map(String) : op.roleId ? [String(op.roleId)] : [] } async edit(id: string, item: Partial): Promise | null> { const op = this.root.operator if (op) { const opRoleIds = this.getOperatorRoleIds(op) if (opRoleIds.includes(String(id))) { throw new ShrimpError({ t: 'shrimp.cannotEditOwnRole' }) } } return super.edit(id, item) } private async fillSuperAdminPowers(role: WithId | null): Promise | null> { if (role && role.isSuperAdmin) { const allPowers = await this.allPower() role.powers = allPowers.map(p => p.key) } return role } async list(query?: IPageQuery & any): Promise>> { const result = await super.list(query) for (const item of result.list) { if (item.isSuperAdmin) { const allPowers = await this.allPower() item.powers = allPowers.map(p => p.key) } } return result } async get(id: string): Promise | null> { const result = await super.get(id) return this.fillSuperAdminPowers(result) } async delete(id: string): Promise { const op = this.root.operator if (op) { const opRoleIds = this.getOperatorRoleIds(op) if (opRoleIds.includes(String(id))) { throw new ShrimpError({ t: 'shrimp.cannotDeleteOwnRole' }) } } return super.delete(id) } async clear(): Promise { if (this.root.operator) { throw new ShrimpError({ t: 'shrimp.cannotClearAllRoles' }) } return super.clear() } }