correction faille de securité
All checks were successful
Build and Push App / build (push) Successful in 5m50s
All checks were successful
Build and Push App / build (push) Successful in 5m50s
This commit is contained in:
@@ -77,7 +77,9 @@ const BusinessDetailPage = () => {
|
|||||||
|
|
||||||
// 2. Try API (Database)
|
// 2. Try API (Database)
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/businesses/${id}`);
|
const res = await fetch(`/api/businesses/${id}`, {
|
||||||
|
headers: user ? { 'x-user-id': user.id } : {}
|
||||||
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setBusiness(data);
|
setBusiness(data);
|
||||||
@@ -104,7 +106,10 @@ const BusinessDetailPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Also increment views count in DB (Legacy support)
|
// Also increment views count in DB (Legacy support)
|
||||||
fetch(`/api/businesses/${id}/view`, { method: 'POST' }).catch(console.error);
|
fetch(`/api/businesses/${id}/view`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: user ? { 'x-user-id': user.id } : {}
|
||||||
|
}).catch(console.error);
|
||||||
} else {
|
} else {
|
||||||
setBusiness(null);
|
setBusiness(null);
|
||||||
}
|
}
|
||||||
@@ -135,7 +140,9 @@ const BusinessDetailPage = () => {
|
|||||||
// 5. Fetch all ratings
|
// 5. Fetch all ratings
|
||||||
try {
|
try {
|
||||||
setLoadingRatings(true);
|
setLoadingRatings(true);
|
||||||
const ratingsRes = await fetch(`/api/businesses/${id}/ratings`);
|
const ratingsRes = await fetch(`/api/businesses/${id}/ratings`, {
|
||||||
|
headers: user ? { 'x-user-id': user.id } : {}
|
||||||
|
});
|
||||||
if (ratingsRes.ok) {
|
if (ratingsRes.ok) {
|
||||||
const ratingsData = await ratingsRes.json();
|
const ratingsData = await ratingsRes.json();
|
||||||
setRatings(ratingsData);
|
setRatings(ratingsData);
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import { Search, Loader2 } from 'lucide-react';
|
|||||||
import { Business, Country } from '../../types';
|
import { Business, Country } from '../../types';
|
||||||
import BusinessCard from '../../components/BusinessCard';
|
import BusinessCard from '../../components/BusinessCard';
|
||||||
import AnnuaireHero from '../../components/AnnuaireHero';
|
import AnnuaireHero from '../../components/AnnuaireHero';
|
||||||
|
import { useUser } from '../../components/UserProvider';
|
||||||
|
|
||||||
const AnnuairePageContent = () => {
|
const AnnuairePageContent = () => {
|
||||||
|
const { user } = useUser();
|
||||||
const [businesses, setBusinesses] = useState<Business[]>([]);
|
const [businesses, setBusinesses] = useState<Business[]>([]);
|
||||||
const [featuredBusiness, setFeaturedBusiness] = useState<Business | null>(null);
|
const [featuredBusiness, setFeaturedBusiness] = useState<Business | null>(null);
|
||||||
const [countries, setCountries] = useState<Country[]>([]);
|
const [countries, setCountries] = useState<Country[]>([]);
|
||||||
@@ -41,7 +43,10 @@ const AnnuairePageContent = () => {
|
|||||||
const fetchHeadliner = async () => {
|
const fetchHeadliner = async () => {
|
||||||
try {
|
try {
|
||||||
// Disable caching to ensure we get fresh data and more variety
|
// Disable caching to ensure we get fresh data and more variety
|
||||||
const res = await fetch('/api/businesses?featured=true', { cache: 'no-store' });
|
const res = await fetch('/api/businesses?featured=true', {
|
||||||
|
cache: 'no-store',
|
||||||
|
headers: user ? { 'x-user-id': user.id } : {}
|
||||||
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
// Truly random selection from the pool of featured businesses
|
// Truly random selection from the pool of featured businesses
|
||||||
@@ -65,7 +70,9 @@ const AnnuairePageContent = () => {
|
|||||||
if (filterCountry !== 'All') params.append('countryId', filterCountry);
|
if (filterCountry !== 'All') params.append('countryId', filterCountry);
|
||||||
if (searchQuery) params.append('q', searchQuery);
|
if (searchQuery) params.append('q', searchQuery);
|
||||||
|
|
||||||
const res = await fetch(`/api/businesses?${params.toString()}`);
|
const res = await fetch(`/api/businesses?${params.toString()}`, {
|
||||||
|
headers: user ? { 'x-user-id': user.id } : {}
|
||||||
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
setBusinesses(data);
|
setBusinesses(data);
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { USER_SAFE_SELECT } from '@/lib/auth-utils'
|
import { USER_SAFE_SELECT, getAuthUser } from '@/lib/auth-utils'
|
||||||
|
|
||||||
type Params = { params: Promise<{ id: string }> }
|
type Params = { params: Promise<{ id: string }> }
|
||||||
|
|
||||||
// GET /api/businesses/[id]
|
// GET /api/businesses/[id]
|
||||||
export async function GET(
|
export async function GET(
|
||||||
_request: NextRequest,
|
request: NextRequest,
|
||||||
context: { params: Promise<{ id: string }> }
|
context: { params: Promise<{ id: string }> }
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
|
const user = await getAuthUser(request)
|
||||||
|
if (!user) {
|
||||||
|
return NextResponse.json({ error: 'Authentification requise' }, { status: 401 })
|
||||||
|
}
|
||||||
|
|
||||||
const { id } = await context.params
|
const { id } = await context.params
|
||||||
const business = await prisma.business.findFirst({
|
const business = await prisma.business.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@@ -26,7 +31,13 @@ export async function GET(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (!business) return NextResponse.json({ error: 'Non trouvé' }, { status: 404 })
|
if (!business) return NextResponse.json({ error: 'Non trouvé' }, { status: 404 })
|
||||||
return NextResponse.json(business)
|
|
||||||
|
// Mask technical IDs
|
||||||
|
const { id: bizId, ownerId, ...rest } = business
|
||||||
|
return NextResponse.json({
|
||||||
|
...rest,
|
||||||
|
id: business.slug || bizId
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('GET /api/businesses/[id] error:', error)
|
console.error('GET /api/businesses/[id] error:', error)
|
||||||
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 })
|
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 })
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { USER_SAFE_SELECT } from '@/lib/auth-utils'
|
import { USER_SAFE_SELECT, getAuthUser } from '@/lib/auth-utils'
|
||||||
|
|
||||||
// GET /api/businesses — List all businesses (Mock + DB)
|
// GET /api/businesses — List all businesses (Mock + DB)
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
|
const user = await getAuthUser(request)
|
||||||
|
if (!user) {
|
||||||
|
return NextResponse.json({ error: 'Authentification requise' }, { status: 401 })
|
||||||
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const category = searchParams.get('category')
|
const category = searchParams.get('category')
|
||||||
const q = searchParams.get('q')?.toLowerCase()
|
const q = searchParams.get('q')?.toLowerCase()
|
||||||
@@ -52,8 +57,18 @@ export async function GET(request: NextRequest) {
|
|||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2. Return DB results
|
// 2. Map results to mask technical IDs
|
||||||
return NextResponse.json(dbBusinesses)
|
const safeBusinesses = dbBusinesses.map(biz => {
|
||||||
|
const { id, ownerId, ...rest } = biz
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
// Return slug as the primary identifier if available,
|
||||||
|
// otherwise return a masked version or keep it but it's now "safe"
|
||||||
|
id: biz.slug || id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return NextResponse.json(safeBusinesses)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('GET /api/businesses error:', error)
|
console.error('GET /api/businesses error:', error)
|
||||||
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 })
|
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 })
|
||||||
|
|||||||
@@ -43,12 +43,29 @@ export async function POST(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: 'businessId requis' }, { status: 400 });
|
return NextResponse.json({ error: 'businessId requis' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve business ID (in case slug was passed)
|
||||||
|
const business = await prisma.business.findFirst({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ id: businessId },
|
||||||
|
{ slug: businessId }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: { id: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!business) {
|
||||||
|
return NextResponse.json({ error: 'Entreprise non trouvée' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const actualBusinessId = business.id;
|
||||||
|
|
||||||
// Check if already favorited
|
// Check if already favorited
|
||||||
const existing = await prisma.favorite.findUnique({
|
const existing = await prisma.favorite.findUnique({
|
||||||
where: {
|
where: {
|
||||||
userId_businessId: {
|
userId_businessId: {
|
||||||
userId,
|
userId,
|
||||||
businessId
|
businessId: actualBusinessId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -64,7 +81,7 @@ export async function POST(request: NextRequest) {
|
|||||||
await prisma.favorite.create({
|
await prisma.favorite.create({
|
||||||
data: {
|
data: {
|
||||||
userId,
|
userId,
|
||||||
businessId
|
businessId: actualBusinessId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return NextResponse.json({ favorited: true });
|
return NextResponse.json({ favorited: true });
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const ForgotPasswordPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
<form className="mt-8 space-y-6" onSubmit={handleSubmit} method="POST">
|
||||||
<input type="hidden" name="csrf_token" value={csrfToken} />
|
<input type="hidden" name="csrf_token" value={csrfToken} />
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-red-50 border-l-4 border-red-500 p-4 rounded text-red-700 text-sm">
|
<div className="bg-red-50 border-l-4 border-red-500 p-4 rounded text-red-700 text-sm">
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ const LoginPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
<form className="space-y-6" onSubmit={handleSubmit} method="POST">
|
||||||
<input type="hidden" name="csrf_token" value={csrfToken} />
|
<input type="hidden" name="csrf_token" value={csrfToken} />
|
||||||
<div className="rounded-md shadow-sm -space-y-px">
|
<div className="rounded-md shadow-sm -space-y-px">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useUser } from '../components/UserProvider';
|
|||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { settings } = useUser();
|
const { user, settings } = useUser();
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [featuredBusinesses, setFeaturedBusinesses] = useState<Business[]>([]);
|
const [featuredBusinesses, setFeaturedBusinesses] = useState<Business[]>([]);
|
||||||
const [posts, setPosts] = useState<BlogPost[]>([]);
|
const [posts, setPosts] = useState<BlogPost[]>([]);
|
||||||
@@ -22,7 +22,9 @@ const HomePage = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchFeatured = async () => {
|
const fetchFeatured = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/businesses?featured=true');
|
const res = await fetch('/api/businesses?featured=true', {
|
||||||
|
headers: user ? { 'x-user-id': user.id } : {}
|
||||||
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
setFeaturedBusinesses(data.slice(0, 4)); // Show top 4
|
setFeaturedBusinesses(data.slice(0, 4)); // Show top 4
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const RegisterPage = () => {
|
|||||||
<p className="text-gray-600">Redirection vers la page de connexion...</p>
|
<p className="text-gray-600">Redirection vers la page de connexion...</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
<form className="space-y-6" onSubmit={handleSubmit} method="POST">
|
||||||
<input type="hidden" name="csrf_token" value={csrfToken} />
|
<input type="hidden" name="csrf_token" value={csrfToken} />
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-red-50 border-l-4 border-red-500 p-4 rounded-r-lg flex items-center gap-3 text-red-700 animate-in slide-in-from-top-2 duration-300">
|
<div className="bg-red-50 border-l-4 border-red-500 p-4 rounded-r-lg flex items-center gap-3 text-red-700 animate-in slide-in-from-top-2 duration-300">
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ const ResetPasswordForm = () => {
|
|||||||
<p className="mt-2">Redirection vers la page de connexion...</p>
|
<p className="mt-2">Redirection vers la page de connexion...</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
<form className="mt-8 space-y-6" onSubmit={handleSubmit} method="POST">
|
||||||
<input type="hidden" name="csrf_token" value={csrfToken} />
|
<input type="hidden" name="csrf_token" value={csrfToken} />
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-red-50 border-l-4 border-red-500 p-4 rounded text-red-700 text-sm">
|
<div className="bg-red-50 border-l-4 border-red-500 p-4 rounded text-red-700 text-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user