feat: 优化项目目录结构

This commit is contained in:
2025-12-12 17:25:26 +08:00
parent ae627d0496
commit b89f83291e
235 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AdminController } from './admin.controller';
describe('AdminController', () => {
let controller: AdminController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AdminController],
}).compile();
controller = module.get<AdminController>(AdminController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,4 @@
import { Controller } from '@nestjs/common';
@Controller('admin')
export class AdminController {}

View File

@@ -0,0 +1,26 @@
import { Module } from '@nestjs/common';
import { AdminController } from './admin.controller';
import { AdminUserController } from './controller/admin-user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from 'src/user/entities/user.entity';
import { UserModule } from 'src/user/user.module';
import { AdminWebResourceController } from './controller/web/admin-web-resource.controller';
import { AdminWebBlogController } from './controller/web/admin-web-blog.controller';
import { ResourceModule } from 'src/resource/resource.module';
import { BlogModule } from 'src/blog/blog.module';
@Module({
imports: [
TypeOrmModule.forFeature([User]),
UserModule,
ResourceModule,
BlogModule,
],
controllers: [
AdminController,
AdminUserController,
AdminWebResourceController,
AdminWebBlogController,
],
})
export class AdminModule {}

View File

@@ -0,0 +1,83 @@
import {
Body,
Controller,
Delete,
Get,
Param,
ParseUUIDPipe,
Post,
Put,
Query,
UseGuards,
} 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 { RolesGuard } from 'src/common/guard/roles.guard';
import { Roles } from 'src/common/decorators/role.decorator';
import { Role } from 'src/auth/role.enum';
import { AuthGuard } from '@nestjs/passport';
@Controller('admin/user')
@UseGuards(AuthGuard('jwt'), RolesGuard)
@Roles(Role.Admin)
export class AdminUserController {
constructor(private readonly userService: UserService) {}
@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 });
}
@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);
}
@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);
}
}

View File

@@ -0,0 +1,64 @@
import {
Body,
Controller,
Delete,
Get,
Param,
ParseUUIDPipe,
Post,
Put,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { CreateBlogDto } from 'src/admin/dto/admin-web/create-blog.dto';
import { SetBlogPasswordDto } from 'src/admin/dto/admin-web/set-blog-password.dto';
import { UpdateBlogDto } from 'src/admin/dto/admin-web/update-blog.dto';
import { Role } from 'src/auth/role.enum';
import { BlogService } from 'src/blog/blog.service';
import { Roles } from 'src/common/decorators/role.decorator';
import { RolesGuard } from 'src/common/guard/roles.guard';
@Controller('/admin/web/blog')
@UseGuards(AuthGuard('jwt'), RolesGuard)
@Roles(Role.Admin)
export class AdminWebBlogController {
constructor(private readonly adminWebBlogService: BlogService) {}
@Get()
async list() {
return this.adminWebBlogService.list({
withAll: true,
});
}
@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: UpdateBlogDto,
) {
return this.adminWebBlogService.update(id, dto);
}
@Post(':id/password')
async setPassword(
@Param('id', new ParseUUIDPipe({ version: '4' })) id: string,
@Body() dto: SetBlogPasswordDto,
) {
return this.adminWebBlogService.setPassword(id, dto.password);
}
@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);
}
}

View File

@@ -0,0 +1,52 @@
import {
Body,
Controller,
Delete,
Get,
Param,
ParseUUIDPipe,
Post,
Put,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { CreateResourceDto } from 'src/admin/dto/admin-web/create-resource.dto';
import { Role } from 'src/auth/role.enum';
import { Roles } from 'src/common/decorators/role.decorator';
import { RolesGuard } from 'src/common/guard/roles.guard';
import { ResourceService } from 'src/resource/resource.service';
@Controller('/admin/web/resource')
@UseGuards(AuthGuard('jwt'), RolesGuard)
@Roles(Role.Admin)
export class AdminWebResourceController {
constructor(private readonly resourceService: ResourceService) {}
@Get()
async list() {
return this.resourceService.findAll();
}
@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);
}
@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);
}
}

View File

@@ -0,0 +1,9 @@
import { IsString } from 'class-validator';
export class CreatePermissionDto {
@IsString()
name: string;
@IsString()
description: string;
}

View File

@@ -0,0 +1,8 @@
import { ArrayMinSize, IsArray, IsUUID } from 'class-validator';
export class DeleteRolePermissionsDto {
@IsArray()
@ArrayMinSize(1)
@IsUUID('4', { each: true })
permissionIds: string[];
}

View File

@@ -0,0 +1,8 @@
import { ArrayMinSize, IsArray, IsUUID } from 'class-validator';
export class SetRolePermissionsDto {
@IsArray()
@ArrayMinSize(1)
@IsUUID('4', { each: true })
permissionIds: string[];
}

View File

@@ -0,0 +1,9 @@
import { IsString } from 'class-validator';
export class CreateRoleDto {
@IsString()
name: string;
@IsString()
localName: string;
}

View File

@@ -0,0 +1,13 @@
import { IsBoolean, IsDateString, IsOptional, IsUUID } from 'class-validator';
export class CreateUserRoleDto {
@IsUUID('4')
roleId: string;
@IsBoolean()
isEnabled: boolean;
@IsOptional()
@IsDateString()
expiredAt?: Date;
}

View File

@@ -0,0 +1,6 @@
import { IsUUID } from 'class-validator';
export class DeleteUserRoleDto {
@IsUUID('4')
roleId: string;
}

View File

@@ -0,0 +1,32 @@
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.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.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;
}

View File

@@ -0,0 +1,3 @@
import { PaginationDto } from '../common/pagination.dto';
export class ListDto extends PaginationDto {}

View File

@@ -0,0 +1,8 @@
import { Transform } from 'class-transformer';
import { IsBoolean } from 'class-validator';
export class RemoveUserDto {
@Transform(({ value }) => value === 'true')
@IsBoolean({ message: '需指定删除类型' })
soft: boolean;
}

View File

@@ -0,0 +1,11 @@
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;
}

View File

@@ -0,0 +1,35 @@
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(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() // 标记字段为可选
@IsString({ message: '手机号不得为空', always: false })
@Matches(/^1[3456789]\d{9}$/, {
message: '请输入有效的手机号码',
// 仅在值不为 null 或 undefined 时验证
always: false,
})
phone?: string;
}

View File

@@ -0,0 +1,19 @@
import { IsEnum, IsString } from 'class-validator';
import { BlogPermission } from 'src/blog/blog.permission.enum';
export class CreateBlogDto {
@IsString()
title: string;
@IsString()
description: string;
@IsString()
contentUrl: string;
@IsEnum(BlogPermission, { each: true, message: '请求类型错误' })
permissions: BlogPermission[];
@IsString()
password: string; // 允许空串
}

View File

@@ -0,0 +1,28 @@
import { Type } from 'class-transformer';
import { IsString, ValidateNested } from 'class-validator';
class ResourceTagDto {
@IsString()
name: string;
@IsString()
type: string;
}
export class CreateResourceDto {
@IsString()
title: string;
@IsString()
description: string;
@IsString()
imageUrl: string;
@IsString()
link: string;
@ValidateNested({ each: true })
@Type(() => ResourceTagDto)
tags: ResourceTagDto[];
}

View File

@@ -0,0 +1,6 @@
import { IsString } from 'class-validator';
export class SetBlogPasswordDto {
@IsString()
password: string;
}

View File

@@ -0,0 +1,16 @@
import { IsEnum, IsString } from 'class-validator';
import { BlogPermission } from 'src/blog/blog.permission.enum';
export class UpdateBlogDto {
@IsString()
title: string;
@IsString()
description: string;
@IsString()
contentUrl: string;
@IsEnum(BlogPermission, { each: true, message: '请求类型错误' })
permissions: BlogPermission[];
}

View File

@@ -0,0 +1,16 @@
import { Type } from 'class-transformer';
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)
pageSize?: number = 20;
}