wui há 7 meses atrás
pai
commit
4ad97cc6dc

+ 94 - 3
src/controllers/user.controller.ts

@@ -1,6 +1,13 @@
 import { FastifyRequest, FastifyReply, FastifyInstance } from 'fastify'
 import { UserService } from '../services/user.service'
-import { ListUserQuery, LoginBody, RegisterBody, ResetPasswordBody } from '../dto/user.dto'
+import {
+  ListUserQuery,
+  LoginBody,
+  RegisterBody,
+  ResetPasswordBody,
+  CreateUserBody,
+  UpdateUserBody
+} from '../dto/user.dto'
 
 export class UserController {
   private userService: UserService
@@ -92,8 +99,92 @@ export class UserController {
 
   async list(request: FastifyRequest<{ Querystring: ListUserQuery }>, reply: FastifyReply) {
     try {
-      const users = await this.userService.list(request.query.page, request.query.size, request.query.role)
-      return reply.send(users)
+      const { user, query } = request
+
+      if (!user) {
+        return reply.code(403).send({
+          error: 'Forbidden',
+          message: 'Unauthorized access'
+        })
+      }
+      const parentId = user.id
+      const { page, size } = query
+
+      const result = await this.userService.findAllChildUsers(parentId, page, size)
+
+      return reply.send(result)
+    } catch (error) {
+      return reply.code(500).send(error)
+    }
+  }
+
+  async createUser(request: FastifyRequest<{ Body: CreateUserBody }>, reply: FastifyReply) {
+    try {
+      const { password, name, role } = request.body
+
+      const existingUser = await this.userService.findByName(name)
+      if (existingUser) {
+        return reply.code(400).send({ message: '用户名已存在' })
+      }
+
+      const user = await this.userService.create(password, name, role, request.user.id)
+
+      return reply.code(201).send({
+        user: {
+          id: user.id,
+          name: user.name,
+          role: user.role,
+          createdAt: user.createdAt,
+          updatedAt: user.updatedAt
+        }
+      })
+    } catch (error) {
+      return reply.code(500).send(error)
+    }
+  }
+
+  async updateUser(request: FastifyRequest<{ Body: UpdateUserBody }>, reply: FastifyReply) {
+    try {
+      const { id, name, password, role } = request.body
+
+      try {
+        await this.userService.findById(id)
+      } catch (error) {
+        return reply.code(404).send({ message: '用户不存在' })
+      }
+
+      if (name) {
+        const existingUser = await this.userService.findByName(name)
+        if (existingUser && existingUser.id !== id) {
+          return reply.code(400).send({ message: '用户名已存在' })
+        }
+      }
+
+      const updatedUser = await this.userService.updateUser(id, { name, password, role })
+
+      return reply.send({
+        user: {
+          id: updatedUser.id,
+          name: updatedUser.name,
+          role: updatedUser.role,
+          createdAt: updatedUser.createdAt,
+          updatedAt: updatedUser.updatedAt
+        }
+      })
+    } catch (error) {
+      return reply.code(500).send(error)
+    }
+  }
+
+  async getChildApiUsers(request: FastifyRequest<{ Querystring: { parentId?: number } }>, reply: FastifyReply) {
+    try {
+      const parentId = request.query.parentId || request.user.id
+
+      const childUsers = await this.userService.findChildChannelUsers(parentId)
+
+      return reply.send({
+        users: childUsers
+      })
     } catch (error) {
       return reply.code(500).send(error)
     }

+ 14 - 0
src/dto/user.dto.ts

@@ -1,6 +1,7 @@
 import { FastifyRequest } from 'fastify'
 import { UserRole } from '../entities/user.entity'
 import { Pagination } from './common.dto'
+
 export interface RegisterBody {
   name: string
   password: string
@@ -18,3 +19,16 @@ export interface ResetPasswordBody {
 export interface ListUserQuery extends Pagination {
   role?: UserRole | UserRole[]
 }
+
+export interface CreateUserBody {
+  name: string
+  password: string
+  role?: UserRole
+}
+
+export interface UpdateUserBody {
+  id: number
+  name?: string
+  password?: string
+  role?: UserRole
+}

+ 4 - 3
src/entities/user.entity.ts

@@ -1,5 +1,4 @@
 import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'
-import { Exclude } from 'class-transformer'
 
 export enum UserRole {
   ADMIN = 'admin',
@@ -12,10 +11,9 @@ export class User {
   id: number
 
   @Column()
-  @Exclude()
   password: string
 
-  @Column()
+  @Column({ unique: true, length: 100 })
   name: string
 
   @CreateDateColumn()
@@ -30,4 +28,7 @@ export class User {
     default: UserRole.USER
   })
   role: UserRole
+
+  @Column({ nullable: true, default: 0 })
+  parentId: number
 }

+ 39 - 0
src/middlewares/auth.middleware.ts

@@ -1,4 +1,5 @@
 import { FastifyRequest, FastifyReply } from 'fastify'
+import { UserRole } from '../entities/user.entity'
 
 export async function authenticate(request: FastifyRequest, reply: FastifyReply) {
   try {
@@ -10,3 +11,41 @@ export async function authenticate(request: FastifyRequest, reply: FastifyReply)
     })
   }
 }
+
+export function hasRole(role: UserRole): (request: FastifyRequest, reply: FastifyReply) => Promise<void> {
+  return async (request: FastifyRequest, reply: FastifyReply) => {
+    try {
+      await request.jwtVerify()
+
+      if (request.user.role !== role) {
+        return reply.code(403).send({
+          message: 'Access denied. Insufficient Permissions.'
+        })
+      }
+    } catch (err) {
+      reply.code(401).send({
+        message: 'Unauthorized',
+        error: err
+      })
+    }
+  }
+}
+
+export function hasAnyRole(...roles: UserRole[]): (request: FastifyRequest, reply: FastifyReply) => Promise<void> {
+  return async (request: FastifyRequest, reply: FastifyReply) => {
+    try {
+      await request.jwtVerify()
+
+      if (!roles.includes(request.user.role)) {
+        return reply.code(403).send({
+          message: 'Access denied. Insufficient Permissions.'
+        })
+      }
+    } catch (err) {
+      reply.code(401).send({
+        message: 'Unauthorized',
+        error: err
+      })
+    }
+  }
+}

+ 25 - 4
src/routes/user.routes.ts

@@ -1,8 +1,8 @@
 import { FastifyInstance } from 'fastify'
 import { UserController } from '../controllers/user.controller'
-import { authenticate } from '../middlewares/auth.middleware'
-import { ResetPasswordBody, ListUserQuery } from '../dto/user.dto'
-
+import { authenticate, hasRole } from '../middlewares/auth.middleware'
+import { ResetPasswordBody, ListUserQuery, CreateUserBody, UpdateUserBody } from '../dto/user.dto'
+import { UserRole } from '../entities/user.entity'
 export default async function userRoutes(fastify: FastifyInstance) {
   const userController = new UserController(fastify)
 
@@ -16,7 +16,28 @@ export default async function userRoutes(fastify: FastifyInstance) {
   )
   fastify.get<{ Querystring: ListUserQuery }>(
     '/',
-    { onRequest: [authenticate] },
+    { onRequest: [hasRole(UserRole.ADMIN)] },
     userController.list.bind(userController)
   )
+
+  // 新增用户接口
+  fastify.post<{ Body: CreateUserBody }>(
+    '/create',
+    { onRequest: [hasRole(UserRole.ADMIN)] },
+    userController.createUser.bind(userController)
+  )
+
+  // 修改用户接口
+  fastify.post<{ Body: UpdateUserBody }>(
+    '/update',
+    { onRequest: [hasRole(UserRole.ADMIN)] },
+    userController.updateUser.bind(userController)
+  )
+
+  // 获取api子用户接口
+  fastify.get<{ Querystring: { parentId?: number } }>(
+    '/children',
+    { onRequest: [hasRole(UserRole.ADMIN)] },
+    userController.getChildApiUsers.bind(userController)
+  )
 }

+ 80 - 2
src/services/user.service.ts

@@ -11,12 +11,13 @@ export class UserService {
     this.userRepository = app.dataSource.getRepository(User)
   }
 
-  async create(password: string, name: string): Promise<User> {
+  async create(password: string, name: string, role: UserRole = UserRole.USER, parentId?: number): Promise<User> {
     const hashedPassword = await bcrypt.hash(password, 10)
     const user = this.userRepository.create({
       name,
       password: hashedPassword,
-      role: UserRole.USER
+      role,
+      parentId
     })
     return this.userRepository.save(user)
   }
@@ -62,4 +63,81 @@ export class UserService {
       }
     }
   }
+
+  async updateUser(id: number, data: Partial<User>): Promise<User> {
+    if (data.password) {
+      data.password = await bcrypt.hash(data.password, 10)
+    }
+
+    await this.userRepository.update(id, data)
+    return this.findById(id)
+  }
+
+  async findChildChannelUsers(parentId: number): Promise<Partial<User>[]> {
+    const result: Partial<User>[] = []
+
+    const findChildren = async (pid: number) => {
+      const users = await this.userRepository.find({
+        select: ['id', 'name', 'role'],
+        where: { parentId: pid }
+      })
+
+      result.push(...users)
+
+      for (const user of users) {
+        await findChildren(user.id)
+      }
+    }
+
+    await findChildren(parentId)
+    return result
+  }
+
+  async findAllChildUsers(
+    parentId: number,
+    page: number = 0,
+    size: number = 20
+  ): Promise<PaginationResponse<Partial<User>>> {
+    const result: Partial<User>[] = []
+
+    const currentUser = await this.userRepository.findOne({
+      select: ['id', 'name', 'role', 'parentId', 'createdAt', 'updatedAt'],
+      where: { id: parentId }
+    })
+
+    if (currentUser) {
+      result.push(currentUser)
+    }
+
+    const findChildren = async (pid: number) => {
+      const users = await this.userRepository.find({
+        select: ['id', 'name', 'role', 'parentId', 'createdAt', 'updatedAt'],
+        where: { parentId: pid }
+      })
+
+      for (const user of users) {
+        result.push(user)
+        await findChildren(user.id)
+      }
+    }
+
+    await findChildren(parentId)
+
+    result.sort((a, b) => (a.id || 0) - (b.id || 0))
+
+    const total = result.length
+    const paginatedUsers = result.slice(
+      (Number(page) || 0) * (Number(size) || 20),
+      ((Number(page) || 0) + 1) * (Number(size) || 20)
+    )
+
+    return {
+      content: paginatedUsers,
+      metadata: {
+        total,
+        page: Number(page) || 0,
+        size: Number(size) || 20
+      }
+    }
+  }
 }