|
1 | | -import { useEffect, useState } from 'react'; |
2 | | -import { |
3 | | - collection, |
4 | | - addDoc, |
5 | | - query, |
6 | | - where, |
7 | | - onSnapshot, |
8 | | - updateDoc, |
9 | | - deleteDoc, |
10 | | - doc, |
11 | | - orderBy, |
12 | | - getDocs |
13 | | -} from 'firebase/firestore'; |
14 | | -import { db } from '@/lib/firebase'; |
15 | | -import { useAuth } from '@/hooks/useAuth'; |
16 | | -import { useAuthGuard } from '@/hooks/useAuthGuard'; |
17 | | -import { Affiliate } from '@/types'; |
| 1 | +import { useAffiliatesSupabase } from '@/hooks/useAffiliatesSupabase'; |
18 | 2 |
|
19 | 3 | export const useAffiliates = (campaignId?: string) => { |
20 | | - const [affiliates, setAffiliates] = useState<Affiliate[]>([]); |
21 | | - const [loading, setLoading] = useState(true); |
22 | | - const { user, loading: authLoading } = useAuth(); |
23 | | - const { requireAuthentication, requireOwnership } = useAuthGuard(); |
24 | | - |
25 | | - useEffect(() => { |
26 | | - console.log('👥 useAffiliates - Effect triggered with security checks'); |
27 | | - console.log('👥 authLoading:', authLoading, 'user:', !!user, 'campaignId:', campaignId); |
28 | | - |
29 | | - // PROTECTION STRICTE : Aucune requête avant auth complète |
30 | | - if (authLoading) { |
31 | | - console.log('👥 Auth encore en cours, pas de requête Firebase'); |
32 | | - return; |
33 | | - } |
34 | | - |
35 | | - if (!user) { |
36 | | - console.log('👥 SECURITY - No user, clearing affiliates and blocking requests'); |
37 | | - setAffiliates([]); |
38 | | - setLoading(false); |
39 | | - return; |
40 | | - } |
41 | | - |
42 | | - console.log('👥 SECURITY - Auth OK, starting secure Firestore query for user:', user.uid); |
43 | | - |
44 | | - let unsubscribe: (() => void) | undefined; |
45 | | - |
46 | | - const loadAffiliates = async () => { |
47 | | - try { |
48 | | - // Si pas de campaignId spécifique, on vérifie d'abord quelles campagnes existent encore |
49 | | - if (!campaignId) { |
50 | | - console.log('👥 SECURITY - Loading all affiliates, checking existing campaigns first'); |
51 | | - |
52 | | - // Récupérer d'abord toutes les campagnes existantes de l'utilisateur |
53 | | - const campaignsQuery = query( |
54 | | - collection(db, 'campaigns'), |
55 | | - where('userId', '==', user.uid) |
56 | | - ); |
57 | | - const campaignsSnapshot = await getDocs(campaignsQuery); |
58 | | - const existingCampaignIds = campaignsSnapshot.docs.map(doc => doc.id); |
59 | | - |
60 | | - console.log('👥 SECURITY - Existing campaigns:', existingCampaignIds.length); |
61 | | - |
62 | | - if (existingCampaignIds.length === 0) { |
63 | | - console.log('👥 SECURITY - No campaigns exist, no affiliates to show'); |
64 | | - setAffiliates([]); |
65 | | - setLoading(false); |
66 | | - return; |
67 | | - } |
68 | | - |
69 | | - // Maintenant récupérer seulement les affiliés des campagnes qui existent encore |
70 | | - const affiliatesRef = collection(db, 'affiliates'); |
71 | | - const q = query( |
72 | | - affiliatesRef, |
73 | | - where('campaignId', 'in', existingCampaignIds), |
74 | | - where('userId', '==', user.uid), |
75 | | - orderBy('createdAt', 'desc') |
76 | | - ); |
77 | | - |
78 | | - unsubscribe = onSnapshot(q, (snapshot) => { |
79 | | - console.log('👥 SECURITY - Firestore snapshot received, docs:', snapshot.docs.length); |
80 | | - |
81 | | - const affiliatesData = snapshot.docs.map(doc => { |
82 | | - const data = doc.data(); |
83 | | - |
84 | | - // VÉRIFICATION DE SÉCURITÉ : s'assurer que l'affilié appartient bien à l'utilisateur |
85 | | - if (data.userId !== user.uid) { |
86 | | - console.log('👥 SECURITY - Blocking affiliate not owned by user:', doc.id); |
87 | | - return null; |
88 | | - } |
89 | | - |
90 | | - return { |
91 | | - id: doc.id, |
92 | | - ...data, |
93 | | - createdAt: data.createdAt?.toDate(), |
94 | | - }; |
95 | | - }).filter(Boolean) as Affiliate[]; |
96 | | - |
97 | | - console.log('👥 SECURITY - Secured affiliates loaded (filtered by existing campaigns):', affiliatesData.length); |
98 | | - setAffiliates(affiliatesData); |
99 | | - setLoading(false); |
100 | | - }, (error) => { |
101 | | - console.error('👥 SECURITY - Firestore error:', error); |
102 | | - setLoading(false); |
103 | | - }); |
104 | | - |
105 | | - } else { |
106 | | - // Mode campagne spécifique (comportement original) |
107 | | - const affiliatesRef = collection(db, 'affiliates'); |
108 | | - const q = query( |
109 | | - affiliatesRef, |
110 | | - where('campaignId', '==', campaignId), |
111 | | - where('userId', '==', user.uid), |
112 | | - orderBy('createdAt', 'desc') |
113 | | - ); |
114 | | - |
115 | | - unsubscribe = onSnapshot(q, (snapshot) => { |
116 | | - console.log('👥 SECURITY - Firestore snapshot received, docs:', snapshot.docs.length); |
117 | | - |
118 | | - const affiliatesData = snapshot.docs.map(doc => { |
119 | | - const data = doc.data(); |
120 | | - |
121 | | - // VÉRIFICATION DE SÉCURITÉ : s'assurer que l'affilié appartient bien à l'utilisateur |
122 | | - if (data.userId !== user.uid) { |
123 | | - console.log('👥 SECURITY - Blocking affiliate not owned by user:', doc.id); |
124 | | - return null; |
125 | | - } |
126 | | - |
127 | | - return { |
128 | | - id: doc.id, |
129 | | - ...data, |
130 | | - createdAt: data.createdAt?.toDate(), |
131 | | - }; |
132 | | - }).filter(Boolean) as Affiliate[]; |
133 | | - |
134 | | - console.log('👥 SECURITY - Secured affiliates loaded:', affiliatesData.length); |
135 | | - setAffiliates(affiliatesData); |
136 | | - setLoading(false); |
137 | | - }, (error) => { |
138 | | - console.error('👥 SECURITY - Firestore error:', error); |
139 | | - setLoading(false); |
140 | | - }); |
141 | | - } |
142 | | - } catch (error) { |
143 | | - console.error('👥 SECURITY - Error loading affiliates:', error); |
144 | | - setLoading(false); |
145 | | - } |
146 | | - }; |
147 | | - |
148 | | - loadAffiliates(); |
149 | | - |
150 | | - // Cleanup function |
151 | | - return () => { |
152 | | - if (unsubscribe) { |
153 | | - unsubscribe(); |
154 | | - } |
155 | | - }; |
156 | | - }, [user, authLoading, campaignId]); |
157 | | - |
158 | | - const generateTrackingCode = () => { |
159 | | - return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); |
160 | | - }; |
161 | | - |
162 | | - const createAffiliate = async (affiliateData: Omit<Affiliate, 'id' | 'createdAt' | 'userId' | 'trackingCode'>) => { |
163 | | - requireAuthentication('créer un affilié'); |
164 | | - |
165 | | - console.log('👥 SECURITY - Creating affiliate for authenticated user:', user?.uid); |
166 | | - |
167 | | - const newAffiliate = { |
168 | | - ...affiliateData, |
169 | | - userId: user!.uid, |
170 | | - trackingCode: generateTrackingCode(), |
171 | | - createdAt: new Date(), |
172 | | - }; |
173 | | - |
174 | | - const docRef = await addDoc(collection(db, 'affiliates'), newAffiliate); |
175 | | - console.log('👥 SECURITY - Affiliate created securely:', docRef.id); |
176 | | - return docRef.id; |
177 | | - }; |
178 | | - |
179 | | - const updateAffiliate = async (id: string, updates: Partial<Affiliate>) => { |
180 | | - requireAuthentication('modifier un affilié'); |
181 | | - |
182 | | - // Vérifier que l'affilié appartient à l'utilisateur |
183 | | - const affiliate = affiliates.find(a => a.id === id); |
184 | | - if (affiliate) { |
185 | | - requireOwnership(affiliate.userId, 'affilié'); |
186 | | - } |
187 | | - |
188 | | - console.log('👥 SECURITY - Updating affiliate:', id); |
189 | | - const affiliateRef = doc(db, 'affiliates', id); |
190 | | - await updateDoc(affiliateRef, updates); |
191 | | - }; |
192 | | - |
193 | | - const deleteAffiliate = async (id: string) => { |
194 | | - requireAuthentication('supprimer un affilié'); |
195 | | - |
196 | | - // Vérifier que l'affilié appartient à l'utilisateur |
197 | | - const affiliate = affiliates.find(a => a.id === id); |
198 | | - if (affiliate) { |
199 | | - requireOwnership(affiliate.userId, 'affilié'); |
200 | | - } |
201 | | - |
202 | | - console.log('👥 SECURITY - Starting secure cascade deletion for affiliate:', id); |
203 | | - |
204 | | - try { |
205 | | - // 1. Supprimer tous les clics de cet affilié |
206 | | - console.log('👥 SECURITY - Deleting affiliate clicks...'); |
207 | | - const clicksQuery = query( |
208 | | - collection(db, 'clicks'), |
209 | | - where('affiliateId', '==', id) |
210 | | - ); |
211 | | - const clicksSnapshot = await getDocs(clicksQuery); |
212 | | - console.log('👥 SECURITY - Clicks found for affiliate:', clicksSnapshot.size); |
213 | | - |
214 | | - const deleteClicksPromises = clicksSnapshot.docs.map(doc => { |
215 | | - console.log('👥 SECURITY - Deleting affiliate click:', doc.id); |
216 | | - return deleteDoc(doc.ref); |
217 | | - }); |
218 | | - await Promise.all(deleteClicksPromises); |
219 | | - |
220 | | - // 2. Supprimer tous les liens courts de cet affilié |
221 | | - console.log('👥 SECURITY - Deleting affiliate short links...'); |
222 | | - const shortLinksQuery = query( |
223 | | - collection(db, 'shortLinks'), |
224 | | - where('affiliateId', '==', id) |
225 | | - ); |
226 | | - const shortLinksSnapshot = await getDocs(shortLinksQuery); |
227 | | - console.log('👥 SECURITY - Short links found for affiliate:', shortLinksSnapshot.size); |
228 | | - |
229 | | - const deleteShortLinksPromises = shortLinksSnapshot.docs.map(doc => { |
230 | | - console.log('👥 SECURITY - Deleting affiliate short link:', doc.id); |
231 | | - return deleteDoc(doc.ref); |
232 | | - }); |
233 | | - await Promise.all(deleteShortLinksPromises); |
234 | | - |
235 | | - // 3. Supprimer toutes les conversions de cet affilié |
236 | | - console.log('👥 SECURITY - Deleting affiliate conversions...'); |
237 | | - const conversionsQuery = query( |
238 | | - collection(db, 'conversions'), |
239 | | - where('affiliateId', '==', id) |
240 | | - ); |
241 | | - const conversionsSnapshot = await getDocs(conversionsQuery); |
242 | | - console.log('👥 SECURITY - Conversions found for affiliate:', conversionsSnapshot.size); |
243 | | - |
244 | | - const deleteConversionsPromises = conversionsSnapshot.docs.map(doc => { |
245 | | - console.log('👥 SECURITY - Deleting affiliate conversion:', doc.id); |
246 | | - return deleteDoc(doc.ref); |
247 | | - }); |
248 | | - await Promise.all(deleteConversionsPromises); |
249 | | - |
250 | | - // 4. Finalement, supprimer l'affilié lui-même |
251 | | - console.log('👥 SECURITY - Deleting affiliate...'); |
252 | | - const affiliateRef = doc(db, 'affiliates', id); |
253 | | - await deleteDoc(affiliateRef); |
254 | | - |
255 | | - console.log('✅ SECURITY - Secure cascade deletion completed for affiliate:', id); |
256 | | - |
257 | | - } catch (error) { |
258 | | - console.error('❌ SECURITY - Error during secure cascade deletion:', error); |
259 | | - throw error; |
260 | | - } |
261 | | - }; |
262 | | - |
263 | | - return { |
264 | | - affiliates, |
265 | | - loading, |
266 | | - createAffiliate, |
267 | | - updateAffiliate, |
268 | | - deleteAffiliate, |
269 | | - }; |
| 4 | + // Use the new Supabase-based hook |
| 5 | + return useAffiliatesSupabase(campaignId); |
270 | 6 | }; |
0 commit comments