diff --git a/tone-page-server/src/admin/admin.module.ts b/tone-page-server/src/admin/admin.module.ts index 524388b..776f7bc 100644 --- a/tone-page-server/src/admin/admin.module.ts +++ b/tone-page-server/src/admin/admin.module.ts @@ -16,9 +16,7 @@ import { BlogModule } from 'src/blog/blog.module'; @Module({ imports: [ - TypeOrmModule.forFeature([ - User, - ]), + TypeOrmModule.forFeature([User]), UserModule, RoleModule, ResourceModule, @@ -35,4 +33,4 @@ import { BlogModule } from 'src/blog/blog.module'; AdminWebBlogController, ], }) -export class AdminModule { } +export class AdminModule {} diff --git a/tone-page-server/src/admin/controller/admin-permission.controller.ts b/tone-page-server/src/admin/controller/admin-permission.controller.ts index 0521d72..eef3234 100644 --- a/tone-page-server/src/admin/controller/admin-permission.controller.ts +++ b/tone-page-server/src/admin/controller/admin-permission.controller.ts @@ -1,31 +1,31 @@ -import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Post } from "@nestjs/common"; -import { PermissionService } from "src/role/services/permission.service"; -import { CreatePermissionDto } from "../dto/admin-permission/create-permission.dto"; +import { + Body, + Controller, + Delete, + Get, + Param, + ParseUUIDPipe, + Post, +} from '@nestjs/common'; +import { PermissionService } from 'src/role/services/permission.service'; +import { CreatePermissionDto } from '../dto/admin-permission/create-permission.dto'; @Controller('admin/permission') export class AdminPermissionController { + constructor(private readonly permissionService: PermissionService) {} - constructor( - private readonly permissionService: PermissionService, - ) { } + @Get() + async list() { + return this.permissionService.list(); + } - @Get() - async list() { - return this.permissionService.list(); - } + @Post() + async create(@Body() dto: CreatePermissionDto) { + return this.permissionService.create(dto); + } - @Post() - async create( - @Body() dto: CreatePermissionDto - ) { - return this.permissionService.create(dto); - } - - @Delete(':id') - async delete( - @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, - ) { - return this.permissionService.delete(id); - } - -} \ No newline at end of file + @Delete(':id') + async delete(@Param('id', new ParseUUIDPipe({ version: '4' })) id: string) { + return this.permissionService.delete(id); + } +} diff --git a/tone-page-server/src/admin/controller/admin-role-permission.controller.ts b/tone-page-server/src/admin/controller/admin-role-permission.controller.ts index cc62760..3dc224c 100644 --- a/tone-page-server/src/admin/controller/admin-role-permission.controller.ts +++ b/tone-page-server/src/admin/controller/admin-role-permission.controller.ts @@ -1,37 +1,51 @@ -import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Post } from "@nestjs/common"; -import { PermissionService } from "src/role/services/permission.service"; -import { RolePermissionService } from "src/role/services/role-permission.service"; -import { SetRolePermissionsDto } from "../dto/admin-role-permission/set-role-permissions.dto"; +import { + Body, + Controller, + Delete, + Get, + Param, + ParseUUIDPipe, + Post, +} from '@nestjs/common'; +import { PermissionService } from 'src/role/services/permission.service'; +import { RolePermissionService } from 'src/role/services/role-permission.service'; +import { SetRolePermissionsDto } from '../dto/admin-role-permission/set-role-permissions.dto'; @Controller('admin/roles/:roleId/permission') export class AdminRolePermissionController { + constructor( + private readonly rolePermissionService: RolePermissionService, + private readonly permissionService: PermissionService, + ) {} - constructor( - private readonly rolePermissionService: RolePermissionService, - private readonly permissionService: PermissionService, - ) { } + @Get() + async getRolePermissions( + @Param('roleId', new ParseUUIDPipe({ version: '4' })) roleId: string, + ) { + const permissionIds = + await this.rolePermissionService.findPermissionIdsByRoleIds([roleId]); + return await this.permissionService.findPermissionByIds(permissionIds); + } - @Get() - async getRolePermissions( - @Param('roleId', new ParseUUIDPipe({ version: '4' })) roleId: string, - ) { - const permissionIds = await this.rolePermissionService.findPermissionIdsByRoleIds([roleId]); - return await this.permissionService.findPermissionByIds(permissionIds); - } + @Post() + async setRolePermissions( + @Param('roleId', new ParseUUIDPipe({ version: '4' })) roleId: string, + @Body() dto: SetRolePermissionsDto, + ) { + return await this.rolePermissionService.addRolePermissions( + roleId, + dto.permissionIds, + ); + } - @Post() - async setRolePermissions( - @Param('roleId', new ParseUUIDPipe({ version: '4' })) roleId: string, - @Body() dto: SetRolePermissionsDto, - ) { - return await this.rolePermissionService.addRolePermissions(roleId, dto.permissionIds); - } - - @Delete() - async DeleteRolePermissionsDto( - @Param('roleId', new ParseUUIDPipe({ version: '4' })) roleId: string, - @Body() dto: SetRolePermissionsDto, - ) { - return await this.rolePermissionService.deleteRolePermissions(roleId, dto.permissionIds); - } -} \ No newline at end of file + @Delete() + async DeleteRolePermissionsDto( + @Param('roleId', new ParseUUIDPipe({ version: '4' })) roleId: string, + @Body() dto: SetRolePermissionsDto, + ) { + return await this.rolePermissionService.deleteRolePermissions( + roleId, + dto.permissionIds, + ); + } +} diff --git a/tone-page-server/src/admin/controller/admin-role.controller.ts b/tone-page-server/src/admin/controller/admin-role.controller.ts index 4bd0c7d..627975e 100644 --- a/tone-page-server/src/admin/controller/admin-role.controller.ts +++ b/tone-page-server/src/admin/controller/admin-role.controller.ts @@ -1,30 +1,31 @@ -import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Post } from "@nestjs/common"; -import { RoleService } from "src/role/services/role.service"; -import { CreateRoleDto } from "../dto/admin-role/create-role.dto"; +import { + Body, + Controller, + Delete, + Get, + Param, + ParseUUIDPipe, + Post, +} from '@nestjs/common'; +import { RoleService } from 'src/role/services/role.service'; +import { CreateRoleDto } from '../dto/admin-role/create-role.dto'; @Controller('admin/role') export class AdminRoleController { + constructor(private readonly roleService: RoleService) {} - constructor( - private readonly roleService: RoleService, - ) { } + @Get() + async list() { + return this.roleService.list(); + } - @Get() - async list() { - return this.roleService.list(); - } + @Post() + async create(@Body() dto: CreateRoleDto) { + return this.roleService.create(dto); + } - @Post() - async create( - @Body() dto: CreateRoleDto - ) { - return this.roleService.create(dto); - } - - @Delete(':id') - async delete( - @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, - ) { - return this.roleService.delete(id); - } -} \ No newline at end of file + @Delete(':id') + async delete(@Param('id', new ParseUUIDPipe({ version: '4' })) id: string) { + return this.roleService.delete(id); + } +} diff --git a/tone-page-server/src/admin/controller/admin-user-role.controller.ts b/tone-page-server/src/admin/controller/admin-user-role.controller.ts index 6d07430..2978913 100644 --- a/tone-page-server/src/admin/controller/admin-user-role.controller.ts +++ b/tone-page-server/src/admin/controller/admin-user-role.controller.ts @@ -1,43 +1,50 @@ -import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Post } from "@nestjs/common"; -import { RoleService } from "src/role/services/role.service"; -import { UserRoleService } from "src/role/services/user-role.service"; -import { CreateUserRoleDto } from "../dto/admin-user-role/create-user-role.dto"; -import { DeleteUserRoleDto } from "../dto/admin-user-role/delete-user-role.dto"; +import { + Body, + Controller, + Delete, + Get, + Param, + ParseUUIDPipe, + Post, +} from '@nestjs/common'; +import { RoleService } from 'src/role/services/role.service'; +import { UserRoleService } from 'src/role/services/user-role.service'; +import { CreateUserRoleDto } from '../dto/admin-user-role/create-user-role.dto'; +import { DeleteUserRoleDto } from '../dto/admin-user-role/delete-user-role.dto'; @Controller('admin/users/:userId/role') export class AdminUserRoleController { + constructor( + private readonly userRoleService: UserRoleService, + private readonly roleService: RoleService, + ) {} - constructor( - private readonly userRoleService: UserRoleService, - private readonly roleService: RoleService, - ) { } + @Get() + async getUserRoles( + @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, + ) { + const userRoleIds = await this.userRoleService.findRoleIdsByUserId(userId); + return await this.roleService.findRolesByRoleIds(userRoleIds); + } - @Get() - async getUserRoles( - @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, - ) { - const userRoleIds = await this.userRoleService.findRoleIdsByUserId(userId); - return await this.roleService.findRolesByRoleIds(userRoleIds); - } + @Post() + async setUserRoles( + @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, + @Body() dto: CreateUserRoleDto, + ) { + return this.userRoleService.addUserRole({ + userId, + roleId: dto.roleId, + isEnabled: dto.isEnabled, + expiredAt: dto.expiredAt, + }); + } - @Post() - async setUserRoles( - @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, - @Body() dto: CreateUserRoleDto, - ) { - return this.userRoleService.addUserRole({ - userId, - roleId: dto.roleId, - isEnabled: dto.isEnabled, - expiredAt: dto.expiredAt, - }); - } - - @Delete() - async deleteUserRoles( - @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, - @Body() dto: DeleteUserRoleDto, - ) { - return this.userRoleService.deleteUserRole(userId, dto.roleId); - } -} \ No newline at end of file + @Delete() + async deleteUserRoles( + @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, + @Body() dto: DeleteUserRoleDto, + ) { + return this.userRoleService.deleteUserRole(userId, dto.roleId); + } +} diff --git a/tone-page-server/src/admin/controller/admin-user.controller.ts b/tone-page-server/src/admin/controller/admin-user.controller.ts index acfe19f..d960816 100644 --- a/tone-page-server/src/admin/controller/admin-user.controller.ts +++ b/tone-page-server/src/admin/controller/admin-user.controller.ts @@ -1,69 +1,76 @@ -import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Post, Put, Query } from "@nestjs/common"; -import { ListDto } from "../dto/admin-user/list.dto"; -import { CreateDto } from "../dto/admin-user/create.dto"; -import { UserService } from "src/user/user.service"; -import { UpdateDto } from "../dto/admin-user/update.dto"; -import { UpdatePasswordDto } from "../dto/admin-user/update-password.dto"; -import { RemoveUserDto } from "../dto/admin-user/remove.dto"; +import { + Body, + Controller, + Delete, + Get, + Param, + ParseUUIDPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ListDto } from '../dto/admin-user/list.dto'; +import { CreateDto } from '../dto/admin-user/create.dto'; +import { UserService } from 'src/user/user.service'; +import { UpdateDto } from '../dto/admin-user/update.dto'; +import { UpdatePasswordDto } from '../dto/admin-user/update-password.dto'; +import { RemoveUserDto } from '../dto/admin-user/remove.dto'; @Controller('admin/user') export class AdminUserController { + constructor(private readonly userService: UserService) {} - constructor( - private readonly userService: UserService, - ) { } + @Get() + async list(@Query() listDto: ListDto) { + return this.userService.list(listDto.page, listDto.pageSize); + } - @Get() - async list( - @Query() listDto: ListDto - ) { - return this.userService.list(listDto.page, listDto.pageSize); - } + @Get(':userId') + async get( + @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, + ) { + return this.userService.findOne({ userId }); + } - @Get(':userId') - async get( - @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, - ) { - return this.userService.findOne({ userId }); - } + @Post() + async create(@Body() createDto: CreateDto) { + return this.userService.create({ + ...createDto, + ...(createDto.password && + (() => { + const salt = this.userService.generateSalt(); + return { + salt, + password_hash: this.userService.hashPassword( + createDto.password, + salt, + ), + }; + })()), + }); + } - @Post() - async create( - @Body() createDto: CreateDto - ) { - return this.userService.create({ - ...createDto, - ...createDto.password && (() => { - const salt = this.userService.generateSalt(); - return { - salt, - password_hash: this.userService.hashPassword(createDto.password, salt), - } - })(), - }); - } + @Put(':userId') + async update( + @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, + @Body() updateDto: UpdateDto, + ) { + return this.userService.update(userId, updateDto); + } - @Put(':userId') - async update( - @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, - @Body() updateDto: UpdateDto, - ) { - return this.userService.update(userId, updateDto); - } + @Delete(':userId') + async delete( + @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, + @Query() dto: RemoveUserDto, + ) { + return this.userService.delete(userId, dto.soft); + } - @Delete(':userId') - async delete( - @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, - @Query() dto: RemoveUserDto, - ) { - return this.userService.delete(userId, dto.soft); - } - - @Post(':userId/password') - async setPassword( - @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, - @Body() updatePasswordDto: UpdatePasswordDto, - ) { - return this.userService.setPassword(userId, updatePasswordDto.password); - } -} \ No newline at end of file + @Post(':userId/password') + async setPassword( + @Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string, + @Body() updatePasswordDto: UpdatePasswordDto, + ) { + return this.userService.setPassword(userId, updatePasswordDto.password); + } +} diff --git a/tone-page-server/src/admin/controller/web/admin-web-blog.controller.ts b/tone-page-server/src/admin/controller/web/admin-web-blog.controller.ts index b70203d..3af673a 100644 --- a/tone-page-server/src/admin/controller/web/admin-web-blog.controller.ts +++ b/tone-page-server/src/admin/controller/web/admin-web-blog.controller.ts @@ -1,45 +1,45 @@ -import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Post, Put } from "@nestjs/common"; -import { CreateBlogDto } from "src/admin/dto/admin-web/create-blog.dto"; -import { BlogService } from "src/blog/blog.service"; +import { + Body, + Controller, + Delete, + Get, + Param, + ParseUUIDPipe, + Post, + Put, +} from '@nestjs/common'; +import { CreateBlogDto } from 'src/admin/dto/admin-web/create-blog.dto'; +import { BlogService } from 'src/blog/blog.service'; @Controller('/admin/web/blog') export class AdminWebBlogController { + constructor(private readonly adminWebBlogService: BlogService) {} - constructor( - private readonly adminWebBlogService: BlogService, - ) { } + @Get() + async list() { + return this.adminWebBlogService.list(); + } - @Get() - async list() { - return this.adminWebBlogService.list(); - } + @Post() + async create(@Body() dto: CreateBlogDto) { + return this.adminWebBlogService.create(dto); + } - @Post() - async create( - @Body() dto: CreateBlogDto, - ) { - return this.adminWebBlogService.create(dto); - } + @Put(':id') + async update( + @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, + @Body() dto: CreateBlogDto, + ) { + return this.adminWebBlogService.update(id, dto); + } - @Put(':id') - async update( - @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, - @Body() dto: CreateBlogDto, - ) { - return this.adminWebBlogService.update(id, dto); - } + @Get(':id') + async get(@Param('id', new ParseUUIDPipe({ version: '4' })) id: string) { + return this.adminWebBlogService.findById(id); + } - @Get(':id') - async get( - @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, - ) { - return this.adminWebBlogService.findById(id); - } - - @Delete(':id') - async remove( - @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, - ) { - return this.adminWebBlogService.remove(id); - } -} \ No newline at end of file + @Delete(':id') + async remove(@Param('id', new ParseUUIDPipe({ version: '4' })) id: string) { + return this.adminWebBlogService.remove(id); + } +} diff --git a/tone-page-server/src/admin/controller/web/admin-web-resource.controller.ts b/tone-page-server/src/admin/controller/web/admin-web-resource.controller.ts index 9b13278..7d2cec5 100644 --- a/tone-page-server/src/admin/controller/web/admin-web-resource.controller.ts +++ b/tone-page-server/src/admin/controller/web/admin-web-resource.controller.ts @@ -1,41 +1,45 @@ -import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Post, Put } from "@nestjs/common"; -import { CreateResourceDto } from "src/admin/dto/admin-web/create-resource.dto"; -import { ResourceService } from "src/resource/resource.service"; +import { + Body, + Controller, + Delete, + Get, + Param, + ParseUUIDPipe, + Post, + Put, +} from '@nestjs/common'; +import { CreateResourceDto } from 'src/admin/dto/admin-web/create-resource.dto'; +import { ResourceService } from 'src/resource/resource.service'; @Controller('/admin/web/resource') export class AdminWebResourceController { + constructor(private readonly resourceService: ResourceService) {} - constructor( - private readonly resourceService: ResourceService, - ) { } + @Get() + async list() { + return this.resourceService.findAll(); + } - @Get() - async list() { - return this.resourceService.findAll(); - } + @Get(':id') + async get(@Param('id', new ParseUUIDPipe({ version: '4' })) id: string) { + return this.resourceService.findById(id); + } - @Get(':id') - async get(@Param('id', new ParseUUIDPipe({ version: '4' })) id: string) { - return this.resourceService.findById(id); - } + @Post() + async create(@Body() data: CreateResourceDto) { + return this.resourceService.create(data); + } - @Post() - async create(@Body() data: CreateResourceDto) { - return this.resourceService.create(data); - } + @Put(':id') + async update( + @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, + @Body() data: CreateResourceDto, + ) { + return this.resourceService.update(id, data); + } - @Put(':id') - async update( - @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, - @Body() data: CreateResourceDto - ) { - return this.resourceService.update(id, data); - } - - @Delete(':id') - async delete( - @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, - ) { - return this.resourceService.delete(id); - } -} \ No newline at end of file + @Delete(':id') + async delete(@Param('id', new ParseUUIDPipe({ version: '4' })) id: string) { + return this.resourceService.delete(id); + } +} diff --git a/tone-page-server/src/admin/dto/admin-permission/create-permission.dto.ts b/tone-page-server/src/admin/dto/admin-permission/create-permission.dto.ts index 35f0797..059ddd3 100644 --- a/tone-page-server/src/admin/dto/admin-permission/create-permission.dto.ts +++ b/tone-page-server/src/admin/dto/admin-permission/create-permission.dto.ts @@ -1,9 +1,9 @@ -import { IsString } from "class-validator"; +import { IsString } from 'class-validator'; export class CreatePermissionDto { - @IsString() - name: string + @IsString() + name: string; - @IsString() - description: string; -} \ No newline at end of file + @IsString() + description: string; +} diff --git a/tone-page-server/src/admin/dto/admin-role-permission/delete-role-permissions.dto.ts b/tone-page-server/src/admin/dto/admin-role-permission/delete-role-permissions.dto.ts index 48dfb6c..55078fb 100644 --- a/tone-page-server/src/admin/dto/admin-role-permission/delete-role-permissions.dto.ts +++ b/tone-page-server/src/admin/dto/admin-role-permission/delete-role-permissions.dto.ts @@ -1,8 +1,8 @@ -import { ArrayMinSize, IsArray, IsUUID } from "class-validator"; +import { ArrayMinSize, IsArray, IsUUID } from 'class-validator'; export class DeleteRolePermissionsDto { - @IsArray() - @ArrayMinSize(1) - @IsUUID('4', { each: true }) - permissionIds: string[]; -} \ No newline at end of file + @IsArray() + @ArrayMinSize(1) + @IsUUID('4', { each: true }) + permissionIds: string[]; +} diff --git a/tone-page-server/src/admin/dto/admin-role-permission/set-role-permissions.dto.ts b/tone-page-server/src/admin/dto/admin-role-permission/set-role-permissions.dto.ts index cfe8b3f..7ee7700 100644 --- a/tone-page-server/src/admin/dto/admin-role-permission/set-role-permissions.dto.ts +++ b/tone-page-server/src/admin/dto/admin-role-permission/set-role-permissions.dto.ts @@ -1,8 +1,8 @@ -import { ArrayMinSize, IsArray, IsUUID } from "class-validator"; +import { ArrayMinSize, IsArray, IsUUID } from 'class-validator'; export class SetRolePermissionsDto { - @IsArray() - @ArrayMinSize(1) - @IsUUID('4', { each: true }) - permissionIds: string[]; -} \ No newline at end of file + @IsArray() + @ArrayMinSize(1) + @IsUUID('4', { each: true }) + permissionIds: string[]; +} diff --git a/tone-page-server/src/admin/dto/admin-role/create-role.dto.ts b/tone-page-server/src/admin/dto/admin-role/create-role.dto.ts index dd0fb94..6894832 100644 --- a/tone-page-server/src/admin/dto/admin-role/create-role.dto.ts +++ b/tone-page-server/src/admin/dto/admin-role/create-role.dto.ts @@ -1,9 +1,9 @@ -import { IsString } from "class-validator"; +import { IsString } from 'class-validator'; export class CreateRoleDto { - @IsString() - name: string + @IsString() + name: string; - @IsString() - localName: string; -} \ No newline at end of file + @IsString() + localName: string; +} diff --git a/tone-page-server/src/admin/dto/admin-user-role/create-user-role.dto.ts b/tone-page-server/src/admin/dto/admin-user-role/create-user-role.dto.ts index a5b3258..dded6a6 100644 --- a/tone-page-server/src/admin/dto/admin-user-role/create-user-role.dto.ts +++ b/tone-page-server/src/admin/dto/admin-user-role/create-user-role.dto.ts @@ -1,13 +1,13 @@ -import { IsBoolean, IsDateString, IsOptional, IsUUID } from "class-validator"; +import { IsBoolean, IsDateString, IsOptional, IsUUID } from 'class-validator'; export class CreateUserRoleDto { - @IsUUID('4') - roleId: string; + @IsUUID('4') + roleId: string; - @IsBoolean() - isEnabled: boolean; + @IsBoolean() + isEnabled: boolean; - @IsOptional() - @IsDateString() - expiredAt?: Date; -} \ No newline at end of file + @IsOptional() + @IsDateString() + expiredAt?: Date; +} diff --git a/tone-page-server/src/admin/dto/admin-user-role/delete-user-role.dto.ts b/tone-page-server/src/admin/dto/admin-user-role/delete-user-role.dto.ts index dcf1691..eabf853 100644 --- a/tone-page-server/src/admin/dto/admin-user-role/delete-user-role.dto.ts +++ b/tone-page-server/src/admin/dto/admin-user-role/delete-user-role.dto.ts @@ -1,6 +1,6 @@ -import { IsUUID } from "class-validator"; +import { IsUUID } from 'class-validator'; export class DeleteUserRoleDto { - @IsUUID('4') - roleId: string; -} \ No newline at end of file + @IsUUID('4') + roleId: string; +} diff --git a/tone-page-server/src/admin/dto/admin-user/create.dto.ts b/tone-page-server/src/admin/dto/admin-user/create.dto.ts index 42b2c36..f1aa725 100644 --- a/tone-page-server/src/admin/dto/admin-user/create.dto.ts +++ b/tone-page-server/src/admin/dto/admin-user/create.dto.ts @@ -1,31 +1,32 @@ -import { IsString, Length, Matches, ValidateIf } from "class-validator"; +import { IsString, Length, Matches, ValidateIf } from 'class-validator'; export class CreateDto { - @ValidateIf(o => o.username !== null) - @IsString({ message: '用户名不得为空' }) - @Length(4, 32, { message: '用户名长度只能为4~32' }) - username: string | null; + @ValidateIf((o) => o.username !== null) + @IsString({ message: '用户名不得为空' }) + @Length(4, 32, { message: '用户名长度只能为4~32' }) + username: string | null; - @ValidateIf(o => o.nickname !== null) - @IsString({ message: '昵称不得为空' }) - @Length(1, 30, { message: '昵称长度只能为1~30' }) - nickname: string | null; + @ValidateIf((o) => o.nickname !== null) + @IsString({ message: '昵称不得为空' }) + @Length(1, 30, { message: '昵称长度只能为1~30' }) + nickname: string | null; - @ValidateIf(o => o.email !== null) - @IsString({ message: '邮箱不得为空' }) - @Length(6, 254, { message: '邮箱长度只能为6~254' }) - email: string | null; + @ValidateIf((o) => o.email !== null) + @IsString({ message: '邮箱不得为空' }) + @Length(6, 254, { message: '邮箱长度只能为6~254' }) + email: string | null; - @ValidateIf(o => o.phone !== null) - @IsString({ message: '手机号不得为空' }) - @Length(11, 11, { message: '手机号长度只能为11' }) - phone: string | null; + @ValidateIf((o) => o.phone !== null) + @IsString({ message: '手机号不得为空' }) + @Length(11, 11, { message: '手机号长度只能为11' }) + phone: string | null; - @ValidateIf(o => o.password !== null) - @IsString({ message: '密码不得为空' }) - @Length(6, 32, { message: '密码长度只能为6~32' }) - @Matches(/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{6,32}$/, - { message: '密码必须包含字母和数字,且长度在6~32之间' } - ) - password: string | null; -} \ No newline at end of file + @ValidateIf((o) => o.password !== null) + @IsString({ message: '密码不得为空' }) + @Length(6, 32, { message: '密码长度只能为6~32' }) + @Matches( + /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{6,32}$/, + { message: '密码必须包含字母和数字,且长度在6~32之间' }, + ) + password: string | null; +} diff --git a/tone-page-server/src/admin/dto/admin-user/list.dto.ts b/tone-page-server/src/admin/dto/admin-user/list.dto.ts index 4ca6179..007ee48 100644 --- a/tone-page-server/src/admin/dto/admin-user/list.dto.ts +++ b/tone-page-server/src/admin/dto/admin-user/list.dto.ts @@ -1,5 +1,3 @@ -import { PaginationDto } from "../common/pagination.dto"; +import { PaginationDto } from '../common/pagination.dto'; -export class ListDto extends PaginationDto { - -} \ No newline at end of file +export class ListDto extends PaginationDto {} diff --git a/tone-page-server/src/admin/dto/admin-user/remove.dto.ts b/tone-page-server/src/admin/dto/admin-user/remove.dto.ts index 9ed5da0..3fb38b0 100644 --- a/tone-page-server/src/admin/dto/admin-user/remove.dto.ts +++ b/tone-page-server/src/admin/dto/admin-user/remove.dto.ts @@ -1,8 +1,8 @@ -import { Transform } from "class-transformer"; -import { IsBoolean } from "class-validator"; +import { Transform } from 'class-transformer'; +import { IsBoolean } from 'class-validator'; export class RemoveUserDto { - @Transform(({ value }) => value === 'true') - @IsBoolean({ message: '需指定删除类型' }) - soft: boolean; -} \ No newline at end of file + @Transform(({ value }) => value === 'true') + @IsBoolean({ message: '需指定删除类型' }) + soft: boolean; +} diff --git a/tone-page-server/src/admin/dto/admin-user/update-password.dto.ts b/tone-page-server/src/admin/dto/admin-user/update-password.dto.ts index 6224bfa..62ff0f2 100644 --- a/tone-page-server/src/admin/dto/admin-user/update-password.dto.ts +++ b/tone-page-server/src/admin/dto/admin-user/update-password.dto.ts @@ -1,10 +1,11 @@ -import { IsString, Length, Matches } from "class-validator"; +import { IsString, Length, Matches } from 'class-validator'; export class UpdatePasswordDto { - @IsString({ message: '密码不得为空' }) - @Length(6, 32, { message: '密码长度只能为6~32' }) - @Matches(/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{6,32}$/, - { message: '密码必须包含字母和数字,且长度在6~32之间' } - ) - password: string; -} \ No newline at end of file + @IsString({ message: '密码不得为空' }) + @Length(6, 32, { message: '密码长度只能为6~32' }) + @Matches( + /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{6,32}$/, + { message: '密码必须包含字母和数字,且长度在6~32之间' }, + ) + password: string; +} diff --git a/tone-page-server/src/admin/dto/admin-user/update.dto.ts b/tone-page-server/src/admin/dto/admin-user/update.dto.ts index bf8d841..9cb9bb5 100644 --- a/tone-page-server/src/admin/dto/admin-user/update.dto.ts +++ b/tone-page-server/src/admin/dto/admin-user/update.dto.ts @@ -1,29 +1,35 @@ -import { IsEmail, IsOptional, IsString, Length, Matches } from "class-validator"; +import { + IsEmail, + IsOptional, + IsString, + Length, + Matches, +} from 'class-validator'; export class UpdateDto { - @IsString({ message: '用户名不得为空' }) - @Length(4, 32, { message: '用户名长度只能为4~32' }) - username: string; + @IsString({ message: '用户名不得为空' }) + @Length(4, 32, { message: '用户名长度只能为4~32' }) + username: string; - @IsString({ message: '昵称不得为空' }) - @Length(1, 30, { message: '昵称长度只能为1~30' }) - nickname: string; + @IsString({ message: '昵称不得为空' }) + @Length(1, 30, { message: '昵称长度只能为1~30' }) + nickname: string; - @IsOptional() - @IsEmail({}, { message: '请输入有效的邮箱地址', always: false }) - @Length(6, 254, { - message: '邮箱长度只能为6~254', - // 仅在值不为 null 或 undefined 时验证 - always: false - }) - email?: string; + @IsOptional() + @IsEmail({}, { message: '请输入有效的邮箱地址', always: false }) + @Length(6, 254, { + message: '邮箱长度只能为6~254', + // 仅在值不为 null 或 undefined 时验证 + always: false, + }) + email?: string; - @IsOptional() // 标记字段为可选 - @IsString({ message: '手机号不得为空', always: false }) - @Matches(/^1[3456789]\d{9}$/, { - message: '请输入有效的手机号码', - // 仅在值不为 null 或 undefined 时验证 - always: false - }) - phone?: string; -} \ No newline at end of file + @IsOptional() // 标记字段为可选 + @IsString({ message: '手机号不得为空', always: false }) + @Matches(/^1[3456789]\d{9}$/, { + message: '请输入有效的手机号码', + // 仅在值不为 null 或 undefined 时验证 + always: false, + }) + phone?: string; +} diff --git a/tone-page-server/src/admin/dto/admin-web/create-blog.dto.ts b/tone-page-server/src/admin/dto/admin-web/create-blog.dto.ts index 07de657..c4977ff 100644 --- a/tone-page-server/src/admin/dto/admin-web/create-blog.dto.ts +++ b/tone-page-server/src/admin/dto/admin-web/create-blog.dto.ts @@ -1,12 +1,12 @@ -import { IsString } from "class-validator"; +import { IsString } from 'class-validator'; export class CreateBlogDto { - @IsString() - title: string; + @IsString() + title: string; - @IsString() - description: string; + @IsString() + description: string; - @IsString() - contentUrl: string; -} \ No newline at end of file + @IsString() + contentUrl: string; +} diff --git a/tone-page-server/src/admin/dto/admin-web/create-resource.dto.ts b/tone-page-server/src/admin/dto/admin-web/create-resource.dto.ts index 4496619..c9279f3 100644 --- a/tone-page-server/src/admin/dto/admin-web/create-resource.dto.ts +++ b/tone-page-server/src/admin/dto/admin-web/create-resource.dto.ts @@ -1,28 +1,28 @@ -import { Type } from "class-transformer"; -import { IsString, ValidateNested } from "class-validator"; +import { Type } from 'class-transformer'; +import { IsString, ValidateNested } from 'class-validator'; class ResourceTagDto { - @IsString() - name: string; - - @IsString() - type: string; + @IsString() + name: string; + + @IsString() + type: string; } export class CreateResourceDto { - @IsString() - title: string; + @IsString() + title: string; - @IsString() - description: string; + @IsString() + description: string; - @IsString() - imageUrl: string; + @IsString() + imageUrl: string; - @IsString() - link: string; + @IsString() + link: string; - @ValidateNested({ each: true }) - @Type(() => ResourceTagDto) - tags: ResourceTagDto[]; -} \ No newline at end of file + @ValidateNested({ each: true }) + @Type(() => ResourceTagDto) + tags: ResourceTagDto[]; +} diff --git a/tone-page-server/src/admin/dto/common/pagination.dto.ts b/tone-page-server/src/admin/dto/common/pagination.dto.ts index be888cb..99e5607 100644 --- a/tone-page-server/src/admin/dto/common/pagination.dto.ts +++ b/tone-page-server/src/admin/dto/common/pagination.dto.ts @@ -1,16 +1,16 @@ import { Type } from 'class-transformer'; -import { IsInt, IsOptional, Max, Min } from 'class-validator'; +import { IsInt, IsOptional, Min } from 'class-validator'; export class PaginationDto { - @IsOptional() - @Type(() => Number) - @IsInt() - @Min(1) - page?: number = 1; + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + page?: number = 1; - @IsOptional() - @Type(() => Number) - @IsInt() - @Min(1) - pageSize?: number = 20; -} \ No newline at end of file + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + pageSize?: number = 20; +} diff --git a/tone-page-server/src/app.module.ts b/tone-page-server/src/app.module.ts index 349355f..c6b7b7a 100644 --- a/tone-page-server/src/app.module.ts +++ b/tone-page-server/src/app.module.ts @@ -42,4 +42,4 @@ import { OssModule } from './oss/oss.module'; controllers: [AppController], providers: [AppService], }) -export class AppModule { } +export class AppModule {} diff --git a/tone-page-server/src/auth/auth.controller.ts b/tone-page-server/src/auth/auth.controller.ts index a978e1f..929a113 100644 --- a/tone-page-server/src/auth/auth.controller.ts +++ b/tone-page-server/src/auth/auth.controller.ts @@ -1,4 +1,11 @@ -import { BadRequestException, Body, Controller, Get, Post, Request, UseGuards } from '@nestjs/common'; +import { + BadRequestException, + Body, + Controller, + Post, + Request, + UseGuards, +} from '@nestjs/common'; import { LoginDto } from './dto/login.dto'; import { AuthService } from './auth.service'; import { AuthGuard } from '@nestjs/passport'; @@ -6,32 +13,31 @@ import { UserSessionService } from 'src/user/services/user-session.service'; @Controller('auth') export class AuthController { + constructor( + private readonly authService: AuthService, + private readonly userSessionService: UserSessionService, + ) {} - constructor( - private readonly authService: AuthService, - private readonly userSessionService: UserSessionService, - ) { } - - @Post('login') - async login(@Body() loginDto: LoginDto) { - switch (loginDto.type) { - case 'password': - return this.authService.loginWithPassword(loginDto); - case 'phone': - return this.authService.loginWithPhone(loginDto); - case 'email': - return this.authService.loginWithEmail(loginDto); - default: - throw new BadRequestException('服务器错误'); - } + @Post('login') + async login(@Body() loginDto: LoginDto) { + switch (loginDto.type) { + case 'password': + return this.authService.loginWithPassword(loginDto); + case 'phone': + return this.authService.loginWithPhone(loginDto); + case 'email': + return this.authService.loginWithEmail(loginDto); + default: + throw new BadRequestException('服务器错误'); } + } - @UseGuards(AuthGuard('jwt')) - @Post('logout') - async logout(@Request() req) { - const { userId, sessionId } = req.user; - await this.userSessionService.invalidateSession(userId, sessionId); - - return true; - } + @UseGuards(AuthGuard('jwt')) + @Post('logout') + async logout(@Request() req) { + const { userId, sessionId } = req.user; + await this.userSessionService.invalidateSession(userId, sessionId); + + return true; + } } diff --git a/tone-page-server/src/auth/auth.module.ts b/tone-page-server/src/auth/auth.module.ts index 1ab0df8..6c13af9 100644 --- a/tone-page-server/src/auth/auth.module.ts +++ b/tone-page-server/src/auth/auth.module.ts @@ -25,21 +25,12 @@ import { OptionalAuthGuard } from './strategies/OptionalAuthGuard'; signOptions: { expiresIn: configService.get('JWT_EXPIRES_IN', '1d'), }, - }) + }), }), VerificationModule, ], controllers: [AuthController], - providers: [ - AuthService, - JwtStrategy, - OptionalAuthGuard, - ], - exports: [ - PassportModule, - JwtStrategy, - AuthService, - OptionalAuthGuard, - ] + providers: [AuthService, JwtStrategy, OptionalAuthGuard], + exports: [PassportModule, JwtStrategy, AuthService, OptionalAuthGuard], }) -export class AuthModule { } +export class AuthModule {} diff --git a/tone-page-server/src/auth/auth.service.ts b/tone-page-server/src/auth/auth.service.ts index c28b843..0d6ca0b 100644 --- a/tone-page-server/src/auth/auth.service.ts +++ b/tone-page-server/src/auth/auth.service.ts @@ -10,136 +10,146 @@ import { VerificationService } from 'src/verification/verification.service'; @Injectable() export class AuthService { + constructor( + private readonly userService: UserService, + private readonly jwtService: JwtService, + private readonly userSessionService: UserSessionService, + private readonly verificationService: VerificationService, + ) {} - constructor( - private readonly userService: UserService, - private readonly jwtService: JwtService, - private readonly userSessionService: UserSessionService, - private readonly verificationService: VerificationService, - ) { } + async loginWithPassword(loginDto: LoginDto) { + const { account, password } = loginDto; + // 依次使用邮箱登录、手机号、账号 + const user = await this.userService.findOne( + [{ email: account }, { phone: account }, { username: account }], + { + withDeleted: true, + }, + ); - async loginWithPassword(loginDto: LoginDto) { - const { account, password } = loginDto; - // 依次使用邮箱登录、手机号、账号 - const user = await this.userService.findOne([ - { email: account }, - { phone: account }, - { username: account }, - ], { - withDeleted: true, - }); - - if (user && user.deletedAt !== null) { - throw new BadRequestException('该账号注销中'); - } - - if (user === null || !user.password_hash || !user.salt) { - throw new BadRequestException('账户或密码错误'); - } - - // 判断密码是否正确 - const hashedPassword = this.hashPassword(password, user.salt); - if (hashedPassword !== user.password_hash) { - throw new BadRequestException('账户或密码错误'); - } - - // 登录成功,颁发token - return { - token: await this.generateToken(user), - } + if (user && user.deletedAt !== null) { + throw new BadRequestException('该账号注销中'); } - async loginWithPhone(loginDto: LoginDto) { - const { phone, code } = loginDto; - // 先判断验证码是否正确 - const isValid = this.verificationService.verifyPhoneCode(phone, code, 'login'); - switch (isValid) { - case 0: - break; - case -1: - throw new BadRequestException('验证码已过期'); - case -2: - throw new BadRequestException('验证码错误'); - case -3: - throw new BadRequestException('验证码已失效'); - default: - throw new BadRequestException('验证码错误'); - } - - // 判断用户是否存在,若不存在则进行注册 - let user = await this.userService.findOne({ phone }, { withDeleted: true }); - if (user && user.deletedAt !== null) { - throw new BadRequestException('该账号注销中,请使用其他手机号'); - } - - if (!user) { - // 执行注册操作 - user = await this.userService.create({ phone: phone }); - } - - if (!user || !user.userId) {// 注册失败或用户信息错误 - throw new BadRequestException('请求失败,请稍后再试'); - } - - // 登录,颁发token - return { - token: await this.generateToken(user), - } + if (user === null || !user.password_hash || !user.salt) { + throw new BadRequestException('账户或密码错误'); } - async loginWithEmail(loginDto: LoginDto) { - const { email, code } = loginDto; - // 先判断验证码是否正确 - const isValid = this.verificationService.verifyEmailCode(email, code, 'login'); - switch (isValid) { - case 0: - break; - case -1: - throw new BadRequestException('验证码已过期,请重新获取'); - case -2: - throw new BadRequestException('验证码错误'); - case -3: - throw new BadRequestException('验证码已失效,请重新获取'); - default: - throw new BadRequestException('验证码错误,请稍后再试'); - } - - // 判断用户是否存在,若不存在则进行注册 - let user = await this.userService.findOne({ email }, { withDeleted: true }); - if (user && user.deletedAt !== null) { - throw new BadRequestException('该账号注销中,请使用其他邮箱'); - } - - if (!user) { - // 执行注册操作 - user = await this.userService.create({ email: email }); - } - - if (!user || !user.userId) {// 注册失败或用户信息错误 - throw new BadRequestException('请求失败,请稍后再试'); - } - - // 登录,颁发token - return { - token: await this.generateToken(user), - } + // 判断密码是否正确 + const hashedPassword = this.hashPassword(password, user.salt); + if (hashedPassword !== user.password_hash) { + throw new BadRequestException('账户或密码错误'); } - private hashPassword(password: string, salt: string): string { - return createHash('sha256').update(`${password}${salt}`).digest('hex'); + // 登录成功,颁发token + return { + token: await this.generateToken(user), + }; + } + + async loginWithPhone(loginDto: LoginDto) { + const { phone, code } = loginDto; + // 先判断验证码是否正确 + const isValid = this.verificationService.verifyPhoneCode( + phone, + code, + 'login', + ); + switch (isValid) { + case 0: + break; + case -1: + throw new BadRequestException('验证码已过期'); + case -2: + throw new BadRequestException('验证码错误'); + case -3: + throw new BadRequestException('验证码已失效'); + default: + throw new BadRequestException('验证码错误'); } - private async generateToken(user: User) { - const payload = { - userId: user.userId, - sessionId: uuidv4(), - } - - // 存储 - await this.userSessionService.createSession(payload.userId, payload.sessionId); - - // 颁发token - return this.jwtService.sign(payload); + // 判断用户是否存在,若不存在则进行注册 + let user = await this.userService.findOne({ phone }, { withDeleted: true }); + if (user && user.deletedAt !== null) { + throw new BadRequestException('该账号注销中,请使用其他手机号'); } -} \ No newline at end of file + if (!user) { + // 执行注册操作 + user = await this.userService.create({ phone: phone }); + } + + if (!user || !user.userId) { + // 注册失败或用户信息错误 + throw new BadRequestException('请求失败,请稍后再试'); + } + + // 登录,颁发token + return { + token: await this.generateToken(user), + }; + } + + async loginWithEmail(loginDto: LoginDto) { + const { email, code } = loginDto; + // 先判断验证码是否正确 + const isValid = this.verificationService.verifyEmailCode( + email, + code, + 'login', + ); + switch (isValid) { + case 0: + break; + case -1: + throw new BadRequestException('验证码已过期,请重新获取'); + case -2: + throw new BadRequestException('验证码错误'); + case -3: + throw new BadRequestException('验证码已失效,请重新获取'); + default: + throw new BadRequestException('验证码错误,请稍后再试'); + } + + // 判断用户是否存在,若不存在则进行注册 + let user = await this.userService.findOne({ email }, { withDeleted: true }); + if (user && user.deletedAt !== null) { + throw new BadRequestException('该账号注销中,请使用其他邮箱'); + } + + if (!user) { + // 执行注册操作 + user = await this.userService.create({ email: email }); + } + + if (!user || !user.userId) { + // 注册失败或用户信息错误 + throw new BadRequestException('请求失败,请稍后再试'); + } + + // 登录,颁发token + return { + token: await this.generateToken(user), + }; + } + + private hashPassword(password: string, salt: string): string { + return createHash('sha256').update(`${password}${salt}`).digest('hex'); + } + + private async generateToken(user: User) { + const payload = { + userId: user.userId, + sessionId: uuidv4(), + }; + + // 存储 + await this.userSessionService.createSession( + payload.userId, + payload.sessionId, + ); + + // 颁发token + return this.jwtService.sign(payload); + } +} diff --git a/tone-page-server/src/auth/dto/login.dto.ts b/tone-page-server/src/auth/dto/login.dto.ts index 4ef215e..a97b4b6 100644 --- a/tone-page-server/src/auth/dto/login.dto.ts +++ b/tone-page-server/src/auth/dto/login.dto.ts @@ -1,31 +1,31 @@ import { IsEnum, IsString, Length, ValidateIf } from 'class-validator'; export class LoginDto { - @IsEnum(['password', 'phone', 'email'], { message: '请求类型错误' }) - type: 'password' | 'phone' | 'email'; + @IsEnum(['password', 'phone', 'email'], { message: '请求类型错误' }) + type: 'password' | 'phone' | 'email'; - @ValidateIf(o => o.type === 'password') - @IsString({ message: '账户必须输入' }) - @Length(1, 254, { message: '账户异常' })// 用户名、邮箱、手机号 - account?: string; + @ValidateIf((o) => o.type === 'password') + @IsString({ message: '账户必须输入' }) + @Length(1, 254, { message: '账户异常' }) // 用户名、邮箱、手机号 + account?: string; - @ValidateIf(o => o.type === 'password') - @IsString({ message: '密码必须输入' }) - @Length(6, 32, { message: '密码异常' })// 6-32位 - password?: string; + @ValidateIf((o) => o.type === 'password') + @IsString({ message: '密码必须输入' }) + @Length(6, 32, { message: '密码异常' }) // 6-32位 + password?: string; - @ValidateIf(o => o.type === 'phone') - @IsString({ message: '手机号必须输入' }) - @Length(11, 11, { message: '手机号异常' })// 中国大陆,11位数字 - phone?: string; + @ValidateIf((o) => o.type === 'phone') + @IsString({ message: '手机号必须输入' }) + @Length(11, 11, { message: '手机号异常' }) // 中国大陆,11位数字 + phone?: string; - @ValidateIf(o => o.type === 'email') - @IsString({ message: '邮箱必须输入' }) - @Length(6, 254, { message: '邮箱异常' })// RFC 5321 - email?: string; + @ValidateIf((o) => o.type === 'email') + @IsString({ message: '邮箱必须输入' }) + @Length(6, 254, { message: '邮箱异常' }) // RFC 5321 + email?: string; - @ValidateIf(o => o.type === 'phone' || o.type === 'email') - @IsString({ message: '验证码必须输入' }) - @Length(6, 6, { message: '验证码异常' })// 6位数字 - code?: string; -} \ No newline at end of file + @ValidateIf((o) => o.type === 'phone' || o.type === 'email') + @IsString({ message: '验证码必须输入' }) + @Length(6, 6, { message: '验证码异常' }) // 6位数字 + code?: string; +} diff --git a/tone-page-server/src/auth/strategies/OptionalAuthGuard.ts b/tone-page-server/src/auth/strategies/OptionalAuthGuard.ts index 863232e..9609201 100644 --- a/tone-page-server/src/auth/strategies/OptionalAuthGuard.ts +++ b/tone-page-server/src/auth/strategies/OptionalAuthGuard.ts @@ -1,22 +1,28 @@ -import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; -import { AuthGuard } from "@nestjs/passport"; -import { Observable, retry } from "rxjs"; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; @Injectable() export class OptionalAuthGuard extends AuthGuard('jwt') implements CanActivate { - async canActivate(context: ExecutionContext): Promise { - try { - await super.canActivate(context); - return true; - } catch (error) { - return true;// 如果验证失败,仍然允许访问 - } + async canActivate(context: ExecutionContext): Promise { + try { + await super.canActivate(context); + return true; + } catch (error) { + console.error('OptionalAuthGuard error:', error); + return true; // 如果验证失败,仍然允许访问 } + } - handleRequest(err: any, user: any, info: any, context: ExecutionContext, status?: any): TUser { - if (err || !user) { - return null; // 如果没有用户信息,返回null - } - return user; // 如果有用户信息,返回用户对象 + handleRequest( + err: any, + user: any, + // info: any, + // context: ExecutionContext, + // status?: any, + ): TUser { + if (err || !user) { + return null; // 如果没有用户信息,返回null } -} \ No newline at end of file + return user; // 如果有用户信息,返回用户对象 + } +} diff --git a/tone-page-server/src/auth/strategies/jwt.strategy.ts b/tone-page-server/src/auth/strategies/jwt.strategy.ts index b121219..11ec8cd 100644 --- a/tone-page-server/src/auth/strategies/jwt.strategy.ts +++ b/tone-page-server/src/auth/strategies/jwt.strategy.ts @@ -1,33 +1,36 @@ -import { Injectable, UnauthorizedException } from "@nestjs/common"; -import { ConfigService } from "@nestjs/config"; -import { PassportStrategy } from "@nestjs/passport"; -import { ExtractJwt, Strategy } from "passport-jwt"; -import { UserSessionService } from "src/user/services/user-session.service"; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { UserSessionService } from 'src/user/services/user-session.service'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { - constructor( - private readonly userSessionService: UserSessionService, - private readonly configService: ConfigService, - ) { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - ignoreExpiration: false, - secretOrKey: configService.get('JWT_SECRET', 'tone-page'), - }) + constructor( + private readonly userSessionService: UserSessionService, + private readonly configService: ConfigService, + ) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET', 'tone-page'), + }); + } + + async validate(payload: any) { + const { userId, sessionId } = payload ?? {}; + + const isValidSession = await this.userSessionService.isSessionValid( + userId, + sessionId, + ); + if (!isValidSession) { + throw new UnauthorizedException('登录凭证已过期,请重新登录'); } - async validate(payload: any) { - const { userId, sessionId } = payload ?? {}; - - const isValidSession = await this.userSessionService.isSessionValid(userId, sessionId); - if (!isValidSession) { - throw new UnauthorizedException('登录凭证已过期,请重新登录'); - } - - return { - userId, - sessionId, - } - } -} \ No newline at end of file + return { + userId, + sessionId, + }; + } +} diff --git a/tone-page-server/src/blog/blog.controller.ts b/tone-page-server/src/blog/blog.controller.ts index 25ebdf8..ed93f30 100644 --- a/tone-page-server/src/blog/blog.controller.ts +++ b/tone-page-server/src/blog/blog.controller.ts @@ -1,4 +1,14 @@ -import { BadRequestException, Body, Controller, Get, Param, ParseUUIDPipe, Post, Req, Request, UseGuards } from '@nestjs/common'; +import { + BadRequestException, + Body, + Controller, + Get, + Param, + ParseUUIDPipe, + Post, + Request, + UseGuards, +} from '@nestjs/common'; import { BlogService } from './blog.service'; import { OptionalAuthGuard } from 'src/auth/strategies/OptionalAuthGuard'; import { UserService } from 'src/user/user.service'; @@ -6,86 +16,91 @@ import { createBlogCommentDto } from './dto/create.blogcomment.dto'; @Controller('blog') export class BlogController { + constructor( + private readonly blogService: BlogService, + private readonly userService: UserService, + ) {} - constructor( - private readonly blogService: BlogService, - private readonly userService: UserService, - ) { } + @Get() + getBlogs() { + return this.blogService.list(); + } - @Get() - getBlogs() { - return this.blogService.list(); - } + @Get(':id') + async getBlog(@Param('id', new ParseUUIDPipe({ version: '4' })) id: string) { + const blog = await this.blogService.findById(id); + if (!blog) throw new BadRequestException('文章不存在'); - @Get(':id') - async getBlog( - @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, - ) { - const blog = await this.blogService.findById(id); - if (!blog) throw new BadRequestException('文章不存在'); + const blogDataRes = await fetch(`${blog.contentUrl}`); + const blogContent = await blogDataRes.text(); - const blogDataRes = await fetch(`${blog.contentUrl}`); - const blogContent = await blogDataRes.text(); + await this.blogService.incrementViewCount(id); + return { + id: blog.id, + title: blog.title, + createdAt: blog.createdAt, + content: blogContent, + }; + } - await this.blogService.incrementViewCount(id); - return { - id: blog.id, - title: blog.title, - createdAt: blog.createdAt, - content: blogContent, - }; - } + @Get(':id/comments') + async getBlogComments( + @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, + ) { + const blog = await this.blogService.findById(id); + if (!blog) throw new BadRequestException('文章不存在'); - @Get(':id/comments') - async getBlogComments( - @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, - ) { - const blog = await this.blogService.findById(id); - if (!blog) throw new BadRequestException('文章不存在'); + return await this.blogService.getComments(id); + } - return await this.blogService.getComments(id); - } + // 该接口允许匿名评论,但仍需验证userId合法性 + @UseGuards(OptionalAuthGuard) + @Post(':id/comment') + async createBlogComment( + @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, + @Body() commentData: createBlogCommentDto, + @Request() req, + ) { + const { userId } = req.user || {}; + const blog = await this.blogService.findById(id); + if (!blog) throw new BadRequestException('文章不存在'); - // 该接口允许匿名评论,但仍需验证userId合法性 - @UseGuards(OptionalAuthGuard) - @Post(':id/comment') - async createBlogComment( - @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, - @Body() commentData: createBlogCommentDto, - @Request() req, - ) { - const { userId } = req.user || {}; - const blog = await this.blogService.findById(id); - if (!blog) throw new BadRequestException('文章不存在'); + const user = userId ? await this.userService.findOne({ userId }) : null; - let user = userId ? await this.userService.findOne({ userId }) : null; - - // 获取IP归属地 - const ip = req.ip || req.socket.remoteAddress || req.headers['x-forwarded-for'] || '未知'; - let address = '未知'; - if (!['::1'].includes(ip)) { - const addressRes = await (await fetch(`https://mesh.if.iqiyi.com/aid/ip/info?version=1.1.1&ip=${ip}`)).json(); - if (addressRes?.code == 0) { - const country: string = addressRes?.data?.countryCN || '未知'; - const province: string = addressRes?.data?.provinceCN || '中国'; - if (country !== '中国') { - // 非中国,显示国家 - address = country; - } else { - // 中国,显示省份 - address = province; - } - } + // 获取IP归属地 + const ip = + req.ip || + req.socket.remoteAddress || + req.headers['x-forwarded-for'] || + '未知'; + let address = '未知'; + if (!['::1'].includes(ip)) { + const addressRes = await ( + await fetch( + `https://mesh.if.iqiyi.com/aid/ip/info?version=1.1.1&ip=${ip}`, + ) + ).json(); + if (addressRes?.code == 0) { + const country: string = addressRes?.data?.countryCN || '未知'; + const province: string = addressRes?.data?.provinceCN || '中国'; + if (country !== '中国') { + // 非中国,显示国家 + address = country; + } else { + // 中国,显示省份 + address = province; } - - const comment = { - ...commentData, - blogId: id, - user: user, - ip: ip, - address: address, - }; - - return await this.blogService.createComment(comment); + } } + + const comment = { + ...commentData, + blogId: id, + user: user, + ip: ip, + address: address, + }; + + return await this.blogService.createComment(comment); + } } diff --git a/tone-page-server/src/blog/blog.module.ts b/tone-page-server/src/blog/blog.module.ts index 00f2eda..75766e7 100644 --- a/tone-page-server/src/blog/blog.module.ts +++ b/tone-page-server/src/blog/blog.module.ts @@ -8,9 +8,13 @@ import { AuthModule } from 'src/auth/auth.module'; import { UserModule } from 'src/user/user.module'; @Module({ - imports: [TypeOrmModule.forFeature([Blog, BlogComment]), AuthModule, UserModule], + imports: [ + TypeOrmModule.forFeature([Blog, BlogComment]), + AuthModule, + UserModule, + ], controllers: [BlogController], providers: [BlogService], exports: [BlogService], }) -export class BlogModule { } +export class BlogModule {} diff --git a/tone-page-server/src/blog/blog.service.ts b/tone-page-server/src/blog/blog.service.ts index 189a488..1d3af3f 100644 --- a/tone-page-server/src/blog/blog.service.ts +++ b/tone-page-server/src/blog/blog.service.ts @@ -3,63 +3,61 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Blog } from './entity/Blog.entity'; import { Repository } from 'typeorm'; import { BlogComment } from './entity/BlogComment'; -import { UserService } from 'src/user/user.service'; @Injectable() export class BlogService { + constructor( + @InjectRepository(Blog) + private readonly blogRepository: Repository, + @InjectRepository(BlogComment) + private readonly blogCommentRepository: Repository, + ) {} - constructor( - @InjectRepository(Blog) - private readonly blogRepository: Repository, - @InjectRepository(BlogComment) - private readonly blogCommentRepository: Repository, - ) { } + async list() { + return this.blogRepository.find({ + where: { deletedAt: null }, + order: { + createdAt: 'DESC', + }, + }); + } - async list() { - return this.blogRepository.find({ - where: { deletedAt: null }, - order: { - createdAt: 'DESC', - } - }) - } + async create(blog: Partial) { + const newBlog = this.blogRepository.create(blog); + return this.blogRepository.save(newBlog); + } - async create(blog: Partial) { - const newBlog = this.blogRepository.create(blog); - return this.blogRepository.save(newBlog); - } + async update(id: string, blog: Partial) { + await this.blogRepository.update(id, blog); + return this.blogRepository.findOneBy({ id }); + } - async update(id: string, blog: Partial) { - await this.blogRepository.update(id, blog); - return this.blogRepository.findOneBy({ id }); - } + async remove(id: string) { + const blog = await this.blogRepository.findOneBy({ id }); + if (!blog) return null; + return this.blogRepository.softRemove(blog); + } - async remove(id: string) { - const blog = await this.blogRepository.findOneBy({ id }); - if (!blog) return null; - return this.blogRepository.softRemove(blog); - } + async findById(id: string) { + return this.blogRepository.findOneBy({ id }); + } - async findById(id: string) { - return this.blogRepository.findOneBy({ id }); - } + async incrementViewCount(id: string) { + await this.blogRepository.increment({ id }, 'viewCount', 1); + } - async incrementViewCount(id: string) { - await this.blogRepository.increment({ id }, 'viewCount', 1); - } + async getComments(id: string) { + return this.blogCommentRepository.find({ + where: { blogId: id }, + relations: ['user'], + order: { + createdAt: 'DESC', + }, + }); + } - async getComments(id: string) { - return this.blogCommentRepository.find({ - where: { blogId: id }, - relations: ['user'], - order: { - createdAt: 'DESC', - } - }); - } - - async createComment(comment: Partial) { - const newComment = this.blogCommentRepository.create(comment); - return this.blogCommentRepository.save(newComment); - } + async createComment(comment: Partial) { + const newComment = this.blogCommentRepository.create(comment); + return this.blogCommentRepository.save(newComment); + } } diff --git a/tone-page-server/src/blog/dto/create.blogcomment.dto.ts b/tone-page-server/src/blog/dto/create.blogcomment.dto.ts index b2c65e8..2cc4bad 100644 --- a/tone-page-server/src/blog/dto/create.blogcomment.dto.ts +++ b/tone-page-server/src/blog/dto/create.blogcomment.dto.ts @@ -1,10 +1,10 @@ -import { IsOptional, IsString, IsUUID } from "class-validator"; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class createBlogCommentDto { - @IsString({ message: '评论内容不能为空' }) - content: string; + @IsString({ message: '评论内容不能为空' }) + content: string; - @IsOptional() - @IsUUID('4', { message: '父评论ID格式错误' }) - parentId?: string; -} \ No newline at end of file + @IsOptional() + @IsUUID('4', { message: '父评论ID格式错误' }) + parentId?: string; +} diff --git a/tone-page-server/src/blog/entity/Blog.entity.ts b/tone-page-server/src/blog/entity/Blog.entity.ts index ebbfcd5..8b85163 100644 --- a/tone-page-server/src/blog/entity/Blog.entity.ts +++ b/tone-page-server/src/blog/entity/Blog.entity.ts @@ -1,35 +1,43 @@ -import { Column, CreateDateColumn, DeleteDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; -import { BlogComment } from "./BlogComment"; +import { + Column, + CreateDateColumn, + DeleteDateColumn, + Entity, + OneToMany, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { BlogComment } from './BlogComment'; @Entity() export class Blog { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column() - title: string; + @Column() + title: string; - @Column() - description: string; + @Column() + description: string; - @Column() - contentUrl: string; + @Column() + contentUrl: string; - @Column({ default: 0 }) - viewCount: number; + @Column({ default: 0 }) + viewCount: number; - @CreateDateColumn({ precision: 3 }) - createdAt: Date; + @CreateDateColumn({ precision: 3 }) + createdAt: Date; - @UpdateDateColumn({ precision: 3 }) - updatedAt: Date; + @UpdateDateColumn({ precision: 3 }) + updatedAt: Date; - @DeleteDateColumn({ precision: 3, nullable: true }) - deletedAt: Date; + @DeleteDateColumn({ precision: 3, nullable: true }) + deletedAt: Date; - // 权限关系 TODO + // 权限关系 TODO - // 关系 - @OneToMany(() => BlogComment, blog => blog.id) - comments: BlogComment[]; -} \ No newline at end of file + // 关系 + @OneToMany(() => BlogComment, (blog) => blog.id) + comments: BlogComment[]; +} diff --git a/tone-page-server/src/blog/entity/BlogComment.ts b/tone-page-server/src/blog/entity/BlogComment.ts index 01e0bbf..76a0b64 100644 --- a/tone-page-server/src/blog/entity/BlogComment.ts +++ b/tone-page-server/src/blog/entity/BlogComment.ts @@ -1,33 +1,41 @@ -import { User } from "src/user/entities/user.entity"; -import { Column, CreateDateColumn, DeleteDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { User } from 'src/user/entities/user.entity'; +import { + Column, + CreateDateColumn, + DeleteDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; @Entity() export class BlogComment { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column() - content: string; + @Column() + content: string; - @Column() - ip: string; + @Column() + ip: string; - @Column() - address: string; + @Column() + address: string; - @CreateDateColumn({ precision: 3 }) - createdAt: Date; + @CreateDateColumn({ precision: 3 }) + createdAt: Date; - @DeleteDateColumn({ precision: 3, nullable: true }) - deletedAt: Date; + @DeleteDateColumn({ precision: 3, nullable: true }) + deletedAt: Date; - @ManyToOne(() => User, { nullable: true }) - @JoinColumn({ name: 'userId' }) - user: User | null; + @ManyToOne(() => User, { nullable: true }) + @JoinColumn({ name: 'userId' }) + user: User | null; - @Column({ type: 'uuid', nullable: true }) - blogId: string | null; + @Column({ type: 'uuid', nullable: true }) + blogId: string | null; - @Column({ type: 'uuid', nullable: true }) - parentId: string | null; -} \ No newline at end of file + @Column({ type: 'uuid', nullable: true }) + parentId: string | null; +} diff --git a/tone-page-server/src/common/decorators/permissions.decorator.ts b/tone-page-server/src/common/decorators/permissions.decorator.ts index 4d9055a..ad21bf4 100644 --- a/tone-page-server/src/common/decorators/permissions.decorator.ts +++ b/tone-page-server/src/common/decorators/permissions.decorator.ts @@ -1,3 +1,4 @@ -import { SetMetadata } from "@nestjs/common"; +import { SetMetadata } from '@nestjs/common'; -export const Permissions = (...permissions: string[]) => SetMetadata('permissions', permissions); \ No newline at end of file +export const Permissions = (...permissions: string[]) => + SetMetadata('permissions', permissions); diff --git a/tone-page-server/src/common/decorators/role.decorator.ts b/tone-page-server/src/common/decorators/role.decorator.ts index fe0b8c3..b037672 100644 --- a/tone-page-server/src/common/decorators/role.decorator.ts +++ b/tone-page-server/src/common/decorators/role.decorator.ts @@ -1,3 +1,3 @@ -import { SetMetadata } from "@nestjs/common"; +import { SetMetadata } from '@nestjs/common'; -export const Roles = (...roles: string[]) => SetMetadata('roles', roles); \ No newline at end of file +export const Roles = (...roles: string[]) => SetMetadata('roles', roles); diff --git a/tone-page-server/src/common/guard/permission.guard.ts b/tone-page-server/src/common/guard/permission.guard.ts index 2f6013c..eccf67c 100644 --- a/tone-page-server/src/common/guard/permission.guard.ts +++ b/tone-page-server/src/common/guard/permission.guard.ts @@ -1,40 +1,47 @@ -import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { PermissionService } from "src/role/services/permission.service"; -import { RolePermissionService } from "src/role/services/role-permission.service"; -import { UserRoleService } from "src/role/services/user-role.service"; +import { PermissionService } from 'src/role/services/permission.service'; +import { RolePermissionService } from 'src/role/services/role-permission.service'; +import { UserRoleService } from 'src/role/services/user-role.service'; @Injectable() export class PermissionGuard implements CanActivate { - constructor( - private reflector: Reflector, - private readonly userRoleService: UserRoleService, - private readonly rolePermissionService: RolePermissionService, - private readonly permissionService: PermissionService, - ) { } + constructor( + private reflector: Reflector, + private readonly userRoleService: UserRoleService, + private readonly rolePermissionService: RolePermissionService, + private readonly permissionService: PermissionService, + ) {} - async canActivate(context: ExecutionContext): Promise { - const requiredPermissions = this.reflector.getAllAndOverride('permissions', [ - context.getHandler(), - context.getClass(), - ]); + async canActivate(context: ExecutionContext): Promise { + const requiredPermissions = this.reflector.getAllAndOverride( + 'permissions', + [context.getHandler(), context.getClass()], + ); - if (!requiredPermissions) return true; + if (!requiredPermissions) return true; - const request = context.switchToHttp().getRequest(); - const userId = request.user?.userId; + const request = context.switchToHttp().getRequest(); + const userId = request.user?.userId; - if (!userId) return false; + if (!userId) return false; - // 查询用户拥有的有效角色ID - const userRoleIds = await this.userRoleService.findValidRoleIdsByUserId(userId); + // 查询用户拥有的有效角色ID + const userRoleIds = + await this.userRoleService.findValidRoleIdsByUserId(userId); - // 查询用户拥有的有效角色ID对应的权限ID - const userPermissionIds = await this.rolePermissionService.findPermissionIdsByRoleIds(userRoleIds); + // 查询用户拥有的有效角色ID对应的权限ID + const userPermissionIds = + await this.rolePermissionService.findPermissionIdsByRoleIds(userRoleIds); - // 查询用户拥有的权限ID对应的权限名 - const userPermissionNames = await this.permissionService.findPermissionNamesByPermissionIds(userPermissionIds); + // 查询用户拥有的权限ID对应的权限名 + const userPermissionNames = + await this.permissionService.findPermissionNamesByPermissionIds( + userPermissionIds, + ); - return requiredPermissions.every(permission => userPermissionNames.includes(permission)) - } -} \ No newline at end of file + return requiredPermissions.every((permission) => + userPermissionNames.includes(permission), + ); + } +} diff --git a/tone-page-server/src/common/guard/roles.guard.ts b/tone-page-server/src/common/guard/roles.guard.ts index 214496b..1fac9e6 100644 --- a/tone-page-server/src/common/guard/roles.guard.ts +++ b/tone-page-server/src/common/guard/roles.guard.ts @@ -1,36 +1,37 @@ -import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { RoleService } from "src/role/services/role.service"; -import { UserRoleService } from "src/role/services/user-role.service"; +import { RoleService } from 'src/role/services/role.service'; +import { UserRoleService } from 'src/role/services/user-role.service'; @Injectable() export class RolesGuard implements CanActivate { - constructor( - private reflector: Reflector, - private readonly userRoleService: UserRoleService, - private readonly roleService: RoleService, - ) { } + constructor( + private reflector: Reflector, + private readonly userRoleService: UserRoleService, + private readonly roleService: RoleService, + ) {} - async canActivate(context: ExecutionContext): Promise { - const requiredRoles = this.reflector.getAllAndOverride('roles', [ - context.getHandler(), - context.getClass(), - ]); + async canActivate(context: ExecutionContext): Promise { + const requiredRoles = this.reflector.getAllAndOverride('roles', [ + context.getHandler(), + context.getClass(), + ]); - if (!requiredRoles) return true; - + if (!requiredRoles) return true; - const request = context.switchToHttp().getRequest(); - const userId = request.user?.userId; + const request = context.switchToHttp().getRequest(); + const userId = request.user?.userId; - if (!userId) return false; + if (!userId) return false; - // 查询用户拥有的有效角色Id - const userRoleIds = await this.userRoleService.findValidRoleIdsByUserId(userId); + // 查询用户拥有的有效角色Id + const userRoleIds = + await this.userRoleService.findValidRoleIdsByUserId(userId); - // 查询用户角色Id对应的角色名 - const userRoleNames = await this.roleService.findRoleNamesByRoleIds(userRoleIds); + // 查询用户角色Id对应的角色名 + const userRoleNames = + await this.roleService.findRoleNamesByRoleIds(userRoleIds); - return requiredRoles.some(role => userRoleNames.includes(role)); - } -} \ No newline at end of file + return requiredRoles.some((role) => userRoleNames.includes(role)); + } +} diff --git a/tone-page-server/src/common/interceptors/response.interceptor.ts b/tone-page-server/src/common/interceptors/response.interceptor.ts index 5e9eb0e..62f0c51 100644 --- a/tone-page-server/src/common/interceptors/response.interceptor.ts +++ b/tone-page-server/src/common/interceptors/response.interceptor.ts @@ -1,16 +1,24 @@ -import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common"; -import { Observable } from "rxjs"; -import { map } from "rxjs/operators"; +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; @Injectable() export class ResponseInterceptor implements NestInterceptor { - intercept(context: ExecutionContext, next: CallHandler): Observable | Promise> { - return next.handle().pipe( - map(data => ({ - statusCode: 200, - message: '请求成功', - data, - })), - ); - } -} \ No newline at end of file + intercept( + context: ExecutionContext, + next: CallHandler, + ): Observable | Promise> { + return next.handle().pipe( + map((data) => ({ + statusCode: 200, + message: '请求成功', + data, + })), + ); + } +} diff --git a/tone-page-server/src/main.ts b/tone-page-server/src/main.ts index 0ebddec..abd22c7 100644 --- a/tone-page-server/src/main.ts +++ b/tone-page-server/src/main.ts @@ -5,22 +5,26 @@ import { ResponseInterceptor } from './common/interceptors/response.interceptor' async function bootstrap() { const app = await NestFactory.create(AppModule); - app.useGlobalPipes(new ValidationPipe({ - transform: true, - whitelist: true, - forbidNonWhitelisted: true, - stopAtFirstError: true, - exceptionFactory: (errors) => { - const error = errors[0]; - const firstConstraint = error.constraints ? Object.values(error.constraints)[0] : '验证失败'; + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + forbidNonWhitelisted: true, + stopAtFirstError: true, + exceptionFactory: (errors) => { + const error = errors[0]; + const firstConstraint = error.constraints + ? Object.values(error.constraints)[0] + : '验证失败'; - throw new BadRequestException({ - message: firstConstraint, - error: 'Bad Request', - statusCode: 400 - }); - } - })); + throw new BadRequestException({ + message: firstConstraint, + error: 'Bad Request', + statusCode: 400, + }); + }, + }), + ); app.useGlobalInterceptors(new ResponseInterceptor()); await app.listen(process.env.PORT ?? 3001); } diff --git a/tone-page-server/src/notification/notification.service.ts b/tone-page-server/src/notification/notification.service.ts index cdcffeb..6461a7b 100644 --- a/tone-page-server/src/notification/notification.service.ts +++ b/tone-page-server/src/notification/notification.service.ts @@ -2,37 +2,40 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class NotificationService { + sendEmail(email: string, subject: string, content: string) { + throw new Error( + `Email sending is not implemented yet. Email: ${email}, Subject: ${subject}, Content: ${content}`, + ); + } - sendEmail(email: string, subject: string, content: string) { - - } - - /** - * @deprecated 短信签名暂未通过 - */ - async sendSMS(phone: string, type: 'login', code: string) { - // const config = new $OpenApi.Config({ - // accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID, - // accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET, - // }) - // config.endpoint = 'dysmsapi.aliyuncs.com'; - // const client = new Client(config); - // const request = new $dysmsapi.SendSmsRequest({}); - // request.phoneNumbers = phone; - // request.signName = (() => { - // switch (type) { - // case 'login': - // return process.env.ALIYUN_SMS_LOGIN_SIGN_NAME; - // default: - // throw new Error('Unknown SMS type'); - // } - // })(); - // request.templateCode = code; - - // await client.sendSms(request).then(a => { - // console.log(a) - // }).catch(err => { - // console.error(err); - // }) - } + /** + * @deprecated 短信签名暂未通过 + */ + async sendSMS(phone: string, type: 'login', code: string) { + throw new Error( + `SMS sending is not implemented yet. Phone: ${phone}, Type: ${type}, Code: ${code}`, + ); + // const config = new $OpenApi.Config({ + // accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID, + // accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET, + // }) + // config.endpoint = 'dysmsapi.aliyuncs.com'; + // const client = new Client(config); + // const request = new $dysmsapi.SendSmsRequest({}); + // request.phoneNumbers = phone; + // request.signName = (() => { + // switch (type) { + // case 'login': + // return process.env.ALIYUN_SMS_LOGIN_SIGN_NAME; + // default: + // throw new Error('Unknown SMS type'); + // } + // })(); + // request.templateCode = code; + // await client.sendSms(request).then(a => { + // console.log(a) + // }).catch(err => { + // console.error(err); + // }) + } } diff --git a/tone-page-server/src/oss/oss.controller.ts b/tone-page-server/src/oss/oss.controller.ts index 0669e23..ac22da3 100644 --- a/tone-page-server/src/oss/oss.controller.ts +++ b/tone-page-server/src/oss/oss.controller.ts @@ -4,18 +4,15 @@ import { AuthGuard } from '@nestjs/passport'; @Controller('oss') export class OssController { + constructor(private readonly ossService: OssService) {} - constructor( - private readonly ossService: OssService, - ) { } - - @UseGuards(AuthGuard('jwt')) - @Get('sts') - async getStsToken(@Request() req) { - const { userId, sessionId } = req.user; - return { - ...await this.ossService.getStsToken(`${userId}`), - userId, - } - } + @UseGuards(AuthGuard('jwt')) + @Get('sts') + async getStsToken(@Request() req) { + const { userId } = req.user; + return { + ...(await this.ossService.getStsToken(`${userId}`)), + userId, + }; + } } diff --git a/tone-page-server/src/oss/oss.module.ts b/tone-page-server/src/oss/oss.module.ts index f8211f8..6d3ae18 100644 --- a/tone-page-server/src/oss/oss.module.ts +++ b/tone-page-server/src/oss/oss.module.ts @@ -4,6 +4,6 @@ import { OssController } from './oss.controller'; @Module({ providers: [OssService], - controllers: [OssController] + controllers: [OssController], }) export class OssModule {} diff --git a/tone-page-server/src/oss/oss.service.ts b/tone-page-server/src/oss/oss.service.ts index 3894afc..3ad5c62 100644 --- a/tone-page-server/src/oss/oss.service.ts +++ b/tone-page-server/src/oss/oss.service.ts @@ -3,51 +3,51 @@ import { STS } from 'ali-oss'; @Injectable() export class OssService { + private sts = new STS({ + accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID, + accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET, + }); - private sts = new STS({ - accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID, - accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET, - }); + private stsCache: { + [session: string]: { + credentials: { + AccessKeyId: string; + AccessKeySecret: string; + SecurityToken: string; + Expiration: string; + }; + expireTime: number; // 时间戳,单位为毫秒 + }; + } = {}; - private stsCache: { - [session: string]: { - credentials: { - AccessKeyId: string; - AccessKeySecret: string; - SecurityToken: string; - Expiration: string; - }; - expireTime: number; // 时间戳,单位为毫秒 - } - } = {}; - - /** @todo 该方法存在缓存穿透问题,待优化 */ - async getStsToken(session: string) { - if (this.stsCache[session]) { - const cached = this.stsCache[session]; - // 检查缓存是否过期 - if (cached.expireTime > Date.now()) { - return cached.credentials; - } else { - // 如果过期,删除缓存 - delete this.stsCache[session]; - } - } - - return this.sts.assumeRole( - process.env.ALIYUN_OSS_STS_ROLE_ARN, ``, 3600, `${session}`, - ).then((res) => { - // 缓存 - this.stsCache[session] = { - credentials: res.credentials, - expireTime: new Date(res.credentials.Expiration).getTime() - 5 * 60 * 1000, // 提前5分钟过期, - }; - - return res.credentials; - }).catch(err => { - console.error('获取STS Token失败:', err); - throw new Error('获取STS Token失败'); - }) + /** @todo 该方法存在缓存穿透问题,待优化 */ + async getStsToken(session: string) { + if (this.stsCache[session]) { + const cached = this.stsCache[session]; + // 检查缓存是否过期 + if (cached.expireTime > Date.now()) { + return cached.credentials; + } else { + // 如果过期,删除缓存 + delete this.stsCache[session]; + } } + return this.sts + .assumeRole(process.env.ALIYUN_OSS_STS_ROLE_ARN, ``, 3600, `${session}`) + .then((res) => { + // 缓存 + this.stsCache[session] = { + credentials: res.credentials, + expireTime: + new Date(res.credentials.Expiration).getTime() - 5 * 60 * 1000, // 提前5分钟过期, + }; + + return res.credentials; + }) + .catch((err) => { + console.error('获取STS Token失败:', err); + throw new Error('获取STS Token失败'); + }); + } } diff --git a/tone-page-server/src/resource/entity/resource.entity.ts b/tone-page-server/src/resource/entity/resource.entity.ts index 3634b11..9236d9c 100644 --- a/tone-page-server/src/resource/entity/resource.entity.ts +++ b/tone-page-server/src/resource/entity/resource.entity.ts @@ -1,34 +1,41 @@ -import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; +import { + Column, + CreateDateColumn, + Entity, + Index, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; type ResourceTag = { - name: string; - type: string; -} + name: string; + type: string; +}; @Entity() export class Resource { - @PrimaryGeneratedColumn('uuid') - @Index() - id: string; + @PrimaryGeneratedColumn('uuid') + @Index() + id: string; - @Column() - title: string; + @Column() + title: string; - @Column() - description: string; + @Column() + description: string; - @Column() - imageUrl: string; + @Column() + imageUrl: string; - @Column() - link: string; + @Column() + link: string; - @Column('jsonb') - tags: ResourceTag[]; + @Column('jsonb') + tags: ResourceTag[]; - @CreateDateColumn({ precision: 3 }) - createdAt: Date; + @CreateDateColumn({ precision: 3 }) + createdAt: Date; - @UpdateDateColumn({ precision: 3 }) - updatedAt: Date; -} \ No newline at end of file + @UpdateDateColumn({ precision: 3 }) + updatedAt: Date; +} diff --git a/tone-page-server/src/resource/resource.controller.ts b/tone-page-server/src/resource/resource.controller.ts index b7db8af..5e78481 100644 --- a/tone-page-server/src/resource/resource.controller.ts +++ b/tone-page-server/src/resource/resource.controller.ts @@ -3,13 +3,10 @@ import { ResourceService } from './resource.service'; @Controller('resource') export class ResourceController { + constructor(private readonly resourceService: ResourceService) {} - constructor( - private readonly resourceService: ResourceService, - ) { } - - @Get() - async getResource() { - return this.resourceService.findAll(); - } + @Get() + async getResource() { + return this.resourceService.findAll(); + } } diff --git a/tone-page-server/src/resource/resource.module.ts b/tone-page-server/src/resource/resource.module.ts index 88ee340..b6dd653 100644 --- a/tone-page-server/src/resource/resource.module.ts +++ b/tone-page-server/src/resource/resource.module.ts @@ -5,7 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { Resource } from './entity/resource.entity'; @Module({ - imports:[TypeOrmModule.forFeature([Resource])], + imports: [TypeOrmModule.forFeature([Resource])], controllers: [ResourceController], providers: [ResourceService], exports: [ResourceService], diff --git a/tone-page-server/src/resource/resource.service.ts b/tone-page-server/src/resource/resource.service.ts index e3f8fff..8334320 100644 --- a/tone-page-server/src/resource/resource.service.ts +++ b/tone-page-server/src/resource/resource.service.ts @@ -5,34 +5,34 @@ import { InjectRepository } from '@nestjs/typeorm'; @Injectable() export class ResourceService { - constructor( - @InjectRepository(Resource) - private readonly resourceRepository: Repository, - ) { } + constructor( + @InjectRepository(Resource) + private readonly resourceRepository: Repository, + ) {} - async findAll(): Promise { - return this.resourceRepository.find({ - order: { - createdAt: 'DESC', - } - }); - } + async findAll(): Promise { + return this.resourceRepository.find({ + order: { + createdAt: 'DESC', + }, + }); + } - async findById(id: string): Promise { - return this.resourceRepository.findOne({ where: { id } }); - } + async findById(id: string): Promise { + return this.resourceRepository.findOne({ where: { id } }); + } - async create(data: Partial): Promise { - const resource = this.resourceRepository.create(data); - return this.resourceRepository.save(resource); - } + async create(data: Partial): Promise { + const resource = this.resourceRepository.create(data); + return this.resourceRepository.save(resource); + } - async update(id: string, data: Partial): Promise { - await this.resourceRepository.update(id, data); - return this.resourceRepository.findOne({ where: { id } }); - } + async update(id: string, data: Partial): Promise { + await this.resourceRepository.update(id, data); + return this.resourceRepository.findOne({ where: { id } }); + } - async delete(id: string): Promise { - await this.resourceRepository.delete(id); - } + async delete(id: string): Promise { + await this.resourceRepository.delete(id); + } } diff --git a/tone-page-server/src/role/entities/permission.entity.ts b/tone-page-server/src/role/entities/permission.entity.ts index 5a2ae04..104d7fe 100644 --- a/tone-page-server/src/role/entities/permission.entity.ts +++ b/tone-page-server/src/role/entities/permission.entity.ts @@ -1,13 +1,13 @@ -import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class Permission { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ unique: true }) - name: string; + @Column({ unique: true }) + name: string; - @Column() - description: string; -} \ No newline at end of file + @Column() + description: string; +} diff --git a/tone-page-server/src/role/entities/role-permission.entity.ts b/tone-page-server/src/role/entities/role-permission.entity.ts index 24fe6a9..4bab1ed 100644 --- a/tone-page-server/src/role/entities/role-permission.entity.ts +++ b/tone-page-server/src/role/entities/role-permission.entity.ts @@ -1,11 +1,11 @@ -import { Entity, Index, PrimaryColumn } from "typeorm"; +import { Entity, Index, PrimaryColumn } from 'typeorm'; @Entity() @Index(['roleId', 'permissionId'], { unique: true }) export class RolePermission { - @PrimaryColumn('uuid') - roleId: string; + @PrimaryColumn('uuid') + roleId: string; - @PrimaryColumn('uuid') - permissionId: string; -} \ No newline at end of file + @PrimaryColumn('uuid') + permissionId: string; +} diff --git a/tone-page-server/src/role/entities/role.entity.ts b/tone-page-server/src/role/entities/role.entity.ts index 762907d..60a5fe6 100644 --- a/tone-page-server/src/role/entities/role.entity.ts +++ b/tone-page-server/src/role/entities/role.entity.ts @@ -1,13 +1,13 @@ -import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class Role { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ unique: true }) - name: string; + @Column({ unique: true }) + name: string; - @Column() - localName: string; -} \ No newline at end of file + @Column() + localName: string; +} diff --git a/tone-page-server/src/role/entities/user-role.entity.ts b/tone-page-server/src/role/entities/user-role.entity.ts index 6623a20..6b126b4 100644 --- a/tone-page-server/src/role/entities/user-role.entity.ts +++ b/tone-page-server/src/role/entities/user-role.entity.ts @@ -1,23 +1,29 @@ -import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn } from "typeorm"; +import { + Column, + CreateDateColumn, + Entity, + Index, + PrimaryGeneratedColumn, +} from 'typeorm'; @Entity() @Index(['userId', 'roleId']) export class UserRole { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column('uuid') - roleId: string; + @Column('uuid') + roleId: string; - @Column('uuid') - userId: string + @Column('uuid') + userId: string; - @Column() - isEnabled: boolean; + @Column() + isEnabled: boolean; - @CreateDateColumn({ precision: 3 }) - createdAt: Date; + @CreateDateColumn({ precision: 3 }) + createdAt: Date; - @Column({ nullable: true, precision: 3 }) - expiredAt?: Date; -} \ No newline at end of file + @Column({ nullable: true, precision: 3 }) + expiredAt?: Date; +} diff --git a/tone-page-server/src/role/role.module.ts b/tone-page-server/src/role/role.module.ts index ba66ff0..e21a29d 100644 --- a/tone-page-server/src/role/role.module.ts +++ b/tone-page-server/src/role/role.module.ts @@ -10,8 +10,20 @@ import { UserRole } from './entities/user-role.entity'; import { PermissionService } from './services/permission.service'; @Module({ - imports: [TypeOrmModule.forFeature([Role, Permission, RolePermission, UserRole])], - providers: [RolePermissionService, RoleService, UserRoleService, PermissionService], - exports: [RolePermissionService, RoleService, UserRoleService, PermissionService], + imports: [ + TypeOrmModule.forFeature([Role, Permission, RolePermission, UserRole]), + ], + providers: [ + RolePermissionService, + RoleService, + UserRoleService, + PermissionService, + ], + exports: [ + RolePermissionService, + RoleService, + UserRoleService, + PermissionService, + ], }) -export class RoleModule { } +export class RoleModule {} diff --git a/tone-page-server/src/role/services/permission.service.ts b/tone-page-server/src/role/services/permission.service.ts index 6a6ee84..20dee22 100644 --- a/tone-page-server/src/role/services/permission.service.ts +++ b/tone-page-server/src/role/services/permission.service.ts @@ -1,51 +1,59 @@ -import { BadRequestException, Injectable } from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; -import { Permission } from "../entities/permission.entity"; -import { In, Repository } from "typeorm"; +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Permission } from '../entities/permission.entity'; +import { In, Repository } from 'typeorm'; @Injectable() export class PermissionService { + constructor( + @InjectRepository(Permission) + private readonly permissionRepository: Repository, + ) {} - constructor( - @InjectRepository(Permission) - private readonly permissionRepository: Repository, - ) { } + async findPermissionNamesByPermissionIds( + permissionIds: string[], + ): Promise { + const permissions = + await this.findPermissionsByPermissionIds(permissionIds); + return permissions.map((permission) => permission.name); + } - async findPermissionNamesByPermissionIds(permissionIds: string[]): Promise { - const permissions = await this.findPermissionsByPermissionIds(permissionIds); - return permissions.map(permission => permission.name); + async findPermissionsByPermissionIds( + permissionIds: string[], + ): Promise { + return this.permissionRepository.find({ + where: { + id: In(permissionIds), + }, + }); + } + + async findPermissionByIds(permissionIds: string[]): Promise { + return this.permissionRepository.find({ + where: { + id: In(permissionIds), + }, + }); + } + + async list() { + return this.permissionRepository.find(); + } + + async create( + permission: Pick, + ): Promise { + const newPermission = this.permissionRepository.create(permission); + return this.permissionRepository.save(newPermission); + } + + async delete(permissionId: string): Promise { + const existingPermission = await this.permissionRepository.findOne({ + where: { id: permissionId }, + }); + if (!existingPermission) { + throw new BadRequestException('Permission not found'); } - - async findPermissionsByPermissionIds(permissionIds: string[]): Promise { - return this.permissionRepository.find({ - where: { - id: In(permissionIds), - } - }) - } - - async findPermissionByIds(permissionIds: string[]): Promise { - return this.permissionRepository.find({ - where: { - id: In(permissionIds), - } - }); - } - - async list() { - return this.permissionRepository.find(); - } - - async create(permission: Pick): Promise { - const newPermission = this.permissionRepository.create(permission); - return this.permissionRepository.save(newPermission); - } - - async delete(permissionId: string): Promise { - const existingPermission = await this.permissionRepository.findOne({ where: { id: permissionId } }); - if (!existingPermission) { - throw new BadRequestException('Permission not found'); - } - await this.permissionRepository.delete(existingPermission.id); - } -} \ No newline at end of file + await this.permissionRepository.delete(existingPermission.id); + } +} diff --git a/tone-page-server/src/role/services/role-permission.service.ts b/tone-page-server/src/role/services/role-permission.service.ts index 87381ba..8036e22 100644 --- a/tone-page-server/src/role/services/role-permission.service.ts +++ b/tone-page-server/src/role/services/role-permission.service.ts @@ -1,42 +1,47 @@ -import { Injectable } from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; -import { RolePermission } from "../entities/role-permission.entity"; -import { In, Repository } from "typeorm"; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { RolePermission } from '../entities/role-permission.entity'; +import { In, Repository } from 'typeorm'; @Injectable() export class RolePermissionService { + constructor( + @InjectRepository(RolePermission) + private readonly rolePermissionRepository: Repository, + ) {} - constructor( - @InjectRepository(RolePermission) - private readonly rolePermissionRepository: Repository, - ) { } + async findPermissionIdsByRoleIds(roleIds: string[]): Promise { + const rolePermissions = await this.rolePermissionRepository.find({ + where: { + roleId: In(roleIds), + }, + }); - async findPermissionIdsByRoleIds(roleIds: string[]): Promise { - const rolePermissions = await this.rolePermissionRepository.find({ - where: { - roleId: In(roleIds), - } - }); + return rolePermissions.map((rp) => rp.permissionId); + } - return rolePermissions.map(rp => rp.permissionId); - } + async addRolePermissions( + roleId: string, + permissionIds: string[], + ): Promise { + const rolePermissions = permissionIds.map((permissionId) => { + const rolePermission = this.rolePermissionRepository.create({ + roleId, + permissionId, + }); + return rolePermission; + }); - async addRolePermissions(roleId: string, permissionIds: string[]): Promise { - const rolePermissions = permissionIds.map(permissionId => { - const rolePermission = this.rolePermissionRepository.create({ - roleId, - permissionId, - }); - return rolePermission; - }); + await this.rolePermissionRepository.save(rolePermissions); + } - await this.rolePermissionRepository.save(rolePermissions); - } - - async deleteRolePermissions(roleId: string, permissionIds: string[]): Promise { - await this.rolePermissionRepository.delete({ - roleId, - permissionId: In(permissionIds), - }); - } -} \ No newline at end of file + async deleteRolePermissions( + roleId: string, + permissionIds: string[], + ): Promise { + await this.rolePermissionRepository.delete({ + roleId, + permissionId: In(permissionIds), + }); + } +} diff --git a/tone-page-server/src/role/services/role.service.ts b/tone-page-server/src/role/services/role.service.ts index 9c3f352..b1d807e 100644 --- a/tone-page-server/src/role/services/role.service.ts +++ b/tone-page-server/src/role/services/role.service.ts @@ -1,43 +1,44 @@ -import { BadRequestException, Injectable } from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; -import { Role } from "../entities/role.entity"; -import { In, Repository } from "typeorm"; +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Role } from '../entities/role.entity'; +import { In, Repository } from 'typeorm'; @Injectable() export class RoleService { + constructor( + @InjectRepository(Role) + private readonly roleRepository: Repository, + ) {} - constructor( - @InjectRepository(Role) - private readonly roleRepository: Repository, - ) { } + async findRoleNamesByRoleIds(roleIds: string[]): Promise { + const roles = await this.findRolesByRoleIds(roleIds); + return roles.map((role) => role.name); + } - async findRoleNamesByRoleIds(roleIds: string[]): Promise { - const roles = await this.findRolesByRoleIds(roleIds); - return roles.map(role => role.name); + async findRolesByRoleIds(roleIds: string[]): Promise { + return this.roleRepository.find({ + where: { + id: In(roleIds), + }, + }); + } + + async create(role: Pick): Promise { + const newRole = this.roleRepository.create(role); + return this.roleRepository.save(newRole); + } + + async list(): Promise { + return this.roleRepository.find(); + } + + async delete(roleId: string): Promise { + const existingRole = await this.roleRepository.findOne({ + where: { id: roleId }, + }); + if (!existingRole) { + throw new BadRequestException('Role not found'); } - - async findRolesByRoleIds(roleIds: string[]): Promise { - return this.roleRepository.find({ - where: { - id: In(roleIds), - } - }) - } - - async create(role: Pick): Promise { - const newRole = this.roleRepository.create(role); - return this.roleRepository.save(newRole); - } - - async list(): Promise { - return this.roleRepository.find(); - } - - async delete(roleId: string): Promise { - const existingRole = await this.roleRepository.findOne({ where: { id: roleId } }); - if (!existingRole) { - throw new BadRequestException('Role not found'); - } - await this.roleRepository.delete(existingRole.id); - } -} \ No newline at end of file + await this.roleRepository.delete(existingRole.id); + } +} diff --git a/tone-page-server/src/role/services/user-role.service.ts b/tone-page-server/src/role/services/user-role.service.ts index 4b7d1ad..b3317bc 100644 --- a/tone-page-server/src/role/services/user-role.service.ts +++ b/tone-page-server/src/role/services/user-role.service.ts @@ -1,57 +1,59 @@ -import { Injectable } from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; -import { UserRole } from "src/role/entities/user-role.entity"; -import { IsNull, MoreThanOrEqual, Repository } from "typeorm"; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { UserRole } from 'src/role/entities/user-role.entity'; +import { IsNull, MoreThanOrEqual, Repository } from 'typeorm'; @Injectable() export class UserRoleService { - constructor( - @InjectRepository(UserRole) - private readonly userRoleRepository: Repository, - ) { } + constructor( + @InjectRepository(UserRole) + private readonly userRoleRepository: Repository, + ) {} - async findRoleIdsByUserId(userId: string): Promise { - const userRoles = await this.userRoleRepository.find({ - where: { - userId, - } - }); + async findRoleIdsByUserId(userId: string): Promise { + const userRoles = await this.userRoleRepository.find({ + where: { + userId, + }, + }); - return userRoles.map(ur => ur.roleId); - } + return userRoles.map((ur) => ur.roleId); + } - async findValidRoleIdsByUserId(userId: string): Promise { - return (await this.findValidRolesByUserId(userId)).map(ur => ur.roleId); - } + async findValidRoleIdsByUserId(userId: string): Promise { + return (await this.findValidRolesByUserId(userId)).map((ur) => ur.roleId); + } - async findValidRolesByUserId(userId: string) { - const now = new Date(); + async findValidRolesByUserId(userId: string) { + const now = new Date(); - return this.userRoleRepository.find({ - where: [ - { - userId, - isEnabled: true, - expiredAt: MoreThanOrEqual(now), - }, - { - userId, - isEnabled: true, - expiredAt: IsNull(), - } - ] - }) - } + return this.userRoleRepository.find({ + where: [ + { + userId, + isEnabled: true, + expiredAt: MoreThanOrEqual(now), + }, + { + userId, + isEnabled: true, + expiredAt: IsNull(), + }, + ], + }); + } - async addUserRole(userRole: Pick): Promise { - const newUserRole = this.userRoleRepository.create(userRole); - await this.userRoleRepository.save(newUserRole); - } + async addUserRole( + userRole: Pick, + ): Promise { + const newUserRole = this.userRoleRepository.create(userRole); + await this.userRoleRepository.save(newUserRole); + } - async deleteUserRole(userId: string, roleId: string): Promise { - await this.userRoleRepository.delete({ - userId, - roleId, - }); - } -} \ No newline at end of file + async deleteUserRole(userId: string, roleId: string): Promise { + await this.userRoleRepository.delete({ + userId, + roleId, + }); + } +} diff --git a/tone-page-server/src/user/dto/update-user-password.dto.ts b/tone-page-server/src/user/dto/update-user-password.dto.ts index e437759..002ca4f 100644 --- a/tone-page-server/src/user/dto/update-user-password.dto.ts +++ b/tone-page-server/src/user/dto/update-user-password.dto.ts @@ -1,10 +1,11 @@ -import { IsString, Length, Matches } from "class-validator"; +import { IsString, Length, Matches } from 'class-validator'; export class UpdateUserPasswordDto { - @IsString({ message: '密码不得为空' }) - @Length(6, 32, { message: '密码长度只能为6~32' }) - @Matches(/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{6,32}$/, - { message: '密码必须包含字母和数字,且长度在6~32之间' } - ) - password: string; -} \ No newline at end of file + @IsString({ message: '密码不得为空' }) + @Length(6, 32, { message: '密码长度只能为6~32' }) + @Matches( + /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{6,32}$/, + { message: '密码必须包含字母和数字,且长度在6~32之间' }, + ) + password: string; +} diff --git a/tone-page-server/src/user/entities/user-session.entity.ts b/tone-page-server/src/user/entities/user-session.entity.ts index 99d226a..e15bafe 100644 --- a/tone-page-server/src/user/entities/user-session.entity.ts +++ b/tone-page-server/src/user/entities/user-session.entity.ts @@ -1,20 +1,27 @@ -import { Column, CreateDateColumn, DeleteDateColumn, Entity, Index, PrimaryGeneratedColumn } from "typeorm"; +import { + Column, + CreateDateColumn, + DeleteDateColumn, + Entity, + Index, + PrimaryGeneratedColumn, +} from 'typeorm'; @Entity() @Index(['sessionId', 'userId']) export class UserSession { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ length: 36 }) - sessionId: string; + @Column({ length: 36 }) + sessionId: string; - @Column({ length: 36 }) - userId: string; + @Column({ length: 36 }) + userId: string; - @CreateDateColumn({ precision: 3 }) - createdAt: Date; + @CreateDateColumn({ precision: 3 }) + createdAt: Date; - @DeleteDateColumn({ nullable: true, precision: 3 }) - deletedAt: Date; -} \ No newline at end of file + @DeleteDateColumn({ nullable: true, precision: 3 }) + deletedAt: Date; +} diff --git a/tone-page-server/src/user/entities/user.entity.ts b/tone-page-server/src/user/entities/user.entity.ts index 5b79091..217d27d 100644 --- a/tone-page-server/src/user/entities/user.entity.ts +++ b/tone-page-server/src/user/entities/user.entity.ts @@ -1,72 +1,87 @@ -import { BeforeInsert, Column, CreateDateColumn, DeleteDateColumn, Entity, Index, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; +import { + BeforeInsert, + Column, + CreateDateColumn, + DeleteDateColumn, + Entity, + Index, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; import { v4 as uuidv4 } from 'uuid'; @Entity() -@Index("IDX_user_userid", ["userId"], { unique: true }) -@Index("IDX_user_username", ["username"], { unique: true }) -@Index("IDX_user_email", ["email"], { unique: true, where: "email IS NOT NULL" }) -@Index("IDX_user_phone", ["phone"], { unique: true, where: "phone IS NOT NULL" }) +@Index('IDX_user_userid', ['userId'], { unique: true }) +@Index('IDX_user_username', ['username'], { unique: true }) +@Index('IDX_user_email', ['email'], { + unique: true, + where: 'email IS NOT NULL', +}) +@Index('IDX_user_phone', ['phone'], { + unique: true, + where: 'phone IS NOT NULL', +}) export class User { - @PrimaryGeneratedColumn('uuid') - userId: string; + @PrimaryGeneratedColumn('uuid') + userId: string; - @Column({ length: 32 }) - username: string; + @Column({ length: 32 }) + username: string; - @Column({ length: 30 }) - nickname: string; + @Column({ length: 30 }) + nickname: string; - @BeforeInsert() - generateDefaults() { - if (!this.username) { - this.username = `user_${uuidv4().replace(/-/g, '').slice(0, 27)}`; - } - if (!this.nickname) { - this.nickname = `用户_${uuidv4().replace(/-/g, '').slice(0, 8)}`; - } + @BeforeInsert() + generateDefaults() { + if (!this.username) { + this.username = `user_${uuidv4().replace(/-/g, '').slice(0, 27)}`; } + if (!this.nickname) { + this.nickname = `用户_${uuidv4().replace(/-/g, '').slice(0, 8)}`; + } + } - @Column({ nullable: true, type: 'char', length: 32 }) - salt: string; + @Column({ nullable: true, type: 'char', length: 32 }) + salt: string; - @Column({ nullable: true, type: 'char', length: 64 }) - password_hash: string; + @Column({ nullable: true, type: 'char', length: 64 }) + password_hash: string; - @Column({ - nullable: true, - length: 254, - transformer: { - to: (value: string | null) => value?.trim() || null, - from: (value: string | null) => value, - } - })// RFC 5321 - email: string | null; + @Column({ + nullable: true, + length: 254, + transformer: { + to: (value: string | null) => value?.trim() || null, + from: (value: string | null) => value, + }, + }) // RFC 5321 + email: string | null; - @Column({ - nullable: true, - length: 20, - transformer: { - to: (value: string | null) => value?.trim() || null, - from: (value: string | null) => value, - } - })// China Mainland - phone: string | null; + @Column({ + nullable: true, + length: 20, + transformer: { + to: (value: string | null) => value?.trim() || null, + from: (value: string | null) => value, + }, + }) // China Mainland + phone: string | null; - @Column({ - nullable: true, - transformer: { - to: (value: string | null) => value?.trim() || null, - from: (value: string | null) => value, - } - }) - avatar: string; + @Column({ + nullable: true, + transformer: { + to: (value: string | null) => value?.trim() || null, + from: (value: string | null) => value, + }, + }) + avatar: string; - @CreateDateColumn({ precision: 3 }) - createdAt: Date; + @CreateDateColumn({ precision: 3 }) + createdAt: Date; - @UpdateDateColumn({ precision: 3 }) - updatedAt: Date; + @UpdateDateColumn({ precision: 3 }) + updatedAt: Date; - @DeleteDateColumn({ nullable: true, precision: 3 }) - deletedAt: Date; -} \ No newline at end of file + @DeleteDateColumn({ nullable: true, precision: 3 }) + deletedAt: Date; +} diff --git a/tone-page-server/src/user/services/user-session.service.ts b/tone-page-server/src/user/services/user-session.service.ts index 6bc0a70..e48cb61 100644 --- a/tone-page-server/src/user/services/user-session.service.ts +++ b/tone-page-server/src/user/services/user-session.service.ts @@ -1,47 +1,46 @@ -import { InjectRepository } from "@nestjs/typeorm"; -import { Injectable } from "@nestjs/common"; -import { UserSession } from "../entities/user-session.entity"; -import { Repository } from "typeorm"; +import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common'; +import { UserSession } from '../entities/user-session.entity'; +import { Repository } from 'typeorm'; @Injectable() export class UserSessionService { - constructor( - @InjectRepository(UserSession) - private readonly userSessionRepository: Repository, - ) { } + constructor( + @InjectRepository(UserSession) + private readonly userSessionRepository: Repository, + ) {} + async createSession(userId: string, sessionId: string): Promise { + const session = this.userSessionRepository.create({ + userId, + sessionId, + }); + return await this.userSessionRepository.save(session); + } - async createSession(userId: string, sessionId: string): Promise { - const session = this.userSessionRepository.create({ - userId, - sessionId, - }); - return await this.userSessionRepository.save(session); + async isSessionValid(userId: string, sessionId: string): Promise { + const session = await this.userSessionRepository.findOne({ + where: { + userId, + sessionId, + deletedAt: null, + }, + }); + + return !!session; + } + + async invalidateSession(userId: string, sessionId: string): Promise { + const session = await this.userSessionRepository.findOne({ + where: { + userId, + sessionId, + deletedAt: null, + }, + }); + + if (session) { + await this.userSessionRepository.softDelete(session.id); } - - async isSessionValid(userId: string, sessionId: string): Promise { - const session = await this.userSessionRepository.findOne({ - where: { - userId, - sessionId, - deletedAt: null, - } - }); - - return !!session; - } - - async invalidateSession(userId: string, sessionId: string): Promise { - const session = await this.userSessionRepository.findOne({ - where: { - userId, - sessionId, - deletedAt: null, - } - }); - - if (session) { - await this.userSessionRepository.softDelete(session.id); - } - } -} \ No newline at end of file + } +} diff --git a/tone-page-server/src/user/user.controller.ts b/tone-page-server/src/user/user.controller.ts index 78603d6..4991321 100644 --- a/tone-page-server/src/user/user.controller.ts +++ b/tone-page-server/src/user/user.controller.ts @@ -6,25 +6,21 @@ import { AuthService } from 'src/auth/auth.service'; @Controller('user') export class UserController { + constructor( + private readonly userService: UserService, + private readonly authService: AuthService, + ) {} - constructor( - private readonly userService: UserService, - private readonly authService: AuthService, - ) { } + @UseGuards(AuthGuard('jwt')) + @Get('me') + async getMe(@Request() req) { + const { user } = req; + return this.userService.findOne({ userId: user.userId }); + } - @UseGuards(AuthGuard('jwt')) - @Get('me') - async getMe(@Request() req) { - const { user } = req; - return this.userService.findOne({ userId: user.userId }); - } - - @UseGuards(AuthGuard('jwt')) - @Put('password') - async update( - @Request() req, - @Body() dto: UpdateUserPasswordDto, - ) { - return this.userService.setPassword(req.user.userId, dto.password); - } + @UseGuards(AuthGuard('jwt')) + @Put('password') + async update(@Request() req, @Body() dto: UpdateUserPasswordDto) { + return this.userService.setPassword(req.user.userId, dto.password); + } } diff --git a/tone-page-server/src/user/user.module.ts b/tone-page-server/src/user/user.module.ts index 00f7280..25cd032 100644 --- a/tone-page-server/src/user/user.module.ts +++ b/tone-page-server/src/user/user.module.ts @@ -8,12 +8,12 @@ import { AuthModule } from 'src/auth/auth.module'; import { UserSessionService } from './services/user-session.service'; @Module({ - imports: [ - TypeOrmModule.forFeature([User, UserSession]), - forwardRef(() => AuthModule),// 解决循环依赖问题 - ], - controllers: [UserController], - providers: [UserService, UserSessionService], - exports: [UserService, UserSessionService], + imports: [ + TypeOrmModule.forFeature([User, UserSession]), + forwardRef(() => AuthModule), // 解决循环依赖问题 + ], + controllers: [UserController], + providers: [UserService, UserSessionService], + exports: [UserService, UserSessionService], }) -export class UserModule { } +export class UserModule {} diff --git a/tone-page-server/src/user/user.service.ts b/tone-page-server/src/user/user.service.ts index 366003f..a7756f4 100644 --- a/tone-page-server/src/user/user.service.ts +++ b/tone-page-server/src/user/user.service.ts @@ -1,124 +1,138 @@ -import { BadRequestException, ConflictException, Injectable } from '@nestjs/common'; +import { + BadRequestException, + ConflictException, + Injectable, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; import { QueryFailedError, Repository } from 'typeorm'; -import { createHash, ECDH } from 'crypto'; +import { createHash } from 'crypto'; import { v4 as uuid } from 'uuid'; -type UserFindOptions = Partial>; +type UserFindOptions = Partial< + Pick +>; @Injectable() export class UserService { - constructor( - @InjectRepository(User) - private readonly userRepository: Repository, - ) { } + constructor( + @InjectRepository(User) + private readonly userRepository: Repository, + ) {} - async findOne(options: UserFindOptions | UserFindOptions[], additionalOptions?: { withDeleted?: boolean }): Promise { - if (Object.keys(options).length === 0) { - throw new BadRequestException('查询条件不能为空'); - } - return this.userRepository.findOne({ - where: options, - withDeleted: additionalOptions?.withDeleted || false, - }); + async findOne( + options: UserFindOptions | UserFindOptions[], + additionalOptions?: { withDeleted?: boolean }, + ): Promise { + if (Object.keys(options).length === 0) { + throw new BadRequestException('查询条件不能为空'); + } + return this.userRepository.findOne({ + where: options, + withDeleted: additionalOptions?.withDeleted || false, + }); + } + + async create(user: Partial): Promise { + try { + const newUser = this.userRepository.create(user); + return await this.userRepository.save(newUser); + } catch (error) { + if (error instanceof QueryFailedError) { + throw new ConflictException(this.getDuplicateErrorMessage(error)); + } + throw new BadRequestException('创建用户失败'); + } + } + + async update(userId: string, user: Partial): Promise { + const existingUser = await this.userRepository.findOne({ + where: { userId }, + }); + if (!existingUser) { + throw new BadRequestException('User not found'); + } + try { + Object.assign(existingUser, user); + return await this.userRepository.save(existingUser); + } catch (error) { + if (error instanceof QueryFailedError) { + throw new ConflictException(this.getDuplicateErrorMessage(error)); + } + } + } + + async delete(userId: string, soft: boolean) { + const existingUser = await this.userRepository.findOne({ + where: { userId }, + withDeleted: true, + }); + if (!existingUser) { + throw new BadRequestException('用户不存在'); } - async create(user: Partial): Promise { - try { - const newUser = this.userRepository.create(user); - return await this.userRepository.save(newUser); - } catch (error) { - if (error instanceof QueryFailedError) { - throw new ConflictException(this.getDuplicateErrorMessage(error)); - } - throw new BadRequestException('创建用户失败'); - } + if (existingUser.deletedAt && soft) { + throw new BadRequestException('账户已注销,不得重复操作'); } - async update(userId: string, user: Partial): Promise { - const existingUser = await this.userRepository.findOne({ where: { userId } }); - if (!existingUser) { - throw new BadRequestException('User not found'); - } - try { - Object.assign(existingUser, user); - return await this.userRepository.save(existingUser); - } catch (error) { - if (error instanceof QueryFailedError) { - throw new ConflictException(this.getDuplicateErrorMessage(error)); - } - } + if (!existingUser.deletedAt && !soft) { + throw new BadRequestException('账号未注销,请先注销再执行删除操作'); } - async delete(userId: string, soft: boolean) { - const existingUser = await this.userRepository.findOne({ where: { userId }, withDeleted: true }); - if (!existingUser) { - throw new BadRequestException('用户不存在'); - } + return soft + ? await this.userRepository.softDelete(existingUser.userId) + : await this.userRepository.delete(existingUser.userId); + } - if (existingUser.deletedAt && soft) { - throw new BadRequestException('账户已注销,不得重复操作') - } + hashPassword(password: string, salt: string): string { + return createHash('sha256').update(`${password}${salt}`).digest('hex'); + } - if (!existingUser.deletedAt && !soft) { - throw new BadRequestException('账号未注销,请先注销再执行删除操作') - } + generateSalt(): string { + return uuid().replace(/-/g, ''); + } - return soft - ? await this.userRepository.softDelete(existingUser.userId) - : await this.userRepository.delete(existingUser.userId) + async setPassword(userId: string, password: string): Promise { + const user = await this.userRepository.findOne({ where: { userId } }); + if (!user) { + throw new BadRequestException('User not found'); } + const salt = this.generateSalt(); + user.password_hash = this.hashPassword(password, salt); + user.salt = salt; + return this.userRepository.save(user); + } - hashPassword(password: string, salt: string): string { - return createHash('sha256').update(`${password}${salt}`).digest('hex'); + private getDuplicateErrorMessage(error: QueryFailedError): string { + // 根据具体的错误信息返回友好的提示 + if (error.message.includes('IDX_user_username')) { + return '账户名已被使用'; } - - generateSalt(): string { - return uuid().replace(/-/g, ''); + if (error.message.includes('IDX_user_email')) { + return '邮箱已被使用'; } - - async setPassword(userId: string, password: string): Promise { - const user = await this.userRepository.findOne({ where: { userId } }); - if (!user) { - throw new BadRequestException('User not found'); - } - const salt = this.generateSalt(); - user.password_hash = this.hashPassword(password, salt); - user.salt = salt; - return this.userRepository.save(user); + if (error.message.includes('IDX_user_phone')) { + return '手机号已被使用'; } + return '数据已存在,请检查输入'; + } - private getDuplicateErrorMessage(error: QueryFailedError): string { - // 根据具体的错误信息返回友好的提示 - if (error.message.includes('IDX_user_username')) { - return '账户名已被使用'; - } - if (error.message.includes('IDX_user_email')) { - return '邮箱已被使用'; - } - if (error.message.includes('IDX_user_phone')) { - return '手机号已被使用'; - } - return '数据已存在,请检查输入'; - } + async list(page = 1, pageSize = 20) { + const queryBuilder = this.userRepository.createQueryBuilder('user'); - async list(page = 1, pageSize = 20) { - const queryBuilder = this.userRepository.createQueryBuilder('user') + queryBuilder.withDeleted(); - queryBuilder.withDeleted(); + queryBuilder.orderBy('user.createdAt', 'DESC'); - queryBuilder.orderBy('user.createdAt', 'DESC'); + queryBuilder.skip((page - 1) * pageSize); + queryBuilder.take(pageSize); - queryBuilder.skip((page - 1) * pageSize); - queryBuilder.take(pageSize); - - const [items, total] = await queryBuilder.getManyAndCount(); - return { - items, - total, - page, - pageSize, - } - } + const [items, total] = await queryBuilder.getManyAndCount(); + return { + items, + total, + page, + pageSize, + }; + } } diff --git a/tone-page-server/src/verification/dto/send-verification-code.dto.ts b/tone-page-server/src/verification/dto/send-verification-code.dto.ts index d73e221..1e5e7a5 100644 --- a/tone-page-server/src/verification/dto/send-verification-code.dto.ts +++ b/tone-page-server/src/verification/dto/send-verification-code.dto.ts @@ -1,19 +1,19 @@ -import { IsEnum, IsString, Length, ValidateIf } from "class-validator"; +import { IsEnum, IsString, Length, ValidateIf } from 'class-validator'; export class SendVerificationCodeDto { - @IsEnum(['phone', 'email'], { message: '请求类型错误' }) - targetType: 'phone' | 'email'; + @IsEnum(['phone', 'email'], { message: '请求类型错误' }) + targetType: 'phone' | 'email'; - @IsEnum(['login'], { message: '请求类型错误' }) - type: 'login' + @IsEnum(['login'], { message: '请求类型错误' }) + type: 'login'; - @ValidateIf(o => o.targetType === 'phone') - @IsString({ message: '手机号必须输入' }) - @Length(11, 11, { message: '手机号异常' })// 中国大陆,11位数字 - phone?: string; + @ValidateIf((o) => o.targetType === 'phone') + @IsString({ message: '手机号必须输入' }) + @Length(11, 11, { message: '手机号异常' }) // 中国大陆,11位数字 + phone?: string; - @ValidateIf(o => o.targetType === 'email') - @IsString({ message: '邮箱必须输入' }) - @Length(6, 254, { message: '邮箱异常' })// RFC 5321 - email?: string; -} \ No newline at end of file + @ValidateIf((o) => o.targetType === 'email') + @IsString({ message: '邮箱必须输入' }) + @Length(6, 254, { message: '邮箱异常' }) // RFC 5321 + email?: string; +} diff --git a/tone-page-server/src/verification/verification.controller.ts b/tone-page-server/src/verification/verification.controller.ts index d085765..aa4905f 100644 --- a/tone-page-server/src/verification/verification.controller.ts +++ b/tone-page-server/src/verification/verification.controller.ts @@ -4,25 +4,22 @@ import { VerificationService } from './verification.service'; @Controller('verification') export class VerificationController { + constructor(private readonly verificationService: VerificationService) {} - constructor( - private readonly verificationService: VerificationService, - ) { } - - @Post('send') - async sendVerificationCode(@Body() dto: SendVerificationCodeDto) { - switch (dto.type) { - case 'login': - switch (dto.targetType) { - case 'phone': - return this.verificationService.sendPhoneCode(dto.phone, dto.type); - case 'email': - return this.verificationService.sendEmailCode(dto.email, dto.type); - default: - throw new BadRequestException('不支持的目标类型'); - } - default: - throw new BadRequestException('不支持的验证码类型'); + @Post('send') + async sendVerificationCode(@Body() dto: SendVerificationCodeDto) { + switch (dto.type) { + case 'login': + switch (dto.targetType) { + case 'phone': + return this.verificationService.sendPhoneCode(dto.phone, dto.type); + case 'email': + return this.verificationService.sendEmailCode(dto.email, dto.type); + default: + throw new BadRequestException('不支持的目标类型'); } + default: + throw new BadRequestException('不支持的验证码类型'); } + } } diff --git a/tone-page-server/src/verification/verification.module.ts b/tone-page-server/src/verification/verification.module.ts index 5fdd0d2..ecc516e 100644 --- a/tone-page-server/src/verification/verification.module.ts +++ b/tone-page-server/src/verification/verification.module.ts @@ -9,4 +9,4 @@ import { NotificationModule } from 'src/notification/notification.module'; exports: [VerificationService], imports: [NotificationModule], }) -export class VerificationModule { } +export class VerificationModule {} diff --git a/tone-page-server/src/verification/verification.service.ts b/tone-page-server/src/verification/verification.service.ts index fa937a3..e41ef73 100644 --- a/tone-page-server/src/verification/verification.service.ts +++ b/tone-page-server/src/verification/verification.service.ts @@ -3,97 +3,96 @@ import { NotificationService } from 'src/notification/notification.service'; @Injectable() export class VerificationService { + private readonly logger = new Logger(VerificationService.name); - private readonly logger = new Logger(VerificationService.name); + constructor(private readonly notificationService: NotificationService) {} - constructor( - private readonly notificationService: NotificationService, - ) { } - - private pool: Map = new Map(); - - async sendPhoneCode(phone: string, type: 'login') { - const key = `phone:${phone}:${type}`; - // 检测是否在冷却时间内 - // TODO - - // 生成验证码 - const code = this.generateCode(); - this.logger.log(`Phone[${phone}] code: ${code}`); - - // 发送验证码 - // await this.notificationService.sendSMS(phone, type, code); - // 存储验证码 - this.saveCode(key, code); - return true; + private pool: Map< + string, + { + code: string; + createdAt: number; + expiredAt: number; + tryCount: number; + maxTryCount: number; } + > = new Map(); - async sendEmailCode(email: string, type: 'login') { - const key = `email:${email}:${type}`; - // 检测是否在冷却时间内 - // TODO + async sendPhoneCode(phone: string, type: 'login') { + const key = `phone:${phone}:${type}`; + // 检测是否在冷却时间内 + // TODO - // 生成验证码 - const code = this.generateCode(); - this.logger.log(`Email[${email}] code: ${code}`); - // 发送验证码 - // TODO + // 生成验证码 + const code = this.generateCode(); + this.logger.log(`Phone[${phone}] code: ${code}`); - // 存储验证码 - this.saveCode(key, code); - return true; + // 发送验证码 + // await this.notificationService.sendSMS(phone, type, code); + // 存储验证码 + this.saveCode(key, code); + return true; + } + + async sendEmailCode(email: string, type: 'login') { + const key = `email:${email}:${type}`; + // 检测是否在冷却时间内 + // TODO + + // 生成验证码 + const code = this.generateCode(); + this.logger.log(`Email[${email}] code: ${code}`); + // 发送验证码 + // TODO + + // 存储验证码 + this.saveCode(key, code); + return true; + } + + private saveCode(key: string, code: string) { + this.pool.set(key, { + code: code, + createdAt: Date.now(), + expiredAt: Date.now() + 10 * 60 * 1000, // 10分钟过期 + tryCount: 0, + maxTryCount: 5, + }); + } + + verifyPhoneCode(phone: string, code: string, type: 'login') { + const key = `phone:${phone}:${type}`; + return this.verifyCode(key, code); + } + + verifyEmailCode(email: string, code: string, type: 'login') { + const key = `email:${email}:${type}`; + return this.verifyCode(key, code); + } + + /** + * @returns 0: 验证码正确, -1: 验证码不存在或已过期, -2: 验证码错误, -3: 超过最大尝试次数 + */ + private verifyCode(key: string, code: string) { + const data = this.pool.get(key); + if (!data) { + return -1; } - - private saveCode(key: string, code: string) { - this.pool.set(key, { - code: code, - createdAt: Date.now(), - expiredAt: Date.now() + 10 * 60 * 1000, // 10分钟过期 - tryCount: 0, - maxTryCount: 5, - }); + if (data.tryCount >= data.maxTryCount) { + return -3; } - - verifyPhoneCode(phone: string, code: string, type: 'login') { - const key = `phone:${phone}:${type}`; - return this.verifyCode(key, code); + if (data.expiredAt < Date.now()) { + return -1; } - - verifyEmailCode(email: string, code: string, type: 'login') { - const key = `email:${email}:${type}`; - return this.verifyCode(key, code); + if (data.code !== code) { + data.tryCount++; + return -2; } + this.pool.delete(key); + return 0; + } - /** - * @returns 0: 验证码正确, -1: 验证码不存在或已过期, -2: 验证码错误, -3: 超过最大尝试次数 - */ - private verifyCode(key: string, code: string) { - const data = this.pool.get(key); - if (!data) { - return -1; - } - if (data.tryCount >= data.maxTryCount) { - return -3; - } - if (data.expiredAt < Date.now()) { - return -1; - } - if (data.code !== code) { - data.tryCount++; - return -2; - } - this.pool.delete(key); - return 0; - } - - private generateCode() { - return Math.floor(100000 + Math.random() * 900000).toString(); - } + private generateCode() { + return Math.floor(100000 + Math.random() * 900000).toString(); + } } -