@blackcode_sa/metaestetics-api 1.5.30 → 1.5.32

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.5.30",
4
+ "version": "1.5.32",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -8,7 +8,11 @@ import { UserRole } from "../types";
8
8
  // Import types needed by admin consumers (like Cloud Functions)
9
9
  import { Clinic, ClinicLocation } from "../types/clinic";
10
10
  import { ClinicInfo } from "../types/profile";
11
- import { Practitioner, PractitionerToken } from "../types/practitioner";
11
+ import {
12
+ Practitioner,
13
+ PractitionerToken,
14
+ PractitionerTokenStatus,
15
+ } from "../types/practitioner";
12
16
  import { DoctorInfo } from "../types/clinic";
13
17
  import { Procedure, ProcedureSummaryInfo } from "../types/procedure";
14
18
  import { PatientProfile } from "../types/patient";
@@ -36,7 +40,11 @@ export type {
36
40
  // Re-export types needed by cloud functions
37
41
  export type { Clinic, ClinicLocation } from "../types/clinic";
38
42
  export type { ClinicInfo } from "../types/profile";
39
- export type { Practitioner, PractitionerToken } from "../types/practitioner";
43
+ export type {
44
+ Practitioner,
45
+ PractitionerToken,
46
+ PractitionerTokenStatus,
47
+ } from "../types/practitioner";
40
48
  export type { DoctorInfo } from "../types/clinic";
41
49
  export type { Procedure, ProcedureSummaryInfo } from "../types/procedure";
42
50
  export type { PatientProfile as Patient } from "../types/patient";
package/src/index.ts CHANGED
@@ -26,6 +26,7 @@ export {
26
26
  } from "./services/documentation-templates";
27
27
  export { CalendarServiceV2 } from "./services/calendar/calendar-refactored.service";
28
28
  export { SyncedCalendarsService } from "./services/calendar/synced-calendars.service";
29
+ export { ReviewService } from "./services/reviews/reviews.service";
29
30
 
30
31
  // Backoffice services
31
32
  export { BrandService } from "./backoffice/services/brand.service";
@@ -259,6 +260,10 @@ export {
259
260
  export { Contraindication } from "./backoffice/types/static/contraindication.types";
260
261
  export { ProcedureFamily } from "./backoffice/types/static/procedure-family.types";
261
262
  export { TreatmentBenefit } from "./backoffice/types/static/treatment-benefit.types";
263
+ export {
264
+ RequirementType,
265
+ TimeUnit,
266
+ } from "./backoffice/types/requirement.types";
262
267
 
263
268
  // Documentation Templates types
264
269
  export type {
@@ -1,4 +1,4 @@
1
- import { Firestore, Timestamp } from "firebase/firestore";
1
+ import { Firestore, Timestamp } from 'firebase/firestore';
2
2
  import {
3
3
  CalendarEvent,
4
4
  CalendarEventStatus,
@@ -7,23 +7,23 @@ import {
7
7
  CalendarSyncStatus,
8
8
  CreateCalendarEventData,
9
9
  UpdateCalendarEventData,
10
- } from "../../../types/calendar";
10
+ } from '../../../types/calendar';
11
11
  import {
12
12
  createClinicCalendarEventUtil,
13
13
  updateClinicCalendarEventUtil,
14
14
  deleteClinicCalendarEventUtil,
15
15
  checkAutoConfirmAppointmentsUtil,
16
- } from "./clinic.utils";
16
+ } from './clinic.utils';
17
17
  import {
18
18
  createPatientCalendarEventUtil,
19
19
  updatePatientCalendarEventUtil,
20
20
  deletePatientCalendarEventUtil,
21
- } from "./patient.utils";
21
+ } from './patient.utils';
22
22
  import {
23
23
  createPractitionerCalendarEventUtil,
24
24
  updatePractitionerCalendarEventUtil,
25
25
  deletePractitionerCalendarEventUtil,
26
- } from "./practitioner.utils";
26
+ } from './practitioner.utils';
27
27
 
28
28
  /**
29
29
  * Creates an appointment across all relevant calendars (practitioner, patient, clinic)
@@ -42,14 +42,14 @@ export async function createAppointmentUtil(
42
42
  patientId: string,
43
43
  eventData: Omit<
44
44
  CreateCalendarEventData,
45
- | "id"
46
- | "createdAt"
47
- | "updatedAt"
48
- | "clinicBranchId"
49
- | "practitionerProfileId"
50
- | "patientProfileId"
45
+ | 'id'
46
+ | 'createdAt'
47
+ | 'updatedAt'
48
+ | 'clinicBranchId'
49
+ | 'practitionerProfileId'
50
+ | 'patientProfileId'
51
51
  >,
52
- generateId: () => string
52
+ generateId: () => string,
53
53
  ): Promise<CalendarEvent> {
54
54
  // TODO: Add validation for appointment data
55
55
  // - Check if all entities exist
@@ -64,15 +64,10 @@ export async function createAppointmentUtil(
64
64
  const autoConfirm = await checkAutoConfirmAppointmentsUtil(db, clinicId);
65
65
 
66
66
  // Set the initial status based on auto-confirm setting
67
- const initialStatus = autoConfirm
68
- ? CalendarEventStatus.CONFIRMED
69
- : CalendarEventStatus.PENDING;
67
+ const initialStatus = autoConfirm ? CalendarEventStatus.CONFIRMED : CalendarEventStatus.PENDING;
70
68
 
71
69
  // Prepare the event data with all required IDs
72
- const appointmentData: Omit<
73
- CreateCalendarEventData,
74
- "id" | "createdAt" | "updatedAt"
75
- > = {
70
+ const appointmentData: Omit<CreateCalendarEventData, 'id' | 'createdAt' | 'updatedAt'> = {
76
71
  ...eventData,
77
72
  clinicBranchId: clinicId,
78
73
  practitionerProfileId: practitionerId,
@@ -86,29 +81,25 @@ export async function createAppointmentUtil(
86
81
  db,
87
82
  clinicId,
88
83
  appointmentData,
89
- () => eventId // Use the same ID for all calendars
84
+ () => eventId, // Use the same ID for all calendars
90
85
  );
91
86
 
92
87
  const practitionerPromise = createPractitionerCalendarEventUtil(
93
88
  db,
94
89
  practitionerId,
95
90
  appointmentData,
96
- () => eventId // Use the same ID for all calendars
91
+ () => eventId, // Use the same ID for all calendars
97
92
  );
98
93
 
99
94
  const patientPromise = createPatientCalendarEventUtil(
100
95
  db,
101
96
  patientId,
102
97
  appointmentData,
103
- () => eventId // Use the same ID for all calendars
98
+ () => eventId, // Use the same ID for all calendars
104
99
  );
105
100
 
106
101
  // Wait for all operations to complete
107
- const [clinicEvent] = await Promise.all([
108
- clinicPromise,
109
- practitionerPromise,
110
- patientPromise,
111
- ]);
102
+ const [clinicEvent] = await Promise.all([clinicPromise, practitionerPromise, patientPromise]);
112
103
 
113
104
  // Return the event from the clinic calendar
114
105
  return clinicEvent;
@@ -130,7 +121,7 @@ export async function updateAppointmentUtil(
130
121
  practitionerId: string,
131
122
  patientId: string,
132
123
  eventId: string,
133
- updateData: Omit<UpdateCalendarEventData, "updatedAt">
124
+ updateData: Omit<UpdateCalendarEventData, 'updatedAt'>,
134
125
  ): Promise<CalendarEvent> {
135
126
  // TODO: Add validation for update data
136
127
  // - Check if event exists in all calendars
@@ -138,33 +129,19 @@ export async function updateAppointmentUtil(
138
129
  // - Check for overlapping events
139
130
 
140
131
  // Update the event in all three calendars
141
- const clinicPromise = updateClinicCalendarEventUtil(
142
- db,
143
- clinicId,
144
- eventId,
145
- updateData
146
- );
132
+ const clinicPromise = updateClinicCalendarEventUtil(db, clinicId, eventId, updateData);
147
133
 
148
134
  const practitionerPromise = updatePractitionerCalendarEventUtil(
149
135
  db,
150
136
  practitionerId,
151
137
  eventId,
152
- updateData
138
+ updateData,
153
139
  );
154
140
 
155
- const patientPromise = updatePatientCalendarEventUtil(
156
- db,
157
- patientId,
158
- eventId,
159
- updateData
160
- );
141
+ const patientPromise = updatePatientCalendarEventUtil(db, patientId, eventId, updateData);
161
142
 
162
143
  // Wait for all operations to complete
163
- const [clinicEvent] = await Promise.all([
164
- clinicPromise,
165
- practitionerPromise,
166
- patientPromise,
167
- ]);
144
+ const [clinicEvent] = await Promise.all([clinicPromise, practitionerPromise, patientPromise]);
168
145
 
169
146
  // Return the event from the clinic calendar
170
147
  return clinicEvent;
@@ -183,16 +160,12 @@ export async function deleteAppointmentUtil(
183
160
  clinicId: string,
184
161
  practitionerId: string,
185
162
  patientId: string,
186
- eventId: string
163
+ eventId: string,
187
164
  ): Promise<void> {
188
165
  // Delete the event from all three calendars
189
166
  const clinicPromise = deleteClinicCalendarEventUtil(db, clinicId, eventId);
190
167
 
191
- const practitionerPromise = deletePractitionerCalendarEventUtil(
192
- db,
193
- practitionerId,
194
- eventId
195
- );
168
+ const practitionerPromise = deletePractitionerCalendarEventUtil(db, practitionerId, eventId);
196
169
 
197
170
  const patientPromise = deletePatientCalendarEventUtil(db, patientId, eventId);
198
171
 
@@ -214,16 +187,11 @@ export async function confirmAppointmentUtil(
214
187
  clinicId: string,
215
188
  practitionerId: string,
216
189
  patientId: string,
217
- eventId: string
190
+ eventId: string,
218
191
  ): Promise<CalendarEvent> {
219
- return updateAppointmentUtil(
220
- db,
221
- clinicId,
222
- practitionerId,
223
- patientId,
224
- eventId,
225
- { status: CalendarEventStatus.CONFIRMED }
226
- );
192
+ return updateAppointmentUtil(db, clinicId, practitionerId, patientId, eventId, {
193
+ status: CalendarEventStatus.CONFIRMED,
194
+ });
227
195
  }
228
196
 
229
197
  /**
@@ -240,16 +208,11 @@ export async function rejectAppointmentUtil(
240
208
  clinicId: string,
241
209
  practitionerId: string,
242
210
  patientId: string,
243
- eventId: string
211
+ eventId: string,
244
212
  ): Promise<CalendarEvent> {
245
- return updateAppointmentUtil(
246
- db,
247
- clinicId,
248
- practitionerId,
249
- patientId,
250
- eventId,
251
- { status: CalendarEventStatus.REJECTED }
252
- );
213
+ return updateAppointmentUtil(db, clinicId, practitionerId, patientId, eventId, {
214
+ status: CalendarEventStatus.REJECTED,
215
+ });
253
216
  }
254
217
 
255
218
  /**
@@ -266,16 +229,11 @@ export async function cancelAppointmentUtil(
266
229
  clinicId: string,
267
230
  practitionerId: string,
268
231
  patientId: string,
269
- eventId: string
232
+ eventId: string,
270
233
  ): Promise<CalendarEvent> {
271
- return updateAppointmentUtil(
272
- db,
273
- clinicId,
274
- practitionerId,
275
- patientId,
276
- eventId,
277
- { status: CalendarEventStatus.CANCELED }
278
- );
234
+ return updateAppointmentUtil(db, clinicId, practitionerId, patientId, eventId, {
235
+ status: CalendarEventStatus.CANCELED,
236
+ });
279
237
  }
280
238
 
281
239
  /**
@@ -294,21 +252,14 @@ export async function rescheduleAppointmentUtil(
294
252
  practitionerId: string,
295
253
  patientId: string,
296
254
  eventId: string,
297
- newEventTime: CalendarEventTime
255
+ newEventTime: CalendarEventTime,
298
256
  ): Promise<CalendarEvent> {
299
257
  // TODO: Add validation for new event time
300
258
  // - Validate event time (start < end)
301
259
  // - Check for overlapping events
302
260
 
303
- return updateAppointmentUtil(
304
- db,
305
- clinicId,
306
- practitionerId,
307
- patientId,
308
- eventId,
309
- {
310
- status: CalendarEventStatus.RESCHEDULED,
311
- eventTime: newEventTime,
312
- }
313
- );
261
+ return updateAppointmentUtil(db, clinicId, practitionerId, patientId, eventId, {
262
+ status: CalendarEventStatus.RESCHEDULED,
263
+ eventTime: newEventTime,
264
+ });
314
265
  }
@@ -426,6 +426,12 @@ export class ClinicService extends BaseService {
426
426
  );
427
427
  }
428
428
 
429
+ /**
430
+ * Get clinics based on multiple filtering criteria
431
+ *
432
+ * @param filters - Various filters to apply
433
+ * @returns Filtered clinics and the last document for pagination
434
+ */
429
435
  async getClinicsByFilters(filters: {
430
436
  center?: { latitude: number; longitude: number };
431
437
  radiusInKm?: number;
@@ -95,7 +95,7 @@ export async function getClinicsByFilters(
95
95
  }
96
96
 
97
97
  // Add ordering to make pagination consistent
98
- constraints.push(orderBy(documentId()));
98
+ constraints.push(orderBy("location.geohash"));
99
99
 
100
100
  let clinicsResult: (Clinic & { distance?: number })[] = [];
101
101
  let lastVisibleDoc = null;
@@ -227,6 +227,32 @@ export async function getClinicsByFilters(
227
227
  // Apply filters that couldn't be applied in the query
228
228
  let filteredClinics = clinics;
229
229
 
230
+ // Calculate distance for each clinic if center coordinates are provided
231
+ if (filters.center) {
232
+ const center = filters.center;
233
+ const clinicsWithDistance: (Clinic & { distance: number })[] = [];
234
+
235
+ filteredClinics.forEach((clinic) => {
236
+ const distance = distanceBetween(
237
+ [center.latitude, center.longitude],
238
+ [clinic.location.latitude, clinic.location.longitude]
239
+ );
240
+
241
+ clinicsWithDistance.push({
242
+ ...clinic,
243
+ distance: distance / 1000, // Convert to kilometers
244
+ });
245
+ });
246
+
247
+ // Replace filtered clinics with the version that includes distances
248
+ filteredClinics = clinicsWithDistance;
249
+
250
+ // Sort by distance - use type assertion to fix type error
251
+ (filteredClinics as (Clinic & { distance: number })[]).sort(
252
+ (a, b) => a.distance - b.distance
253
+ );
254
+ }
255
+
230
256
  // Filter by multiple tags if more than one tag was specified
231
257
  if (filters.tags && filters.tags.length > 1) {
232
258
  filteredClinics = filteredClinics.filter((clinic) => {
@@ -612,7 +612,7 @@ export class ProcedureService extends BaseService {
612
612
  }
613
613
 
614
614
  // Add ordering to make pagination consistent
615
- constraints.push(orderBy(documentId()));
615
+ constraints.push(orderBy("clinicInfo.location.geohash"));
616
616
 
617
617
  // Add pagination if specified
618
618
  if (filters.pagination && filters.pagination > 0 && filters.lastDoc) {
@@ -743,16 +743,54 @@ export class ProcedureService extends BaseService {
743
743
  return { ...doc.data(), id: doc.id } as Procedure;
744
744
  });
745
745
 
746
- // Apply filters that couldn't be applied in the query
747
- let filteredProcedures = this.applyInMemoryFilters(procedures, filters);
746
+ // Calculate distance for each procedure if location is provided
747
+ if (filters.location) {
748
+ const center = filters.location;
749
+ const proceduresWithDistance: (Procedure & { distance: number })[] =
750
+ [];
751
+
752
+ procedures.forEach((procedure) => {
753
+ const distance = distanceBetween(
754
+ [center.latitude, center.longitude],
755
+ [
756
+ procedure.clinicInfo.location.latitude,
757
+ procedure.clinicInfo.location.longitude,
758
+ ]
759
+ );
760
+
761
+ proceduresWithDistance.push({
762
+ ...procedure,
763
+ distance: distance / 1000, // Convert to kilometers
764
+ });
765
+ });
766
+
767
+ // Replace procedures with version that includes distances
768
+ let filteredProcedures = proceduresWithDistance;
769
+
770
+ // Apply in-memory filters
771
+ filteredProcedures = this.applyInMemoryFilters(
772
+ filteredProcedures,
773
+ filters
774
+ );
775
+
776
+ // Sort by distance
777
+ filteredProcedures.sort((a, b) => a.distance - b.distance);
778
+
779
+ proceduresResult = filteredProcedures;
780
+ } else {
781
+ // Apply filters that couldn't be applied in the query
782
+ let filteredProcedures = this.applyInMemoryFilters(
783
+ procedures,
784
+ filters
785
+ );
786
+ proceduresResult = filteredProcedures;
787
+ }
748
788
 
749
789
  // Set last document for pagination
750
790
  lastVisibleDoc =
751
791
  querySnapshot.docs.length > 0
752
792
  ? querySnapshot.docs[querySnapshot.docs.length - 1]
753
793
  : null;
754
-
755
- proceduresResult = filteredProcedures;
756
794
  }
757
795
 
758
796
  return {