feat: Wabenhain Honig-Shop Anwendung

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.
This commit is contained in:
Nick Szaunig committed 2026-08-17 14:30:28 +02:00
1 parent 5c76fcc266
commit c7927f2883
240 files changed
+26096 -137

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
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;
}