diff --git a/src/modules/user/controllers/account.controller.ts b/src/modules/user/controllers/account.controller.ts new file mode 100644 index 0000000..6e8752d --- /dev/null +++ b/src/modules/user/controllers/account.controller.ts @@ -0,0 +1,105 @@ +import { + Body, + Controller, + Get, + Patch, + Post, + Request, + SerializeOptions, + UseGuards, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; + +import { pick } from 'lodash'; + +import { Depends } from '@/modules/restful/decorators/depend.decorator'; + +import { Guest } from '../decorators/guest.decorator'; +import { RequestUser } from '../decorators/user.request.decorator'; +import { UpdateAccountDto, UpdatePasswordDto } from '../dtos/account.dto'; +import { CredentialDto, RegisterDto } from '../dtos/auth.dto'; +import { UserEntity } from '../entities/UserEntity'; +import { LocalAuthGuard } from '../guards/local.auth.guard'; +import { AuthService } from '../services/auth.service'; +import { UserService } from '../services/user.service'; +import { UserModule } from '../user.module'; + +@ApiTags('账户操作') +@Depends(UserModule) +@Controller('account') +export class AccountController { + constructor( + protected authService: AuthService, + protected userService: UserService, + ) {} + + /** + * 使用用户名密码注册用户 + * @param data + */ + @Post('register') + @Guest() + async register(@Body() data: RegisterDto) { + return this.authService.register(data); + } + + /** + * 用户登录[凭证(可以是用户名,邮箱,手机号等)+密码登录] + * @param user + * @param _data + */ + @Post('login') + @Guest() + @UseGuards(LocalAuthGuard) + async login(@RequestUser() user: ClassToPlain, @Body() _data: CredentialDto) { + return { token: await this.authService.createToken(user.id) }; + } + + /** + * 注销登录 + * @param req + */ + @Post('logout') + @ApiBearerAuth() + async logout(@Request() req: any) { + return this.authService.logout(req); + } + + /** + * 获取账户信息[只有用户自己才能查询] + * @param user + */ + @Get('profile') + @ApiBearerAuth() + @SerializeOptions({ groups: ['user-detail'] }) + async profile(@RequestUser() user: ClassToPlain) { + return this.userService.detail(user.id); + } + + /** + * 更改账户信息 + * @param user + * @param data + */ + @Patch() + @ApiBearerAuth() + @SerializeOptions({ groups: ['user-detail'] }) + async update(@RequestUser() user: ClassToPlain, @Body() data: UpdateAccountDto) { + return this.userService.update({ id: user.id, ...pick(data, ['username', 'nickname']) }); + } + + /** + * 修改密码[必须知道原密码] + * @param user + * @param data + */ + @Patch('change-password') + @ApiBearerAuth() + @SerializeOptions({ groups: ['user-detail'] }) + async changePassword( + @RequestUser() user: ClassToPlain, + @Body() data: UpdatePasswordDto, + ) { + return this.authService.changePassword(user, data); + } +} diff --git a/src/modules/user/controllers/index.ts b/src/modules/user/controllers/index.ts new file mode 100644 index 0000000..ca75ce2 --- /dev/null +++ b/src/modules/user/controllers/index.ts @@ -0,0 +1,2 @@ +export * from './account.controller'; +export * from './user.controller'; diff --git a/src/modules/user/controllers/user.controller.ts b/src/modules/user/controllers/user.controller.ts new file mode 100644 index 0000000..76ceaf0 --- /dev/null +++ b/src/modules/user/controllers/user.controller.ts @@ -0,0 +1,95 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseUUIDPipe, + Patch, + Post, + SerializeOptions, +} from '@nestjs/common'; + +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; + +import { DeleteWithTrashDto, RestoreDto } from '@/modules/content/dtos/delete.with.trash.dto'; +import { Depends } from '@/modules/restful/decorators/depend.decorator'; +import { UserModule } from '@/modules/user/user.module'; + +import { Guest } from '../decorators/guest.decorator'; +import { CreateUserDto, UpdateUserDto } from '../dtos/user.dto'; +import { UserService } from '../services/user.service'; + +@ApiTags('用户管理') +@Depends(UserModule) +@Controller('users') +export class UserController { + constructor(protected service: UserService) {} + + /** + * 用户列表 + */ + @Get() + @Guest() + @SerializeOptions({ groups: ['user-list'] }) + async list() { + return this.service.list(); + } + + /** + * 获取用户信息 + * @param id + */ + @Get(':id') + @Guest() + @SerializeOptions({ groups: ['user-detail'] }) + async detail(@Param('id', new ParseUUIDPipe()) id: string) { + return this.service.detail(id); + } + + /** + * 新增用户 + * @param data + */ + @Post() + @ApiBearerAuth() + @SerializeOptions({ groups: ['user-detail'] }) + async store(@Body() data: CreateUserDto) { + return this.service.create(data); + } + + /** + * 更新用户 + * @param data + */ + @Patch() + @ApiBearerAuth() + @SerializeOptions({ groups: ['user-detail'] }) + async update(@Body() data: UpdateUserDto) { + return this.service.update(data); + } + + /** + * 批量删除用户 + * @param data + */ + @Delete() + @ApiBearerAuth() + @SerializeOptions({ groups: ['user-list'] }) + async delete(@Body() data: DeleteWithTrashDto) { + const { ids, trash } = data; + return this.service.delete(ids, trash); + } + + /** + * 批量恢复用户 + * @param data + */ + @Patch('restore') + @ApiBearerAuth() + @SerializeOptions({ groups: ['user-list'] }) + async restore(@Body() data: RestoreDto) { + const { ids } = data; + return this.service.restore(ids); + } +} diff --git a/src/modules/user/interceptors/user.interceptor.ts b/src/modules/user/interceptors/user.interceptor.ts new file mode 100644 index 0000000..4db30d5 --- /dev/null +++ b/src/modules/user/interceptors/user.interceptor.ts @@ -0,0 +1,17 @@ +import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common'; +import { isNil } from 'lodash'; +import { Observable } from 'rxjs'; + +export class UserInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request: any = context.switchToHttp().getRequest(); + if (!isNil(request.user?.id)) { + if (isNil(request.body)) { + request.body = { userId: request.user.id }; + } else { + request.body.userId = request.user.id; + } + } + return next.handle(); + } +} diff --git a/src/modules/user/routes.ts b/src/modules/user/routes.ts new file mode 100644 index 0000000..df0ebae --- /dev/null +++ b/src/modules/user/routes.ts @@ -0,0 +1,24 @@ +import { RouteOption, TagOption } from '../restful/types'; + +import * as controllers from './controllers'; + +export function createUserApi() { + const routes: Record<'app', RouteOption[]> = { + app: [ + { + name: 'app.user', + path: 'user', + controllers: Object.values(controllers), + }, + ], + }; + + const tags: Record<'app', (string | TagOption)[]> = { + app: [ + { name: '用户管理', description: '对用户进行CRUD操作' }, + { name: '账户操作', description: '注册登录、查看修改账户信息、修改密码等' }, + ], + }; + + return { routes, tags }; +} diff --git a/src/modules/user/user.module.ts b/src/modules/user/user.module.ts new file mode 100644 index 0000000..309e84a --- /dev/null +++ b/src/modules/user/user.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class UserModule {}