v0.5.0: 重新组织目录结构,按职责拆分
- Shrimp.ts (875 行) 按类拆分到 Shrimp/(7 文件: Shrimp, ShrimpModule, ShrimpTableModule, ShrimpPower, ShrimpCrud, UserRoleModule, OperationLogModule) - forMongo/: MongoShrimpCrud + MongoShrimp(原 ShrimpServer 的 mongo 装配部分,无 express) - forExpress/: buildShrimpRouter + jwtAuth + types - main/: index(0 依赖核心) + expmon(express+mongo 整合,导出 ShrimpServer) + types(纯协议) - 入口子路径 server → expmon(express+mongo 整合方案命名更准确) - 删除冗余的 index.ts / core.ts(合并为 main/index.ts)
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
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<WithId<IUser>> {
|
||||
constructor(public parent: Shrimp | ShrimpModule, dataSource: ShrimpCrud<WithId<IUser>>, fields?: IFieldConfig[]) {
|
||||
super(parent, "user", dataSource, fields || userFields, { t: "user.menuManage" })
|
||||
this.initCrud()
|
||||
}
|
||||
|
||||
protected async verifyPassword(storedPassword: string, plainPassword: string): Promise<boolean> {
|
||||
return Shrimp.passwordHasher.verify(plainPassword, storedPassword)
|
||||
}
|
||||
|
||||
protected getUserRoleIds(user: WithId<IUser>): string[] {
|
||||
return Array.isArray(user.roleId) ? user.roleId.map(String) : user.roleId ? [String(user.roleId)] : []
|
||||
}
|
||||
|
||||
async getUser(username: string, password: string): Promise<WithId<IUser>> {
|
||||
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<IModuleInfo[]> {
|
||||
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<IUser>): Promise<WithId<IUser> | 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<boolean> {
|
||||
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<boolean> {
|
||||
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: "string" },
|
||||
{ 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<WithId<IRole>, IRoleProto> {
|
||||
constructor(public parent: Shrimp | ShrimpModule, dataSource: ShrimpCrud<WithId<IRole>>, 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<IRole>): Promise<WithId<IRole> | 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<IRole> | null): Promise<WithId<IRole> | 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<IPageResult<WithId<IRole>>> {
|
||||
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<WithId<IRole> | null> {
|
||||
const result = await super.get(id)
|
||||
return this.fillSuperAdminPowers(result)
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<boolean> {
|
||||
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<boolean> {
|
||||
if (this.root.operator) {
|
||||
throw new ShrimpError({ t: 'shrimp.cannotClearAllRoles' })
|
||||
}
|
||||
return super.clear()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user