feat: implement core platform features including business directory, admin dashboard, authentication, and API infrastructure
Some checks failed
Build and Push App / build (push) Failing after 16m2s
Some checks failed
Build and Push App / build (push) Failing after 16m2s
This commit is contained in:
115
app/api/businesses/[id]/rate/route.ts
Normal file
115
app/api/businesses/[id]/rate/route.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
context: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const { id: paramId } = await context.params;
|
||||
const userId = req.headers.get('x-user-id');
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Utilisateur non identifié' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { value, comment } = await req.json();
|
||||
|
||||
if (!value || value < 1 || value > 5) {
|
||||
return NextResponse.json({ error: 'Valeur de note invalide (1-5)' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 1. Resolve actual Business UUID if paramId is a slug
|
||||
const business = await prisma.business.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{ id: paramId },
|
||||
{ slug: paramId }
|
||||
]
|
||||
},
|
||||
select: { id: true }
|
||||
});
|
||||
|
||||
if (!business) {
|
||||
return NextResponse.json({ error: "Les données de démonstration ne peuvent pas recevoir d'avis. Veuillez créer votre propre entreprise pour tester." }, { status: 403 });
|
||||
}
|
||||
|
||||
const businessId = business.id;
|
||||
const ownerId = (business as any).ownerId;
|
||||
|
||||
// 2. Security: Don't allow owner to rate themselves
|
||||
if (userId === ownerId) {
|
||||
return NextResponse.json({ error: "Vous ne pouvez pas noter votre propre entreprise." }, { status: 403 });
|
||||
}
|
||||
|
||||
// 3. Fetch existing rating to check rules
|
||||
const existingRating = await prisma.rating.findUnique({
|
||||
where: {
|
||||
userId_businessId: {
|
||||
userId,
|
||||
businessId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (existingRating) {
|
||||
// Rule: Score can only be re-evaluated UPWARDS
|
||||
if (value < existingRating.value) {
|
||||
return NextResponse.json({
|
||||
error: `La note ne peut être réévaluée qu'à la hausse. Votre note actuelle est de ${existingRating.value} étoiles.`
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
// Rule: Comment is IMMUTABLE once set
|
||||
// We only update the value. If the comment was empty, we can set it once.
|
||||
await prisma.rating.update({
|
||||
where: { id: existingRating.id },
|
||||
data: {
|
||||
value,
|
||||
comment: existingRating.comment ? undefined : comment, // Only set if it was null
|
||||
status: 'PENDING' // Reset to pending for re-moderation
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 4. Create new rating
|
||||
await prisma.rating.create({
|
||||
data: {
|
||||
value,
|
||||
comment,
|
||||
userId,
|
||||
businessId,
|
||||
status: 'PENDING'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Aggregate all ratings for this business
|
||||
const stats = await prisma.rating.aggregate({
|
||||
where: { businessId },
|
||||
_avg: { value: true },
|
||||
_count: { value: true }
|
||||
});
|
||||
|
||||
const newRating = stats._avg.value || 0;
|
||||
const newRatingCount = stats._count.value || 0;
|
||||
|
||||
// 4. Update the business
|
||||
const updatedBusiness = await prisma.business.update({
|
||||
where: { id: businessId },
|
||||
data: {
|
||||
rating: newRating,
|
||||
ratingCount: newRatingCount
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
rating: updatedBusiness.rating,
|
||||
ratingCount: updatedBusiness.ratingCount,
|
||||
message: 'Vote enregistré avec succès'
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error rating business:', error);
|
||||
return NextResponse.json({ error: 'Erreur serveur lors du vote' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
49
app/api/businesses/[id]/rating/me/route.ts
Normal file
49
app/api/businesses/[id]/rating/me/route.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
context: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const { id: paramId } = await context.params;
|
||||
const userId = req.headers.get('x-user-id');
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ rating: null });
|
||||
}
|
||||
|
||||
try {
|
||||
// Resolve actual Business UUID if paramId is a slug
|
||||
const business = await prisma.business.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{ id: paramId },
|
||||
{ slug: paramId }
|
||||
]
|
||||
},
|
||||
select: { id: true }
|
||||
});
|
||||
|
||||
if (!business) {
|
||||
return NextResponse.json({ rating: null });
|
||||
}
|
||||
|
||||
const rating = await prisma.rating.findUnique({
|
||||
where: {
|
||||
userId_businessId: {
|
||||
userId,
|
||||
businessId: business.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
rating: rating ? rating.value : null,
|
||||
status: rating ? rating.status : null,
|
||||
comment: rating ? rating.comment : null
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Error fetching user rating:', error);
|
||||
return NextResponse.json({ error: 'Erreur lors de la récupération de votre note' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
55
app/api/businesses/[id]/ratings/route.ts
Normal file
55
app/api/businesses/[id]/ratings/route.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
context: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const { id: paramId } = await context.params;
|
||||
|
||||
try {
|
||||
// 1. Resolve actual Business UUID if paramId is a slug
|
||||
const business = await prisma.business.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{ id: paramId },
|
||||
{ slug: paramId }
|
||||
]
|
||||
},
|
||||
select: { id: true }
|
||||
});
|
||||
|
||||
if (!business) {
|
||||
// Return empty array for mock/missing businesses instead of error
|
||||
return NextResponse.json([]);
|
||||
}
|
||||
|
||||
const businessId = business.id;
|
||||
|
||||
// 2. Fetch all approved ratings with user info
|
||||
const ratings = await prisma.rating.findMany({
|
||||
where: {
|
||||
businessId,
|
||||
status: 'APPROVED'
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
avatar: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json(ratings);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error fetching ratings:', error);
|
||||
return NextResponse.json({ error: 'Erreur serveur lors de la récupération des avis' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import prisma from '../../../../lib/prisma'
|
||||
import prisma from '@/lib/prisma'
|
||||
|
||||
type Params = { params: Promise<{ id: string }> }
|
||||
|
||||
@@ -49,12 +49,48 @@ export async function PATCH(
|
||||
|
||||
// DELETE /api/businesses/[id]
|
||||
export async function DELETE(
|
||||
_request: NextRequest,
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ id: string }> }
|
||||
): Promise<Response> {
|
||||
try {
|
||||
const { id } = await context.params
|
||||
const headerUserId = request.headers.get('x-user-id')
|
||||
|
||||
// 1. Find business to check owner
|
||||
const business = await prisma.business.findUnique({
|
||||
where: { id },
|
||||
select: { ownerId: true }
|
||||
})
|
||||
|
||||
if (!business) {
|
||||
return NextResponse.json({ error: 'Boutique non trouvée' }, { status: 404 })
|
||||
}
|
||||
|
||||
// 2. Auth check (Owner or Admin)
|
||||
const user = headerUserId ? await prisma.user.findUnique({ where: { id: headerUserId } }) : null
|
||||
const isAdmin = user?.role === 'ADMIN'
|
||||
const isOwner = business.ownerId === headerUserId
|
||||
|
||||
if (!isOwner && !isAdmin) {
|
||||
return NextResponse.json({ error: 'Non autorisé' }, { status: 403 })
|
||||
}
|
||||
|
||||
// 3. Delete business
|
||||
await prisma.business.delete({ where: { id } })
|
||||
|
||||
// 4. Update user role if they don't have other businesses
|
||||
if (headerUserId && !isAdmin) {
|
||||
const otherBusinesses = await prisma.business.findFirst({
|
||||
where: { ownerId: headerUserId }
|
||||
})
|
||||
if (!otherBusinesses) {
|
||||
await prisma.user.update({
|
||||
where: { id: headerUserId },
|
||||
data: { role: 'VISITOR' }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
console.error('DELETE /api/businesses/[id] error:', error)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Force refresh for Next.js 16 params fix
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import prisma from '../../../../../lib/prisma';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
@@ -13,10 +13,27 @@ export async function POST(
|
||||
return NextResponse.json({ error: 'ID requis' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Attempt to increment in DB
|
||||
// Note: We don't care about mock businesses here as they are static
|
||||
// 1. Resolve actual Business UUID if id is a slug
|
||||
const businessFound = await prisma.business.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{ id },
|
||||
{ slug: id }
|
||||
]
|
||||
},
|
||||
select: { id: true }
|
||||
});
|
||||
|
||||
if (!businessFound) {
|
||||
// If business not found in DB (might be mock), just return success but no update
|
||||
return NextResponse.json({ success: true, message: 'Mock business, skipping view count' });
|
||||
}
|
||||
|
||||
const businessId = businessFound.id;
|
||||
|
||||
// 2. Attempt to increment in DB
|
||||
const business = await prisma.business.update({
|
||||
where: { id },
|
||||
where: { id: businessId },
|
||||
data: {
|
||||
viewCount: {
|
||||
increment: 1,
|
||||
@@ -26,8 +43,7 @@ export async function POST(
|
||||
|
||||
return NextResponse.json({ success: true, viewCount: business.viewCount });
|
||||
} catch (error) {
|
||||
// If business not found in DB (might be mock), just return success but no update
|
||||
console.error('Error incrementing view count:', error);
|
||||
return NextResponse.json({ success: false, error: 'Business not found or error' }, { status: 404 });
|
||||
return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user