travelmarine-backend/src/catalog/catalog.service.ts

308 lines
9.7 KiB
TypeScript

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<CatalogItemLongDto | null> {
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<MainPageCatalogResponseDto | null> {
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<CatalogResponseDto> {
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<CatalogItemShortDto[]> {
const allYachts = await this.prisma.yacht.findMany({
orderBy: { id: 'asc' },
});
return allYachts.map((row) =>
this.toShortDto(this.yachtRowToLongDto(row)),
);
}
async getCatalogByUserId(userId: number): Promise<CatalogItemShortDto[]> {
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<CatalogItemLongDto> {
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;
}
}