- Lib reviews: constants/types (client-safe) + DB helpers (server-only) - API POST /api/bookings/[bookingId]/review : avis locataire après séjour COMPLETED - API POST /api/reviews/[reviewId]/response : réponse loueur - API GET /api/carbets/[carbetId]/reviews : liste + stats agrégées - Fiche carbet : note moyenne + nombre d'avis + liste avec réponses loueur - Carte carbet : étoiles + note moyenne + compteur - /mes-reservations : formulaire d'avis pour les séjours terminés du locataire
88 lines
2.4 KiB
TypeScript
88 lines
2.4 KiB
TypeScript
// Server-only: this module pulls in Prisma. Do not import from client
|
|
// components — `@/lib/reviews` (constants/types) is the safe surface.
|
|
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
import type { CarbetReview, CarbetReviewStats } from "@/lib/reviews";
|
|
|
|
// Aggregate stats used on cards and detail pages. Returns null average when
|
|
// there are no reviews so the caller can render a neutral "no reviews yet"
|
|
// state rather than a misleading 0.0.
|
|
export async function getCarbetReviewStats(
|
|
carbetId: string,
|
|
): Promise<CarbetReviewStats> {
|
|
const agg = await prisma.review.aggregate({
|
|
where: { carbetId },
|
|
_count: { _all: true },
|
|
_avg: { rating: true },
|
|
});
|
|
|
|
const count = agg._count._all;
|
|
const avg = agg._avg.rating;
|
|
|
|
return {
|
|
count,
|
|
averageRating: count > 0 && avg !== null ? Number(avg) : null,
|
|
};
|
|
}
|
|
|
|
export async function getCarbetReviewStatsMany(
|
|
carbetIds: string[],
|
|
): Promise<Map<string, CarbetReviewStats>> {
|
|
if (carbetIds.length === 0) {
|
|
return new Map();
|
|
}
|
|
|
|
const rows = await prisma.review.groupBy({
|
|
by: ["carbetId"],
|
|
where: { carbetId: { in: carbetIds } },
|
|
_count: { _all: true },
|
|
_avg: { rating: true },
|
|
});
|
|
|
|
const map = new Map<string, CarbetReviewStats>();
|
|
for (const id of carbetIds) {
|
|
map.set(id, { count: 0, averageRating: null });
|
|
}
|
|
for (const row of rows) {
|
|
const avg = row._avg.rating;
|
|
map.set(row.carbetId, {
|
|
count: row._count._all,
|
|
averageRating: avg !== null ? Number(avg) : null,
|
|
});
|
|
}
|
|
return map;
|
|
}
|
|
|
|
export async function listCarbetReviews(
|
|
carbetId: string,
|
|
limit = 20,
|
|
): Promise<CarbetReview[]> {
|
|
const reviews = await prisma.review.findMany({
|
|
where: { carbetId },
|
|
orderBy: { createdAt: "desc" },
|
|
take: limit,
|
|
select: {
|
|
id: true,
|
|
rating: true,
|
|
comment: true,
|
|
hostResponse: true,
|
|
hostRespondedAt: true,
|
|
createdAt: true,
|
|
author: { select: { firstName: true } },
|
|
booking: { select: { startDate: true, endDate: true } },
|
|
},
|
|
});
|
|
|
|
return reviews.map((review) => ({
|
|
id: review.id,
|
|
rating: review.rating,
|
|
comment: review.comment,
|
|
hostResponse: review.hostResponse,
|
|
hostRespondedAt: review.hostRespondedAt?.toISOString() ?? null,
|
|
createdAt: review.createdAt.toISOString(),
|
|
authorFirstName: review.author.firstName,
|
|
stayStartDate: review.booking.startDate.toISOString(),
|
|
stayEndDate: review.booking.endDate.toISOString(),
|
|
}));
|
|
}
|