parent
dc4391a79f
commit
dcbe19c36e
@ -0,0 +1,108 @@ |
|||||||
|
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common'; |
||||||
|
import { AuthDeptService } from './auth-dept.service'; |
||||||
|
import { CreateAuthDeptDto } from './dto/create-auth-dept.dto'; |
||||||
|
import { UpdateAuthDeptDto } from './dto/update-auth-dept.dto'; |
||||||
|
import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger'; |
||||||
|
import { PacInfo } from '@common/decorator/pac-info/pac-info.decorator'; |
||||||
|
import { PacInfoType } from '@utils/myType'; |
||||||
|
import { GetAuthDeptDto } from '@app/auth-dept/dto/get-auth-dept.dto'; |
||||||
|
import { |
||||||
|
GetUserForAttDto |
||||||
|
} from "@dto/GetUserForAtt.dto"; |
||||||
|
import {DeptLinkUserDto} from "@dto/AttLinkUser.dto"; |
||||||
|
|
||||||
|
@ApiTags('部门服务') |
||||||
|
@Controller('authDept') |
||||||
|
export class AuthDeptController { |
||||||
|
constructor(private readonly authDeptService: AuthDeptService) {} |
||||||
|
|
||||||
|
@ApiOperation({ |
||||||
|
summary: '添加部门', |
||||||
|
description: '部门', |
||||||
|
}) |
||||||
|
@ApiProduces('application/json') |
||||||
|
@Post() |
||||||
|
create(@Body() createAuthDeptDto: CreateAuthDeptDto, @PacInfo() pacInfo: PacInfoType) { |
||||||
|
return this.authDeptService.create(createAuthDeptDto, pacInfo); |
||||||
|
} |
||||||
|
|
||||||
|
@ApiOperation({ |
||||||
|
summary: '获取部门列表', |
||||||
|
description: '查询部门分页或者列表', |
||||||
|
}) |
||||||
|
@ApiProduces('application/json') |
||||||
|
@Get() |
||||||
|
findAll(@Query() getAuthDeptDto: GetAuthDeptDto) { |
||||||
|
return this.authDeptService.findAll(getAuthDeptDto); |
||||||
|
} |
||||||
|
|
||||||
|
@ApiOperation({ |
||||||
|
summary: '获取部门详细信息', |
||||||
|
description: '查询部门详细信息,目录菜单列表,数据权限范围', |
||||||
|
}) |
||||||
|
@ApiProduces('application/json') |
||||||
|
@Get(':id') |
||||||
|
findOne(@Param('id') id: string) { |
||||||
|
return this.authDeptService.findOne(id); |
||||||
|
} |
||||||
|
|
||||||
|
@ApiOperation({ |
||||||
|
summary: '获取部门树', |
||||||
|
description: '查询部门基本信息,树结构', |
||||||
|
}) |
||||||
|
@ApiProduces('application/json') |
||||||
|
@Get('/tree/:pid') |
||||||
|
findTree(@Param('pid') pid: string) { |
||||||
|
return this.authDeptService.findTree(pid); |
||||||
|
} |
||||||
|
|
||||||
|
@ApiOperation({ |
||||||
|
summary: '更新部门信息', |
||||||
|
description: '更新部门信息', |
||||||
|
}) |
||||||
|
@ApiProduces('application/json') |
||||||
|
@Patch(':id') |
||||||
|
update(@Param('id') id: string, @Body() updateAuthDeptDto: UpdateAuthDeptDto, @PacInfo() pacInfo: PacInfoType) { |
||||||
|
return this.authDeptService.update(id, updateAuthDeptDto, pacInfo); |
||||||
|
} |
||||||
|
|
||||||
|
@ApiOperation({ |
||||||
|
summary: '删除目标部门', |
||||||
|
description: '删除目标部门信息', |
||||||
|
}) |
||||||
|
@ApiProduces('application/json') |
||||||
|
@Delete(':id') |
||||||
|
remove(@Param('id') id: string, @PacInfo() pacInfo: PacInfoType) { |
||||||
|
return this.authDeptService.remove(id, pacInfo); |
||||||
|
} |
||||||
|
|
||||||
|
@ApiOperation({ |
||||||
|
summary: '获取部门下的用户分页', |
||||||
|
description: '获取部门下的用户分页', |
||||||
|
}) |
||||||
|
@ApiProduces('application/json') |
||||||
|
@Get('/user/:pid') |
||||||
|
findUser(@Param('pid') pid: string,@Query() getUserForAttDto: GetUserForAttDto) { |
||||||
|
return this.authDeptService.findUser(pid, getUserForAttDto); |
||||||
|
} |
||||||
|
|
||||||
|
@ApiOperation({ |
||||||
|
summary: '给部门绑定账户', |
||||||
|
description: '给部门关联上账户,最大200个', |
||||||
|
}) |
||||||
|
@ApiProduces('application/json') |
||||||
|
@Post('/user') |
||||||
|
linkUser(@Body() deptLinkUserDto: DeptLinkUserDto, @PacInfo() pacInfo: PacInfoType) { |
||||||
|
return this.authDeptService.linkUser(deptLinkUserDto, pacInfo); |
||||||
|
} |
||||||
|
|
||||||
|
@ApiOperation({ |
||||||
|
summary: '给部门解绑账户', |
||||||
|
description: '给部门取消关联上账户,最大200个', |
||||||
|
}) |
||||||
|
@ApiProduces('application/json') |
||||||
|
@Delete('/user') |
||||||
|
unlinkUser(@Body() deptLinkUserDto: DeptLinkUserDto) { |
||||||
|
return this.authDeptService.unlinkUser(deptLinkUserDto); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
import { Module } from '@nestjs/common'; |
||||||
|
import { AuthDeptService } from './auth-dept.service'; |
||||||
|
import { AuthDeptController } from './auth-dept.controller'; |
||||||
|
|
||||||
|
@Module({ |
||||||
|
controllers: [AuthDeptController], |
||||||
|
providers: [AuthDeptService], |
||||||
|
}) |
||||||
|
export class AuthDeptModule {} |
@ -0,0 +1,560 @@ |
|||||||
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; |
||||||
|
import { CreateAuthDeptDto } from './dto/create-auth-dept.dto'; |
||||||
|
import { UpdateAuthDeptDto } from './dto/update-auth-dept.dto'; |
||||||
|
import { GetAuthDeptDto } from '@app/auth-dept/dto/get-auth-dept.dto'; |
||||||
|
import { PacInfoType } from '@utils/myType'; |
||||||
|
import { LoggerService } from '@service/logger/logger.service'; |
||||||
|
import { MysqlService } from '@common/service/mysql/mysql.service'; |
||||||
|
import { RedisService } from '@common/service/redis/redis.service'; |
||||||
|
import { Snowflake } from '@service/snowflake/snowflake.service'; |
||||||
|
import { ConfigService } from '@nestjs/config'; |
||||||
|
import { pacAuthDept, pacAuthLinkUserDept, pacAuthRole, pacAuthUser, pacCoreDict } from '@entities/schema'; |
||||||
|
import { and, asc, count, desc, eq, inArray, isNull, like, or, SQL, sql } from 'drizzle-orm'; |
||||||
|
import { isExistKey, isTrueEnum } from '@utils/boolean.enum'; |
||||||
|
import { likeQuery } from '@utils/likeQuery'; |
||||||
|
import { alias, QueryBuilder } from 'drizzle-orm/mysql-core'; |
||||||
|
import { customDrizzleRowWithRecursive } from '@utils/customDrizzleRowWithRecursive'; |
||||||
|
import { GetUserForAttDto } from '@dto/GetUserForAtt.dto'; |
||||||
|
import { |
||||||
|
DeptLinkUserDto |
||||||
|
} from "@dto/AttLinkUser.dto"; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class AuthDeptService { |
||||||
|
// 分页数据格式
|
||||||
|
private readonly deptPageType = { |
||||||
|
deptId: pacAuthDept.deptId, |
||||||
|
pid: pacAuthDept.pid, |
||||||
|
grade: pacAuthDept.grade, |
||||||
|
deptName: pacAuthDept.deptName, |
||||||
|
deptDesc: pacAuthDept.deptDesc, |
||||||
|
deptType: pacAuthDept.deptType, |
||||||
|
deptTypeKey: pacCoreDict.dictKey, |
||||||
|
deptTypeName: pacCoreDict.dictName, |
||||||
|
deptLeader: pacAuthDept.deptLeader, |
||||||
|
deptLeaderName: pacAuthUser.nickname, |
||||||
|
defaultRole: pacAuthDept.defaultRole, |
||||||
|
defaultRoleKey: pacAuthRole.roleKey, |
||||||
|
defaultRoleName: pacAuthRole.roleName, |
||||||
|
haveChildren: pacAuthDept.haveChildren, |
||||||
|
orderNum: pacAuthDept.orderNum, |
||||||
|
status: pacAuthDept.status, |
||||||
|
createby: pacAuthDept.createby, |
||||||
|
createtime: pacAuthDept.createtime, |
||||||
|
updateby: pacAuthDept.updateby, |
||||||
|
updatetime: pacAuthDept.updatetime, |
||||||
|
}; |
||||||
|
|
||||||
|
// 列表数据格式
|
||||||
|
private readonly deptListType = { |
||||||
|
deptId: pacAuthDept.deptId, |
||||||
|
pid: pacAuthDept.pid, |
||||||
|
grade: pacAuthDept.grade, |
||||||
|
deptName: pacAuthDept.deptName, |
||||||
|
deptType: pacAuthDept.deptType, |
||||||
|
deptTypeKey: pacCoreDict.dictKey, |
||||||
|
deptTypeName: pacCoreDict.dictName, |
||||||
|
deptLeader: pacAuthDept.deptLeader, |
||||||
|
deptLeaderName: pacAuthUser.nickname, |
||||||
|
haveChildren: pacAuthDept.haveChildren, |
||||||
|
orderNum: pacAuthDept.orderNum, |
||||||
|
}; |
||||||
|
constructor( |
||||||
|
private readonly logger: LoggerService, |
||||||
|
private readonly mysqlService: MysqlService, |
||||||
|
private readonly redisService: RedisService, |
||||||
|
private readonly snowflake: Snowflake, |
||||||
|
private readonly config: ConfigService, |
||||||
|
) {} |
||||||
|
|
||||||
|
/** Service |
||||||
|
* NAME: create |
||||||
|
* DESC: 创建部门信息 |
||||||
|
* DATE: 2024-06-25 15:53:04 - |
||||||
|
* */ |
||||||
|
public async create(createAuthDeptDto: CreateAuthDeptDto, pacInfo: PacInfoType) { |
||||||
|
// ! 加目标锁,同级,而不是全局
|
||||||
|
const lock = await this.redisService.distributedLock('DEPT' + createAuthDeptDto.pid + '-' + createAuthDeptDto.deptName, createAuthDeptDto.deptName); |
||||||
|
|
||||||
|
// ? 存在正在进行写入的部门
|
||||||
|
if (!lock) throw new HttpException('服务繁忙,部门名称重复!', HttpStatus.CONFLICT); |
||||||
|
|
||||||
|
// @ 核心逻辑
|
||||||
|
|
||||||
|
try { |
||||||
|
// ! 同级查重
|
||||||
|
const result = await this.checkRepeatForDeptName(createAuthDeptDto.deptName, createAuthDeptDto.pid); |
||||||
|
|
||||||
|
// ? 是否存在重复的部门
|
||||||
|
if (result.length > 0) throw new HttpException('部门标识重复!', HttpStatus.CONFLICT); |
||||||
|
|
||||||
|
// ! 获取上级信息
|
||||||
|
let pGrade = 0; |
||||||
|
if (createAuthDeptDto.pid != 0 as any) { |
||||||
|
const pDept = await this.getDeptForDeptId(createAuthDeptDto.pid); |
||||||
|
if (pDept.length == 0) throw new HttpException('上级部门不存在!', HttpStatus.BAD_REQUEST); |
||||||
|
pGrade = pDept[0].grade; |
||||||
|
} |
||||||
|
|
||||||
|
// ! 添加部门数据
|
||||||
|
const newPacCoreDict = await this.addDeptData(createAuthDeptDto, pGrade + 1, pacInfo.userId); |
||||||
|
|
||||||
|
// ! 解锁
|
||||||
|
lock(); |
||||||
|
|
||||||
|
// !更新父节点的子节点数量
|
||||||
|
await this.haveChildrenSelfIncreasing(createAuthDeptDto.pid); |
||||||
|
|
||||||
|
// ! 返回结果
|
||||||
|
return newPacCoreDict; |
||||||
|
} catch (e) { |
||||||
|
// ! 解锁
|
||||||
|
lock(); |
||||||
|
|
||||||
|
// ! 抛出错误
|
||||||
|
throw e; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Service |
||||||
|
* NAME: findAll |
||||||
|
* DESC: 获取部门分页/列表 |
||||||
|
* DATE: 2024-06-25 15:53:04 - |
||||||
|
* */ |
||||||
|
public async findAll(getAuthDeptDto: GetAuthDeptDto) { |
||||||
|
if (isTrueEnum(getAuthDeptDto['isList'])) { |
||||||
|
return await this.getList(getAuthDeptDto); |
||||||
|
} else { |
||||||
|
return await this.getPage(getAuthDeptDto); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Service |
||||||
|
* NAME: findOne |
||||||
|
* DESC: 获取部门详细信息 |
||||||
|
* DATE: 2024-06-28 11:30:43 - |
||||||
|
* */ |
||||||
|
public findOne(id: string) { |
||||||
|
return this.getMore(id); |
||||||
|
} |
||||||
|
|
||||||
|
/** Service |
||||||
|
* NAME: findTree |
||||||
|
* DESC: 获取部门树 |
||||||
|
* DATE: 2024-06-28 11:30:47 - |
||||||
|
* */ |
||||||
|
public async findTree(pid: string) { |
||||||
|
const [result] = await this.getRoleTree(pid ? pid : '0'); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** Service |
||||||
|
* NAME: update |
||||||
|
* DESC: 更新部门信息 |
||||||
|
* DATE: 2024-06-28 11:30:50 - |
||||||
|
* */ |
||||||
|
public async update(id: string, updateAuthDeptDto: UpdateAuthDeptDto, pacInfo: PacInfoType) { |
||||||
|
return this.updateDept(id, updateAuthDeptDto, pacInfo.userId); |
||||||
|
} |
||||||
|
|
||||||
|
/** Service |
||||||
|
* NAME: remove |
||||||
|
* DESC: 删除部门信息 |
||||||
|
* DATE: 2024-06-28 11:30:54 - |
||||||
|
* */ |
||||||
|
public async remove(id: string, pacInfo: PacInfoType) { |
||||||
|
return await this.deleteDept(id, pacInfo.userId); |
||||||
|
} |
||||||
|
|
||||||
|
/** Service |
||||||
|
* NAME: findUser |
||||||
|
* DESC: 查找部门下的用户分页 |
||||||
|
* DATE: 2024-06-28 17:17:16 - |
||||||
|
* */ |
||||||
|
public async findUser(id: string, getUserForAttDto: GetUserForAttDto) { |
||||||
|
return this.getDeptUser(id, getUserForAttDto); |
||||||
|
} |
||||||
|
|
||||||
|
/** Service |
||||||
|
* NAME: linkUser |
||||||
|
* DESC: 关联账户给部门 |
||||||
|
* DATE: 2024-06-28 17:17:16 - |
||||||
|
* */ |
||||||
|
public async linkUser(deptLinkUserDto: DeptLinkUserDto, pacInfo: PacInfoType) { |
||||||
|
return this.linkUserForDept(deptLinkUserDto, pacInfo); |
||||||
|
} |
||||||
|
|
||||||
|
/** Service |
||||||
|
* NAME: unlinkUser |
||||||
|
* DESC: 取消关联账户给部门 |
||||||
|
* DATE: 2024-06-27 15:38:54 - |
||||||
|
* */ |
||||||
|
public async unlinkUser(deptLinkUserDto: DeptLinkUserDto) { |
||||||
|
return this.unlinkUserForDept(deptLinkUserDto); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 同级查重
|
||||||
|
private checkRepeatForDeptName(deptName: string, pid: string) { |
||||||
|
return this.mysqlService.db |
||||||
|
.select() |
||||||
|
.from(pacAuthDept) |
||||||
|
.where(and(isNull(pacAuthDept.deleteby), eq(pacAuthDept.pid, pid), eq(pacAuthDept.deptName, deptName))); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 根据id查部门信息
|
||||||
|
private getDeptForDeptId(deptId: string) { |
||||||
|
return this.mysqlService.db |
||||||
|
.select() |
||||||
|
.from(pacAuthDept) |
||||||
|
.where(and(isNull(pacAuthDept.deleteby), eq(pacAuthDept.deptId, deptId))); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 更新父级子元素数量
|
||||||
|
private async haveChildrenSelfIncreasing(id: string, isAdd = true) { |
||||||
|
return this.mysqlService.db |
||||||
|
.update(pacAuthDept) |
||||||
|
.set({ |
||||||
|
haveChildren: isAdd ? sql`${pacAuthDept.haveChildren} + 1` : sql`${pacAuthDept.haveChildren} - 1`, |
||||||
|
}) |
||||||
|
.where(eq(pacAuthDept.deptId as any, id)); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 添加部门数据
|
||||||
|
private async addDeptData(createAuthDeptDto: CreateAuthDeptDto, grade, userId) { |
||||||
|
// ! 生成雪花id,用于部门主键
|
||||||
|
const id = await this.snowflake.generate(); |
||||||
|
|
||||||
|
// ! 定义写入的部门数据
|
||||||
|
const newDeptData: typeof pacAuthDept.$inferInsert = { |
||||||
|
pid: createAuthDeptDto.pid, |
||||||
|
deptId: id as any, |
||||||
|
grade: grade, |
||||||
|
deptName: createAuthDeptDto.deptName, |
||||||
|
deptDesc: createAuthDeptDto.deptDesc, |
||||||
|
deptType: createAuthDeptDto.deptType, |
||||||
|
deptLeader: createAuthDeptDto.deptLeader, |
||||||
|
defaultRole: createAuthDeptDto.defaultRole, |
||||||
|
orderNum: createAuthDeptDto.orderNum, |
||||||
|
createby: userId, |
||||||
|
createtime: sql`now()` as any, |
||||||
|
}; |
||||||
|
return await this.mysqlService.db.insert(pacAuthDept).values(newDeptData); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 差距查询构建器
|
||||||
|
private queryBuilder(getAuthDeptDto: GetAuthDeptDto, selectData = undefined) { |
||||||
|
// ! 定义基础查询函数
|
||||||
|
// 启用动态查询模式 $dynamic
|
||||||
|
const query = this.mysqlService.db |
||||||
|
.select(selectData) |
||||||
|
.from(pacAuthDept) |
||||||
|
.orderBy( |
||||||
|
isTrueEnum(getAuthDeptDto.isAsc) ? asc(pacAuthDept.orderNum) : desc(pacAuthDept.orderNum), |
||||||
|
isTrueEnum(getAuthDeptDto.isAsc) ? asc(pacAuthDept.deptId) : desc(pacAuthDept.deptId), |
||||||
|
) |
||||||
|
.leftJoin(pacCoreDict, eq(pacAuthDept.deptType, pacCoreDict.dictId)) |
||||||
|
.leftJoin(pacAuthUser, eq(pacAuthDept.deptLeader, pacAuthUser.userId)) |
||||||
|
.$dynamic(); |
||||||
|
|
||||||
|
// 查询条件集合
|
||||||
|
const wl = []; |
||||||
|
|
||||||
|
// ? 未删除
|
||||||
|
wl.push(isNull(pacAuthDept.deleteby)); |
||||||
|
|
||||||
|
// ? 模糊查询
|
||||||
|
wl.push( |
||||||
|
or(like(pacAuthDept.deptName, likeQuery(getAuthDeptDto.deptInfo)), like(pacAuthDept.deptDesc, likeQuery(getAuthDeptDto.deptInfo))).if( |
||||||
|
isExistKey(getAuthDeptDto, 'deptInfo'), |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
// ? 按照层级查
|
||||||
|
wl.push(eq(pacAuthDept.pid, getAuthDeptDto.hierarchy).if(isExistKey(getAuthDeptDto, 'hierarchy'))); |
||||||
|
|
||||||
|
// ? 是否查部门类型
|
||||||
|
wl.push(eq(pacAuthDept.deptType, getAuthDeptDto.deptType).if(isExistKey(getAuthDeptDto, 'deptType'))); |
||||||
|
|
||||||
|
// ? 是否查字典状态
|
||||||
|
wl.push(eq(pacAuthDept.status, getAuthDeptDto.status as any).if(isExistKey(getAuthDeptDto, 'status'))); |
||||||
|
|
||||||
|
query.where(and(...wl)); |
||||||
|
return query; |
||||||
|
} |
||||||
|
|
||||||
|
// DB 查分页
|
||||||
|
private async getPage(getAuthDeptDto: GetAuthDeptDto) { |
||||||
|
const offset = (getAuthDeptDto.pageNumber - 1) * getAuthDeptDto.pageSize; |
||||||
|
|
||||||
|
// ! 使用基础查询构建查询总记录数
|
||||||
|
const totalCountQuery = this.queryBuilder(getAuthDeptDto, { |
||||||
|
totalCount: sql`COUNT(*)`, |
||||||
|
}); |
||||||
|
|
||||||
|
// ! 使用基础查询构建分页查询
|
||||||
|
// 重命名表
|
||||||
|
const userTable1 = alias(pacAuthUser, 'userTable1'); |
||||||
|
const userTable2 = alias(pacAuthUser, 'userTable2'); |
||||||
|
const paginatedQuery = this.queryBuilder(getAuthDeptDto, { |
||||||
|
...this.deptPageType, |
||||||
|
updateName: userTable1.nickname, |
||||||
|
createName: userTable2.nickname, |
||||||
|
}) |
||||||
|
.leftJoin(pacAuthRole, eq(pacAuthDept.defaultRole, pacAuthRole.roleId)) |
||||||
|
.leftJoin(userTable2, eq(pacAuthDept.createby, userTable2.userId)) |
||||||
|
.leftJoin(userTable1, eq(pacAuthDept.updateby, userTable1.userId)) |
||||||
|
.limit(getAuthDeptDto.pageSize) |
||||||
|
.offset(offset); |
||||||
|
|
||||||
|
return { |
||||||
|
total: (await totalCountQuery)[0].totalCount, |
||||||
|
rowData: await paginatedQuery, |
||||||
|
searchData: getAuthDeptDto, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// DB 查列表
|
||||||
|
private async getList(getAuthDeptDto: GetAuthDeptDto) { |
||||||
|
return this.queryBuilder(getAuthDeptDto, this.deptListType); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 查树
|
||||||
|
private getRoleTree(pid: string = '0') { |
||||||
|
console.log(pid); |
||||||
|
|
||||||
|
// ! 基础层级
|
||||||
|
const baseQueryBuilder = new QueryBuilder(); |
||||||
|
const baseQuery = baseQueryBuilder |
||||||
|
.select({ |
||||||
|
...this.deptListType, |
||||||
|
level: sql`0`.as('level'), |
||||||
|
}) |
||||||
|
.from(pacAuthDept) |
||||||
|
.leftJoin(pacCoreDict, eq(pacAuthDept.deptType, pacCoreDict.dictId)) |
||||||
|
.leftJoin(pacAuthUser, eq(pacAuthDept.deptLeader, pacAuthUser.userId)) |
||||||
|
.where(and(isNull(pacAuthDept.deleteby), eq(pacAuthDept.pid, pid))); |
||||||
|
|
||||||
|
// ! 递归层级
|
||||||
|
const recursiveQueryBuilder = new QueryBuilder(); |
||||||
|
const recursiveQuery = recursiveQueryBuilder |
||||||
|
.select({ |
||||||
|
...this.deptListType, |
||||||
|
level: sql`deptHierarchy.level + 1`.as('level'), |
||||||
|
}) |
||||||
|
.from(pacAuthDept) |
||||||
|
.leftJoin(pacCoreDict, eq(pacAuthDept.deptType, pacCoreDict.dictId)) |
||||||
|
.leftJoin(pacAuthUser, eq(pacAuthDept.deptLeader, pacAuthUser.userId)) |
||||||
|
.where(isNull(pacAuthDept.deleteby)) |
||||||
|
.innerJoin(sql`deptHierarchy`, sql`deptHierarchy.deptId = ${pacAuthDept.pid}`); |
||||||
|
|
||||||
|
const rowName: SQL = customDrizzleRowWithRecursive(this.deptListType); |
||||||
|
|
||||||
|
// ! 执行原始SQL查询
|
||||||
|
return this.mysqlService.db.execute( |
||||||
|
sql`WITH RECURSIVE deptHierarchy(${rowName}) AS(${baseQuery} UNION ALL ${recursiveQuery}) SELECT * FROM deptHierarchy`, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 查详情
|
||||||
|
private async getMore(id: string) { |
||||||
|
const userTable1 = alias(pacAuthUser, 'userTable1'); |
||||||
|
const userTable2 = alias(pacAuthUser, 'userTable2'); |
||||||
|
|
||||||
|
const result = await this.mysqlService.db |
||||||
|
.select({ ...this.deptPageType, updateName: userTable1.nickname, createName: userTable2.nickname }) |
||||||
|
.from(pacAuthDept) |
||||||
|
.leftJoin(pacCoreDict, eq(pacAuthDept.deptType, pacCoreDict.dictId)) |
||||||
|
.leftJoin(pacAuthUser, eq(pacAuthDept.deptLeader, pacAuthUser.userId)) |
||||||
|
.leftJoin(pacAuthRole, eq(pacAuthDept.defaultRole, pacAuthRole.roleId)) |
||||||
|
.leftJoin(userTable2, eq(pacAuthDept.createby, userTable2.userId)) |
||||||
|
.leftJoin(userTable1, eq(pacAuthDept.updateby, userTable1.userId)) |
||||||
|
.where(and(isNull(pacAuthDept.deleteby), eq(pacAuthDept.deptId, id))); |
||||||
|
|
||||||
|
if (result.length === 0) { |
||||||
|
throw new HttpException('未找到目标部门信息!', HttpStatus.BAD_REQUEST); |
||||||
|
} |
||||||
|
const [{ userCount }] = await this.mysqlService.db |
||||||
|
.select({ userCount: count(pacAuthUser.userId) }) |
||||||
|
.from(pacAuthUser) |
||||||
|
.leftJoin(pacAuthLinkUserDept, eq(pacAuthLinkUserDept.userId, pacAuthUser.userId)) |
||||||
|
.where(and(isNull(pacAuthUser.deleteby), eq(pacAuthLinkUserDept.deptId, id))); |
||||||
|
|
||||||
|
return { |
||||||
|
...result[0], |
||||||
|
userCount, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// DB 删除
|
||||||
|
private async deleteDept(id: string, userId) { |
||||||
|
// ! 查找部门信息
|
||||||
|
const deptData = await this.mysqlService.db |
||||||
|
.select() |
||||||
|
.from(pacAuthDept) |
||||||
|
.where(and(isNull(pacAuthDept.deleteby), eq(pacAuthDept.deptId, id))); |
||||||
|
|
||||||
|
if (deptData.length === 0) throw new HttpException('未找到目标部门信息,无法删除!', HttpStatus.BAD_REQUEST); |
||||||
|
|
||||||
|
// ? 判断是否存在子项
|
||||||
|
if (deptData[0].haveChildren != 0) throw new HttpException('该部门存在子项!', HttpStatus.BAD_REQUEST); |
||||||
|
|
||||||
|
// ! 判断父节点是否存在
|
||||||
|
if (deptData[0].pid != 0) { |
||||||
|
// ! 减少父级子节点数量
|
||||||
|
await this.haveChildrenSelfIncreasing(deptData[0].pid, false); |
||||||
|
} |
||||||
|
|
||||||
|
// ! 删除部门数据
|
||||||
|
return await this.mysqlService.db |
||||||
|
.update(pacAuthDept) |
||||||
|
.set({ |
||||||
|
deletetime: sql`now()`, |
||||||
|
deleteby: userId, |
||||||
|
}) |
||||||
|
.where(eq(pacAuthDept.deptId, id)); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 修改
|
||||||
|
private updateDept(id, updateAuthDeptDto: UpdateAuthDeptDto, userId) { |
||||||
|
return this.mysqlService.db |
||||||
|
.update(pacAuthDept) |
||||||
|
.set({ |
||||||
|
deptName: updateAuthDeptDto.deptName, |
||||||
|
deptDesc: updateAuthDeptDto.deptDesc, |
||||||
|
deptType: updateAuthDeptDto.deptType, |
||||||
|
deptLeader: updateAuthDeptDto.deptLeader, |
||||||
|
defaultRole: updateAuthDeptDto.defaultRole, |
||||||
|
orderNum: updateAuthDeptDto.orderNum, |
||||||
|
status: updateAuthDeptDto.status, |
||||||
|
updateby: userId, |
||||||
|
updatetime: sql`now()`, |
||||||
|
}) |
||||||
|
.where(eq(pacAuthDept.deptId, id)); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 查找部门下的账户查询构建
|
||||||
|
private getDeptUserQuery(roleId: string, getUserForRoleDto: GetUserForAttDto, selectData = null) { |
||||||
|
const query = this.mysqlService.db |
||||||
|
.select(selectData) |
||||||
|
.from(pacAuthUser) |
||||||
|
.orderBy(isTrueEnum(getUserForRoleDto.isAsc) ? asc(pacAuthUser.userId) : desc(pacAuthUser.userId)) |
||||||
|
.leftJoin(pacCoreDict, eq(pacAuthUser.userType, pacCoreDict.dictId)) |
||||||
|
.leftJoin(pacAuthLinkUserDept, eq(pacAuthLinkUserDept.userId, pacAuthUser.userId)) |
||||||
|
.$dynamic(); |
||||||
|
const wl = []; |
||||||
|
|
||||||
|
// ? 未删除
|
||||||
|
wl.push(isNull(pacAuthUser.deleteby)); |
||||||
|
|
||||||
|
// ? 模糊查询
|
||||||
|
wl.push( |
||||||
|
or( |
||||||
|
like(pacAuthUser.nickname, likeQuery(getUserForRoleDto.userInfo)), |
||||||
|
like(pacAuthUser.userDesc, likeQuery(getUserForRoleDto.userInfo)), |
||||||
|
like(pacAuthUser.userEmail, likeQuery(getUserForRoleDto.userInfo)), |
||||||
|
like(pacAuthUser.userPhone, likeQuery(getUserForRoleDto.userInfo)), |
||||||
|
like(pacAuthUser.username, likeQuery(getUserForRoleDto.userInfo)), |
||||||
|
).if(isExistKey(getUserForRoleDto, 'userInfo')), |
||||||
|
); |
||||||
|
|
||||||
|
// ? 目标roleId
|
||||||
|
wl.push(eq(pacAuthLinkUserDept.deptId, roleId)); |
||||||
|
|
||||||
|
// ? 用户类型
|
||||||
|
wl.push(eq(pacAuthUser.userType, getUserForRoleDto.userType).if(isExistKey(getUserForRoleDto, 'userType'))); |
||||||
|
query.where(and(...wl)); |
||||||
|
return query; |
||||||
|
} |
||||||
|
|
||||||
|
// DB 查找部门下的账户
|
||||||
|
private async getDeptUser(deptId: string, getUserForAttDto: GetUserForAttDto) { |
||||||
|
console.log(getUserForAttDto); |
||||||
|
const offset = (getUserForAttDto.pageNumber - 1) * getUserForAttDto.pageSize; |
||||||
|
|
||||||
|
// ! 使用基础查询构建查询总记录数
|
||||||
|
const totalCountQuery = this.getDeptUserQuery(deptId, getUserForAttDto, { |
||||||
|
totalCount: sql`COUNT(*)`, |
||||||
|
}); |
||||||
|
|
||||||
|
// ! 使用基础查询构建分页查询
|
||||||
|
// 重命名表
|
||||||
|
const userTable1 = alias(pacAuthUser, 'userTable1'); |
||||||
|
const userTable2 = alias(pacAuthUser, 'userTable2'); |
||||||
|
const paginatedQuery = this.getDeptUserQuery(deptId, getUserForAttDto, { |
||||||
|
userId: pacAuthUser.userId, |
||||||
|
username: pacAuthUser.username, |
||||||
|
nickname: pacAuthUser.nickname, |
||||||
|
userType: pacAuthUser.userType, |
||||||
|
userTypeName: pacCoreDict.dictName, |
||||||
|
userTypeKey: pacCoreDict.dictKey, |
||||||
|
userEmail: pacAuthUser.userEmail, |
||||||
|
avatar: pacAuthUser.avatar, |
||||||
|
userPhone: pacAuthUser.userPhone, |
||||||
|
userDesc: pacAuthUser.userDesc, |
||||||
|
haveChildren: pacAuthUser.haveChildren, |
||||||
|
createby: pacAuthUser.createby, |
||||||
|
createName: userTable2.nickname, |
||||||
|
createtime: pacAuthUser.createtime, |
||||||
|
updateName: userTable1.nickname, |
||||||
|
}) |
||||||
|
.leftJoin(userTable2, eq(pacAuthUser.createby, userTable2.userId)) |
||||||
|
.leftJoin(userTable1, eq(pacAuthUser.updateby, userTable1.userId)) |
||||||
|
.limit(getUserForAttDto.pageSize) |
||||||
|
.offset(offset); |
||||||
|
|
||||||
|
return { |
||||||
|
total: (await totalCountQuery)[0].totalCount, |
||||||
|
rowData: await paginatedQuery, |
||||||
|
searchData: getUserForAttDto, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// DB 查找部门账户关联数据
|
||||||
|
private async getDeptLinkUser(deptId: string, userIdList: string[]) { |
||||||
|
return this.mysqlService.db |
||||||
|
.select({ |
||||||
|
id: pacAuthLinkUserDept.userId, |
||||||
|
index: pacAuthLinkUserDept.index, |
||||||
|
}) |
||||||
|
.from(pacAuthLinkUserDept) |
||||||
|
.where(and(eq(pacAuthLinkUserDept.deptId, deptId), inArray(pacAuthLinkUserDept.userId, userIdList))); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 给部门关联上账户
|
||||||
|
private async linkUserForDept(deptLinkUserDto: DeptLinkUserDto, pacInfo: PacInfoType) { |
||||||
|
// ! 去重账户ID
|
||||||
|
const deduplicateUserId = Array.from(new Set(deptLinkUserDto.userIdList)); |
||||||
|
|
||||||
|
// ! 查找部门是否存在这些账户
|
||||||
|
const existUser = await this.getDeptLinkUser(deptLinkUserDto.deptId, deduplicateUserId); |
||||||
|
|
||||||
|
// ! 获取已存在的账户Id
|
||||||
|
const existUserIdList = existUser.map((user) => user.id); |
||||||
|
|
||||||
|
// ! 过滤已经存在的账户
|
||||||
|
const userIdList = deptLinkUserDto.userIdList.filter((i) => !existUserIdList.includes(i)); |
||||||
|
|
||||||
|
return await this.mysqlService.db.insert(pacAuthLinkUserDept).values( |
||||||
|
userIdList.map((i) => ({ |
||||||
|
deptId: deptLinkUserDto.deptId, |
||||||
|
userId: i, |
||||||
|
createby: pacInfo.userId, |
||||||
|
createtime: sql`now()` as any, |
||||||
|
})), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// DB 取消部门关联的账户
|
||||||
|
private async unlinkUserForDept(deptLinkUserDto: DeptLinkUserDto) { |
||||||
|
// ! 去重账户ID
|
||||||
|
const deduplicateUserId = Array.from(new Set(deptLinkUserDto.userIdList)); |
||||||
|
|
||||||
|
// ! 查找部门是否存在这些账户
|
||||||
|
const existUser = await this.getDeptLinkUser(deptLinkUserDto.deptId, deduplicateUserId); |
||||||
|
|
||||||
|
// ! 获取已存在的账户Id
|
||||||
|
const existUserIndexList = existUser.map((user) => user.index); |
||||||
|
|
||||||
|
return await this.mysqlService.db.delete(pacAuthLinkUserDept).where(inArray(pacAuthLinkUserDept.index, existUserIndexList)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,107 @@ |
|||||||
|
import { ApiProperty } from '@nestjs/swagger'; |
||||||
|
import Trim from '@common/decorator/trim/trim.decorator'; |
||||||
|
import { IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator'; |
||||||
|
|
||||||
|
export class CreateAuthDeptDto { |
||||||
|
@ApiProperty({ |
||||||
|
description: '部门父ID', |
||||||
|
type: String, |
||||||
|
example: '0', |
||||||
|
required: false, |
||||||
|
minLength: 1, |
||||||
|
maxLength: 32, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '部门关联属性应为字符串格式!' }) |
||||||
|
@IsOptional() |
||||||
|
readonly pid?: string = '0'; |
||||||
|
|
||||||
|
@ApiProperty({ |
||||||
|
description: '部门名称', |
||||||
|
type: String, |
||||||
|
example: '研发部', |
||||||
|
required: true, |
||||||
|
minLength: 1, |
||||||
|
maxLength: 32, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '部门名称应为字符串格式!' }) |
||||||
|
@Length(1, 32, { message: '部门名称长度控制在1到32位之间!' }) |
||||||
|
deptName: string; |
||||||
|
|
||||||
|
@ApiProperty({ |
||||||
|
description: '部门描述', |
||||||
|
type: String, |
||||||
|
example: '0', |
||||||
|
required: false, |
||||||
|
minLength: 1, |
||||||
|
maxLength: 255, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '部门描述应为字符串格式!' }) |
||||||
|
@Length(1, 255, { message: '请将部门描述长度控制在1到255位之间!' }) |
||||||
|
@IsOptional() |
||||||
|
deptDesc: string; |
||||||
|
|
||||||
|
@ApiProperty({ |
||||||
|
description: '部门类型,来自于字典', |
||||||
|
type: String, |
||||||
|
example: '0', |
||||||
|
required: false, |
||||||
|
minLength: 19, |
||||||
|
maxLength: 19, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '部门类型格式不正确!' }) |
||||||
|
@Length(19, 19, { message: '部门类型格式不正确!' }) |
||||||
|
@IsOptional() |
||||||
|
deptType: string; |
||||||
|
|
||||||
|
@ApiProperty({ |
||||||
|
description: '部门负责人,来自于用户', |
||||||
|
type: String, |
||||||
|
example: '0', |
||||||
|
required: false, |
||||||
|
minLength: 19, |
||||||
|
maxLength: 19, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '部门负责人格式不正确!' }) |
||||||
|
@Length(1, 19, { message: '部门负责人格式不正确!' }) |
||||||
|
@IsOptional() |
||||||
|
deptLeader: string; |
||||||
|
|
||||||
|
@ApiProperty({ |
||||||
|
description: '部门默认角色,来自于角色', |
||||||
|
type: String, |
||||||
|
example: '0', |
||||||
|
required: false, |
||||||
|
minLength: 19, |
||||||
|
maxLength: 19, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '部门默认角色格式不正确!' }) |
||||||
|
@Length(19, 19, { message: '部门默认角色格式不正确!' }) |
||||||
|
@IsOptional() |
||||||
|
defaultRole: string; |
||||||
|
|
||||||
|
@ApiProperty({ |
||||||
|
description: '排序', |
||||||
|
type: Number, |
||||||
|
example: 10, |
||||||
|
required: false, |
||||||
|
minimum: -1000, |
||||||
|
maximum: 1000, |
||||||
|
}) |
||||||
|
@IsOptional() |
||||||
|
@IsInt({ |
||||||
|
message: '排序必须是整数!', |
||||||
|
}) |
||||||
|
@Min(-1000, { |
||||||
|
message: '排序不能小于-1000!', |
||||||
|
}) |
||||||
|
@Max(1000, { |
||||||
|
message: '排序不能超过1000', |
||||||
|
}) |
||||||
|
orderNum: number; |
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
// | ------------------------------------------------------------
|
||||||
|
// | @版本: version 0.1
|
||||||
|
// | @创建人: 【Nie-x7129】
|
||||||
|
// | @E-mail: x71291@outlook.com
|
||||||
|
// | @所在项目: pac-auth
|
||||||
|
// | @文件描述: get-auth-dept.dto.ts -
|
||||||
|
// | @创建时间: 2024-06-28 11:20
|
||||||
|
// | @更新时间: 2024-06-28 11:20
|
||||||
|
// | @修改记录:
|
||||||
|
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||||
|
// | =
|
||||||
|
// | ------------------------------------------------------------
|
||||||
|
|
||||||
|
import { GetDto } from '@dto/get.dto'; |
||||||
|
import { ApiProperty } from '@nestjs/swagger'; |
||||||
|
import Trim from '@common/decorator/trim/trim.decorator'; |
||||||
|
import { IsEnum, IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator'; |
||||||
|
import { BooleanEnum } from '@utils/boolean.enum'; |
||||||
|
import Int from '@common/decorator/int/int.descrator'; |
||||||
|
|
||||||
|
export class GetAuthDeptDto extends GetDto { |
||||||
|
@ApiProperty({ |
||||||
|
description: '角色', |
||||||
|
type: String, |
||||||
|
example: '管理员', |
||||||
|
required: false, |
||||||
|
minLength: 1, |
||||||
|
maxLength: 128, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '角色信息应为字符串格式!' }) |
||||||
|
@Length(0, 128, { message: '请将角色信息长度控制在1到128位之间!' }) |
||||||
|
@IsOptional() |
||||||
|
readonly deptInfo?: string; |
||||||
|
|
||||||
|
@ApiProperty({ |
||||||
|
description: '角色类型,来自于字典', |
||||||
|
type: String, |
||||||
|
example: '0', |
||||||
|
required: false, |
||||||
|
minLength: 19, |
||||||
|
maxLength: 19, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '角色类型格式不正确!' }) |
||||||
|
@Length(19, 19, { message: '角色类型格式不正确!' }) |
||||||
|
@IsOptional() |
||||||
|
readonly deptType: string; |
||||||
|
|
||||||
|
@ApiProperty({ |
||||||
|
description: '角色状态', |
||||||
|
type: Number, |
||||||
|
example: 0, |
||||||
|
required: false, |
||||||
|
minimum: -100, |
||||||
|
maximum: 100, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@Int() |
||||||
|
@IsInt({ |
||||||
|
message: '角色状态必须是整数!', |
||||||
|
}) |
||||||
|
@Min(-100, { |
||||||
|
message: '角色状态需要大于-100!', |
||||||
|
}) |
||||||
|
@Max(100, { |
||||||
|
message: '角色状态不能超过100', |
||||||
|
}) |
||||||
|
@IsOptional() |
||||||
|
readonly status?: string; |
||||||
|
|
||||||
|
@ApiProperty({ |
||||||
|
description: '角色层级id', |
||||||
|
type: Number, |
||||||
|
example: 0, |
||||||
|
required: false, |
||||||
|
minimum: 0, |
||||||
|
maximum: 100, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '角色层级id应为字符串格式!' }) |
||||||
|
@Length(1, 20, { message: '角色层级id格式错误!' }) |
||||||
|
@IsOptional() |
||||||
|
readonly hierarchy?: string; |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
import { ApiProperty, PartialType } from '@nestjs/swagger'; |
||||||
|
import { CreateAuthDeptDto } from './create-auth-dept.dto'; |
||||||
|
import { IsInt, IsOptional, Max, Min } from 'class-validator'; |
||||||
|
|
||||||
|
export class UpdateAuthDeptDto extends PartialType(CreateAuthDeptDto) { |
||||||
|
@ApiProperty({ |
||||||
|
description: '状态', |
||||||
|
type: Number, |
||||||
|
example: 10, |
||||||
|
required: false, |
||||||
|
minimum: -100, |
||||||
|
maximum: 100, |
||||||
|
}) |
||||||
|
@IsOptional() |
||||||
|
@IsInt({ |
||||||
|
message: '状态必须是整数!', |
||||||
|
}) |
||||||
|
@Min(-1000, { |
||||||
|
message: '状态不能小于-100!', |
||||||
|
}) |
||||||
|
@Max(1000, { |
||||||
|
message: '状态不能超过100', |
||||||
|
}) |
||||||
|
readonly status: number; |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
// | ------------------------------------------------------------
|
||||||
|
// | @版本: version 0.1
|
||||||
|
// | @创建人: 【Nie-x7129】
|
||||||
|
// | @E-mail: x71291@outlook.com
|
||||||
|
// | @所在项目: pac-auth
|
||||||
|
// | @文件描述: GetUserForAtt.dto.ts -
|
||||||
|
// | @创建时间: 2024-06-28 16:55
|
||||||
|
// | @更新时间: 2024-06-28 16:55
|
||||||
|
// | @修改记录:
|
||||||
|
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||||
|
// | =
|
||||||
|
// | ------------------------------------------------------------
|
||||||
|
|
||||||
|
import { GetDto } from '@dto/get.dto'; |
||||||
|
import { ApiProperty } from '@nestjs/swagger'; |
||||||
|
import Trim from '@common/decorator/trim/trim.decorator'; |
||||||
|
import { IsOptional, IsString, Length } from 'class-validator'; |
||||||
|
|
||||||
|
export class GetUserForAttDto extends GetDto { |
||||||
|
@ApiProperty({ |
||||||
|
description: '用户信息', |
||||||
|
type: String, |
||||||
|
example: '哈哈', |
||||||
|
required: false, |
||||||
|
minLength: 1, |
||||||
|
maxLength: 128, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '用户信息应为字符串格式!' }) |
||||||
|
@Length(0, 128, { message: '请将用户信息长度控制在1到128位之间!' }) |
||||||
|
@IsOptional() |
||||||
|
readonly userInfo?: string; |
||||||
|
|
||||||
|
@ApiProperty({ |
||||||
|
description: '用户类型,来自于字典', |
||||||
|
type: String, |
||||||
|
example: '0', |
||||||
|
required: false, |
||||||
|
minLength: 19, |
||||||
|
maxLength: 19, |
||||||
|
}) |
||||||
|
@Trim() |
||||||
|
@IsString({ message: '用户类型格式不正确!' }) |
||||||
|
@Length(19, 19, { message: '用户类型格式不正确!' }) |
||||||
|
@IsOptional() |
||||||
|
readonly userType: string; |
||||||
|
} |
@ -1,3 +1,2 @@ |
|||||||
import { relations } from "drizzle-orm/relations"; |
import { relations } from 'drizzle-orm/relations'; |
||||||
import { } from "./schema"; |
import {} from './schema'; |
||||||
|
|
||||||
|
Loading…
Reference in new issue