@blackcode_sa/metaestetics-api 1.4.8 → 1.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +31 -16
- package/dist/index.d.ts +31 -16
- package/dist/index.js +742 -187
- package/dist/index.mjs +761 -199
- package/package.json +1 -1
- package/src/services/auth.service.ts +33 -11
- package/src/services/clinic/clinic-group.service.ts +63 -9
- package/src/services/clinic/clinic.service.ts +32 -9
- package/src/services/clinic/utils/admin.utils.ts +6 -51
- package/src/services/clinic/utils/clinic-group.utils.ts +178 -45
- package/src/services/clinic/utils/clinic.utils.ts +482 -39
- package/src/services/clinic/utils/photos.utils.ts +188 -0
- package/src/services/clinic/utils/review.utils.ts +24 -17
- package/src/services/clinic/utils/tag.utils.ts +33 -8
- package/src/types/clinic/index.ts +8 -5
- package/src/validations/clinic.schema.ts +6 -3
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
Timestamp,
|
|
12
12
|
Firestore,
|
|
13
13
|
QueryConstraint,
|
|
14
|
+
addDoc,
|
|
15
|
+
writeBatch,
|
|
14
16
|
} from "firebase/firestore";
|
|
15
17
|
import {
|
|
16
18
|
Clinic,
|
|
@@ -19,6 +21,8 @@ import {
|
|
|
19
21
|
ClinicReview,
|
|
20
22
|
ClinicTag,
|
|
21
23
|
ClinicGroup,
|
|
24
|
+
ClinicBranchSetupData,
|
|
25
|
+
ClinicLocation,
|
|
22
26
|
} from "../../../types/clinic";
|
|
23
27
|
import { geohashForLocation } from "geofire-common";
|
|
24
28
|
import {
|
|
@@ -27,6 +31,12 @@ import {
|
|
|
27
31
|
clinicReviewSchema,
|
|
28
32
|
} from "../../../validations/clinic.schema";
|
|
29
33
|
import { z } from "zod";
|
|
34
|
+
import { FirebaseApp } from "firebase/app";
|
|
35
|
+
import {
|
|
36
|
+
uploadPhoto,
|
|
37
|
+
uploadMultiplePhotos,
|
|
38
|
+
processEntityPhotos,
|
|
39
|
+
} from "./photos.utils";
|
|
30
40
|
|
|
31
41
|
/**
|
|
32
42
|
* Creates a new clinic
|
|
@@ -35,6 +45,7 @@ import { z } from "zod";
|
|
|
35
45
|
* @param creatorAdminId - ID of the admin creating the clinic
|
|
36
46
|
* @param clinicGroupService - Service for clinic group operations
|
|
37
47
|
* @param clinicAdminService - Service for clinic admin operations
|
|
48
|
+
* @param app - Firebase app instance
|
|
38
49
|
* @returns The created clinic
|
|
39
50
|
*/
|
|
40
51
|
export async function createClinic(
|
|
@@ -42,55 +53,224 @@ export async function createClinic(
|
|
|
42
53
|
data: CreateClinicData,
|
|
43
54
|
creatorAdminId: string,
|
|
44
55
|
clinicGroupService: any,
|
|
45
|
-
clinicAdminService: any
|
|
56
|
+
clinicAdminService: any,
|
|
57
|
+
app: FirebaseApp
|
|
46
58
|
): Promise<Clinic> {
|
|
59
|
+
console.log("[CLINIC] Starting clinic creation", { creatorAdminId });
|
|
60
|
+
console.log("[CLINIC] Input data:", JSON.stringify(data, null, 2));
|
|
61
|
+
|
|
47
62
|
// Validacija podataka
|
|
63
|
+
try {
|
|
64
|
+
const validatedData = createClinicSchema.parse(data);
|
|
65
|
+
console.log("[CLINIC] Data validation passed");
|
|
66
|
+
} catch (validationError) {
|
|
67
|
+
console.error("[CLINIC] Data validation failed:", validationError);
|
|
68
|
+
throw validationError;
|
|
69
|
+
}
|
|
70
|
+
|
|
48
71
|
const validatedData = createClinicSchema.parse(data);
|
|
49
72
|
|
|
50
73
|
// Proveravamo da li admin postoji i da li pripada grupi
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
74
|
+
try {
|
|
75
|
+
console.log("[CLINIC] Checking if admin exists and belongs to group");
|
|
76
|
+
const admin = await clinicAdminService.getClinicAdmin(creatorAdminId);
|
|
77
|
+
if (!admin) {
|
|
78
|
+
console.error("[CLINIC] Admin not found", { creatorAdminId });
|
|
79
|
+
throw new Error("Admin not found");
|
|
80
|
+
}
|
|
55
81
|
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
if (admin.clinicGroupId !== validatedData.clinicGroupId) {
|
|
83
|
+
console.error("[CLINIC] Admin does not belong to this clinic group", {
|
|
84
|
+
adminGroupId: admin.clinicGroupId,
|
|
85
|
+
requestedGroupId: validatedData.clinicGroupId,
|
|
86
|
+
});
|
|
87
|
+
throw new Error("Admin does not belong to this clinic group");
|
|
88
|
+
}
|
|
89
|
+
console.log("[CLINIC] Admin verified");
|
|
90
|
+
} catch (adminError) {
|
|
91
|
+
console.error("[CLINIC] Error verifying admin:", adminError);
|
|
92
|
+
throw adminError;
|
|
58
93
|
}
|
|
59
94
|
|
|
60
95
|
// Proveravamo da li grupa postoji
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
96
|
+
try {
|
|
97
|
+
console.log("[CLINIC] Checking if clinic group exists");
|
|
98
|
+
const group = await clinicGroupService.getClinicGroup(
|
|
99
|
+
validatedData.clinicGroupId
|
|
100
|
+
);
|
|
101
|
+
if (!group) {
|
|
102
|
+
console.error("[CLINIC] Clinic group not found", {
|
|
103
|
+
groupId: validatedData.clinicGroupId,
|
|
104
|
+
});
|
|
105
|
+
throw new Error("Clinic group not found");
|
|
106
|
+
}
|
|
107
|
+
console.log("[CLINIC] Clinic group verified");
|
|
108
|
+
} catch (groupError) {
|
|
109
|
+
console.error("[CLINIC] Error verifying clinic group:", groupError);
|
|
110
|
+
throw groupError;
|
|
66
111
|
}
|
|
67
112
|
|
|
68
113
|
// Generišemo geohash za lokaciju
|
|
114
|
+
console.log("[CLINIC] Generating geohash for location");
|
|
69
115
|
if (validatedData.location) {
|
|
70
|
-
|
|
71
|
-
validatedData.location.
|
|
72
|
-
|
|
73
|
-
|
|
116
|
+
try {
|
|
117
|
+
validatedData.location.geohash = geohashForLocation([
|
|
118
|
+
validatedData.location.latitude,
|
|
119
|
+
validatedData.location.longitude,
|
|
120
|
+
]);
|
|
121
|
+
console.log("[CLINIC] Geohash generated successfully", {
|
|
122
|
+
geohash: validatedData.location.geohash,
|
|
123
|
+
});
|
|
124
|
+
} catch (geohashError) {
|
|
125
|
+
console.error("[CLINIC] Error generating geohash:", geohashError);
|
|
126
|
+
throw geohashError;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Generate a unique ID for the clinic
|
|
131
|
+
const clinicId = doc(collection(db, CLINICS_COLLECTION)).id;
|
|
132
|
+
console.log("[CLINIC] Generated clinic ID:", clinicId);
|
|
133
|
+
|
|
134
|
+
// Process photos
|
|
135
|
+
console.log("[CLINIC] Processing photos");
|
|
136
|
+
|
|
137
|
+
// Handle logo upload if provided
|
|
138
|
+
let logoUrl = null;
|
|
139
|
+
if (validatedData.logo) {
|
|
140
|
+
console.log("[CLINIC] Processing logo");
|
|
141
|
+
try {
|
|
142
|
+
logoUrl = await uploadPhoto(
|
|
143
|
+
validatedData.logo,
|
|
144
|
+
"clinics",
|
|
145
|
+
clinicId,
|
|
146
|
+
"logo",
|
|
147
|
+
app
|
|
148
|
+
);
|
|
149
|
+
console.log("[CLINIC] Logo processed", { logoUrl });
|
|
150
|
+
} catch (logoError) {
|
|
151
|
+
console.error("[CLINIC] Error processing logo:", logoError);
|
|
152
|
+
// Continue with clinic creation even if logo upload fails
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Handle regular photos upload
|
|
157
|
+
let processedPhotos: string[] = [];
|
|
158
|
+
if (validatedData.photos && validatedData.photos.length > 0) {
|
|
159
|
+
console.log("[CLINIC] Processing regular photos");
|
|
160
|
+
try {
|
|
161
|
+
processedPhotos = await uploadMultiplePhotos(
|
|
162
|
+
validatedData.photos,
|
|
163
|
+
"clinics",
|
|
164
|
+
clinicId,
|
|
165
|
+
"photo",
|
|
166
|
+
app
|
|
167
|
+
);
|
|
168
|
+
console.log("[CLINIC] Regular photos processed", {
|
|
169
|
+
count: processedPhotos.length,
|
|
170
|
+
});
|
|
171
|
+
} catch (photosError) {
|
|
172
|
+
console.error("[CLINIC] Error processing regular photos:", photosError);
|
|
173
|
+
// Continue with clinic creation even if photos upload fails
|
|
174
|
+
processedPhotos = validatedData.photos.filter(
|
|
175
|
+
(photo) => !photo.startsWith("data:")
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle featured photos upload
|
|
181
|
+
let processedFeaturedPhotos: string[] = [];
|
|
182
|
+
if (validatedData.featuredPhotos && validatedData.featuredPhotos.length > 0) {
|
|
183
|
+
console.log("[CLINIC] Processing featured photos");
|
|
184
|
+
try {
|
|
185
|
+
processedFeaturedPhotos = await uploadMultiplePhotos(
|
|
186
|
+
validatedData.featuredPhotos,
|
|
187
|
+
"clinics",
|
|
188
|
+
clinicId,
|
|
189
|
+
"featured",
|
|
190
|
+
app
|
|
191
|
+
);
|
|
192
|
+
console.log("[CLINIC] Featured photos processed", {
|
|
193
|
+
count: processedFeaturedPhotos.length,
|
|
194
|
+
});
|
|
195
|
+
} catch (featuredError) {
|
|
196
|
+
console.error(
|
|
197
|
+
"[CLINIC] Error processing featured photos:",
|
|
198
|
+
featuredError
|
|
199
|
+
);
|
|
200
|
+
// Continue with clinic creation even if featured photos upload fails
|
|
201
|
+
processedFeaturedPhotos = validatedData.featuredPhotos.filter(
|
|
202
|
+
(photo) => !photo.startsWith("data:")
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Handle photos with tags
|
|
208
|
+
let processedPhotosWithTags = validatedData.photosWithTags || [];
|
|
209
|
+
if (processedPhotosWithTags.length > 0) {
|
|
210
|
+
console.log("[CLINIC] Processing photos with tags");
|
|
211
|
+
try {
|
|
212
|
+
const updatedPhotosWithTags = [];
|
|
213
|
+
for (const photoWithTag of processedPhotosWithTags) {
|
|
214
|
+
if (photoWithTag.url && photoWithTag.url.startsWith("data:")) {
|
|
215
|
+
const uploadedUrl = await uploadPhoto(
|
|
216
|
+
photoWithTag.url,
|
|
217
|
+
"clinics",
|
|
218
|
+
clinicId,
|
|
219
|
+
`tagged-${photoWithTag.tag}`,
|
|
220
|
+
app
|
|
221
|
+
);
|
|
222
|
+
if (uploadedUrl) {
|
|
223
|
+
updatedPhotosWithTags.push({
|
|
224
|
+
url: uploadedUrl,
|
|
225
|
+
tag: photoWithTag.tag,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
updatedPhotosWithTags.push(photoWithTag);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
processedPhotosWithTags = updatedPhotosWithTags;
|
|
233
|
+
console.log("[CLINIC] Photos with tags processed", {
|
|
234
|
+
count: processedPhotosWithTags.length,
|
|
235
|
+
});
|
|
236
|
+
} catch (tagsError) {
|
|
237
|
+
console.error("[CLINIC] Error processing photos with tags:", tagsError);
|
|
238
|
+
// Continue with clinic creation even if photos with tags upload fails
|
|
239
|
+
processedPhotosWithTags =
|
|
240
|
+
validatedData.photosWithTags?.filter(
|
|
241
|
+
(photo) => !photo.url.startsWith("data:")
|
|
242
|
+
) || [];
|
|
243
|
+
}
|
|
74
244
|
}
|
|
75
245
|
|
|
76
246
|
const now = Timestamp.now();
|
|
247
|
+
console.log("[CLINIC] Preparing clinic data object");
|
|
248
|
+
|
|
77
249
|
const clinicData: Clinic = {
|
|
78
250
|
...validatedData,
|
|
79
|
-
id:
|
|
251
|
+
id: clinicId,
|
|
80
252
|
description: validatedData.description || undefined,
|
|
81
253
|
location: {
|
|
82
|
-
|
|
254
|
+
address: validatedData.location.address || "",
|
|
255
|
+
city: validatedData.location.city || "",
|
|
256
|
+
country: validatedData.location.country || "",
|
|
257
|
+
postalCode: validatedData.location.postalCode || "",
|
|
258
|
+
latitude: validatedData.location.latitude || 0,
|
|
259
|
+
longitude: validatedData.location.longitude || 0,
|
|
83
260
|
geohash: validatedData.location.geohash || undefined,
|
|
84
261
|
},
|
|
85
262
|
contactInfo: {
|
|
86
|
-
|
|
263
|
+
email: validatedData.contactInfo.email || "",
|
|
264
|
+
phoneNumber: validatedData.contactInfo.phoneNumber || "",
|
|
87
265
|
alternativePhoneNumber:
|
|
88
266
|
validatedData.contactInfo.alternativePhoneNumber || undefined,
|
|
89
267
|
website: validatedData.contactInfo.website || undefined,
|
|
90
268
|
},
|
|
269
|
+
logo: logoUrl || undefined,
|
|
91
270
|
tags: validatedData.tags || [],
|
|
92
|
-
featuredPhotos: [],
|
|
93
|
-
photos:
|
|
271
|
+
featuredPhotos: processedFeaturedPhotos || [],
|
|
272
|
+
photos: processedPhotos || [],
|
|
273
|
+
photosWithTags: processedPhotosWithTags,
|
|
94
274
|
doctors: [],
|
|
95
275
|
doctorsInfo: [],
|
|
96
276
|
services: [],
|
|
@@ -107,24 +287,82 @@ export async function createClinic(
|
|
|
107
287
|
|
|
108
288
|
try {
|
|
109
289
|
// Validiramo kompletan objekat
|
|
110
|
-
|
|
290
|
+
console.log("[CLINIC] Validating complete clinic object");
|
|
291
|
+
try {
|
|
292
|
+
clinicSchema.parse(clinicData);
|
|
293
|
+
console.log("[CLINIC] Clinic validation passed");
|
|
294
|
+
} catch (schemaError) {
|
|
295
|
+
console.error(
|
|
296
|
+
"[CLINIC] Clinic validation failed:",
|
|
297
|
+
JSON.stringify(schemaError, null, 2)
|
|
298
|
+
);
|
|
299
|
+
throw schemaError;
|
|
300
|
+
}
|
|
111
301
|
|
|
112
302
|
// Čuvamo u Firestore
|
|
113
|
-
|
|
303
|
+
console.log("[CLINIC] Saving clinic to Firestore", {
|
|
304
|
+
clinicId: clinicData.id,
|
|
305
|
+
});
|
|
306
|
+
try {
|
|
307
|
+
await setDoc(doc(db, CLINICS_COLLECTION, clinicData.id), clinicData);
|
|
308
|
+
console.log("[CLINIC] Clinic saved successfully");
|
|
309
|
+
} catch (firestoreError) {
|
|
310
|
+
console.error("[CLINIC] Error saving to Firestore:", firestoreError);
|
|
311
|
+
throw firestoreError;
|
|
312
|
+
}
|
|
114
313
|
|
|
115
314
|
// Dodajemo kliniku u grupaciju
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
315
|
+
console.log("[CLINIC] Adding clinic to clinic group");
|
|
316
|
+
try {
|
|
317
|
+
const group = await clinicGroupService.getClinicGroup(
|
|
318
|
+
validatedData.clinicGroupId
|
|
319
|
+
);
|
|
320
|
+
if (group) {
|
|
321
|
+
await clinicGroupService.updateClinicGroup(
|
|
322
|
+
validatedData.clinicGroupId,
|
|
323
|
+
{
|
|
324
|
+
clinics: [...group.clinics, clinicData.id],
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
console.log("[CLINIC] Clinic added to group successfully");
|
|
328
|
+
}
|
|
329
|
+
} catch (groupUpdateError) {
|
|
330
|
+
console.error("[CLINIC] Error adding clinic to group:", groupUpdateError);
|
|
331
|
+
// Continue even if adding to group fails
|
|
332
|
+
}
|
|
119
333
|
|
|
120
334
|
// Dodajemo kliniku adminu koji ju je kreirao
|
|
121
|
-
|
|
335
|
+
console.log("[CLINIC] Adding clinic to admin's managed clinics");
|
|
336
|
+
try {
|
|
337
|
+
await clinicAdminService.addClinicToManaged(
|
|
338
|
+
creatorAdminId,
|
|
339
|
+
clinicData.id
|
|
340
|
+
);
|
|
341
|
+
console.log(
|
|
342
|
+
"[CLINIC] Clinic added to admin's managed clinics successfully"
|
|
343
|
+
);
|
|
344
|
+
} catch (adminUpdateError) {
|
|
345
|
+
console.error(
|
|
346
|
+
"[CLINIC] Error adding clinic to admin's managed clinics:",
|
|
347
|
+
adminUpdateError
|
|
348
|
+
);
|
|
349
|
+
// Continue even if adding to admin fails
|
|
350
|
+
}
|
|
122
351
|
|
|
352
|
+
console.log("[CLINIC] Clinic creation completed successfully", {
|
|
353
|
+
clinicId: clinicData.id,
|
|
354
|
+
clinicName: clinicData.name,
|
|
355
|
+
});
|
|
123
356
|
return clinicData;
|
|
124
357
|
} catch (error) {
|
|
125
358
|
if (error instanceof z.ZodError) {
|
|
359
|
+
console.error(
|
|
360
|
+
"[CLINIC] Zod validation error:",
|
|
361
|
+
JSON.stringify(error.errors, null, 2)
|
|
362
|
+
);
|
|
126
363
|
throw new Error("Invalid clinic data: " + error.message);
|
|
127
364
|
}
|
|
365
|
+
console.error("[CLINIC] Unhandled error in createClinic:", error);
|
|
128
366
|
throw error;
|
|
129
367
|
}
|
|
130
368
|
}
|
|
@@ -176,6 +414,7 @@ export async function getClinicsByGroup(
|
|
|
176
414
|
* @param data - Data to update
|
|
177
415
|
* @param adminId - ID of the admin making the update
|
|
178
416
|
* @param clinicAdminService - Service for clinic admin operations
|
|
417
|
+
* @param app - Firebase app instance
|
|
179
418
|
* @returns The updated clinic
|
|
180
419
|
*/
|
|
181
420
|
export async function updateClinic(
|
|
@@ -183,37 +422,231 @@ export async function updateClinic(
|
|
|
183
422
|
clinicId: string,
|
|
184
423
|
data: Partial<Clinic>,
|
|
185
424
|
adminId: string,
|
|
186
|
-
clinicAdminService: any
|
|
425
|
+
clinicAdminService: any,
|
|
426
|
+
app: FirebaseApp
|
|
187
427
|
): Promise<Clinic> {
|
|
428
|
+
console.log("[CLINIC] Starting clinic update", { clinicId, adminId });
|
|
429
|
+
|
|
188
430
|
const clinic = await getClinic(db, clinicId);
|
|
189
431
|
if (!clinic) {
|
|
432
|
+
console.error("[CLINIC] Clinic not found", { clinicId });
|
|
190
433
|
throw new Error("Clinic not found");
|
|
191
434
|
}
|
|
192
435
|
|
|
193
436
|
// Proveravamo da li admin ima prava da ažurira kliniku
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
437
|
+
try {
|
|
438
|
+
console.log("[CLINIC] Checking admin permissions");
|
|
439
|
+
const admin = await clinicAdminService.getClinicAdmin(adminId);
|
|
440
|
+
if (!admin) {
|
|
441
|
+
console.error("[CLINIC] Admin not found", { adminId });
|
|
442
|
+
throw new Error("Admin not found");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Check if admin is either:
|
|
446
|
+
// 1. The owner of the clinic group, OR
|
|
447
|
+
// 2. Has this clinic in their managed clinics list, OR
|
|
448
|
+
// 3. Is listed in the clinic's admins array
|
|
449
|
+
const hasPermission =
|
|
450
|
+
(admin.isGroupOwner && admin.clinicGroupId === clinic.clinicGroupId) ||
|
|
451
|
+
(admin.clinicsManaged.includes(clinicId) &&
|
|
452
|
+
clinic.admins &&
|
|
453
|
+
clinic.admins.includes(adminId));
|
|
454
|
+
|
|
455
|
+
if (!hasPermission) {
|
|
456
|
+
console.error(
|
|
457
|
+
"[CLINIC] Admin does not have permission to update this clinic",
|
|
458
|
+
{
|
|
459
|
+
adminId,
|
|
460
|
+
clinicId,
|
|
461
|
+
isGroupOwner: admin.isGroupOwner,
|
|
462
|
+
clinicsManaged: admin.clinicsManaged,
|
|
463
|
+
isClinicAdmin: clinic.admins && clinic.admins.includes(adminId),
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
throw new Error("Admin does not have permission to update this clinic");
|
|
467
|
+
}
|
|
468
|
+
console.log("[CLINIC] Admin permissions verified");
|
|
469
|
+
} catch (adminError) {
|
|
470
|
+
console.error("[CLINIC] Error verifying admin permissions:", adminError);
|
|
471
|
+
throw adminError;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Process photos in the update data
|
|
475
|
+
let updatedData = { ...data };
|
|
476
|
+
|
|
477
|
+
// Handle logo update if provided
|
|
478
|
+
if (
|
|
479
|
+
data.logo &&
|
|
480
|
+
typeof data.logo === "string" &&
|
|
481
|
+
data.logo.startsWith("data:")
|
|
482
|
+
) {
|
|
483
|
+
console.log("[CLINIC] Processing logo update");
|
|
484
|
+
try {
|
|
485
|
+
const logoUrl = await uploadPhoto(
|
|
486
|
+
data.logo,
|
|
487
|
+
"clinics",
|
|
488
|
+
clinicId,
|
|
489
|
+
"logo",
|
|
490
|
+
app
|
|
491
|
+
);
|
|
492
|
+
console.log("[CLINIC] Logo update processed", { logoUrl });
|
|
493
|
+
|
|
494
|
+
if (logoUrl !== null) {
|
|
495
|
+
updatedData.logo = logoUrl;
|
|
496
|
+
}
|
|
497
|
+
} catch (logoError) {
|
|
498
|
+
console.error("[CLINIC] Error processing logo update:", logoError);
|
|
499
|
+
// Continue with update even if logo upload fails
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Handle regular photos update if provided
|
|
504
|
+
if (data.photos && data.photos.length > 0) {
|
|
505
|
+
console.log("[CLINIC] Processing regular photos update");
|
|
506
|
+
try {
|
|
507
|
+
// Filter out only data URLs that need to be uploaded
|
|
508
|
+
const dataUrlPhotos = data.photos.filter(
|
|
509
|
+
(photo) => typeof photo === "string" && photo.startsWith("data:")
|
|
510
|
+
);
|
|
511
|
+
const existingPhotos = data.photos.filter(
|
|
512
|
+
(photo) => typeof photo === "string" && !photo.startsWith("data:")
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
if (dataUrlPhotos.length > 0) {
|
|
516
|
+
const uploadedPhotos = await uploadMultiplePhotos(
|
|
517
|
+
dataUrlPhotos,
|
|
518
|
+
"clinics",
|
|
519
|
+
clinicId,
|
|
520
|
+
"photo",
|
|
521
|
+
app
|
|
522
|
+
);
|
|
523
|
+
console.log("[CLINIC] Regular photos update processed", {
|
|
524
|
+
count: uploadedPhotos.length,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// Combine existing photos with newly uploaded ones
|
|
528
|
+
updatedData.photos = [...existingPhotos, ...uploadedPhotos];
|
|
529
|
+
}
|
|
530
|
+
} catch (photosError) {
|
|
531
|
+
console.error(
|
|
532
|
+
"[CLINIC] Error processing regular photos update:",
|
|
533
|
+
photosError
|
|
534
|
+
);
|
|
535
|
+
// Continue with update even if photos upload fails
|
|
536
|
+
updatedData.photos = data.photos.filter(
|
|
537
|
+
(photo) => typeof photo === "string" && !photo.startsWith("data:")
|
|
538
|
+
);
|
|
539
|
+
}
|
|
197
540
|
}
|
|
198
541
|
|
|
199
|
-
|
|
200
|
-
|
|
542
|
+
// Handle featured photos update if provided
|
|
543
|
+
if (data.featuredPhotos && data.featuredPhotos.length > 0) {
|
|
544
|
+
console.log("[CLINIC] Processing featured photos update");
|
|
545
|
+
try {
|
|
546
|
+
// Filter out only data URLs that need to be uploaded
|
|
547
|
+
const dataUrlPhotos = data.featuredPhotos.filter(
|
|
548
|
+
(photo) => typeof photo === "string" && photo.startsWith("data:")
|
|
549
|
+
);
|
|
550
|
+
const existingPhotos = data.featuredPhotos.filter(
|
|
551
|
+
(photo) => typeof photo === "string" && !photo.startsWith("data:")
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
if (dataUrlPhotos.length > 0) {
|
|
555
|
+
const uploadedPhotos = await uploadMultiplePhotos(
|
|
556
|
+
dataUrlPhotos,
|
|
557
|
+
"clinics",
|
|
558
|
+
clinicId,
|
|
559
|
+
"featured",
|
|
560
|
+
app
|
|
561
|
+
);
|
|
562
|
+
console.log("[CLINIC] Featured photos update processed", {
|
|
563
|
+
count: uploadedPhotos.length,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// Combine existing photos with newly uploaded ones
|
|
567
|
+
updatedData.featuredPhotos = [...existingPhotos, ...uploadedPhotos];
|
|
568
|
+
}
|
|
569
|
+
} catch (featuredError) {
|
|
570
|
+
console.error(
|
|
571
|
+
"[CLINIC] Error processing featured photos update:",
|
|
572
|
+
featuredError
|
|
573
|
+
);
|
|
574
|
+
// Continue with update even if featured photos upload fails
|
|
575
|
+
updatedData.featuredPhotos = data.featuredPhotos.filter(
|
|
576
|
+
(photo) => typeof photo === "string" && !photo.startsWith("data:")
|
|
577
|
+
);
|
|
578
|
+
}
|
|
201
579
|
}
|
|
202
580
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
581
|
+
// Handle photos with tags update if provided
|
|
582
|
+
if (data.photosWithTags && data.photosWithTags.length > 0) {
|
|
583
|
+
console.log("[CLINIC] Processing photos with tags update");
|
|
584
|
+
try {
|
|
585
|
+
const updatedPhotosWithTags = [];
|
|
586
|
+
|
|
587
|
+
// Process each photo with tag
|
|
588
|
+
for (const photoWithTag of data.photosWithTags) {
|
|
589
|
+
if (photoWithTag.url && photoWithTag.url.startsWith("data:")) {
|
|
590
|
+
// Upload new photo
|
|
591
|
+
const uploadedUrl = await uploadPhoto(
|
|
592
|
+
photoWithTag.url,
|
|
593
|
+
"clinics",
|
|
594
|
+
clinicId,
|
|
595
|
+
`tagged-${photoWithTag.tag}`,
|
|
596
|
+
app
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
if (uploadedUrl) {
|
|
600
|
+
updatedPhotosWithTags.push({
|
|
601
|
+
url: uploadedUrl,
|
|
602
|
+
tag: photoWithTag.tag,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
} else {
|
|
606
|
+
// Keep existing photo
|
|
607
|
+
updatedPhotosWithTags.push(photoWithTag);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
updatedData.photosWithTags = updatedPhotosWithTags;
|
|
612
|
+
console.log("[CLINIC] Photos with tags update processed", {
|
|
613
|
+
count: updatedPhotosWithTags.length,
|
|
614
|
+
});
|
|
615
|
+
} catch (tagsError) {
|
|
616
|
+
console.error(
|
|
617
|
+
"[CLINIC] Error processing photos with tags update:",
|
|
618
|
+
tagsError
|
|
619
|
+
);
|
|
620
|
+
// Continue with update even if photos with tags upload fails
|
|
621
|
+
updatedData.photosWithTags = data.photosWithTags.filter(
|
|
622
|
+
(photo) => !photo.url.startsWith("data:")
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Add timestamp
|
|
628
|
+
updatedData = {
|
|
629
|
+
...updatedData,
|
|
206
630
|
updatedAt: Timestamp.now(),
|
|
207
631
|
};
|
|
208
632
|
|
|
209
|
-
|
|
633
|
+
console.log("[CLINIC] Updating clinic in Firestore");
|
|
634
|
+
try {
|
|
635
|
+
await updateDoc(doc(db, CLINICS_COLLECTION, clinicId), updatedData);
|
|
636
|
+
console.log("[CLINIC] Clinic updated successfully");
|
|
637
|
+
} catch (updateError) {
|
|
638
|
+
console.error("[CLINIC] Error updating clinic in Firestore:", updateError);
|
|
639
|
+
throw updateError;
|
|
640
|
+
}
|
|
210
641
|
|
|
211
|
-
//
|
|
642
|
+
// Return updated data
|
|
212
643
|
const updatedClinic = await getClinic(db, clinicId);
|
|
213
644
|
if (!updatedClinic) {
|
|
645
|
+
console.error("[CLINIC] Failed to retrieve updated clinic");
|
|
214
646
|
throw new Error("Failed to retrieve updated clinic");
|
|
215
647
|
}
|
|
216
648
|
|
|
649
|
+
console.log("[CLINIC] Clinic update completed successfully");
|
|
217
650
|
return updatedClinic;
|
|
218
651
|
}
|
|
219
652
|
|
|
@@ -241,7 +674,17 @@ export async function deactivateClinic(
|
|
|
241
674
|
throw new Error("Admin not found");
|
|
242
675
|
}
|
|
243
676
|
|
|
244
|
-
if
|
|
677
|
+
// Check if admin is either:
|
|
678
|
+
// 1. The owner of the clinic group, OR
|
|
679
|
+
// 2. Has this clinic in their managed clinics list, OR
|
|
680
|
+
// 3. Is listed in the clinic's admins array
|
|
681
|
+
const hasPermission =
|
|
682
|
+
(admin.isGroupOwner && admin.clinicGroupId === clinic.clinicGroupId) ||
|
|
683
|
+
(admin.clinicsManaged.includes(clinicId) &&
|
|
684
|
+
clinic.admins &&
|
|
685
|
+
clinic.admins.includes(adminId));
|
|
686
|
+
|
|
687
|
+
if (!hasPermission) {
|
|
245
688
|
throw new Error("Admin does not have permission to deactivate this clinic");
|
|
246
689
|
}
|
|
247
690
|
|