From 81a5a829a9be5222e02efce81fe5ff57c7ca2c16 Mon Sep 17 00:00:00 2001 From: Sergey Bolshakov Date: Mon, 15 Dec 2025 17:02:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=BE=D0=B2,=20us?= =?UTF-8?q?erId=20=D0=B2=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=B5,=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=84=D0=B0=20=D0=B2=20=D1=81=D0=B2=D0=B0=D0=B3=D0=B3?= =?UTF-8?q?=D0=B5=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/auth/auth.service.ts | 8 ++- src/catalog/catalog.controller.ts | 36 ++++++++++- src/catalog/catalog.module.ts | 5 +- src/catalog/catalog.service.ts | 68 ++++++++++++++++---- src/catalog/dto/create-yacht.dto.ts | 71 +++++++++++++++++++++ src/reservations/reservation-item.dto.ts | 22 +++++++ src/reservations/reservations.controller.ts | 15 ++++- src/reservations/reservations.module.ts | 4 +- src/reservations/reservations.service.ts | 30 ++++++++- 9 files changed, 235 insertions(+), 24 deletions(-) create mode 100644 src/catalog/dto/create-yacht.dto.ts diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 39ec86a..e909b85 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -24,9 +24,13 @@ export class AuthService { return null; } - async login(user: { email: string; password: string }) { + login(user: Omit) { this.logger.log('LOG'); - const payload = { username: user.email, sub: user.password }; + const payload = { + username: user.email, + sub: user.userId, + userId: user.userId, + }; return { access_token: this.jwtService.sign(payload), }; diff --git a/src/catalog/catalog.controller.ts b/src/catalog/catalog.controller.ts index 0c7257d..50358a0 100644 --- a/src/catalog/catalog.controller.ts +++ b/src/catalog/catalog.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Query, Param } from '@nestjs/common'; +import { Controller, Get, Query, Param, Post, Body, HttpCode, HttpStatus } from '@nestjs/common'; import { CatalogService } from './catalog.service'; import { CatalogResponseDto } from './dto/catalog-response.dto'; import { CatalogFiltersDto } from './dto/catalog-filters.dto'; @@ -11,9 +11,13 @@ import { ApiResponse, ApiOperation, ApiProperty, + ApiBody, + ApiTags, } from '@nestjs/swagger'; import { MainPageCatalogResponseDto } from './dto/main-page-catalog-response.dto'; +import { CreateYachtDto } from './dto/create-yacht.dto'; +@ApiTags('catalog') @Controller('catalog') export class CatalogController { constructor(private readonly catalogService: CatalogService) {} @@ -64,6 +68,19 @@ export class CatalogController { return this.catalogService.getMainPageCatalog(); } + @Get('user/:userId') + @ApiOperation({ summary: 'Get catalog items by user ID' }) + @ApiResponse({ + status: 200, + description: 'Catalog items for the specified user', + type: [CatalogItemShortDto], + }) + async getCatalogByUserId( + @Param('userId') userId: string, + ): Promise { + return this.catalogService.getCatalogByUserId(Number(userId)); + } + @Get(':id') @ApiOperation({ summary: 'Get catalog item by ID with full details' }) @ApiResponse({ @@ -76,4 +93,21 @@ export class CatalogController { ): Promise { return this.catalogService.getCatalogItemById(Number(id)); } + + @Post() + @HttpCode(HttpStatus.CREATED) + @ApiOperation({ summary: 'Create a new yacht in catalog' }) + @ApiBody({ type: CreateYachtDto }) + @ApiResponse({ + status: 201, + description: 'Yacht successfully created', + type: CatalogItemLongDto, + }) + @ApiResponse({ + status: 400, + description: 'Bad request', + }) + async createYacht(@Body() createYachtDto: CreateYachtDto): Promise { + return this.catalogService.createYacht(createYachtDto); + } } diff --git a/src/catalog/catalog.module.ts b/src/catalog/catalog.module.ts index 9bd6065..8287a7c 100644 --- a/src/catalog/catalog.module.ts +++ b/src/catalog/catalog.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { CatalogService } from './catalog.service'; import { CatalogController } from './catalog.controller'; import { UsersModule } from '../users/users.module'; @@ -8,10 +8,11 @@ import { ReviewsModule } from '../reviews/reviews.module'; @Module({ imports: [ UsersModule, // This provides UsersService - ReservationsModule, // This provides ReservationsService + forwardRef(() => ReservationsModule), // This provides ReservationsService ReviewsModule, // This provides ReviewsService ], controllers: [CatalogController], providers: [CatalogService], + exports: [CatalogService], // Export for other modules to use }) export class CatalogModule {} diff --git a/src/catalog/catalog.service.ts b/src/catalog/catalog.service.ts index 3628b67..fadf8f4 100644 --- a/src/catalog/catalog.service.ts +++ b/src/catalog/catalog.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Inject, forwardRef } from '@nestjs/common'; import { UsersService } from '../users/users.service'; import { ReservationsService } from '../reservations/reservations.service'; import { ReviewsService } from '../reviews/reviews.service'; @@ -10,11 +10,13 @@ import { CatalogParamsDto } from './dto/catalog-params.dto'; import { CatalogResponseDto } from './dto/catalog-response.dto'; import { MainPageCatalogResponseDto } from './dto/main-page-catalog-response.dto'; import { CatalogFiltersDto } from './dto/catalog-filters.dto'; +import { CreateYachtDto } from './dto/create-yacht.dto'; @Injectable() export class CatalogService { constructor( private readonly usersService: UsersService, + @Inject(forwardRef(() => ReservationsService)) private readonly reservationsService: ReservationsService, private readonly reviewsService: ReviewsService, ) {} @@ -49,7 +51,7 @@ export class CatalogService { power: 1200, description: 'Роскошная моторная яхта Азимут 55 - это воплощение итальянского стиля и российского качества. Идеально подходит для прогулок по Финскому заливу, корпоративных мероприятий и романтических свиданий. На борту: три комфортабельные каюты, просторный салон с панорамным остеклением, полностью оборудованная кухня и две ванные комнаты. Максимальная скорость 32 узла позволяет быстро добраться до самых живописных мест Карельского перешейка.', - owner: null as any, + owner: { userId: 1 } as any, reviews: [], reservations: [], }, @@ -83,7 +85,7 @@ export class CatalogService { power: 1400, description: 'Британский шик и русская душа в одной яхте! Сансикер Манхэттен 52 - выбор настоящих ценителей морских путешествий. Просторный кокпит с мягкими диванами, бар на 8 персон, система мультимедиа премиум-класса. Идеальна для празднования дня рождения на воде или деловой встречи с партнерами. Отличная маневренность позволяет заходить в марины Санкт-Петербурга и Кронштадта.', - owner: null as any, + owner: { userId: 2 } as any, reviews: [], reservations: [], }, @@ -117,7 +119,7 @@ export class CatalogService { power: 1100, description: 'Принцесс V55 - королева российских вод! Эта яхта создана для тех, кто ценит комфорт и элегантность. Четыре уютные каюты с кондиционером, гальюн с душем, полностью оборудованная камбузная зона. Особенность - огромный платц с гидравлическим трапом для купания в Ладожском озере. Отличный выбор для семейного отдыха или рыбалки с друзьями.', - owner: null as any, + owner: { userId: 1 } as any, reviews: [], reservations: [], }, @@ -151,7 +153,7 @@ export class CatalogService { power: 900, description: 'Итальянская страсть в русской стихии! Ферретти 500 сочетает в себе средиземноморский шарм и надежность для суровых условий Балтики. Просторный салон с панорамными окнами, обеденная зона на 6 человек, современная навигационная система. Идеально подходит для фотосессий на фоне разводных мостов Петербурга или романтического ужина под звуки волн.', - owner: null as any, + owner: { userId: 2 } as any, reviews: [], reservations: [], }, @@ -184,7 +186,7 @@ export class CatalogService { power: 1300, description: 'Американская мощь для русского моря! Си Рей 510 Сандансер - самая быстрая яхта в нашем флоте. Развивает скорость до 35 узлов, что позволяет за день обогнуть весь Финский залив. Три комфортабельные каюты, система стабилизации на стоянке, мощная аудиосистема с сабвуфером. Отличный выбор для любителей острых ощущений и скоростных прогулок.', - owner: null as any, + owner: { userId: 1 } as any, reviews: [], reservations: [], }, @@ -217,7 +219,7 @@ export class CatalogService { power: 320, description: 'Немецкое качество для русского характера! Бавария SR41 - надежная и экономичная яхта для спокойных прогулок по Ладоге. Две уютные каюты, просторный кокпит с тентом от дождя, лебедка для подъема парусов. Идеальный выбор для начинающих яхтсменов или семейного отдыха с детьми. Расход топлива всего 15 литров в час!', - owner: null as any, + owner: { userId: 2 } as any, reviews: [], reservations: [], }, @@ -250,7 +252,7 @@ export class CatalogService { power: 250, description: 'Французская элегантность для русского простора! Жанно Мери Фишер 895 - компактная, но вместительная яхта для рыбалки и пикников. Одна просторная каюта, открытый кокпит, столик для барбекю. Отлично подходит для выездов на природу, ночевки в бухтах или обучения детей управлению яхтой. Самый экономичный вариант в нашем флоте!', - owner: null as any, + owner: { userId: 1 } as any, reviews: [], reservations: [], }, @@ -283,7 +285,7 @@ export class CatalogService { power: 425, description: 'Французский траулер для русского севера! Бенето Свифт Троулер 41 создан для длительных путешествий по Белому морю. Экономичный дизельный двигатель, большой запас топлива, система опреснения воды. Две комфортабельные каюты с подогревом пола. Идеальный выбор для экспедиций или многодневных круизов по северным островам.', - owner: null as any, + owner: { userId: 2 } as any, reviews: [], reservations: [], }, @@ -316,7 +318,7 @@ export class CatalogService { power: 90, description: 'Французский катамаран для русского размаха! Лагун 450 - невероятно устойчивая и просторная яхта. Четыре отдельные каюты с санузлами, огромный салон-трансформер, две кухни. Идеально подходит для больших компаний, свадебных церемоний на воде или длительных круизов всей семьей. Не кренится даже в шторм!', - owner: null as any, + owner: { userId: 1 } as any, reviews: [], reservations: [], }, @@ -349,7 +351,7 @@ export class CatalogService { power: 80, description: 'Французский катамаран класса люкс! Фонтен Пажо Люсия 40 - это плавающий пятизвездочный отель. Четыре каюты-люкс с джакузи, салон с камином, профессиональная кухня с шеф-поваром. Система стабилизации на якоре, гидромассажный бассейн на палубе. Выбор настоящих ценителей роскоши и комфорта на воде.', - owner: null as any, + owner: { userId: 2 } as any, reviews: [], reservations: [], }, @@ -382,7 +384,7 @@ export class CatalogService { power: 380, description: 'Французская парусная яхта для русского ветра! Дюфур 460 - мечта любого яхтсмена. Три просторные каюты, кокпит с мягкими сиденьями, современное парусное вооружение. Идеально сбалансированная, легко управляется даже новичками. Отличный выбор для регат, обучения парусному спорту или романтических круизов под парусами.', - owner: null as any, + owner: { userId: 1 } as any, reviews: [], reservations: [], }, @@ -415,7 +417,7 @@ export class CatalogService { power: 1600, description: 'Американская легенда для русского океана! Гранд Бэнкс 60 - экспедиционная яхта для самых смелых путешествий. Три каюты-люкс, салон с библиотекой, зимний сад, сауна. Автономность плавания - 30 дней! Способна пересечь Баренцево море и дойти до Шпицбергена. Выбор для настоящих морских волков и исследователей Арктики.', - owner: null as any, + owner: { userId: 2 } as any, reviews: [], reservations: [], }, @@ -610,4 +612,44 @@ export class CatalogService { async getAllCatalogItems(): Promise { return this.catalogItems.map((item) => this.toShortDto(item)); } + + async getCatalogByUserId(userId: number): Promise { + // Логика определения ownerId: нечетные id -> ownerId=1, четные id -> ownerId=2 + const filteredItems = this.catalogItems.filter((item) => { + return item.owner?.userId === userId; + }); + return filteredItems.map((item) => this.toShortDto(item)); + } + + async createYacht(createYachtDto: CreateYachtDto): Promise { + const newId = Math.max(...this.catalogItems.map((item) => item.id || 0), 0) + 1; + const owner = await this.usersService.findById(createYachtDto.userId); + + const newYacht: CatalogItemLongDto = { + id: newId, + name: createYachtDto.name, + length: createYachtDto.length, + speed: createYachtDto.speed, + minCost: createYachtDto.minCost, + mainImageUrl: createYachtDto.mainImageUrl, + galleryUrls: createYachtDto.galleryUrls, + hasQuickRent: createYachtDto.hasQuickRent, + isFeatured: createYachtDto.isFeatured, + year: createYachtDto.year, + comfortCapacity: createYachtDto.comfortCapacity, + maxCapacity: createYachtDto.maxCapacity, + width: createYachtDto.width, + cabinsCount: createYachtDto.cabinsCount, + matherial: createYachtDto.matherial, + power: createYachtDto.power, + description: createYachtDto.description, + owner: owner || ({ userId: createYachtDto.userId } as any), + reviews: [], + reservations: [], + topText: createYachtDto.topText, + }; + + this.catalogItems.push(newYacht); + return newYacht; + } } diff --git a/src/catalog/dto/create-yacht.dto.ts b/src/catalog/dto/create-yacht.dto.ts new file mode 100644 index 0000000..92058bd --- /dev/null +++ b/src/catalog/dto/create-yacht.dto.ts @@ -0,0 +1,71 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateYachtDto { + @ApiProperty({ example: 'Азимут 55', description: 'Название яхты' }) + name: string; + + @ApiProperty({ example: 16.7, description: 'Длина яхты в метрах' }) + length: number; + + @ApiProperty({ example: 32, description: 'Скорость в узлах' }) + speed: number; + + @ApiProperty({ example: 85000, description: 'Минимальная стоимость аренды' }) + minCost: number; + + @ApiProperty({ + example: 'api/uploads/1765727362318-238005198.jpg', + description: 'URL главного изображения', + }) + mainImageUrl: string; + + @ApiProperty({ + type: [String], + example: ['api/uploads/1765727362318-238005198.jpg'], + description: 'URL галереи изображений', + }) + galleryUrls: string[]; + + @ApiProperty({ example: true, description: 'Доступна быстрая аренда' }) + hasQuickRent: boolean; + + @ApiProperty({ example: false, description: 'Рекомендуемая яхта' }) + isFeatured: boolean; + + @ApiProperty({ example: 2022, description: 'Год выпуска' }) + year: number; + + @ApiProperty({ example: 8, description: 'Комфортная вместимость' }) + comfortCapacity: number; + + @ApiProperty({ example: 12, description: 'Максимальная вместимость' }) + maxCapacity: number; + + @ApiProperty({ example: 4.8, description: 'Ширина в метрах' }) + width: number; + + @ApiProperty({ example: 3, description: 'Количество кают' }) + cabinsCount: number; + + @ApiProperty({ example: 'Стеклопластик', description: 'Материал корпуса' }) + matherial: string; + + @ApiProperty({ example: 1200, description: 'Мощность двигателя' }) + power: number; + + @ApiProperty({ + example: 'Роскошная моторная яхта...', + description: 'Описание яхты', + }) + description: string; + + @ApiProperty({ example: 1, description: 'ID владельца яхты' }) + userId: number; + + @ApiProperty({ + required: false, + example: '🔥 Лучшее предложение', + description: 'Текст для отображения сверху', + }) + topText?: string; +} diff --git a/src/reservations/reservation-item.dto.ts b/src/reservations/reservation-item.dto.ts index 26df0f2..23374e6 100644 --- a/src/reservations/reservation-item.dto.ts +++ b/src/reservations/reservation-item.dto.ts @@ -1,6 +1,28 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { CatalogItemLongDto } from '../catalog/dto/catalog-item.dto'; + export class ReservationItemDto { + @ApiProperty({ example: 1, description: 'ID яхты' }) yachtId: number; + + @ApiProperty({ example: 1, description: 'ID резерватора' }) reservatorId: number; + + @ApiProperty({ example: 1733097600, description: 'Начало резервации (Unix timestamp в UTC)' }) startUtc: number; + + @ApiProperty({ example: 1733133600, description: 'Конец резервации (Unix timestamp в UTC)' }) endUtc: number; } + +export class ReservationWithYachtDto extends ReservationItemDto { + @ApiProperty({ example: 1, description: 'ID резервации' }) + id: number; + + @ApiProperty({ + type: CatalogItemLongDto, + required: false, + description: 'Данные о яхте', + }) + yacht?: CatalogItemLongDto; +} diff --git a/src/reservations/reservations.controller.ts b/src/reservations/reservations.controller.ts index 654b250..da66955 100644 --- a/src/reservations/reservations.controller.ts +++ b/src/reservations/reservations.controller.ts @@ -1,18 +1,29 @@ import { Controller, Get, Post, Body, Param } from '@nestjs/common'; +import { ApiOperation, ApiResponse, ApiBody, ApiParam } from '@nestjs/swagger'; import { ReservationsService } from './reservations.service'; -import { ReservationItemDto } from './reservation-item.dto'; +import { ReservationItemDto, ReservationWithYachtDto } from './reservation-item.dto'; @Controller('reservations') export class ReservationsController { constructor(private readonly reservationsService: ReservationsService) {} @Post() + @ApiOperation({ summary: 'Создать новую резервацию' }) + @ApiBody({ type: ReservationItemDto }) + @ApiResponse({ status: 201, description: 'Резервация успешно создана' }) create(@Body() dto: ReservationItemDto) { return this.reservationsService.createReservation(dto); } @Get('user/:userId') - findByUserId(@Param('userId') userId: string) { + @ApiOperation({ summary: 'Получить резервации по ID пользователя' }) + @ApiParam({ name: 'userId', description: 'ID пользователя', type: Number }) + @ApiResponse({ + status: 200, + description: 'Список резерваций пользователя с данными о яхтах', + type: [ReservationWithYachtDto], + }) + async findByUserId(@Param('userId') userId: string) { return this.reservationsService.getReservationsByUserId(Number(userId)); } diff --git a/src/reservations/reservations.module.ts b/src/reservations/reservations.module.ts index 21d47c6..771502f 100644 --- a/src/reservations/reservations.module.ts +++ b/src/reservations/reservations.module.ts @@ -1,8 +1,10 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { ReservationsService } from './reservations.service'; import { ReservationsController } from './reservations.controller'; +import { CatalogModule } from '../catalog/catalog.module'; @Module({ + imports: [forwardRef(() => CatalogModule)], controllers: [ReservationsController], providers: [ReservationsService], exports: [ReservationsService], // Export for other modules to use diff --git a/src/reservations/reservations.service.ts b/src/reservations/reservations.service.ts index 870d5cf..56148b3 100644 --- a/src/reservations/reservations.service.ts +++ b/src/reservations/reservations.service.ts @@ -1,12 +1,23 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Inject, forwardRef } from '@nestjs/common'; import { ReservationItemDto } from './reservation-item.dto'; +import { CatalogService } from '../catalog/catalog.service'; +import { CatalogItemLongDto } from '../catalog/dto/catalog-item.dto'; export interface Reservation extends ReservationItemDto { id: number; } +export interface ReservationWithYacht extends Reservation { + yacht?: CatalogItemLongDto; +} + @Injectable() export class ReservationsService { + constructor( + @Inject(forwardRef(() => CatalogService)) + private readonly catalogService: CatalogService, + ) {} + private reservations: Reservation[] = [ { id: 1, @@ -70,8 +81,21 @@ export class ReservationsService { return reservation; } - getReservationsByUserId(userId: number): Reservation[] { - return this.reservations.filter((r) => r.reservatorId === userId); + async getReservationsByUserId(userId: number): Promise { + const reservations = this.reservations.filter((r) => r.reservatorId === userId); + + // Populate данные по яхте для каждой резервации + const reservationsWithYacht = await Promise.all( + reservations.map(async (reservation) => { + const yacht = await this.catalogService.getCatalogItemById(reservation.yachtId); + return { + ...reservation, + yacht: yacht || undefined, + }; + }) + ); + + return reservationsWithYacht; } getReservationsByYachtId(yachtId: number): Reservation[] {