From 8453e542a1bcb2a3310e8025d7925ee97a708a26 Mon Sep 17 00:00:00 2001 From: tarasov-ne Date: Fri, 28 Feb 2025 11:27:18 +0300 Subject: [PATCH] Fixed src files --- nestjs/backend/src/.env | 6 +- nestjs/backend/src/app.module.ts | 20 +-- nestjs/backend/src/auth/auth.controller.ts | 76 ++++++++++- nestjs/backend/src/auth/auth.module.ts | 10 +- nestjs/backend/src/auth/auth.service.ts | 16 ++- nestjs/backend/src/main.ts | 8 +- .../src/projects/project-member.entity.ts | 6 +- nestjs/backend/src/projects/project.entity.ts | 9 +- .../src/projects/projects.controller.ts | 94 +++++++++++++- .../backend/src/projects/projects.module.ts | 6 +- .../backend/src/projects/projects.service.ts | 32 +++-- nestjs/backend/src/tasks/task.entity.ts | 14 ++- nestjs/backend/src/tasks/tasks.controller.ts | 118 ++++++++++++++++-- nestjs/backend/src/tasks/tasks.module.ts | 6 +- nestjs/backend/src/tasks/tasks.service.ts | 45 ++++--- nestjs/backend/src/users/user.entity.ts | 6 + nestjs/backend/src/users/users.controller.ts | 22 ++-- nestjs/backend/src/users/users.module.ts | 2 +- nestjs/backend/src/users/users.service.ts | 19 ++- 19 files changed, 408 insertions(+), 107 deletions(-) diff --git a/nestjs/backend/src/.env b/nestjs/backend/src/.env index 0eda8f6..0f171d4 100644 --- a/nestjs/backend/src/.env +++ b/nestjs/backend/src/.env @@ -1,7 +1,7 @@ -DB_HOST=109.107.166.17 +DB_HOST=localhost DB_PORT=5432 -DB_USER=nichtar -DB_PASSWORD=6t30a72 +DB_USER=postgres +DB_PASSWORD=postgres DB_NAME=todo JWT_SECRET=your_secret_key diff --git a/nestjs/backend/src/app.module.ts b/nestjs/backend/src/app.module.ts index dea503a..dfaed3e 100644 --- a/nestjs/backend/src/app.module.ts +++ b/nestjs/backend/src/app.module.ts @@ -1,40 +1,32 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule } from '@nestjs/config'; +import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ProjectsModule } from './projects/projects.module'; import { TasksModule } from './tasks/tasks.module'; -import { AuthModule } from './auth/auth.module'; import { User } from './users/user.entity'; import { Project } from './projects/project.entity'; import { Task } from './tasks/task.entity'; import { ProjectMember } from './projects/project-member.entity'; -console.log('Database Config:', { - host: process.env.DB_HOST, - port: process.env.DB_PORT, - user: process.env.DB_USER, - password: process.env.DB_PASSWORD ? '***HIDDEN***' : 'UNDEFINED', - database: process.env.DB_NAME, -}); - @Module({ imports: [ - ConfigModule.forRoot(), // Читает .env + ConfigModule.forRoot(), TypeOrmModule.forRoot({ type: 'postgres', - host: process.env.DB_HOST || '109.107.166.17', + host: process.env.DB_HOST || 'localhost', port: Number(process.env.DB_PORT) || 5432, - username: process.env.DB_USER || 'nichtar', - password: process.env.DB_PASSWORD || '6t30a72', // Убрал `String()`, оно тут не нужно + username: process.env.DB_USER || 'postgres', + password: process.env.DB_PASSWORD || 'postgres', database: process.env.DB_NAME || 'todo', entities: [User, Project, Task, ProjectMember], synchronize: true, }), + AuthModule, UsersModule, ProjectsModule, TasksModule, - AuthModule, ], }) export class AppModule {} diff --git a/nestjs/backend/src/auth/auth.controller.ts b/nestjs/backend/src/auth/auth.controller.ts index 610f600..870282f 100644 --- a/nestjs/backend/src/auth/auth.controller.ts +++ b/nestjs/backend/src/auth/auth.controller.ts @@ -1,24 +1,88 @@ import { Controller, Post, Body, UnauthorizedException } from '@nestjs/common'; import { AuthService } from './auth.service'; import { UsersService } from '../users/users.service'; +import { ApiTags, ApiOperation, ApiBody, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +class LoginDto { + username: string; + password: string; +} + +class LoginResponse { + access_token: string; +} + +class RegisterDto { + username: string; + email: string; + password: string; +} + +@ApiTags('Auth') @Controller('auth') export class AuthController { constructor( private authService: AuthService, - private usersService: UsersService + private usersService: UsersService, ) {} + @ApiOperation({ summary: 'Register new user' }) + @ApiBody({ + description: 'User registration data', + type: RegisterDto, + examples: { + example1: { + summary: 'Typical registration', + value: { + username: 'john_doe', + email: 'john@example.com', + password: '123456', + }, + }, + }, + }) + @ApiResponse({ + status: 201, + description: 'User created successfully', + }) + @ApiResponse({ + status: 409, + description: 'Username already exists', + }) @Post('register') - async register(@Body() body) { - const existingUser = await this.usersService.findOneByUsername(body.username); - if (existingUser) throw new UnauthorizedException('Username already exists'); - + async register(@Body() body: RegisterDto) { + const existing = await this.usersService.findOneByUsername(body.username); + if (existing) { + throw new UnauthorizedException('Username already exists'); + } return this.usersService.create(body); } + @ApiOperation({ summary: 'Login user' }) + @ApiBody({ + description: 'User login data', + type: LoginDto, + examples: { + example1: { + summary: 'Typical login', + value: { + username: 'john_doe', + password: '123456', + }, + }, + }, + }) + @ApiResponse({ + status: 200, + description: 'User logged in successfully', + type: LoginResponse, + }) + @ApiResponse({ + status: 401, + description: 'Invalid username or password', + }) @Post('login') - async login(@Body() body) { + async login(@Body() body: LoginDto) { const user = await this.authService.validateUser(body.username, body.password); return this.authService.login(user); } diff --git a/nestjs/backend/src/auth/auth.module.ts b/nestjs/backend/src/auth/auth.module.ts index e5473bc..f8390c9 100644 --- a/nestjs/backend/src/auth/auth.module.ts +++ b/nestjs/backend/src/auth/auth.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; -import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; +import { JwtModule } from '@nestjs/jwt'; import { AuthService } from './auth.service'; -import { JwtStrategy } from './jwt.strategy'; -import { UsersModule } from '../users/users.module'; import { AuthController } from './auth.controller'; +import { UsersModule } from '../users/users.module'; +import { JwtStrategy } from './jwt.strategy'; @Module({ imports: [ @@ -12,11 +12,11 @@ import { AuthController } from './auth.controller'; PassportModule, JwtModule.register({ secret: process.env.JWT_SECRET || 'super_secret_key', - signOptions: { expiresIn: '1h' }, + signOptions: { expiresIn: process.env.JWT_EXPIRES_IN || '3600s' }, }), ], - providers: [AuthService, JwtStrategy], controllers: [AuthController], + providers: [AuthService, JwtStrategy], exports: [AuthService], }) export class AuthModule {} diff --git a/nestjs/backend/src/auth/auth.service.ts b/nestjs/backend/src/auth/auth.service.ts index 1c0fb96..1145f3a 100644 --- a/nestjs/backend/src/auth/auth.service.ts +++ b/nestjs/backend/src/auth/auth.service.ts @@ -7,16 +7,20 @@ import * as bcrypt from 'bcrypt'; export class AuthService { constructor( private usersService: UsersService, - private jwtService: JwtService + private jwtService: JwtService, ) {} - async validateUser(username: string, password: string): Promise { + async validateUser(username: string, password: string) { const user = await this.usersService.findOneByUsername(username); - if (user && (await bcrypt.compare(password, user.password))) { - const { password, ...result } = user; - return result; + if (!user) { + throw new UnauthorizedException('User not found'); } - throw new UnauthorizedException('Invalid username or password'); + const isMatch = await bcrypt.compare(password, user.password); + if (!isMatch) { + throw new UnauthorizedException('Invalid password'); + } + const { password: _, ...rest } = user; + return rest; } async login(user: any) { diff --git a/nestjs/backend/src/main.ts b/nestjs/backend/src/main.ts index c8968ed..9039604 100644 --- a/nestjs/backend/src/main.ts +++ b/nestjs/backend/src/main.ts @@ -10,10 +10,10 @@ async function bootstrap() { // Настройка Swagger const config = new DocumentBuilder() - .setTitle('Project Management API') - .setDescription('API documentation for NestJS + Docker project') - .setVersion('1.0') - .addBearerAuth() // Добавляем поддержку JWT (Bearer) + .setTitle('Cool TODO Manager API') + .setDescription('NestJS + PostgreSQL + JWT + Docker') + .setVersion('2.0') + .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, config); diff --git a/nestjs/backend/src/projects/project-member.entity.ts b/nestjs/backend/src/projects/project-member.entity.ts index 8769796..545531d 100644 --- a/nestjs/backend/src/projects/project-member.entity.ts +++ b/nestjs/backend/src/projects/project-member.entity.ts @@ -7,12 +7,12 @@ export class ProjectMember { @PrimaryGeneratedColumn() id: number; - @ManyToOne(() => Project, (project) => project.id) + @ManyToOne(() => Project, (project) => project.id, { eager: true }) project: Project; - @ManyToOne(() => User, (user) => user.id) + @ManyToOne(() => User, (user) => user.id, { eager: true }) user: User; @Column() - role: string; // owner, member + role: string; // 'owner' or 'member' } diff --git a/nestjs/backend/src/projects/project.entity.ts b/nestjs/backend/src/projects/project.entity.ts index af0c803..04b8fb3 100644 --- a/nestjs/backend/src/projects/project.entity.ts +++ b/nestjs/backend/src/projects/project.entity.ts @@ -1,23 +1,30 @@ import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm'; import { User } from '../users/user.entity'; +import { ApiProperty } from '@nestjs/swagger'; @Entity() export class Project { + @ApiProperty({ example: 1, description: 'Project ID' }) @PrimaryGeneratedColumn() id: number; + @ApiProperty({ example: 'My Project', description: 'Title of the project' }) @Column() title: string; + @ApiProperty({ example: 'Optional description', description: 'Project description', required: false }) @Column({ nullable: true }) description: string; - @ManyToOne(() => User, (user) => user.id, { eager: true }) // `eager: true` загружает владельца проекта + @ApiProperty({ description: 'Owner user object' }) + @ManyToOne(() => User, (user) => user.id, { eager: true }) owner: User; + @ApiProperty({ description: 'Date of creation' }) @CreateDateColumn() created_at: Date; + @ApiProperty({ description: 'Date of last update' }) @UpdateDateColumn() updated_at: Date; } diff --git a/nestjs/backend/src/projects/projects.controller.ts b/nestjs/backend/src/projects/projects.controller.ts index 3decfdd..fc0661d 100644 --- a/nestjs/backend/src/projects/projects.controller.ts +++ b/nestjs/backend/src/projects/projects.controller.ts @@ -1,28 +1,116 @@ -import { Controller, Post, Get, Delete, Body, Param, UseGuards, Request } from '@nestjs/common'; +import { Controller, Post, Get, Patch, Delete, Body, Param, UseGuards, Request } from '@nestjs/common'; import { ProjectsService } from './projects.service'; import { Project } from './project.entity'; import { AuthGuard } from '@nestjs/passport'; +import { ApiTags, ApiBearerAuth, ApiOperation, ApiParam, ApiBody, ApiResponse } from '@nestjs/swagger'; +class CreateProjectDto { + title: string; + description?: string; +} + +@ApiTags('Projects') +@ApiBearerAuth() // Все маршруты под Bearer Token (Try it out -> authorize) @Controller('projects') export class ProjectsController { constructor(private readonly projectsService: ProjectsService) {} + @ApiOperation({ summary: 'Create new project' }) + @ApiBody({ + description: 'Project data', + type: CreateProjectDto, + examples: { + example1: { + summary: 'Simple project', + value: { + title: 'My project', + description: 'Test desc', + }, + }, + }, + }) + @ApiResponse({ + status: 201, + description: 'Project created', + type: Project, + }) @UseGuards(AuthGuard('jwt')) @Post('create') - async create(@Request() req, @Body() body): Promise { - return this.projectsService.create({ ...body, ownerId: req.user.userId }); + async create(@Request() req, @Body() body: CreateProjectDto): Promise { + return this.projectsService.create({ + title: body.title, + description: body.description, + ownerId: req.user.userId, + }); } + @ApiOperation({ summary: 'Get all projects' }) + @ApiResponse({ + status: 200, + description: 'List of projects', + type: Project, + isArray: true, + }) @Get() async findAll(): Promise { return this.projectsService.findAll(); } + @ApiOperation({ summary: 'Get single project by ID' }) + @ApiParam({ name: 'id', type: 'number' }) + @ApiResponse({ + status: 200, + description: 'Found project', + type: Project, + }) + @ApiResponse({ + status: 404, + description: 'Project not found', + }) @Get(':id') async findOne(@Param('id') id: number): Promise { return this.projectsService.findOneById(id); } + @ApiOperation({ summary: 'Update project' }) + @ApiParam({ name: 'id', type: 'number' }) + @ApiBody({ + description: 'Fields to update', + schema: { + properties: { + title: { type: 'string', example: 'Updated Title' }, + description: { type: 'string', example: 'Updated desc' }, + }, + }, + }) + @ApiResponse({ + status: 200, + description: 'Project updated', + type: Project, + }) + @ApiResponse({ + status: 404, + description: 'Project not found', + }) + @UseGuards(AuthGuard('jwt')) + @Patch(':id') + async update( + @Param('id') id: number, + @Body() data: { title?: string; description?: string }, + ): Promise { + return this.projectsService.update(id, data); + } + + @ApiOperation({ summary: 'Delete project' }) + @ApiParam({ name: 'id', type: 'number' }) + @ApiResponse({ + status: 200, + description: 'Project deleted', + }) + @ApiResponse({ + status: 404, + description: 'Project not found', + }) @UseGuards(AuthGuard('jwt')) @Delete(':id') async delete(@Param('id') id: number): Promise { diff --git a/nestjs/backend/src/projects/projects.module.ts b/nestjs/backend/src/projects/projects.module.ts index 726139f..4a23cc8 100644 --- a/nestjs/backend/src/projects/projects.module.ts +++ b/nestjs/backend/src/projects/projects.module.ts @@ -3,10 +3,14 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ProjectsService } from './projects.service'; import { ProjectsController } from './projects.controller'; import { Project } from './project.entity'; +import { ProjectMember } from './project-member.entity'; import { UsersModule } from '../users/users.module'; @Module({ - imports: [TypeOrmModule.forFeature([Project]), UsersModule], + imports: [ + TypeOrmModule.forFeature([Project, ProjectMember]), + UsersModule, + ], controllers: [ProjectsController], providers: [ProjectsService], exports: [ProjectsService], diff --git a/nestjs/backend/src/projects/projects.service.ts b/nestjs/backend/src/projects/projects.service.ts index 8f38470..c6e2268 100644 --- a/nestjs/backend/src/projects/projects.service.ts +++ b/nestjs/backend/src/projects/projects.service.ts @@ -9,29 +9,37 @@ export class ProjectsService { constructor( @InjectRepository(Project) private projectsRepository: Repository, - private usersService: UsersService + private usersService: UsersService, ) {} - async create(projectData: { title: string; description?: string; ownerId: number }): Promise { - const owner = await this.usersService.findOneById(projectData.ownerId); // Ищем по ID, а не username + async create(data: { title: string; description?: string; ownerId: number }): Promise { + const owner = await this.usersService.findOneById(data.ownerId); if (!owner) throw new NotFoundException('Owner not found'); - + const project = this.projectsRepository.create({ - title: projectData.title, - description: projectData.description, - owner: owner, + title: data.title, + description: data.description, + owner, }); - - return await this.projectsRepository.save(project); + return this.projectsRepository.save(project); } - async findAll(): Promise { - return await this.projectsRepository.find(); + return this.projectsRepository.find(); } async findOneById(id: number): Promise { - return await this.projectsRepository.findOne({ where: { id } }); + return this.projectsRepository.findOne({ where: { id } }); + } + + async update(id: number, data: { title?: string; description?: string }): Promise { + const project = await this.findOneById(id); + if (!project) throw new NotFoundException('Project not found'); + + if (data.title !== undefined) project.title = data.title; + if (data.description !== undefined) project.description = data.description; + + return this.projectsRepository.save(project); } async delete(id: number): Promise { diff --git a/nestjs/backend/src/tasks/task.entity.ts b/nestjs/backend/src/tasks/task.entity.ts index 6ace138..6af1eab 100644 --- a/nestjs/backend/src/tasks/task.entity.ts +++ b/nestjs/backend/src/tasks/task.entity.ts @@ -1,27 +1,35 @@ import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn } from 'typeorm'; import { Project } from '../projects/project.entity'; import { User } from '../users/user.entity'; +import { ApiProperty } from '@nestjs/swagger'; @Entity() export class Task { + @ApiProperty({ example: 1, description: 'Task ID' }) @PrimaryGeneratedColumn() id: number; + @ApiProperty({ description: 'Project object' }) @ManyToOne(() => Project, (project) => project.id, { eager: true }) project: Project; + @ApiProperty({ example: 'My Task', description: 'Task title' }) @Column() title: string; - @Column({ default: 'todo' }) // Возможные значения: 'todo', 'in_progress', 'done' - status: string; + @ApiProperty({ example: 'todo', description: 'Task status' }) + @Column({ default: 'todo' }) + status: string; // 'todo', 'in_progress', 'done' + @ApiProperty({ description: 'Assigned user object', required: false }) @ManyToOne(() => User, (user) => user.id, { nullable: true, eager: true }) assigned_user?: User; - @Column({ type: 'timestamp', nullable: true }) // Исправлено + @ApiProperty({ example: '2025-03-01T12:00:00Z', required: false }) + @Column({ type: 'timestamp', nullable: true }) deadline?: Date; + @ApiProperty({ description: 'Date of creation' }) @CreateDateColumn() created_at: Date; } diff --git a/nestjs/backend/src/tasks/tasks.controller.ts b/nestjs/backend/src/tasks/tasks.controller.ts index 987c8bb..8293ff2 100644 --- a/nestjs/backend/src/tasks/tasks.controller.ts +++ b/nestjs/backend/src/tasks/tasks.controller.ts @@ -1,33 +1,137 @@ -import { Controller, Post, Get, Patch, Delete, Body, Param } from '@nestjs/common'; +import { Controller, Post, Get, Patch, Delete, Body, Param, UseGuards } from '@nestjs/common'; import { TasksService } from './tasks.service'; import { Task } from './task.entity'; +import { AuthGuard } from '@nestjs/passport'; +import { ApiTags, ApiBearerAuth, ApiOperation, ApiParam, ApiBody, ApiResponse } from '@nestjs/swagger'; +class CreateTaskDto { + title: string; + projectId: number; + assignedUserId?: number; + deadline?: string; +} + +class UpdateTaskDto { + title?: string; + status?: string; // 'todo', 'in_progress', 'done' + deadline?: string; + assignedUserId?: number; +} + +@ApiTags('Tasks') +@ApiBearerAuth() @Controller('tasks') export class TasksController { constructor(private readonly tasksService: TasksService) {} + @ApiOperation({ summary: 'Create new task' }) + @ApiBody({ + description: 'Task creation data', + type: CreateTaskDto, + examples: { + example1: { + summary: 'Simple task', + value: { + title: 'My Task', + projectId: 1, + assignedUserId: 2, + deadline: '2025-03-01T12:00:00Z', + }, + }, + }, + }) + @ApiResponse({ + status: 201, + description: 'Task created', + type: Task, + }) + @UseGuards(AuthGuard('jwt')) @Post('create') - async create(@Body() body): Promise { - return this.tasksService.create(body); + async create(@Body() body: CreateTaskDto): Promise { + return this.tasksService.create({ + title: body.title, + projectId: body.projectId, + assignedUserId: body.assignedUserId, + deadline: body.deadline ? new Date(body.deadline) : undefined, + }); } + @ApiOperation({ summary: 'Get all tasks' }) + @ApiResponse({ + status: 200, + description: 'List of tasks', + type: Task, + isArray: true, + }) @Get() async findAll(): Promise { return this.tasksService.findAll(); } + @ApiOperation({ summary: 'Get task by ID' }) + @ApiParam({ name: 'id', type: 'number' }) + @ApiResponse({ + status: 200, + description: 'Found task', + type: Task, + }) + @ApiResponse({ + status: 404, + description: 'Task not found', + }) @Get(':id') async findOne(@Param('id') id: number): Promise { return this.tasksService.findOneById(id); } - @Patch(':id/status') - async updateStatus(@Param('id') id: number, @Body('status') status: string): Promise { - return this.tasksService.updateStatus(id, status); + @ApiOperation({ summary: 'Update task' }) + @ApiParam({ name: 'id', type: 'number' }) + @ApiBody({ + description: 'Fields to update in the task', + type: UpdateTaskDto, + examples: { + example1: { + summary: 'Update partial fields', + value: { + title: 'Updated Task Title', + status: 'in_progress', + }, + }, + }, + }) + @ApiResponse({ + status: 200, + description: 'Task updated', + type: Task, + }) + @ApiResponse({ + status: 404, + description: 'Task not found', + }) + @UseGuards(AuthGuard('jwt')) + @Patch(':id') + async update(@Param('id') id: number, @Body() body: UpdateTaskDto): Promise { + return this.tasksService.update(id, { + title: body.title, + status: body.status, + deadline: body.deadline ? new Date(body.deadline) : undefined, + assignedUserId: body.assignedUserId, + }); } + @ApiOperation({ summary: 'Delete task' }) + @ApiParam({ name: 'id', type: 'number' }) + @ApiResponse({ + status: 200, + description: 'Task deleted', + }) + @ApiResponse({ + status: 404, + description: 'Task not found', + }) + @UseGuards(AuthGuard('jwt')) @Delete(':id') - async delete(@Param('id') id: number): Promise { + async delete(@Param('id') id: number) { return this.tasksService.delete(id); } } diff --git a/nestjs/backend/src/tasks/tasks.module.ts b/nestjs/backend/src/tasks/tasks.module.ts index e43600c..b4919d1 100644 --- a/nestjs/backend/src/tasks/tasks.module.ts +++ b/nestjs/backend/src/tasks/tasks.module.ts @@ -7,7 +7,11 @@ import { ProjectsModule } from '../projects/projects.module'; import { UsersModule } from '../users/users.module'; @Module({ - imports: [TypeOrmModule.forFeature([Task]), ProjectsModule, UsersModule], + imports: [ + TypeOrmModule.forFeature([Task]), + ProjectsModule, + UsersModule, + ], controllers: [TasksController], providers: [TasksService], exports: [TasksService], diff --git a/nestjs/backend/src/tasks/tasks.service.ts b/nestjs/backend/src/tasks/tasks.service.ts index 094211f..d2af77b 100644 --- a/nestjs/backend/src/tasks/tasks.service.ts +++ b/nestjs/backend/src/tasks/tasks.service.ts @@ -4,7 +4,6 @@ import { Repository } from 'typeorm'; import { Task } from './task.entity'; import { ProjectsService } from '../projects/projects.service'; import { UsersService } from '../users/users.service'; -import { User } from '../users/user.entity'; @Injectable() export class TasksService { @@ -12,47 +11,55 @@ export class TasksService { @InjectRepository(Task) private tasksRepository: Repository, private projectsService: ProjectsService, - private usersService: UsersService + private usersService: UsersService, ) {} - async create(taskData: { title: string; projectId: number; assignedUserId?: number; deadline?: Date }): Promise { - const project = await this.projectsService.findOneById(taskData.projectId); + async create(data: { title: string; projectId: number; assignedUserId?: number; deadline?: Date }) { + const project = await this.projectsService.findOneById(data.projectId); if (!project) throw new NotFoundException('Project not found'); - let assignedUser: User | undefined = undefined; - if (taskData.assignedUserId) { - const user = await this.usersService.findOneById(taskData.assignedUserId); + let user; + if (data.assignedUserId) { + user = await this.usersService.findOneById(data.assignedUserId); if (!user) throw new NotFoundException('Assigned user not found'); - assignedUser = user; // Теперь `assignedUser` всегда `User | undefined` } const task = this.tasksRepository.create({ - title: taskData.title, - project: project, - assigned_user: assignedUser, - deadline: taskData.deadline ?? undefined, // `undefined`, если нет значения + title: data.title, + project, + assigned_user: user, + deadline: data.deadline, }); - return await this.tasksRepository.save(task); + return this.tasksRepository.save(task); } async findAll(): Promise { - return await this.tasksRepository.find(); + return this.tasksRepository.find(); } async findOneById(id: number): Promise { - return await this.tasksRepository.findOne({ where: { id } }); + return this.tasksRepository.findOne({ where: { id } }); } - async updateStatus(id: number, status: string): Promise { + async update(id: number, data: { title?: string; status?: string; deadline?: Date; assignedUserId?: number }) { const task = await this.findOneById(id); if (!task) throw new NotFoundException('Task not found'); - task.status = status; - return await this.tasksRepository.save(task); + if (data.title !== undefined) task.title = data.title; + if (data.status !== undefined) task.status = data.status; + if (data.deadline !== undefined) task.deadline = data.deadline; + + if (data.assignedUserId !== undefined) { + const user = await this.usersService.findOneById(data.assignedUserId); + if (!user) throw new NotFoundException('Assigned user not found'); + task.assigned_user = user; + } + + return this.tasksRepository.save(task); } - async delete(id: number): Promise { + async delete(id: number) { const result = await this.tasksRepository.delete(id); if (result.affected === 0) { throw new NotFoundException('Task not found'); diff --git a/nestjs/backend/src/users/user.entity.ts b/nestjs/backend/src/users/user.entity.ts index 4243c12..1d19910 100644 --- a/nestjs/backend/src/users/user.entity.ts +++ b/nestjs/backend/src/users/user.entity.ts @@ -1,19 +1,25 @@ import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger'; @Entity() export class User { + @ApiProperty({ example: 1, description: 'User ID' }) @PrimaryGeneratedColumn() id: number; + @ApiProperty({ example: 'john_doe', description: 'Unique username' }) @Column({ unique: true }) username: string; + @ApiProperty({ example: 'john@example.com', description: 'Unique email' }) @Column({ unique: true }) email: string; + @ApiProperty({ example: '$2b$10$...', description: 'Hashed password' }) @Column() password: string; + @ApiProperty({ example: '2025-03-01T10:00:00.000Z', description: 'Creation date' }) @CreateDateColumn() created_at: Date; } diff --git a/nestjs/backend/src/users/users.controller.ts b/nestjs/backend/src/users/users.controller.ts index d688555..8a005ff 100644 --- a/nestjs/backend/src/users/users.controller.ts +++ b/nestjs/backend/src/users/users.controller.ts @@ -1,18 +1,26 @@ -import { Controller, Post, Body, Get, Param } from '@nestjs/common'; +import { Controller, Get, Param } from '@nestjs/common'; import { UsersService } from './users.service'; import { User } from './user.entity'; +import { ApiTags, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; +@ApiTags('Users') @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} - @Post('register') - async register(@Body() body): Promise { - return this.usersService.create(body); - } - + @ApiOperation({ summary: 'Get user by username' }) + @ApiParam({ name: 'username', type: 'string', description: 'The username to look up' }) + @ApiResponse({ + status: 200, + description: 'User found', + type: User, + }) + @ApiResponse({ + status: 404, + description: 'User not found', + }) @Get(':username') - async getUserByUsername(@Param('username') username: string): Promise { + async getUser(@Param('username') username: string): Promise { return this.usersService.findOneByUsername(username); } } diff --git a/nestjs/backend/src/users/users.module.ts b/nestjs/backend/src/users/users.module.ts index 763c94f..7317e69 100644 --- a/nestjs/backend/src/users/users.module.ts +++ b/nestjs/backend/src/users/users.module.ts @@ -6,8 +6,8 @@ import { User } from './user.entity'; @Module({ imports: [TypeOrmModule.forFeature([User])], - controllers: [UsersController], providers: [UsersService], + controllers: [UsersController], exports: [UsersService], }) export class UsersModule {} diff --git a/nestjs/backend/src/users/users.service.ts b/nestjs/backend/src/users/users.service.ts index ba4327e..fc6681d 100644 --- a/nestjs/backend/src/users/users.service.ts +++ b/nestjs/backend/src/users/users.service.ts @@ -1,4 +1,4 @@ -import { Injectable, ConflictException } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity'; @@ -8,31 +8,28 @@ import * as bcrypt from 'bcrypt'; export class UsersService { constructor( @InjectRepository(User) - private usersRepository: Repository + private usersRepository: Repository, ) {} async create(userData: Partial): Promise { if (!userData.password) { - throw new ConflictException('Password is required'); + throw new Error('Password is required'); } - const hashedPassword = await bcrypt.hash(userData.password, 10); const user = this.usersRepository.create({ - username: userData.username as string, - email: userData.email as string, + username: userData.username, + email: userData.email, password: hashedPassword, }); - - return await this.usersRepository.save(user); + return this.usersRepository.save(user); } async findOneByUsername(username: string): Promise { - return await this.usersRepository.findOne({ where: { username } }); + return this.usersRepository.findOne({ where: { username } }); } async findOneById(id: number): Promise { - return await this.usersRepository.findOne({ where: { id } }); + return this.usersRepository.findOne({ where: { id } }); } - } -- 2.47.2