feat(rbac): add user-ding-mapping CRUD endpoints
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { IsString, MinLength, IsOptional, IsArray, IsBoolean } from 'class-validator';
|
import { IsString, MinLength, IsOptional, IsArray, IsBoolean, IsInt, IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
export class CreateRoleDto {
|
export class CreateRoleDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@@ -79,3 +79,21 @@ export class UpdateProfileDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
qualifications?: string;
|
qualifications?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CreateUserDingMappingDto {
|
||||||
|
@IsInt()
|
||||||
|
@IsNotEmpty()
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
dingUserId: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
dingName?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
dingMobile?: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
UpdateUserDto,
|
UpdateUserDto,
|
||||||
ResetPasswordDto,
|
ResetPasswordDto,
|
||||||
UpdateProfileDto,
|
UpdateProfileDto,
|
||||||
|
CreateUserDingMappingDto,
|
||||||
} from './dto/rbac.dto';
|
} from './dto/rbac.dto';
|
||||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||||
import { RequirePermission } from '../auth/decorators/permission.decorator';
|
import { RequirePermission } from '../auth/decorators/permission.decorator';
|
||||||
@@ -298,6 +299,32 @@ export class RbacController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- 钉钉用户绑定 ----
|
||||||
|
|
||||||
|
@Get('user-ding-mappings')
|
||||||
|
@RequirePermission('user:view')
|
||||||
|
async getUserDingMappings() {
|
||||||
|
return this.rbacService.getUserDingMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('user-ding-mappings/unbound-users')
|
||||||
|
@RequirePermission('user:view')
|
||||||
|
async getUnboundUsers() {
|
||||||
|
return this.rbacService.getUnboundUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('user-ding-mappings')
|
||||||
|
@RequirePermission('user:edit')
|
||||||
|
async createUserDingMapping(@Body() dto: CreateUserDingMappingDto) {
|
||||||
|
return this.rbacService.createUserDingMapping(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('user-ding-mappings/:id')
|
||||||
|
@RequirePermission('user:delete')
|
||||||
|
async deleteUserDingMapping(@Param('id') id: string) {
|
||||||
|
return this.rbacService.deleteUserDingMapping(+id);
|
||||||
|
}
|
||||||
// ---- 教师工作台 ----
|
// ---- 教师工作台 ----
|
||||||
|
|
||||||
@Get('teacher-workspace')
|
@Get('teacher-workspace')
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Module, OnModuleInit, forwardRef } from '@nestjs/common';
|
import { Module, OnModuleInit, forwardRef } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { Permission, Role, User, Class, ClassStudent, ClassTeacher, ClassSchedule, Student } from '../entities';
|
import { Permission, Role, User, Class, ClassStudent, ClassTeacher, ClassSchedule, Student, UserDingMapping } from '../entities';
|
||||||
import { RbacService } from './rbac.service';
|
import { RbacService } from './rbac.service';
|
||||||
import { RbacController } from './rbac.controller';
|
import { RbacController } from './rbac.controller';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Permission, Role, User, Class, ClassStudent, ClassTeacher, ClassSchedule, Student]), forwardRef(() => AuthModule)],
|
imports: [TypeOrmModule.forFeature([Permission, Role, User, Class, ClassStudent, ClassTeacher, ClassSchedule, Student, UserDingMapping]), forwardRef(() => AuthModule)],
|
||||||
controllers: [RbacController],
|
controllers: [RbacController],
|
||||||
providers: [RbacService],
|
providers: [RbacService],
|
||||||
exports: [RbacService],
|
exports: [RbacService],
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
import { Injectable, Logger, NotFoundException, ConflictException } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository, In } from 'typeorm';
|
import { Repository, In } from 'typeorm';
|
||||||
import * as bcrypt from 'bcryptjs';
|
import * as bcrypt from 'bcryptjs';
|
||||||
import { Permission, Role, User, Class, ClassStudent, ClassTeacher, ClassSchedule, Student } from '../entities';
|
import { Permission, Role, User, Class, ClassStudent, ClassTeacher, ClassSchedule, Student, UserDingMapping } from '../entities';
|
||||||
|
|
||||||
const PRESET_PERMISSIONS: Array<{ code: string; name: string; group: string }> = [
|
const PRESET_PERMISSIONS: Array<{ code: string; name: string; group: string }> = [
|
||||||
{ code: 'dashboard:view', name: '查看数据面板', group: 'dashboard' },
|
{ code: 'dashboard:view', name: '查看数据面板', group: 'dashboard' },
|
||||||
@@ -171,6 +171,7 @@ export class RbacService {
|
|||||||
@InjectRepository(ClassTeacher) private classTeacherRepo: Repository<ClassTeacher>,
|
@InjectRepository(ClassTeacher) private classTeacherRepo: Repository<ClassTeacher>,
|
||||||
@InjectRepository(ClassSchedule) private classScheduleRepo: Repository<ClassSchedule>,
|
@InjectRepository(ClassSchedule) private classScheduleRepo: Repository<ClassSchedule>,
|
||||||
@InjectRepository(Student) private studentRepo: Repository<Student>,
|
@InjectRepository(Student) private studentRepo: Repository<Student>,
|
||||||
|
@InjectRepository(UserDingMapping) private mappingRepo: Repository<UserDingMapping>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async seedData(): Promise<void> {
|
async seedData(): Promise<void> {
|
||||||
@@ -246,6 +247,38 @@ export class RbacService {
|
|||||||
this.logger.log(`种子数据初始化完成: ${allPerms.length} 权限点, ${allRoles.length} 角色`);
|
this.logger.log(`种子数据初始化完成: ${allPerms.length} 权限点, ${allRoles.length} 角色`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 钉钉用户绑定 ----
|
||||||
|
|
||||||
|
async getUserDingMappings(): Promise<UserDingMapping[]> {
|
||||||
|
return this.mappingRepo.find({ relations: ['user'] });
|
||||||
|
}
|
||||||
|
|
||||||
|
async createUserDingMapping(dto: { userId: number; dingUserId: string; dingName?: string; dingMobile?: string }) {
|
||||||
|
const existing = await this.mappingRepo.findOne({ where: { dingUserId: dto.dingUserId } });
|
||||||
|
if (existing) throw new ConflictException(`钉钉用户 ${dto.dingUserId} 已绑定到本地用户 #${existing.userId}`);
|
||||||
|
const userExisting = await this.mappingRepo.findOne({ where: { userId: dto.userId } });
|
||||||
|
if (userExisting) throw new ConflictException(`本地用户 #${dto.userId} 已绑定到钉钉用户 ${userExisting.dingUserId}`);
|
||||||
|
const mapping = this.mappingRepo.create(dto);
|
||||||
|
return this.mappingRepo.save(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUserDingMapping(id: number) {
|
||||||
|
const mapping = await this.mappingRepo.findOne({ where: { id } });
|
||||||
|
if (!mapping) throw new NotFoundException('绑定记录不存在');
|
||||||
|
await this.mappingRepo.remove(mapping);
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUnboundUsers(): Promise<{ id: number; username: string; name: string }[]> {
|
||||||
|
return this.userRepo
|
||||||
|
.createQueryBuilder('u')
|
||||||
|
.leftJoin(UserDingMapping, 'm', 'm.userId = u.id')
|
||||||
|
.where('m.id IS NULL')
|
||||||
|
.andWhere('u.isArchived = false')
|
||||||
|
.select(['u.id', 'u.username', 'u.name'])
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
async findAllRoles(): Promise<Role[]> {
|
async findAllRoles(): Promise<Role[]> {
|
||||||
return this.roleRepo.find({
|
return this.roleRepo.find({
|
||||||
relations: ['permissions'],
|
relations: ['permissions'],
|
||||||
|
|||||||
Reference in New Issue
Block a user