diff --git a/src/app.module.ts b/src/app.module.ts index b0e1663..26e49c2 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,6 +10,7 @@ import { ArticleModule } from './article/article.module'; import { UploadModule } from './upload/upload.module'; import { RecruitmentModule } from './recruitment/recruitment.module'; import { ResumeTemplateModule } from './resume-template/resume-template.module'; +import { ResumeModule } from './resume/resume.module'; const envFilePath = ['.env']; export const IS_DEV = process.env.RUNNING_ENV !== 'prod'; @@ -38,6 +39,7 @@ if (IS_DEV) { UploadModule, RecruitmentModule, ResumeTemplateModule, + ResumeModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/resume-template/entities/resume-template.entity.ts b/src/resume-template/entities/resume-template.entity.ts index 4bb0a15..b7198fb 100644 --- a/src/resume-template/entities/resume-template.entity.ts +++ b/src/resume-template/entities/resume-template.entity.ts @@ -6,7 +6,7 @@ import { UpdateDateColumn, } from 'typeorm'; -@Entity() +@Entity('resumetemplate') export class ResumeTemplateEntity { // id 自增 @PrimaryGeneratedColumn({ diff --git a/src/resume/dto/create-resume.dto.ts b/src/resume/dto/create-resume.dto.ts new file mode 100644 index 0000000..7ff0235 --- /dev/null +++ b/src/resume/dto/create-resume.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty, IsObject } from 'class-validator'; + +export class CreateResumeDto { + @IsObject({ message: 'resumeContent must be an object' }) + @IsNotEmpty({ message: 'resumeContent must be provided' }) + resumeContent: object; +} diff --git a/src/resume/dto/update-resume.dto.ts b/src/resume/dto/update-resume.dto.ts new file mode 100644 index 0000000..dc457e0 --- /dev/null +++ b/src/resume/dto/update-resume.dto.ts @@ -0,0 +1,9 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateResumeDto } from './create-resume.dto'; +import { IsObject, IsOptional } from 'class-validator'; + +export class UpdateResumeDto extends PartialType(CreateResumeDto) { + @IsOptional() + @IsObject({ message: 'resumeContent must be an object' }) + resumeContent?: object; +} diff --git a/src/resume/entities/resume.entity.ts b/src/resume/entities/resume.entity.ts new file mode 100644 index 0000000..bb446bc --- /dev/null +++ b/src/resume/entities/resume.entity.ts @@ -0,0 +1,60 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, +} from 'typeorm'; + +import { UserEntity } from '../../usercenter/entities/usercenter.entity'; + +@Entity('resume') +export class ResumeEntity { + // id 自增 + @PrimaryGeneratedColumn({ + type: 'int', + name: 'resume_id', + comment: '简历id', + }) + resumeId: number; + + // 用户ID + @Column({ + type: 'int', + name: 'resume_user_id', + }) + resumeUserId: number; + + // 简历内容 + @Column({ + type: 'json', + name: 'resume_content', + comment: '简历内容', + }) + resumeContent: object; + + // 创建时间 + @CreateDateColumn({ + type: 'datetime', + name: 'resume_create_time', + comment: '简历创建时间', + }) + resumeCreateTime: Date; + + // 更新时间 + @UpdateDateColumn({ + type: 'datetime', + name: 'resume_update_time', + comment: '简历更新时间', + }) + resumeUpdateTime: Date; + + // 关联用户表,一个用户可以有多个简历 + @ManyToOne(() => UserEntity, (user) => user.resumes, { nullable: false }) + @JoinColumn({ + name: 'resume_user_id', + }) + user: UserEntity; +} diff --git a/src/resume/resume.controller.spec.ts b/src/resume/resume.controller.spec.ts new file mode 100644 index 0000000..33b0a4b --- /dev/null +++ b/src/resume/resume.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ResumeController } from './resume.controller'; +import { ResumeService } from './resume.service'; + +describe('ResumeController', () => { + let controller: ResumeController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ResumeController], + providers: [ResumeService], + }).compile(); + + controller = module.get(ResumeController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/resume/resume.controller.ts b/src/resume/resume.controller.ts new file mode 100644 index 0000000..a13388f --- /dev/null +++ b/src/resume/resume.controller.ts @@ -0,0 +1,123 @@ +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + UseGuards, + Req, + Query, + Request, +} from '@nestjs/common'; +import { ResumeService } from './resume.service'; +import { CreateResumeDto } from './dto/create-resume.dto'; +import { UpdateResumeDto } from './dto/update-resume.dto'; +import { AuthGuard } from '../auth/auth.guard'; + +/** + * 简历控制器 + * 处理简历相关的请求 + */ +@Controller('resume') +export class ResumeController { + /** + * 构造函数 + * @param resumeService - 简历服务实例 + */ + constructor(private readonly resumeService: ResumeService) {} + + /** + * 创建简历信息 + * 需要用户登录认证 + * + * @param createResumeDto - 创建简历的请求体 + * @param req - HTTP 请求对象,包含用户信息 + * @returns 返回创建成功的简历信息 + */ + @Post() + @UseGuards(AuthGuard) + create(@Body() createResumeDto: CreateResumeDto, @Req() req) { + return this.resumeService.create(createResumeDto, req.user.sub); + } + + /** + * 获取所有简历信息 + * 支持分页查询 + * 需要用户登录认证 + * + * @param query - 查询参数,包含分页信息 + * @returns 返回所有简历信息 + * + * @example + * GET /resume?page=1&limit=10 + * 返回第1页,每页10条简历信息 + */ + @Get() + @UseGuards(AuthGuard) + findAll(@Query('page') page?: number, @Query('limit') limit?: number) { + return this.resumeService.findAll({ + page, + limit, + }); + } + + /** + * 获取指定ID的简历信息 + * 需要用户登录认证 + * + * @param id - 简历ID + * @returns 返回指定ID的简历信息 + * + * @example + * GET /resume/1 + * 返回ID为1的简历信息 + */ + @Get(':id') + @UseGuards(AuthGuard) + findOne(@Param('id') id: string) { + return this.resumeService.findOne(+id); + } + + /** + * 更新指定ID的简历信息 + * 需要用户登录认证 + * + * @param id - 简历ID + * @param updateResumeDto - 更新简历的请求体 + * @param req - HTTP 请求对象,包含用户信息 + * @returns 返回更新成功的简历信息 + * + * @example + * PATCH /resume/1 + * 更新ID为1的简历信息 + */ + @Patch(':id') + @UseGuards(AuthGuard) + update( + @Param('id') id: string, + @Body() updateResumeDto: UpdateResumeDto, + @Request() req, + ) { + return this.resumeService.update(+id, updateResumeDto, req.user.sub); + } + + /** + * 删除简历 + * 需要用户登录认证 + * + * @param id - 要删除简历的ID + * @param req - 请求对象,用于获取用户信息 + * @returns 删除成功的消息 + * + * @example + * DELETE /resume/1 + * 删除ID为1的简历 + */ + @Delete(':id') + @UseGuards(AuthGuard) + remove(@Param('id') id: string, @Request() req) { + return this.resumeService.remove(+id, req.user.sub); + } +} diff --git a/src/resume/resume.module.ts b/src/resume/resume.module.ts new file mode 100644 index 0000000..c507b6a --- /dev/null +++ b/src/resume/resume.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ResumeService } from './resume.service'; +import { ResumeController } from './resume.controller'; +import { ResumeEntity } from './entities/resume.entity'; +import { UserEntity } from '../usercenter/entities/usercenter.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([ResumeEntity, UserEntity])], + controllers: [ResumeController], + providers: [ResumeService], + exports: [ResumeService], +}) +export class ResumeModule {} diff --git a/src/resume/resume.service.spec.ts b/src/resume/resume.service.spec.ts new file mode 100644 index 0000000..97b4c3f --- /dev/null +++ b/src/resume/resume.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ResumeService } from './resume.service'; + +describe('ResumeService', () => { + let service: ResumeService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ResumeService], + }).compile(); + + service = module.get(ResumeService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/resume/resume.service.ts b/src/resume/resume.service.ts new file mode 100644 index 0000000..a54a52f --- /dev/null +++ b/src/resume/resume.service.ts @@ -0,0 +1,170 @@ +import { + Injectable, + NotFoundException, + ForbiddenException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { ResumeEntity } from './entities/resume.entity'; +import { CreateResumeDto } from './dto/create-resume.dto'; +import { UpdateResumeDto } from './dto/update-resume.dto'; +import { UserEntity } from '../usercenter/entities/usercenter.entity'; + +/** + * 简历服务类 + * 提供简历的增删改查操作 + */ +@Injectable() +export class ResumeService { + /** + * 构造函数 + * + * @param resumeRepository 简历仓库,用于操作简历数据 + * @param userRepository 用户仓库,用于操作用户数据 + */ + constructor( + @InjectRepository(ResumeEntity) + private readonly resumeRepository: Repository, + @InjectRepository(UserEntity) + private readonly userRepository: Repository, + ) {} + /** + * 创建新简历 + * + * @param createResumeDto 简历创建数据 + * @param userId 创建简历的用户ID + * @returns 返回创建的简历 + */ + async create(createResumeDto: CreateResumeDto, resumeUserId: number) { + const resume = this.resumeRepository.create({ + ...createResumeDto, + resumeUserId, + }); + const savedResume = await this.resumeRepository.save(resume); + return savedResume; + } + /** + * 查找所有简历的信息 + * @param query - 查询参数,包含分页信息 + * @returns 返回所有简历信息和总数 + */ + async findAll(query: { page?: number; limit?: number }) { + const { page = 1, limit = 10 } = query; + const [items, total] = await this.resumeRepository.findAndCount({ + skip: (page - 1) * limit, + take: limit, + }); + return { + items, + total, + }; + } + /** + * 查找指定ID的简历信息 + * + * @param id - 简历ID + * @returns 返回指定ID的简历信息 + * @throws NotFoundException - 如果简历不存在 + * @throws ForbiddenException - 如果用户没有权限查看简历 + * + * @example + * GET /resume/1 + * 返回指定ID的简历信息 + */ + async findOne(id: number) { + const resume = await this.resumeRepository.findOne({ + where: { resumeId: id }, + }); + + if (!resume) { + throw new NotFoundException(`Resume with ID ${id} not found`); + } + + return resume; + } + + /** + * 更新指定ID的简历信息 + * + * @param id - 简历ID + * @param updateResumeDto - 更新简历的请求体 + * @param userId - 更新简历的用户ID + * @returns 返回更新后的简历信息 + * @throws NotFoundException - 如果简历不存在 + */ + async update(id: number, updateResumeDto: UpdateResumeDto, userId: number) { + const resume = await this.resumeRepository.findOne({ + where: { resumeId: id }, + }); + + if (!resume) { + throw new NotFoundException(`Resume with ID ${id} not found`); + } + + // 获取用户信息 + const user = await this.userRepository.findOne({ + where: { userId }, + }); + + if (!user) { + throw new NotFoundException('User not found'); + } + + // 检查用户是否有权限更新简历 + if (resume.resumeUserId !== userId) { + throw new ForbiddenException( + 'You do not have permission to update this resume', + ); + } + + const updatedResume = await this.resumeRepository.save({ + ...resume, + ...updateResumeDto, + }); + + return updatedResume; + } + + /** + * 删除指定ID的简历 + * + * @param id - 简历ID + * @param userId - 删除简历的用户ID + * @returns 返回删除的简历信息 + * @throws NotFoundException - 如果简历不存在 + */ + async remove(id: number, userId: number) { + const resume = await this.resumeRepository.findOne({ + where: { resumeId: id }, + }); + + if (!resume) { + throw new NotFoundException(`Resume with ID ${id} not found`); + } + + // 获取用户信息 + const user = await this.userRepository.findOne({ + where: { userId }, + }); + + if (!user) { + throw new NotFoundException('User not found'); + } + + // 检查用户是否有权限更新简历 + if (resume.resumeUserId !== userId) { + throw new ForbiddenException( + 'You do not have permission to update this resume', + ); + } + + const result = await this.resumeRepository.delete(id); + + // 检查删除操作是否成功 + if (result.affected === 0) { + throw new NotFoundException(`Resume with ID ${id} not found`); + } + + return result; + } +} diff --git a/src/usercenter/entities/usercenter.entity.ts b/src/usercenter/entities/usercenter.entity.ts index 7be434f..fc4c4d8 100644 --- a/src/usercenter/entities/usercenter.entity.ts +++ b/src/usercenter/entities/usercenter.entity.ts @@ -8,6 +8,7 @@ import { } from 'typeorm'; import { ArticleEntity } from '../../article/entities/article.entity'; +import { ResumeEntity } from '../../resume/entities/resume.entity'; @Entity('users') // 表名设置为 'users' export class UserEntity { @@ -118,4 +119,8 @@ export class UserEntity { // 关联文章,一个用户可以有多篇文章 @OneToMany(() => ArticleEntity, (article) => article.user) articles: ArticleEntity[]; + + // 关联简历,一个用户可以有多个简历 + @OneToMany(() => ResumeEntity, (resume) => resume.user) + resumes: ResumeEntity[]; }