Vollstaendige Next.js/Prisma-E-Commerce-Anwendung fuer eine Imkerei: Shop, Warenkorb, Stripe-Checkout, Kundenkonto mit 2FA, Admin-Bereich (Produkte, Ernten, Chargen, Bestellungen, Gutscheine, admin-editierbare Website-Inhalte), i18n (DE/EN), Rate-Limiting/CSP/Audit-Log-Haertung, PostgreSQL- und MySQL-Unterstuetzung ueber austauschbare Prisma- Treiber-Adapter, Unit-/Komponenten-/E2E-Tests.
74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
|
import type { Review } from "@/generated/prisma/client";
|
|
|
|
export type ReviewWithCustomer = Review & {
|
|
customer: { firstName: string; lastName: string };
|
|
};
|
|
|
|
export interface ReviewSummary {
|
|
average: number | null;
|
|
count: number;
|
|
}
|
|
|
|
export async function getApprovedReviews(productId: string): Promise<ReviewWithCustomer[]> {
|
|
return prisma.review.findMany({
|
|
where: { productId, isApproved: true },
|
|
include: { customer: { select: { firstName: true, lastName: true } } },
|
|
orderBy: { createdAt: "desc" },
|
|
});
|
|
}
|
|
|
|
export async function getReviewSummary(productId: string): Promise<ReviewSummary> {
|
|
const result = await prisma.review.aggregate({
|
|
where: { productId, isApproved: true },
|
|
_avg: { rating: true },
|
|
_count: true,
|
|
});
|
|
return { average: result._avg.rating, count: result._count };
|
|
}
|
|
|
|
/**
|
|
* Wie getReviewSummary, aber fuer mehrere Produkte in einer Abfrage — vermeidet
|
|
* N+1-Abfragen beim Rendern von Produktrastern (Shop-Uebersicht u.ae.).
|
|
*/
|
|
export async function getReviewSummariesByProductIds(
|
|
productIds: string[]
|
|
): Promise<Map<string, ReviewSummary>> {
|
|
if (productIds.length === 0) return new Map();
|
|
|
|
const results = await prisma.review.groupBy({
|
|
by: ["productId"],
|
|
where: { productId: { in: productIds }, isApproved: true },
|
|
_avg: { rating: true },
|
|
_count: true,
|
|
});
|
|
|
|
return new Map(results.map((r) => [r.productId, { average: r._avg.rating, count: r._count }]));
|
|
}
|
|
|
|
/** Eigene Bewertung des Kunden fuer dieses Produkt, unabhaengig vom Freischaltungsstatus. */
|
|
export async function getCustomerReview(productId: string, customerId: string): Promise<Review | null> {
|
|
return prisma.review.findUnique({
|
|
where: { productId_customerId: { productId, customerId } },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* "Verifizierter Kauf": mindestens eine bezahlte, nicht stornierte Bestellung
|
|
* des Kunden enthaelt dieses Produkt. Gastbestellungen (ohne customerId)
|
|
* zaehlen bewusst nicht, da eine Bewertung an ein Kundenkonto gebunden ist.
|
|
*/
|
|
export async function hasCustomerPurchased(productId: string, customerId: string): Promise<boolean> {
|
|
const orderItem = await prisma.orderItem.findFirst({
|
|
where: {
|
|
productId,
|
|
order: {
|
|
customerId,
|
|
status: { notIn: ["NEU", "STORNIERT"] },
|
|
},
|
|
},
|
|
select: { id: true },
|
|
});
|
|
return orderItem !== null;
|
|
}
|