@blackcode_sa/metaestetics-api 1.14.78 → 1.15.2
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/admin/index.d.mts +5 -0
- package/dist/admin/index.d.ts +5 -0
- package/dist/admin/index.js +47 -5
- package/dist/admin/index.mjs +47 -5
- package/dist/index.d.mts +302 -1
- package/dist/index.d.ts +302 -1
- package/dist/index.js +2655 -1754
- package/dist/index.mjs +1880 -983
- package/package.json +1 -1
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +59 -6
- package/src/services/__tests__/auth/auth.mock.test.ts +2 -2
- package/src/services/__tests__/auth/auth.setup.ts +6 -1
- package/src/services/__tests__/auth.service.test.ts +8 -44
- package/src/services/__tests__/base.service.test.ts +4 -45
- package/src/services/__tests__/user.service.test.ts +6 -4
- package/src/services/appointment/utils/appointment.utils.ts +0 -3
- package/src/services/appointment/utils/extended-procedure.utils.ts +1 -0
- package/src/services/auth/auth.v2.service.ts +7 -7
- package/src/services/clinic/__tests__/clinic-admin.service.test.ts +11 -33
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +21 -151
- package/src/services/clinic/__tests__/clinic.service.test.ts +17 -69
- package/src/services/clinic/utils/clinic-group.utils.ts +2 -2
- package/src/services/clinic/utils/clinic.utils.ts +28 -22
- package/src/services/clinic/utils/index.ts +0 -1
- package/src/services/notifications/__tests__/notification.service.test.ts +5 -5
- package/src/services/patient/__tests__/patient.service.test.ts +17 -25
- package/src/services/patient/patient.service.ts +136 -0
- package/src/services/patient/utils/body-assessment.utils.ts +159 -0
- package/src/services/patient/utils/docs.utils.ts +1 -1
- package/src/services/patient/utils/hair-scalp-assessment.utils.ts +158 -0
- package/src/services/patient/utils/pre-surgical-assessment.utils.ts +161 -0
- package/src/services/patient/utils/skin-quality-assessment.utils.ts +160 -0
- package/src/services/user/user.v2.service.ts +4 -3
- package/src/types/patient/body-assessment.types.ts +93 -0
- package/src/types/patient/hair-scalp-assessment.types.ts +98 -0
- package/src/types/patient/index.ts +4 -0
- package/src/types/patient/pre-surgical-assessment.types.ts +95 -0
- package/src/types/patient/skin-quality-assessment.types.ts +105 -0
- package/src/validations/patient/body-assessment.schema.ts +82 -0
- package/src/validations/patient/hair-scalp-assessment.schema.ts +70 -0
- package/src/validations/patient/pre-surgical-assessment.schema.ts +78 -0
- package/src/validations/patient/skin-quality-assessment.schema.ts +70 -0
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
query,
|
|
10
10
|
where,
|
|
11
11
|
updateDoc,
|
|
12
|
-
deleteDoc,
|
|
13
12
|
setDoc,
|
|
14
13
|
} from "firebase/firestore";
|
|
15
14
|
|
|
@@ -48,7 +47,12 @@ describe("ClinicGroupService", () => {
|
|
|
48
47
|
analytics: null,
|
|
49
48
|
});
|
|
50
49
|
|
|
51
|
-
clinicGroupService = new ClinicGroupService(
|
|
50
|
+
clinicGroupService = new ClinicGroupService(
|
|
51
|
+
{} as any,
|
|
52
|
+
{} as any,
|
|
53
|
+
{} as any,
|
|
54
|
+
{} as any
|
|
55
|
+
);
|
|
52
56
|
|
|
53
57
|
// Mock Firestore functions
|
|
54
58
|
(doc as jest.Mock).mockReturnValue("doc-ref");
|
|
@@ -57,8 +61,6 @@ describe("ClinicGroupService", () => {
|
|
|
57
61
|
(where as jest.Mock).mockReturnValue("where-clause");
|
|
58
62
|
(setDoc as jest.Mock).mockResolvedValue(undefined);
|
|
59
63
|
(updateDoc as jest.Mock).mockResolvedValue(undefined);
|
|
60
|
-
(deleteDoc as jest.Mock).mockResolvedValue(undefined);
|
|
61
|
-
|
|
62
64
|
// Mock getDoc response
|
|
63
65
|
(getDoc as jest.Mock).mockResolvedValue({
|
|
64
66
|
exists: () => true,
|
|
@@ -92,13 +94,24 @@ describe("ClinicGroupService", () => {
|
|
|
92
94
|
name: "New Clinic Group",
|
|
93
95
|
description: "New Description",
|
|
94
96
|
logo: "new-logo-url",
|
|
95
|
-
|
|
97
|
+
hqLocation: {
|
|
98
|
+
address: "New Address",
|
|
99
|
+
city: "New City",
|
|
100
|
+
country: "New Country",
|
|
101
|
+
postalCode: "12345",
|
|
102
|
+
latitude: 45.0,
|
|
103
|
+
longitude: 20.0,
|
|
104
|
+
},
|
|
96
105
|
contactInfo: {
|
|
97
106
|
email: "contact@new.com",
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
phoneNumber: "+0987654321",
|
|
108
|
+
},
|
|
109
|
+
contactPerson: {
|
|
110
|
+
firstName: "John",
|
|
111
|
+
lastName: "Doe",
|
|
112
|
+
email: "john@new.com",
|
|
100
113
|
},
|
|
101
|
-
|
|
114
|
+
ownerId: null,
|
|
102
115
|
isActive: true,
|
|
103
116
|
};
|
|
104
117
|
|
|
@@ -148,30 +161,6 @@ describe("ClinicGroupService", () => {
|
|
|
148
161
|
});
|
|
149
162
|
});
|
|
150
163
|
|
|
151
|
-
describe("getClinicGroupsByOwner", () => {
|
|
152
|
-
it("treba da vrati kliničke grupe po vlasniku", async () => {
|
|
153
|
-
const result = await clinicGroupService.getClinicGroupsByOwner(
|
|
154
|
-
mockClinicGroup.ownerId
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
expect(collection).toHaveBeenCalled();
|
|
158
|
-
expect(query).toHaveBeenCalled();
|
|
159
|
-
expect(where).toHaveBeenCalledWith(
|
|
160
|
-
"ownerId",
|
|
161
|
-
"==",
|
|
162
|
-
mockClinicGroup.ownerId
|
|
163
|
-
);
|
|
164
|
-
expect(result).toEqual(
|
|
165
|
-
expect.arrayContaining([
|
|
166
|
-
expect.objectContaining({
|
|
167
|
-
id: mockClinicGroup.id,
|
|
168
|
-
ownerId: mockClinicGroup.ownerId,
|
|
169
|
-
}),
|
|
170
|
-
])
|
|
171
|
-
);
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
164
|
describe("updateClinicGroup", () => {
|
|
176
165
|
it("treba da ažurira kliničku grupu", async () => {
|
|
177
166
|
const updates = {
|
|
@@ -205,74 +194,6 @@ describe("ClinicGroupService", () => {
|
|
|
205
194
|
});
|
|
206
195
|
});
|
|
207
196
|
|
|
208
|
-
describe("deleteClinicGroup", () => {
|
|
209
|
-
it("treba da obriše kliničku grupu", async () => {
|
|
210
|
-
await clinicGroupService.deleteClinicGroup(mockClinicGroup.id);
|
|
211
|
-
|
|
212
|
-
expect(deleteDoc).toHaveBeenCalledWith("doc-ref");
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("treba da baci grešku ako grupa ne postoji", async () => {
|
|
216
|
-
(getDoc as jest.Mock).mockResolvedValueOnce({
|
|
217
|
-
exists: () => false,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
await expect(
|
|
221
|
-
clinicGroupService.deleteClinicGroup("non-existent")
|
|
222
|
-
).rejects.toThrow();
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe("addClinicToGroup", () => {
|
|
227
|
-
it("treba da doda kliniku u grupu", async () => {
|
|
228
|
-
const clinicId = "new-clinic-id";
|
|
229
|
-
const result = await clinicGroupService.addClinicToGroup(
|
|
230
|
-
mockClinicGroup.id,
|
|
231
|
-
clinicId
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
expect(updateDoc).toHaveBeenCalled();
|
|
235
|
-
expect(result.clinics).toContain(clinicId);
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
describe("removeClinicFromGroup", () => {
|
|
240
|
-
it("treba da ukloni kliniku iz grupe", async () => {
|
|
241
|
-
const clinicId = mockClinicGroup.clinics[0];
|
|
242
|
-
const result = await clinicGroupService.removeClinicFromGroup(
|
|
243
|
-
mockClinicGroup.id,
|
|
244
|
-
clinicId
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
expect(updateDoc).toHaveBeenCalled();
|
|
248
|
-
expect(result.clinics).not.toContain(clinicId);
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
describe("getClinicGroupByAdmin", () => {
|
|
253
|
-
it("treba da vrati kliničke grupe po admin ID-u", async () => {
|
|
254
|
-
const result = await clinicGroupService.getClinicGroupByAdmin(
|
|
255
|
-
mockClinicGroup.ownerId
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
expect(collection).toHaveBeenCalled();
|
|
259
|
-
expect(query).toHaveBeenCalled();
|
|
260
|
-
expect(where).toHaveBeenCalledWith(
|
|
261
|
-
"admins",
|
|
262
|
-
"array-contains",
|
|
263
|
-
mockClinicGroup.ownerId
|
|
264
|
-
);
|
|
265
|
-
expect(result).toEqual(
|
|
266
|
-
expect.arrayContaining([
|
|
267
|
-
expect.objectContaining({
|
|
268
|
-
id: mockClinicGroup.id,
|
|
269
|
-
ownerId: mockClinicGroup.ownerId,
|
|
270
|
-
}),
|
|
271
|
-
])
|
|
272
|
-
);
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
|
|
276
197
|
describe("deactivateClinicGroup", () => {
|
|
277
198
|
it("treba da deaktivira kliničku grupu", async () => {
|
|
278
199
|
await clinicGroupService.deactivateClinicGroup(mockClinicGroup.id);
|
|
@@ -298,55 +219,4 @@ describe("ClinicGroupService", () => {
|
|
|
298
219
|
});
|
|
299
220
|
});
|
|
300
221
|
|
|
301
|
-
describe("addClinic", () => {
|
|
302
|
-
it("treba da doda kliniku u grupu", async () => {
|
|
303
|
-
const clinicId = "new-clinic-id";
|
|
304
|
-
const result = await clinicGroupService.addClinic(
|
|
305
|
-
mockClinicGroup.id,
|
|
306
|
-
clinicId
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
expect(updateDoc).toHaveBeenCalled();
|
|
310
|
-
expect(result.clinics).toContain(clinicId);
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
describe("removeClinic", () => {
|
|
315
|
-
it("treba da ukloni kliniku iz grupe", async () => {
|
|
316
|
-
const clinicId = mockClinicGroup.clinics[0];
|
|
317
|
-
const result = await clinicGroupService.removeClinic(
|
|
318
|
-
mockClinicGroup.id,
|
|
319
|
-
clinicId
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
expect(updateDoc).toHaveBeenCalled();
|
|
323
|
-
expect(result.clinics).not.toContain(clinicId);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
describe("addAdmin", () => {
|
|
328
|
-
it("treba da doda admina u grupu", async () => {
|
|
329
|
-
const adminId = "new-admin-id";
|
|
330
|
-
const result = await clinicGroupService.addAdmin(
|
|
331
|
-
mockClinicGroup.id,
|
|
332
|
-
adminId
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
expect(updateDoc).toHaveBeenCalled();
|
|
336
|
-
expect(result.admins).toContain(adminId);
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
describe("removeAdmin", () => {
|
|
341
|
-
it("treba da ukloni admina iz grupe", async () => {
|
|
342
|
-
const adminId = "admin-to-remove";
|
|
343
|
-
const result = await clinicGroupService.removeAdmin(
|
|
344
|
-
mockClinicGroup.id,
|
|
345
|
-
adminId
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
expect(updateDoc).toHaveBeenCalled();
|
|
349
|
-
expect(result.admins).not.toContain(adminId);
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
222
|
});
|
|
@@ -50,7 +50,7 @@ describe("ClinicService", () => {
|
|
|
50
50
|
saturday: null,
|
|
51
51
|
sunday: null,
|
|
52
52
|
},
|
|
53
|
-
tags: [ClinicTag.PARKING, ClinicTag.
|
|
53
|
+
tags: [ClinicTag.PARKING, ClinicTag.WIFI],
|
|
54
54
|
customTags: ["special-service"],
|
|
55
55
|
isActive: true,
|
|
56
56
|
createdAt: mockTimestamp.now().toDate(),
|
|
@@ -67,7 +67,14 @@ describe("ClinicService", () => {
|
|
|
67
67
|
analytics: null,
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
clinicService = new ClinicService(
|
|
70
|
+
clinicService = new ClinicService(
|
|
71
|
+
{} as any,
|
|
72
|
+
{} as any,
|
|
73
|
+
{} as any,
|
|
74
|
+
{} as any,
|
|
75
|
+
{} as any,
|
|
76
|
+
{} as any
|
|
77
|
+
);
|
|
71
78
|
|
|
72
79
|
// Mock Firestore functions
|
|
73
80
|
(doc as jest.Mock).mockReturnValue("doc-ref");
|
|
@@ -107,14 +114,14 @@ describe("ClinicService", () => {
|
|
|
107
114
|
|
|
108
115
|
describe("createClinic", () => {
|
|
109
116
|
it("treba da kreira novu kliniku", async () => {
|
|
110
|
-
const clinicData = {
|
|
117
|
+
const clinicData: any = {
|
|
111
118
|
name: "New Clinic",
|
|
112
119
|
description: "New Description",
|
|
113
120
|
logo: "new-logo-url",
|
|
114
|
-
|
|
121
|
+
clinicGroupId: "new-group-id",
|
|
115
122
|
contactInfo: {
|
|
116
123
|
email: "new@clinic.test",
|
|
117
|
-
|
|
124
|
+
phoneNumber: "+0987654321",
|
|
118
125
|
website: "https://new.clinic",
|
|
119
126
|
},
|
|
120
127
|
location: {
|
|
@@ -136,7 +143,7 @@ describe("ClinicService", () => {
|
|
|
136
143
|
sunday: null,
|
|
137
144
|
},
|
|
138
145
|
tags: [ClinicTag.PARKING],
|
|
139
|
-
|
|
146
|
+
admins: ["test-admin-id"],
|
|
140
147
|
isActive: true,
|
|
141
148
|
};
|
|
142
149
|
|
|
@@ -260,40 +267,14 @@ describe("ClinicService", () => {
|
|
|
260
267
|
});
|
|
261
268
|
});
|
|
262
269
|
|
|
263
|
-
describe("searchClinics", () => {
|
|
264
|
-
it("treba da pronađe klinike po tagovima", async () => {
|
|
265
|
-
const searchParams = {
|
|
266
|
-
tags: [ClinicTag.PARKING],
|
|
267
|
-
isActive: true,
|
|
268
|
-
};
|
|
269
|
-
const result = await clinicService.searchClinics(searchParams);
|
|
270
|
-
|
|
271
|
-
expect(collection).toHaveBeenCalled();
|
|
272
|
-
expect(query).toHaveBeenCalled();
|
|
273
|
-
expect(where).toHaveBeenCalledWith(
|
|
274
|
-
"tags",
|
|
275
|
-
"array-contains-any",
|
|
276
|
-
searchParams.tags
|
|
277
|
-
);
|
|
278
|
-
expect(result).toEqual(
|
|
279
|
-
expect.arrayContaining([
|
|
280
|
-
expect.objectContaining({
|
|
281
|
-
id: mockClinic.id,
|
|
282
|
-
tags: expect.arrayContaining(searchParams.tags),
|
|
283
|
-
}),
|
|
284
|
-
])
|
|
285
|
-
);
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
|
|
289
270
|
describe("addTags", () => {
|
|
290
271
|
it("treba da doda tagove klinici", async () => {
|
|
291
272
|
const newTags = [ClinicTag.WIFI];
|
|
292
273
|
const adminId = "test-admin-id";
|
|
293
274
|
const result = await clinicService.addTags(
|
|
294
275
|
mockClinic.id,
|
|
295
|
-
|
|
296
|
-
|
|
276
|
+
adminId,
|
|
277
|
+
{ tags: newTags }
|
|
297
278
|
);
|
|
298
279
|
|
|
299
280
|
expect(updateDoc).toHaveBeenCalled();
|
|
@@ -309,8 +290,8 @@ describe("ClinicService", () => {
|
|
|
309
290
|
const adminId = "test-admin-id";
|
|
310
291
|
const result = await clinicService.removeTags(
|
|
311
292
|
mockClinic.id,
|
|
312
|
-
|
|
313
|
-
|
|
293
|
+
adminId,
|
|
294
|
+
{ tags: tagsToRemove }
|
|
314
295
|
);
|
|
315
296
|
|
|
316
297
|
expect(updateDoc).toHaveBeenCalled();
|
|
@@ -318,37 +299,4 @@ describe("ClinicService", () => {
|
|
|
318
299
|
});
|
|
319
300
|
});
|
|
320
301
|
|
|
321
|
-
describe("addCustomTags", () => {
|
|
322
|
-
it("treba da doda custom tagove klinici", async () => {
|
|
323
|
-
const newCustomTags = ["new-service"];
|
|
324
|
-
const adminId = "test-admin-id";
|
|
325
|
-
const result = await clinicService.addCustomTags(
|
|
326
|
-
mockClinic.id,
|
|
327
|
-
newCustomTags,
|
|
328
|
-
adminId
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
expect(updateDoc).toHaveBeenCalled();
|
|
332
|
-
expect(result.customTags).toEqual(
|
|
333
|
-
expect.arrayContaining([...mockClinic.customTags, ...newCustomTags])
|
|
334
|
-
);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
describe("removeCustomTags", () => {
|
|
339
|
-
it("treba da ukloni custom tagove iz klinike", async () => {
|
|
340
|
-
const customTagsToRemove = [mockClinic.customTags[0]];
|
|
341
|
-
const adminId = "test-admin-id";
|
|
342
|
-
const result = await clinicService.removeCustomTags(
|
|
343
|
-
mockClinic.id,
|
|
344
|
-
customTagsToRemove,
|
|
345
|
-
adminId
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
expect(updateDoc).toHaveBeenCalled();
|
|
349
|
-
expect(result.customTags).not.toEqual(
|
|
350
|
-
expect.arrayContaining(customTagsToRemove)
|
|
351
|
-
);
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
302
|
});
|
|
@@ -74,7 +74,7 @@ export async function createClinicGroup(
|
|
|
74
74
|
let validatedData: CreateClinicGroupData;
|
|
75
75
|
// Validacija podataka
|
|
76
76
|
try {
|
|
77
|
-
validatedData = createClinicGroupSchema.parse(data);
|
|
77
|
+
validatedData = createClinicGroupSchema.parse(data) as CreateClinicGroupData;
|
|
78
78
|
console.log("[CLINIC_GROUP] Data validation passed");
|
|
79
79
|
} catch (validationError) {
|
|
80
80
|
console.error("[CLINIC_GROUP] Data validation failed:", validationError);
|
|
@@ -138,7 +138,7 @@ export async function createClinicGroup(
|
|
|
138
138
|
|
|
139
139
|
// Handle logo upload if provided
|
|
140
140
|
let logoUrl = await uploadPhoto(
|
|
141
|
-
validatedData.logo || null,
|
|
141
|
+
(validatedData.logo as string) || null,
|
|
142
142
|
"clinic-groups",
|
|
143
143
|
groupId,
|
|
144
144
|
"logo",
|
|
@@ -145,7 +145,7 @@ export async function createClinic(
|
|
|
145
145
|
console.log("[CLINIC] Processing logo");
|
|
146
146
|
try {
|
|
147
147
|
logoUrl = await uploadPhoto(
|
|
148
|
-
validatedData.logo,
|
|
148
|
+
validatedData.logo as string,
|
|
149
149
|
"clinics",
|
|
150
150
|
clinicId,
|
|
151
151
|
"logo",
|
|
@@ -164,7 +164,7 @@ export async function createClinic(
|
|
|
164
164
|
console.log("[CLINIC] Processing cover photo");
|
|
165
165
|
try {
|
|
166
166
|
processedCoverPhoto = await uploadPhoto(
|
|
167
|
-
validatedData.coverPhoto,
|
|
167
|
+
validatedData.coverPhoto as string,
|
|
168
168
|
"clinics",
|
|
169
169
|
clinicId,
|
|
170
170
|
"cover",
|
|
@@ -176,19 +176,21 @@ export async function createClinic(
|
|
|
176
176
|
} catch (coverPhotoError) {
|
|
177
177
|
console.error("[CLINIC] Error processing cover photo:", coverPhotoError);
|
|
178
178
|
// Continue with clinic creation even if cover photo upload fails
|
|
179
|
-
|
|
179
|
+
const coverPhotoStr = validatedData.coverPhoto as string;
|
|
180
|
+
processedCoverPhoto = coverPhotoStr.startsWith("data:")
|
|
180
181
|
? null
|
|
181
|
-
:
|
|
182
|
+
: coverPhotoStr;
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
// Handle featured photos upload
|
|
186
187
|
let processedFeaturedPhotos: string[] = [];
|
|
187
|
-
|
|
188
|
+
const featuredPhotosStr = (validatedData.featuredPhotos || []) as string[];
|
|
189
|
+
if (featuredPhotosStr.length > 0) {
|
|
188
190
|
console.log("[CLINIC] Processing featured photos");
|
|
189
191
|
try {
|
|
190
192
|
processedFeaturedPhotos = await uploadMultiplePhotos(
|
|
191
|
-
|
|
193
|
+
featuredPhotosStr,
|
|
192
194
|
"clinics",
|
|
193
195
|
clinicId,
|
|
194
196
|
"featured",
|
|
@@ -203,22 +205,23 @@ export async function createClinic(
|
|
|
203
205
|
featuredError
|
|
204
206
|
);
|
|
205
207
|
// Continue with clinic creation even if featured photos upload fails
|
|
206
|
-
processedFeaturedPhotos =
|
|
208
|
+
processedFeaturedPhotos = featuredPhotosStr.filter(
|
|
207
209
|
(photo) => !photo.startsWith("data:")
|
|
208
210
|
);
|
|
209
211
|
}
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
// Handle photos with tags
|
|
213
|
-
let processedPhotosWithTags = validatedData.photosWithTags || [];
|
|
215
|
+
let processedPhotosWithTags: any[] = validatedData.photosWithTags || [];
|
|
214
216
|
if (processedPhotosWithTags.length > 0) {
|
|
215
217
|
console.log("[CLINIC] Processing photos with tags");
|
|
216
218
|
try {
|
|
217
|
-
const updatedPhotosWithTags = [];
|
|
219
|
+
const updatedPhotosWithTags: any[] = [];
|
|
218
220
|
for (const photoWithTag of processedPhotosWithTags) {
|
|
219
|
-
|
|
221
|
+
const photoUrl = photoWithTag.url as string;
|
|
222
|
+
if (photoUrl && photoUrl.startsWith("data:")) {
|
|
220
223
|
const uploadedUrl = await uploadPhoto(
|
|
221
|
-
|
|
224
|
+
photoUrl,
|
|
222
225
|
"clinics",
|
|
223
226
|
clinicId,
|
|
224
227
|
`tagged-${photoWithTag.tag}`,
|
|
@@ -243,7 +246,7 @@ export async function createClinic(
|
|
|
243
246
|
// Continue with clinic creation even if photos with tags upload fails
|
|
244
247
|
processedPhotosWithTags =
|
|
245
248
|
validatedData.photosWithTags?.filter(
|
|
246
|
-
(photo) => !photo.url.startsWith("data:")
|
|
249
|
+
(photo) => !(photo.url as string).startsWith("data:")
|
|
247
250
|
) || [];
|
|
248
251
|
}
|
|
249
252
|
}
|
|
@@ -254,6 +257,7 @@ export async function createClinic(
|
|
|
254
257
|
const clinicData: Clinic = {
|
|
255
258
|
...validatedData,
|
|
256
259
|
id: clinicId,
|
|
260
|
+
nameLower: (validatedData.name || "").toLowerCase(),
|
|
257
261
|
description: validatedData.description || "",
|
|
258
262
|
location: {
|
|
259
263
|
address: validatedData.location.address || "",
|
|
@@ -617,10 +621,11 @@ export async function updateClinic(
|
|
|
617
621
|
console.log("[CLINIC] Processing featured photos update");
|
|
618
622
|
try {
|
|
619
623
|
// Filter out only data URLs that need to be uploaded
|
|
620
|
-
const
|
|
624
|
+
const allPhotos = data.featuredPhotos as string[];
|
|
625
|
+
const dataUrlPhotos = allPhotos.filter(
|
|
621
626
|
(photo) => typeof photo === "string" && photo.startsWith("data:")
|
|
622
627
|
);
|
|
623
|
-
const existingPhotos =
|
|
628
|
+
const existingPhotos = allPhotos.filter(
|
|
624
629
|
(photo) => typeof photo === "string" && !photo.startsWith("data:")
|
|
625
630
|
);
|
|
626
631
|
|
|
@@ -637,10 +642,10 @@ export async function updateClinic(
|
|
|
637
642
|
});
|
|
638
643
|
|
|
639
644
|
// Combine existing photos with newly uploaded ones
|
|
640
|
-
updatedData.featuredPhotos = [...existingPhotos, ...uploadedPhotos];
|
|
645
|
+
updatedData.featuredPhotos = [...existingPhotos, ...uploadedPhotos] as any;
|
|
641
646
|
} else {
|
|
642
647
|
// If no new photos to upload, just use the existing ones
|
|
643
|
-
updatedData.featuredPhotos = existingPhotos;
|
|
648
|
+
updatedData.featuredPhotos = existingPhotos as any;
|
|
644
649
|
}
|
|
645
650
|
} catch (featuredError) {
|
|
646
651
|
console.error(
|
|
@@ -648,9 +653,9 @@ export async function updateClinic(
|
|
|
648
653
|
featuredError
|
|
649
654
|
);
|
|
650
655
|
// Continue with update even if featured photos upload fails
|
|
651
|
-
updatedData.featuredPhotos = data.featuredPhotos.filter(
|
|
656
|
+
updatedData.featuredPhotos = (data.featuredPhotos as string[]).filter(
|
|
652
657
|
(photo) => typeof photo === "string" && !photo.startsWith("data:")
|
|
653
|
-
);
|
|
658
|
+
) as any;
|
|
654
659
|
}
|
|
655
660
|
}
|
|
656
661
|
|
|
@@ -658,14 +663,15 @@ export async function updateClinic(
|
|
|
658
663
|
if (data.photosWithTags && data.photosWithTags.length > 0) {
|
|
659
664
|
console.log("[CLINIC] Processing photos with tags update");
|
|
660
665
|
try {
|
|
661
|
-
const updatedPhotosWithTags = [];
|
|
666
|
+
const updatedPhotosWithTags: any[] = [];
|
|
662
667
|
|
|
663
668
|
// Process each photo with tag
|
|
664
669
|
for (const photoWithTag of data.photosWithTags) {
|
|
665
|
-
|
|
670
|
+
const photoUrl = photoWithTag.url as string;
|
|
671
|
+
if (photoUrl && photoUrl.startsWith("data:")) {
|
|
666
672
|
// Upload new photo
|
|
667
673
|
const uploadedUrl = await uploadPhoto(
|
|
668
|
-
|
|
674
|
+
photoUrl,
|
|
669
675
|
"clinics",
|
|
670
676
|
clinicId,
|
|
671
677
|
`tagged-${photoWithTag.tag}`,
|
|
@@ -695,7 +701,7 @@ export async function updateClinic(
|
|
|
695
701
|
);
|
|
696
702
|
// Continue with update even if photos with tags upload fails
|
|
697
703
|
updatedData.photosWithTags = data.photosWithTags.filter(
|
|
698
|
-
(photo) => !photo.url.startsWith("data:")
|
|
704
|
+
(photo) => !(photo.url as string).startsWith("data:")
|
|
699
705
|
);
|
|
700
706
|
}
|
|
701
707
|
}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
NotificationStatus,
|
|
5
5
|
NotificationType,
|
|
6
6
|
NOTIFICATIONS_COLLECTION,
|
|
7
|
-
|
|
7
|
+
AppointmentStatusChangeNotification,
|
|
8
8
|
} from "../../../types/notifications";
|
|
9
9
|
import { UserRole } from "../../../types";
|
|
10
10
|
import {
|
|
@@ -37,18 +37,18 @@ jest.mock("../../../config/firebase", () => ({
|
|
|
37
37
|
describe("NotificationService", () => {
|
|
38
38
|
let service: NotificationService;
|
|
39
39
|
const mockNotification: Omit<
|
|
40
|
-
|
|
40
|
+
AppointmentStatusChangeNotification,
|
|
41
41
|
"id" | "createdAt" | "updatedAt"
|
|
42
42
|
> = {
|
|
43
43
|
title: "Test Notification",
|
|
44
44
|
body: "Test Body",
|
|
45
|
-
notificationType: NotificationType.
|
|
45
|
+
notificationType: NotificationType.APPOINTMENT_STATUS_CHANGE,
|
|
46
46
|
userId: "test-user-id",
|
|
47
47
|
status: NotificationStatus.PENDING,
|
|
48
48
|
isRead: false,
|
|
49
49
|
userRole: UserRole.PATIENT,
|
|
50
50
|
appointmentId: "test-appointment-id",
|
|
51
|
-
|
|
51
|
+
newStatus: "confirmed",
|
|
52
52
|
previousStatus: "pending",
|
|
53
53
|
notificationTime: Timestamp.now(),
|
|
54
54
|
notificationTokens: ["test-token"],
|
|
@@ -64,7 +64,7 @@ describe("NotificationService", () => {
|
|
|
64
64
|
|
|
65
65
|
beforeEach(async () => {
|
|
66
66
|
jest.clearAllMocks();
|
|
67
|
-
service = new NotificationService();
|
|
67
|
+
service = new NotificationService({} as any, {} as any, {} as any);
|
|
68
68
|
await (service as any).initialized;
|
|
69
69
|
}, 10000);
|
|
70
70
|
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
CreatePatientProfileData,
|
|
5
5
|
PATIENTS_COLLECTION,
|
|
6
6
|
Gender,
|
|
7
|
-
|
|
7
|
+
LocationData,
|
|
8
8
|
} from "../../../types/patient";
|
|
9
9
|
import { UserRole } from "../../../types";
|
|
10
10
|
import {
|
|
@@ -44,7 +44,7 @@ jest.mock("../../../config/firebase");
|
|
|
44
44
|
|
|
45
45
|
describe("PatientService", () => {
|
|
46
46
|
let service: PatientService;
|
|
47
|
-
const mockLocation:
|
|
47
|
+
const mockLocation: LocationData = {
|
|
48
48
|
latitude: 44.8125,
|
|
49
49
|
longitude: 20.4612,
|
|
50
50
|
geohash: "mock-geohash",
|
|
@@ -52,28 +52,26 @@ describe("PatientService", () => {
|
|
|
52
52
|
|
|
53
53
|
const mockCreateData: CreatePatientProfileData = {
|
|
54
54
|
userRef: "test-user-id",
|
|
55
|
-
|
|
56
|
-
lastName: "Patient",
|
|
57
|
-
gender: Gender.MALE,
|
|
58
|
-
contactInfo: {
|
|
59
|
-
email: "test@example.com",
|
|
60
|
-
phoneNumber: "+381601234567",
|
|
61
|
-
},
|
|
62
|
-
location: {
|
|
63
|
-
latitude: mockLocation.latitude,
|
|
64
|
-
longitude: mockLocation.longitude,
|
|
65
|
-
geohash: mockLocation.geohash,
|
|
66
|
-
},
|
|
55
|
+
displayName: "Test Patient",
|
|
67
56
|
expoTokens: [],
|
|
68
57
|
isActive: true,
|
|
69
58
|
isVerified: false,
|
|
59
|
+
isManual: false,
|
|
70
60
|
};
|
|
71
61
|
|
|
72
62
|
const mockPatientProfile: PatientProfile = {
|
|
73
63
|
...mockCreateData,
|
|
74
|
-
id: mockCreateData.userRef,
|
|
75
|
-
|
|
64
|
+
id: mockCreateData.userRef as string,
|
|
65
|
+
displayName: "Test Patient",
|
|
76
66
|
gamification: { level: 1, points: 0 },
|
|
67
|
+
expoTokens: [],
|
|
68
|
+
isActive: true,
|
|
69
|
+
isVerified: false,
|
|
70
|
+
isManual: false,
|
|
71
|
+
doctors: [],
|
|
72
|
+
clinics: [],
|
|
73
|
+
doctorIds: [],
|
|
74
|
+
clinicIds: [],
|
|
77
75
|
createdAt: Timestamp.now(),
|
|
78
76
|
updatedAt: Timestamp.now(),
|
|
79
77
|
};
|
|
@@ -90,7 +88,7 @@ describe("PatientService", () => {
|
|
|
90
88
|
analytics: null,
|
|
91
89
|
});
|
|
92
90
|
|
|
93
|
-
service = new PatientService();
|
|
91
|
+
service = new PatientService(mockDb as any, mockAuth as any, {} as any);
|
|
94
92
|
|
|
95
93
|
// Mock Firestore functions
|
|
96
94
|
(doc as jest.Mock).mockReturnValue("doc-ref");
|
|
@@ -159,10 +157,6 @@ describe("PatientService", () => {
|
|
|
159
157
|
expect.objectContaining({
|
|
160
158
|
...mockCreateData,
|
|
161
159
|
id: mockCreateData.userRef,
|
|
162
|
-
location: {
|
|
163
|
-
...mockCreateData.location,
|
|
164
|
-
geohash: "hash123",
|
|
165
|
-
},
|
|
166
160
|
gamification: { level: 1, points: 0 },
|
|
167
161
|
})
|
|
168
162
|
);
|
|
@@ -174,8 +168,7 @@ describe("PatientService", () => {
|
|
|
174
168
|
it("treba da vrati profil pacijenta ako postoji", async () => {
|
|
175
169
|
const mockProfile = {
|
|
176
170
|
id: "test-user-id",
|
|
177
|
-
|
|
178
|
-
lastName: "User",
|
|
171
|
+
displayName: "Test User",
|
|
179
172
|
createdAt: mockTimestamp.now().toDate(),
|
|
180
173
|
updatedAt: mockTimestamp.now().toDate(),
|
|
181
174
|
};
|
|
@@ -214,8 +207,7 @@ describe("PatientService", () => {
|
|
|
214
207
|
describe("updatePatientProfile", () => {
|
|
215
208
|
it("treba da ažurira profil pacijenta", async () => {
|
|
216
209
|
const updates = {
|
|
217
|
-
|
|
218
|
-
lastName: "Name",
|
|
210
|
+
displayName: "Updated Name",
|
|
219
211
|
};
|
|
220
212
|
const mockTimestamp = new Date();
|
|
221
213
|
(serverTimestamp as jest.Mock).mockReturnValue(mockTimestamp);
|