karbe/src/lib/carbet-public.ts

153 lines
4.6 KiB
TypeScript

import { cache } from "react";
import { prisma } from "@/lib/prisma";
import { amenityLabel } from "@/lib/amenities";
import { AccessType, CarbetStatus, MediaType, TransportMode } from "@/generated/prisma/enums";
import type { CarbetReview, CarbetReviewStats } from "@/lib/reviews";
import {
getCarbetReviewStats,
listCarbetReviews,
} from "@/lib/reviews-server";
import type { PirogueProvider } from "@/lib/pirogue-providers";
export type PublicCarbetMedia = {
id: string;
type: MediaType;
url: string;
};
export type PublicCarbetDetail = {
id: string;
slug: string;
title: string;
description: string;
river: string;
embarkPoint: string;
pirogueDurationMin: number | null;
accessType: AccessType;
roadAccessNote: string | null;
capacity: number;
nightlyPrice: string;
minStayNights: number | null;
maxStayNights: number | null;
minCapacity: number | null;
seasonalConstraints: unknown;
transportMode: TransportMode | null;
pirogueProvider: PirogueProvider | null;
latitude: string;
longitude: string;
ownerId: string;
ownerFirstName: string;
media: PublicCarbetMedia[];
amenities: { key: string; label: string }[];
reviewStats: CarbetReviewStats;
reviews: CarbetReview[];
};
// Memoized within a single request so generateMetadata() and the page itself
// only hit the database once per render.
export const getPublicCarbet = cache(
async (slug: string): Promise<PublicCarbetDetail | null> => {
const carbet = await prisma.carbet.findFirst({
where: { slug, status: CarbetStatus.PUBLISHED },
select: {
id: true,
slug: true,
title: true,
description: true,
river: true,
embarkPoint: true,
pirogueDurationMin: true,
accessType: true,
roadAccessNote: true,
capacity: true,
nightlyPrice: true,
minStayNights: true,
maxStayNights: true,
minCapacity: true,
seasonalConstraints: true,
transportMode: true,
pirogueProviderId: true,
latitude: true,
longitude: true,
ownerId: true,
owner: { select: { firstName: true } },
pirogueProvider: {
select: {
id: true,
name: true,
contactEmail: true,
contactPhone: true,
rivers: true,
pricingNote: true,
description: true,
active: true,
},
},
media: {
orderBy: { sortOrder: "asc" },
select: { id: true, type: true, s3Url: true },
},
amenities: {
select: { amenity: { select: { key: true, label: true } } },
},
},
});
if (!carbet) return null;
const [reviewStats, reviews] = await Promise.all([
getCarbetReviewStats(carbet.id),
listCarbetReviews(carbet.id, 20),
]);
return {
id: carbet.id,
slug: carbet.slug,
title: carbet.title,
description: carbet.description,
river: carbet.river,
embarkPoint: carbet.embarkPoint,
pirogueDurationMin: carbet.pirogueDurationMin,
accessType: carbet.accessType,
roadAccessNote: carbet.roadAccessNote,
capacity: carbet.capacity,
nightlyPrice: carbet.nightlyPrice.toString(),
minStayNights: carbet.minStayNights,
maxStayNights: carbet.maxStayNights,
minCapacity: carbet.minCapacity,
seasonalConstraints: carbet.seasonalConstraints,
transportMode: carbet.transportMode,
pirogueProvider: carbet.pirogueProvider
? {
id: carbet.pirogueProvider.id,
name: carbet.pirogueProvider.name,
contactEmail: carbet.pirogueProvider.contactEmail,
contactPhone: carbet.pirogueProvider.contactPhone,
rivers: carbet.pirogueProvider.rivers,
pricingNote: carbet.pirogueProvider.pricingNote,
description: carbet.pirogueProvider.description,
active: carbet.pirogueProvider.active,
}
: null,
latitude: carbet.latitude.toString(),
longitude: carbet.longitude.toString(),
ownerId: carbet.ownerId,
ownerFirstName: carbet.owner.firstName,
media: carbet.media.map((m) => ({
id: m.id,
type: m.type,
url: m.s3Url,
})),
amenities: carbet.amenities
.map((entry) => ({
key: entry.amenity.key,
// Prefer the catalogue label so renames roll out without a backfill.
label: amenityLabel(entry.amenity.key) || entry.amenity.label,
}))
.sort((a, b) => a.label.localeCompare(b.label, "fr")),
reviewStats,
reviews,
};
},
);