import { Injectable, Inject, forwardRef } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { ReservationsService } from '../reservations/reservations.service'; import { ReviewsService } from '../reviews/reviews.service'; import { CatalogItemShortDto, CatalogItemLongDto, } from './dto/catalog-item.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 prisma: PrismaService, @Inject(forwardRef(() => ReservationsService)) private readonly reservationsService: ReservationsService, private readonly reviewsService: ReviewsService, ) {} private yachtRowToLongDto(row: { id: number; name: string; length: number; speed: number | null; minCost: number | null; mainImageUrl: string | null; galleryUrls: unknown; hasQuickRent: boolean; isFeatured: boolean; topText: string | null; year: number; comfortCapacity: number | null; maxCapacity: number | null; width: number | null; cabinsCount: number | null; matherial: string | null; power: number | null; description: string | null; userId: number; owner?: { id: number; firstName: string; lastName: string; phone: string; email: string; companyName: string | null; inn: bigint | null; ogrn: bigint | null }; }): CatalogItemLongDto { const galleryUrls = Array.isArray(row.galleryUrls) ? row.galleryUrls : typeof row.galleryUrls === 'string' ? (JSON.parse(row.galleryUrls) as string[]) : []; return { id: row.id, name: row.name, length: row.length, speed: row.speed ?? 0, minCost: row.minCost ?? 0, mainImageUrl: row.mainImageUrl ?? '', galleryUrls: galleryUrls as string[], hasQuickRent: row.hasQuickRent, isFeatured: row.isFeatured, topText: row.topText ?? undefined, year: row.year, comfortCapacity: row.comfortCapacity ?? 0, maxCapacity: row.maxCapacity ?? 0, width: row.width ?? 0, cabinsCount: row.cabinsCount ?? 0, matherial: row.matherial ?? '', power: row.power ?? 0, description: row.description ?? '', owner: row.owner ? { userId: row.owner.id, firstName: row.owner.firstName, lastName: row.owner.lastName, phone: row.owner.phone, email: row.owner.email, companyName: row.owner.companyName ?? undefined, inn: row.owner.inn != null ? Number(row.owner.inn) : undefined, ogrn: row.owner.ogrn != null ? Number(row.owner.ogrn) : undefined, } : ({ userId: row.userId } 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 yacht = await this.prisma.yacht.findUnique({ where: { id }, include: { owner: true }, }); if (!yacht) return null; const item = this.yachtRowToLongDto(yacht); item.reviews = await this.reviewsService.getReviewsByYachtId(id); item.reservations = await this.reservationsService.getReservationsByYachtId(id); return item; } async getMainPageCatalog(): Promise { const allYachts = await this.prisma.yacht.findMany({ include: { owner: true }, orderBy: { id: 'asc' }, }); const fullItems = allYachts.map((row) => this.yachtRowToLongDto(row)); const featuredYacht = fullItems.find((item) => item.isFeatured); const filteredCatalog = fullItems.filter((item) => !item.isFeatured); if (!featuredYacht) return null; const minCost = Math.min( ...filteredCatalog.map((item) => item.minCost || Infinity), ); const mappedRestYachts = filteredCatalog.slice(0, 6).map((item) => ({ ...this.toShortDto(item), isBestOffer: item.minCost === minCost, })); return { featuredYacht: this.toShortDto(featuredYacht), restYachts: mappedRestYachts, }; } async getCatalog(filters?: CatalogFiltersDto): Promise { const allYachts = await this.prisma.yacht.findMany({ include: { owner: true }, orderBy: { id: 'asc' }, }); let filteredItems = allYachts.map((row) => this.toShortDto(this.yachtRowToLongDto(row)), ); const fullItems = allYachts.map((row) => this.yachtRowToLongDto(row)); if (filters?.search) { const searchTerm = filters.search.toLowerCase(); filteredItems = filteredItems.filter((item) => { const fullItem = fullItems.find((fi) => fi.id === item.id); return ( item.name.toLowerCase().includes(searchTerm) || (fullItem && fullItem.description.toLowerCase().includes(searchTerm)) ); }); } if (filters?.minLength !== undefined) { filteredItems = filteredItems.filter( (item) => item.length >= filters!.minLength!, ); } if (filters?.maxLength !== undefined) { filteredItems = filteredItems.filter( (item) => item.length <= filters!.maxLength!, ); } if (filters?.minPrice !== undefined) { filteredItems = filteredItems.filter( (item) => item.minCost >= filters!.minPrice!, ); } if (filters?.maxPrice !== undefined) { filteredItems = filteredItems.filter( (item) => item.minCost <= filters!.maxPrice!, ); } if (filters?.minYear !== undefined || filters?.maxYear !== undefined) { filteredItems = filteredItems.filter((item) => { const fullItem = fullItems.find((fi) => fi.id === item.id); if (!fullItem) return false; if ( filters!.minYear !== undefined && fullItem.year < filters!.minYear! ) return false; if ( filters!.maxYear !== undefined && fullItem.year > filters!.maxYear! ) return false; return true; }); } if (filters?.guests !== undefined) { const totalPeople = filters.guests; filteredItems = filteredItems.filter((item) => { const fullItem = fullItems.find((fi) => fi.id === item.id); return fullItem != null && fullItem.maxCapacity >= totalPeople; }); } if (filters?.quickBooking !== undefined) { filteredItems = filteredItems.filter( (item) => item.hasQuickRent === filters.quickBooking, ); } if (filters?.hasToilet !== undefined) { filteredItems = filteredItems.filter((item) => { const fullItem = fullItems.find((fi) => fi.id === item.id); if (!fullItem) return false; return filters!.hasToilet ? fullItem.cabinsCount > 0 : fullItem.cabinsCount === 0; }); } if (filters?.sortByPrice === 'asc') { filteredItems.sort((a, b) => a.minCost - b.minCost); } else if (filters?.sortByPrice === 'desc') { filteredItems.sort((a, b) => b.minCost - a.minCost); } else { filteredItems.sort((a, b) => a.name.localeCompare(b.name)); } return { items: filteredItems, total: filteredItems.length, filters, search: filters?.search, }; } async getAllCatalogItems(): Promise { const allYachts = await this.prisma.yacht.findMany({ orderBy: { id: 'asc' }, }); return allYachts.map((row) => this.toShortDto(this.yachtRowToLongDto(row)), ); } async getCatalogByUserId(userId: number): Promise { const yachts = await this.prisma.yacht.findMany({ where: { userId }, orderBy: { id: 'asc' }, }); return yachts.map((row) => this.toShortDto(this.yachtRowToLongDto(row)), ); } async createYacht( createYachtDto: CreateYachtDto, ): Promise { const galleryUrls = createYachtDto.galleryUrls ?? []; const created = await this.prisma.yacht.create({ data: { name: createYachtDto.name, year: createYachtDto.year, length: createYachtDto.length, speed: createYachtDto.speed, minCost: createYachtDto.minCost, mainImageUrl: createYachtDto.mainImageUrl, galleryUrls: galleryUrls as object, hasQuickRent: createYachtDto.hasQuickRent, isFeatured: createYachtDto.isFeatured, topText: createYachtDto.topText, comfortCapacity: createYachtDto.comfortCapacity, maxCapacity: createYachtDto.maxCapacity, width: createYachtDto.width, cabinsCount: createYachtDto.cabinsCount, matherial: createYachtDto.matherial, power: createYachtDto.power, description: createYachtDto.description, userId: createYachtDto.userId, }, include: { owner: true }, }); const item = this.yachtRowToLongDto(created); item.reviews = []; item.reservations = []; return item; } }