karbe/src/lib/reviews-server.ts
Karbé Frontend 7515981336 feat(reviews): avis & notes carbet (SYS-8)
- 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
2026-05-30 15:08:55 +00:00

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(),
}));
}