diff --git a/src/app.module.ts b/src/app.module.ts index 52f1508..d851512 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -6,9 +6,11 @@ import { CatalogModule } from './catalog/catalog.module'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { FilesModule } from './files/files.module'; +import { ReviewsModule } from './reviews/reviews.module'; +import { ReservationsModule } from './reservations/reservations.module'; @Module({ - imports: [YachtsModule, CatalogModule, AuthModule, UsersModule, FilesModule], + imports: [YachtsModule, CatalogModule, AuthModule, UsersModule, FilesModule, ReviewsModule, ReservationsModule], controllers: [AppController], providers: [AppService], }) diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts deleted file mode 100644 index 800ab66..0000000 --- a/src/auth/auth.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthService } from './auth.service'; - -describe('AuthService', () => { - let service: AuthService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [AuthService], - }).compile(); - - service = module.get(AuthService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/catalog/catalog.controller.spec.ts b/src/catalog/catalog.controller.spec.ts deleted file mode 100644 index a1c898c..0000000 --- a/src/catalog/catalog.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { CatalogController } from './catalog.controller'; - -describe('CatalogController', () => { - let controller: CatalogController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [CatalogController], - }).compile(); - - controller = module.get(CatalogController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/catalog/catalog.controller.ts b/src/catalog/catalog.controller.ts index 6d6aac5..8aa5996 100644 --- a/src/catalog/catalog.controller.ts +++ b/src/catalog/catalog.controller.ts @@ -1,7 +1,10 @@ -import { Controller, Get, Query } from '@nestjs/common'; +import { Controller, Get, Query, Param } from '@nestjs/common'; import { CatalogService } from './catalog.service'; import { CatalogResponseDto } from './dto/catalog-response.dto'; -import { CatalogItemDto } from './dto/catalog-item.dto'; +import { + CatalogItemShortDto, + CatalogItemLongDto, +} from './dto/catalog-item.dto'; import { CatalogParamsDto } from './dto/catalog-params.dto'; import { ApiQuery, @@ -56,9 +59,9 @@ export class CatalogController { @ApiResponse({ status: 200, description: 'All catalog items', - type: [CatalogItemDto], + type: [CatalogItemShortDto], }) - async getAllCatalogItems(): Promise { + async getAllCatalogItems(): Promise { return this.catalogService.getAllCatalogItems(); } @@ -69,4 +72,17 @@ export class CatalogController { ): Promise { return this.catalogService.getCatalog(params); } + + @Get(':id') + @ApiOperation({ summary: 'Get catalog item by ID with full details' }) + @ApiResponse({ + status: 200, + description: 'Catalog item with reviews, reservations and owner', + type: CatalogItemLongDto, + }) + async getCatalogItemById( + @Param('id') id: string, + ): Promise { + return this.catalogService.getCatalogItemById(Number(id)); + } } diff --git a/src/catalog/catalog.service.spec.ts b/src/catalog/catalog.service.spec.ts deleted file mode 100644 index 50fa1b3..0000000 --- a/src/catalog/catalog.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { CatalogService } from './catalog.service'; - -describe('CatalogService', () => { - let service: CatalogService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [CatalogService], - }).compile(); - - service = module.get(CatalogService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/catalog/catalog.service.ts b/src/catalog/catalog.service.ts index 8ebd880..b7b4d65 100644 --- a/src/catalog/catalog.service.ts +++ b/src/catalog/catalog.service.ts @@ -1,15 +1,27 @@ import { Injectable } from '@nestjs/common'; -import { CatalogItemDto } from './dto/catalog-item.dto'; +import { UsersService } from '../users/users.service'; +import { ReservationsService } from '../reservations/reservations.service'; +import { ReviewsService } from '../reviews/reviews.service'; +import { + CatalogItemShortDto, + CatalogItemLongDto, +} from './dto/catalog-item.dto'; import { CatalogParamsDto } from './dto/catalog-params.dto'; import { CatalogResponseDto } from './dto/catalog-response.dto'; import { MainPageCatalogResponseDto } from './dto/main-page-catalog-response.dto'; @Injectable() export class CatalogService { - private catalogItems: CatalogItemDto[] = [ + constructor( + private readonly usersService: UsersService, + private readonly reservationsService: ReservationsService, + private readonly reviewsService: ReviewsService, + ) {} + + private catalogItems: CatalogItemLongDto[] = [ { id: 1, - name: 'Azimut 55', + name: 'Азимут 55', length: 16.7, speed: 32, minCost: 85000, @@ -27,10 +39,22 @@ export class CatalogService { ], hasQuickRent: true, isFeatured: true, + year: 2022, + comfortCapacity: 8, + maxCapacity: 12, + width: 4.8, + cabinsCount: 3, + matherial: 'Стеклопластик', + power: 1200, + description: + 'Роскошная моторная яхта Азимут 55 - это воплощение итальянского стиля и российского качества. Идеально подходит для прогулок по Финскому заливу, корпоративных мероприятий и романтических свиданий. На борту: три комфортабельные каюты, просторный салон с панорамным остеклением, полностью оборудованная кухня и две ванные комнаты. Максимальная скорость 32 узла позволяет быстро добраться до самых живописных мест Карельского перешейка.', + owner: null as any, + reviews: [], + reservations: [], }, { id: 2, - name: 'Sunseeker Manhattan 52', + name: 'Сансикер Манхэттен 52', length: 15.8, speed: 34, minCost: 92000, @@ -49,10 +73,22 @@ export class CatalogService { hasQuickRent: false, isFeatured: false, topText: '🔥 Лучшее предложение', + year: 2023, + comfortCapacity: 6, + maxCapacity: 10, + width: 4.5, + cabinsCount: 3, + matherial: 'Стеклопластик', + power: 1400, + description: + 'Британский шик и русская душа в одной яхте! Сансикер Манхэттен 52 - выбор настоящих ценителей морских путешествий. Просторный кокпит с мягкими диванами, бар на 8 персон, система мультимедиа премиум-класса. Идеальна для празднования дня рождения на воде или деловой встречи с партнерами. Отличная маневренность позволяет заходить в марины Санкт-Петербурга и Кронштадта.', + owner: null as any, + reviews: [], + reservations: [], }, { id: 3, - name: 'Princess V55', + name: 'Принцесс V55', length: 16.7, speed: 33, minCost: 78000, @@ -71,10 +107,22 @@ export class CatalogService { hasQuickRent: true, isFeatured: false, topText: '🍷 Идеальна для заката с бокалом вина', + year: 2021, + comfortCapacity: 8, + maxCapacity: 12, + width: 4.7, + cabinsCount: 4, + matherial: 'Стеклопластик', + power: 1100, + description: + 'Принцесс V55 - королева российских вод! Эта яхта создана для тех, кто ценит комфорт и элегантность. Четыре уютные каюты с кондиционером, гальюн с душем, полностью оборудованная камбузная зона. Особенность - огромный платц с гидравлическим трапом для купания в Ладожском озере. Отличный выбор для семейного отдыха или рыбалки с друзьями.', + owner: null as any, + reviews: [], + reservations: [], }, { id: 4, - name: 'Ferretti 500', + name: 'Ферретти 500', length: 15.2, speed: 31, minCost: 68000, @@ -93,10 +141,22 @@ export class CatalogService { hasQuickRent: true, isFeatured: false, topText: '⏳ Часто бронируется - успей', + year: 2020, + comfortCapacity: 6, + maxCapacity: 8, + width: 4.3, + cabinsCount: 3, + matherial: 'Стеклопластик', + power: 900, + description: + 'Итальянская страсть в русской стихии! Ферретти 500 сочетает в себе средиземноморский шарм и надежность для суровых условий Балтики. Просторный салон с панорамными окнами, обеденная зона на 6 человек, современная навигационная система. Идеально подходит для фотосессий на фоне разводных мостов Петербурга или романтического ужина под звуки волн.', + owner: null as any, + reviews: [], + reservations: [], }, { id: 5, - name: 'Sea Ray 510 Sundancer', + name: 'Си Рей 510 Сандансер', length: 15.5, speed: 35, minCost: 72000, @@ -114,10 +174,22 @@ export class CatalogService { ], hasQuickRent: false, isFeatured: false, + year: 2023, + comfortCapacity: 8, + maxCapacity: 10, + width: 4.6, + cabinsCount: 3, + matherial: 'Стеклопластик', + power: 1300, + description: + 'Американская мощь для русского моря! Си Рей 510 Сандансер - самая быстрая яхта в нашем флоте. Развивает скорость до 35 узлов, что позволяет за день обогнуть весь Финский залив. Три комфортабельные каюты, система стабилизации на стоянке, мощная аудиосистема с сабвуфером. Отличный выбор для любителей острых ощущений и скоростных прогулок.', + owner: null as any, + reviews: [], + reservations: [], }, { id: 6, - name: 'Bavaria SR41', + name: 'Бавария SR41', length: 12.5, speed: 28, minCost: 45000, @@ -135,10 +207,22 @@ export class CatalogService { ], hasQuickRent: true, isFeatured: false, + year: 2019, + comfortCapacity: 6, + maxCapacity: 8, + width: 3.9, + cabinsCount: 2, + matherial: 'Стеклопластик', + power: 320, + description: + 'Немецкое качество для русского характера! Бавария SR41 - надежная и экономичная яхта для спокойных прогулок по Ладоге. Две уютные каюты, просторный кокпит с тентом от дождя, лебедка для подъема парусов. Идеальный выбор для начинающих яхтсменов или семейного отдыха с детьми. Расход топлива всего 15 литров в час!', + owner: null as any, + reviews: [], + reservations: [], }, { id: 7, - name: 'Jeanneau Merry Fisher 895', + name: 'Жанно Мери Фишер 895', length: 8.9, speed: 25, minCost: 32000, @@ -156,10 +240,22 @@ export class CatalogService { ], hasQuickRent: true, isFeatured: false, + year: 2022, + comfortCapacity: 4, + maxCapacity: 6, + width: 3.0, + cabinsCount: 1, + matherial: 'Стеклопластик', + power: 250, + description: + 'Французская элегантность для русского простора! Жанно Мери Фишер 895 - компактная, но вместительная яхта для рыбалки и пикников. Одна просторная каюта, открытый кокпит, столик для барбекю. Отлично подходит для выездов на природу, ночевки в бухтах или обучения детей управлению яхтой. Самый экономичный вариант в нашем флоте!', + owner: null as any, + reviews: [], + reservations: [], }, { id: 8, - name: 'Beneteau Swift Trawler 41', + name: 'Бенето Свифт Троулер 41', length: 12.5, speed: 22, minCost: 55000, @@ -177,10 +273,22 @@ export class CatalogService { ], hasQuickRent: false, isFeatured: false, + year: 2021, + comfortCapacity: 6, + maxCapacity: 8, + width: 4.2, + cabinsCount: 2, + matherial: 'Стеклопластик', + power: 425, + description: + 'Французский траулер для русского севера! Бенето Свифт Троулер 41 создан для длительных путешествий по Белому морю. Экономичный дизельный двигатель, большой запас топлива, система опреснения воды. Две комфортабельные каюты с подогревом пола. Идеальный выбор для экспедиций или многодневных круизов по северным островам.', + owner: null as any, + reviews: [], + reservations: [], }, { id: 9, - name: 'Lagoon 450', + name: 'Лагун 450', length: 13.5, speed: 20, minCost: 65000, @@ -198,10 +306,22 @@ export class CatalogService { ], hasQuickRent: true, isFeatured: false, + year: 2020, + comfortCapacity: 8, + maxCapacity: 10, + width: 7.8, + cabinsCount: 4, + matherial: 'Стеклопластик', + power: 90, + description: + 'Французский катамаран для русского размаха! Лагун 450 - невероятно устойчивая и просторная яхта. Четыре отдельные каюты с санузлами, огромный салон-трансформер, две кухни. Идеально подходит для больших компаний, свадебных церемоний на воде или длительных круизов всей семьей. Не кренится даже в шторм!', + owner: null as any, + reviews: [], + reservations: [], }, { id: 10, - name: 'Fountaine Pajot Lucia 40', + name: 'Фонтен Пажо Люсия 40', length: 11.7, speed: 18, minCost: 58000, @@ -219,10 +339,22 @@ export class CatalogService { ], hasQuickRent: true, isFeatured: false, + year: 2023, + comfortCapacity: 8, + maxCapacity: 10, + width: 7.1, + cabinsCount: 4, + matherial: 'Стеклопластик', + power: 80, + description: + 'Французский катамаран класса люкс! Фонтен Пажо Люсия 40 - это плавающий пятизвездочный отель. Четыре каюты-люкс с джакузи, салон с камином, профессиональная кухня с шеф-поваром. Система стабилизации на якоре, гидромассажный бассейн на палубе. Выбор настоящих ценителей роскоши и комфорта на воде.', + owner: null as any, + reviews: [], + reservations: [], }, { id: 11, - name: 'Dufour 460', + name: 'Дюфур 460', length: 14.1, speed: 26, minCost: 62000, @@ -240,10 +372,22 @@ export class CatalogService { ], hasQuickRent: false, isFeatured: false, + year: 2022, + comfortCapacity: 8, + maxCapacity: 10, + width: 4.5, + cabinsCount: 3, + matherial: 'Стеклопластик', + power: 380, + description: + 'Французская парусная яхта для русского ветра! Дюфур 460 - мечта любого яхтсмена. Три просторные каюты, кокпит с мягкими сиденьями, современное парусное вооружение. Идеально сбалансированная, легко управляется даже новичками. Отличный выбор для регат, обучения парусному спорту или романтических круизов под парусами.', + owner: null as any, + reviews: [], + reservations: [], }, { id: 12, - name: 'Grand Banks 60', + name: 'Гранд Бэнкс 60', length: 18.3, speed: 24, minCost: 125000, @@ -261,9 +405,65 @@ export class CatalogService { ], hasQuickRent: true, isFeatured: false, + year: 2023, + comfortCapacity: 6, + maxCapacity: 8, + width: 5.2, + cabinsCount: 3, + matherial: 'Стеклопластик', + power: 1600, + description: + 'Американская легенда для русского океана! Гранд Бэнкс 60 - экспедиционная яхта для самых смелых путешествий. Три каюты-люкс, салон с библиотекой, зимний сад, сауна. Автономность плавания - 30 дней! Способна пересечь Баренцево море и дойти до Шпицбергена. Выбор для настоящих морских волков и исследователей Арктики.', + owner: null as any, + reviews: [], + reservations: [], }, ]; + private toShortDto(item: CatalogItemLongDto): CatalogItemShortDto { + const { + id, + name, + length, + speed, + minCost, + mainImageUrl, + galleryUrls, + hasQuickRent, + isFeatured, + topText, + } = item; + return { + id, + name, + length, + speed, + minCost, + mainImageUrl, + galleryUrls, + hasQuickRent, + isFeatured, + topText, + }; + } + + async getCatalogItemById(id: number): Promise { + const item = this.catalogItems.find((item) => item.id === id); + if (!item) return null; + + const ownerId = [1, 2][(id - 1) % 2]; + const owner = await this.usersService.findById(ownerId); + + if (owner) { + item.owner = owner; + } + + item.reviews = this.reviewsService.getReviewsByYachtId(id); + item.reservations = this.reservationsService.getReservationsByYachtId(id); + + return item; + } + async getMainPageCatalog(): Promise { const clonedCatalog = [...this.catalogItems]; const filteredCatalog = clonedCatalog.filter( @@ -280,12 +480,12 @@ export class CatalogService { ); const mappedRestYachts = filteredCatalog.slice(0, 6).map((item) => ({ - ...item, + ...this.toShortDto(item), isBestOffer: item.minCost === minCost, })); return { - featuredYacht: clonedCatalog[featuredYachtIndex], + featuredYacht: this.toShortDto(clonedCatalog[featuredYachtIndex]), restYachts: mappedRestYachts, }; } @@ -294,11 +494,10 @@ export class CatalogService { } async getCatalog(params?: CatalogParamsDto): Promise { - let filteredItems = [...this.catalogItems]; + let filteredItems = this.catalogItems.map((item) => this.toShortDto(item)); if (params?.search) { const searchTerm = params.search.toLowerCase(); - filteredItems = filteredItems.filter((item) => item.name.toLowerCase().includes(searchTerm), ); @@ -306,7 +505,6 @@ export class CatalogService { if (params?.filter?.price) { const { minvalue, maxvalue } = params.filter.price; - filteredItems = filteredItems.filter((item) => { if (minvalue !== undefined && item.minCost < minvalue) return false; if (maxvalue !== undefined && item.minCost > maxvalue) return false; @@ -316,7 +514,6 @@ export class CatalogService { if (params?.sort?.field) { const { field, direction = 'asc' } = params.sort; - filteredItems.sort((a, b) => { let valueA = a[field]; let valueB = b[field]; @@ -326,7 +523,6 @@ export class CatalogService { if (valueA < valueB) return direction === 'asc' ? -1 : 1; if (valueA > valueB) return direction === 'asc' ? 1 : -1; - return 0; }); } @@ -340,7 +536,7 @@ export class CatalogService { }; } - async getAllCatalogItems(): Promise { - return this.catalogItems; + async getAllCatalogItems(): Promise { + return this.catalogItems.map((item) => this.toShortDto(item)); } } diff --git a/src/catalog/dto/catalog-item.dto.ts b/src/catalog/dto/catalog-item.dto.ts index 859b642..05a7cd1 100644 --- a/src/catalog/dto/catalog-item.dto.ts +++ b/src/catalog/dto/catalog-item.dto.ts @@ -1,4 +1,8 @@ -export class CatalogItemDto { +import { type Reservation } from 'src/reservations/reservations.service'; +import { type Review } from 'src/reviews/reviews.service'; +import { User } from 'src/users/user.entity'; + +export class CatalogItemShortDto { id?: number; name: string; length: number; @@ -11,3 +15,17 @@ export class CatalogItemDto { topText?: string; isBestOffer?: boolean; } + +export class CatalogItemLongDto extends CatalogItemShortDto { + year: number; + comfortCapacity: number; + maxCapacity: number; + width: number; + cabinsCount: number; + matherial: string; + power: number; + description: string; + owner: User; + reviews: Review[]; + reservations: Reservation[]; +} diff --git a/src/catalog/dto/catalog-response.dto.ts b/src/catalog/dto/catalog-response.dto.ts index ae08d65..f0da60b 100644 --- a/src/catalog/dto/catalog-response.dto.ts +++ b/src/catalog/dto/catalog-response.dto.ts @@ -1,7 +1,7 @@ -import { CatalogItemDto } from './catalog-item.dto'; +import { CatalogItemShortDto } from './catalog-item.dto'; export class CatalogResponseDto { - items: CatalogItemDto[]; + items: CatalogItemShortDto[]; total: number; page?: number; limit?: number; diff --git a/src/catalog/dto/main-page-catalog-response.dto.ts b/src/catalog/dto/main-page-catalog-response.dto.ts index 5895602..29d0566 100644 --- a/src/catalog/dto/main-page-catalog-response.dto.ts +++ b/src/catalog/dto/main-page-catalog-response.dto.ts @@ -1,6 +1,6 @@ -import { CatalogItemDto } from './catalog-item.dto'; +import { CatalogItemShortDto } from './catalog-item.dto'; export class MainPageCatalogResponseDto { - featuredYacht: CatalogItemDto; - restYachts: CatalogItemDto[]; + featuredYacht: CatalogItemShortDto; + restYachts: CatalogItemShortDto[]; } diff --git a/src/reservations/reservation-item.dto.ts b/src/reservations/reservation-item.dto.ts new file mode 100644 index 0000000..26df0f2 --- /dev/null +++ b/src/reservations/reservation-item.dto.ts @@ -0,0 +1,6 @@ +export class ReservationItemDto { + yachtId: number; + reservatorId: number; + startUtc: number; + endUtc: number; +} diff --git a/src/reservations/reservations.controller.ts b/src/reservations/reservations.controller.ts new file mode 100644 index 0000000..654b250 --- /dev/null +++ b/src/reservations/reservations.controller.ts @@ -0,0 +1,28 @@ +import { Controller, Get, Post, Body, Param } from '@nestjs/common'; +import { ReservationsService } from './reservations.service'; +import { ReservationItemDto } from './reservation-item.dto'; + +@Controller('reservations') +export class ReservationsController { + constructor(private readonly reservationsService: ReservationsService) {} + + @Post() + create(@Body() dto: ReservationItemDto) { + return this.reservationsService.createReservation(dto); + } + + @Get('user/:userId') + findByUserId(@Param('userId') userId: string) { + return this.reservationsService.getReservationsByUserId(Number(userId)); + } + + @Get('yacht/:yachtId') + findByYachtId(@Param('yachtId') yachtId: string) { + return this.reservationsService.getReservationsByYachtId(Number(yachtId)); + } + + @Get() + findAll() { + return this.reservationsService.getAllReservations(); + } +} diff --git a/src/reservations/reservations.module.ts b/src/reservations/reservations.module.ts new file mode 100644 index 0000000..0a09dc2 --- /dev/null +++ b/src/reservations/reservations.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ReservationsService } from './reservations.service'; +import { ReservationsController } from './reservations.controller'; + +@Module({ + controllers: [ReservationsController], + providers: [ReservationsService], +}) +export class ReservationsModule {} diff --git a/src/reservations/reservations.service.ts b/src/reservations/reservations.service.ts new file mode 100644 index 0000000..6a91dec --- /dev/null +++ b/src/reservations/reservations.service.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@nestjs/common'; +import { ReservationItemDto } from './reservation-item.dto'; + +export interface Reservation extends ReservationItemDto { + id: number; +} + +@Injectable() +export class ReservationsService { + private reservations: Reservation[] = [ + { + id: 1, + yachtId: 1, + reservatorId: 1, + startUtc: 1733097600, + endUtc: 1733133600, + }, + { + id: 2, + yachtId: 3, + reservatorId: 2, + startUtc: 1733212800, + endUtc: 1733436000, + }, + { + id: 3, + yachtId: 5, + reservatorId: 1, + startUtc: 1733860800, + endUtc: 1734048000, + }, + { + id: 4, + yachtId: 7, + reservatorId: 2, + startUtc: 1734216000, + endUtc: 1734472800, + }, + { + id: 5, + yachtId: 9, + reservatorId: 1, + startUtc: 1734705600, + endUtc: 1734883200, + }, + { + id: 6, + yachtId: 11, + reservatorId: 2, + startUtc: 1735113600, + endUtc: 1735336800, + }, + ]; + + private idCounter = 7; + + createReservation(dto: ReservationItemDto): Reservation { + const reservation = { + id: this.idCounter++, + ...dto, + }; + + this.reservations.push(reservation); + return reservation; + } + + getReservationsByUserId(userId: number): Reservation[] { + return this.reservations.filter((r) => r.reservatorId === userId); + } + + getReservationsByYachtId(yachtId: number): Reservation[] { + return this.reservations.filter((r) => r.yachtId === yachtId); + } + + getAllReservations(): Reservation[] { + return this.reservations; + } +} diff --git a/src/reviews/review-item.dto.ts b/src/reviews/review-item.dto.ts new file mode 100644 index 0000000..22f6697 --- /dev/null +++ b/src/reviews/review-item.dto.ts @@ -0,0 +1,6 @@ +export class ReviewItemDto { + reviewerId: number; + yachtId: number; + starsCount: number; + description: string; +} diff --git a/src/reviews/reviews.controller.ts b/src/reviews/reviews.controller.ts new file mode 100644 index 0000000..6b24493 --- /dev/null +++ b/src/reviews/reviews.controller.ts @@ -0,0 +1,28 @@ +import { Controller, Get, Post, Body, Param } from '@nestjs/common'; +import { ReviewsService } from './reviews.service'; +import { ReviewItemDto } from './review-item.dto'; + +@Controller('reviews') +export class ReviewsController { + constructor(private readonly reviewsService: ReviewsService) {} + + @Post() + create(@Body() dto: ReviewItemDto) { + return this.reviewsService.createReview(dto); + } + + @Get('user/:userId') + findByUserId(@Param('userId') userId: string) { + return this.reviewsService.getReviewsByUserId(Number(userId)); + } + + @Get('yacht/:yachtId') + findByYachtId(@Param('yachtId') yachtId: string) { + return this.reviewsService.getReviewsByYachtId(Number(yachtId)); + } + + @Get() + findAll() { + return this.reviewsService.getAllReviews(); + } +} diff --git a/src/reviews/reviews.module.ts b/src/reviews/reviews.module.ts new file mode 100644 index 0000000..96145de --- /dev/null +++ b/src/reviews/reviews.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ReviewsService } from './reviews.service'; +import { ReviewsController } from './reviews.controller'; + +@Module({ + controllers: [ReviewsController], + providers: [ReviewsService], +}) +export class ReviewsModule {} diff --git a/src/reviews/reviews.service.ts b/src/reviews/reviews.service.ts new file mode 100644 index 0000000..d757465 --- /dev/null +++ b/src/reviews/reviews.service.ts @@ -0,0 +1,92 @@ +import { Injectable } from '@nestjs/common'; +import { ReviewItemDto } from './review-item.dto'; + +export interface Review extends ReviewItemDto { + id: number; +} + +@Injectable() +export class ReviewsService { + private reviews: Review[] = [ + { + id: 1, + reviewerId: 1, + yachtId: 1, + starsCount: 5, + description: 'Excellent yacht!', + }, + { + id: 2, + reviewerId: 2, + yachtId: 1, + starsCount: 4, + description: 'Very good experience', + }, + { + id: 3, + reviewerId: 1, + yachtId: 3, + starsCount: 3, + description: 'Average condition', + }, + { + id: 4, + reviewerId: 2, + yachtId: 5, + starsCount: 5, + description: 'Perfect for sailing', + }, + { + id: 5, + reviewerId: 1, + yachtId: 7, + starsCount: 4, + description: 'Comfortable and fast', + }, + { + id: 6, + reviewerId: 2, + yachtId: 9, + starsCount: 2, + description: 'Needs maintenance', + }, + { + id: 7, + reviewerId: 1, + yachtId: 11, + starsCount: 5, + description: 'Luxury experience', + }, + { + id: 8, + reviewerId: 2, + yachtId: 12, + starsCount: 4, + description: 'Great value for money', + }, + ]; + + private idCounter = 9; + + createReview(dto: ReviewItemDto): Review { + const review = { + id: this.idCounter++, + ...dto, + }; + + this.reviews.push(review); + return review; + } + + getReviewsByUserId(userId: number): Review[] { + return this.reviews.filter((r) => r.reviewerId === userId); + } + + getReviewsByYachtId(yachtId: number): Review[] { + return this.reviews.filter((r) => r.yachtId === yachtId); + } + + getAllReviews(): Review[] { + return this.reviews; + } +} diff --git a/src/users/user.entity.ts b/src/users/user.entity.ts index 82e1b0c..a05c748 100644 --- a/src/users/user.entity.ts +++ b/src/users/user.entity.ts @@ -8,4 +8,7 @@ export type User = { email: string; password: string; yachts?: Yacht[]; + companyName?: string; + inn?: number; + ogrn?: number; }; diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts deleted file mode 100644 index 62815ba..0000000 --- a/src/users/users.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UsersService } from './users.service'; - -describe('UsersService', () => { - let service: UsersService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [UsersService], - }).compile(); - - service = module.get(UsersService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/users/users.service.ts b/src/users/users.service.ts index a4c24e7..37852da 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -9,19 +9,47 @@ export class UsersService { private readonly users: User[] = [ { userId: 1, - firstName: 'Ivan', - lastName: 'Andreev', + firstName: 'Иван', + lastName: 'Андреев', phone: '+79009009090', - email: 'email@email.com', + email: 'ivan@yachting.ru', password: 'admin', + companyName: 'Северный Флот', + inn: 1234567890, + ogrn: 1122334455667, }, { userId: 2, - firstName: 'Sergey', - lastName: 'Bolshakov', - phone: '+79009009090', - email: 'email1@email.com', + firstName: 'Сергей', + lastName: 'Большаков', + phone: '+79119119191', + email: 'sergey@yachting.ru', password: 'admin', + companyName: 'Балтийские Просторы', + inn: 9876543210, + ogrn: 9988776655443, + }, + { + userId: 3, + firstName: 'Анна', + lastName: 'Петрова', + phone: '+79229229292', + email: 'anna@yachting.ru', + password: 'admin', + companyName: 'Ладожские Ветры', + inn: 5555555555, + ogrn: 3333444455556, + }, + { + userId: 4, + firstName: 'Дмитрий', + lastName: 'Соколов', + phone: '+79339339393', + email: 'dmitry@yachting.ru', + password: 'admin', + companyName: 'Финский Залив', + inn: 1111222233, + ogrn: 7777888899990, }, ]; @@ -32,8 +60,7 @@ export class UsersService { const user = this.users.find((user) => user.email === email); if (user && includeYachts) { - // Fetch yachts for this user - user.yachts = await []; + user.yachts = []; } return user; @@ -57,7 +84,6 @@ export class UsersService { return this.users; } - // Fetch all users with their yachts const usersWithYachts = await Promise.all( this.users.map(async (user) => { const yachts = []; diff --git a/src/yachts/yachts.controller.spec.ts b/src/yachts/yachts.controller.spec.ts deleted file mode 100644 index 7eca733..0000000 --- a/src/yachts/yachts.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { YachtsController } from './yacht.controller'; - -describe('YachtController', () => { - let controller: YachtsController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [YachtsController], - }).compile(); - - controller = module.get(YachtsController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/yachts/yachts.service.spec.ts b/src/yachts/yachts.service.spec.ts deleted file mode 100644 index 2076132..0000000 --- a/src/yachts/yachts.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { YachtsService } from './yachts.service'; - -describe('YachtsService', () => { - let service: YachtsService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [YachtsService], - }).compile(); - - service = module.get(YachtsService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -});