@blackcode_sa/metaestetics-api 1.7.26 → 1.7.27
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 +206 -1
- package/dist/index.d.ts +206 -1
- package/dist/index.js +574 -349
- package/dist/index.mjs +481 -259
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/recommender/admin/index.ts +1 -0
- package/src/recommender/admin/services/recommender.service.admin.ts +5 -0
- package/src/recommender/front/index.ts +1 -0
- package/src/recommender/front/services/onboarding.service.ts +5 -0
- package/src/recommender/front/services/recommender.service.ts +3 -0
- package/src/recommender/index.ts +1 -0
- package/src/services/calendar/calendar.v3.service.ts +313 -0
- package/src/types/calendar/index.ts +29 -0
- package/src/validations/calendar.schema.ts +41 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ export {
|
|
|
31
31
|
FilledDocumentService,
|
|
32
32
|
} from "./services/documentation-templates";
|
|
33
33
|
export { CalendarServiceV2 } from "./services/calendar/calendar-refactored.service";
|
|
34
|
+
export { CalendarServiceV3 } from "./services/calendar/calendar.v3.service";
|
|
34
35
|
export { SyncedCalendarsService } from "./services/calendar/synced-calendars.service";
|
|
35
36
|
export { ReviewService } from "./services/reviews/reviews.service";
|
|
36
37
|
export { AppointmentService } from "./services/appointment/appointment.service";
|
|
@@ -237,6 +238,8 @@ export type {
|
|
|
237
238
|
SyncedCalendarEvent,
|
|
238
239
|
CreateAppointmentParams,
|
|
239
240
|
UpdateAppointmentParams,
|
|
241
|
+
CreateBlockingEventParams,
|
|
242
|
+
UpdateBlockingEventParams,
|
|
240
243
|
} from "./types/calendar";
|
|
241
244
|
|
|
242
245
|
export {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Cloud functions recommender index file
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Here we will add cloud functions for recommender system
|
|
2
|
+
|
|
3
|
+
// Here we will do the calculation logic and cloud logic for all the recommendation calculations
|
|
4
|
+
|
|
5
|
+
// This is a main file, but I want it to use different calculations that will be placed in UTILS folder, not to contain all the logic for easier maintenance and readability
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Frontend recommender index file
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// This service will be used for managing onboarding process for the patient, it will handle all data entry and retreive results
|
|
2
|
+
|
|
3
|
+
// This service will fill special fields and types that will be defined in types folder
|
|
4
|
+
|
|
5
|
+
// This service is not retreiving any recommendations in the UI and is only used by onboarding module (form and survey)
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// This is a frontend UI implementation of recommender service, it will use cloud functions for calculations (HTTP callable), but it will wrap logic for getting results for the frontend
|
|
2
|
+
|
|
3
|
+
// This service should not be heavy, it should fetch recommendations, maybe even handle like/dislike for the results (for further refining if we implement that), but no more than that
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Recommender module re-exportindex file
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { Auth } from "firebase/auth";
|
|
2
|
+
import { Firestore, Timestamp, serverTimestamp } from "firebase/firestore";
|
|
3
|
+
import { FirebaseApp } from "firebase/app";
|
|
4
|
+
import { BaseService } from "../base.service";
|
|
5
|
+
import {
|
|
6
|
+
CalendarEvent,
|
|
7
|
+
CalendarEventStatus,
|
|
8
|
+
CalendarEventTime,
|
|
9
|
+
CalendarEventType,
|
|
10
|
+
CalendarSyncStatus,
|
|
11
|
+
CreateCalendarEventData,
|
|
12
|
+
UpdateCalendarEventData,
|
|
13
|
+
CALENDAR_COLLECTION,
|
|
14
|
+
SearchCalendarEventsParams,
|
|
15
|
+
SearchLocationEnum,
|
|
16
|
+
DateRange,
|
|
17
|
+
CreateBlockingEventParams,
|
|
18
|
+
UpdateBlockingEventParams,
|
|
19
|
+
} from "../../types/calendar";
|
|
20
|
+
import { PRACTITIONERS_COLLECTION } from "../../types/practitioner";
|
|
21
|
+
import { CLINICS_COLLECTION } from "../../types/clinic";
|
|
22
|
+
import { doc, getDoc, setDoc, updateDoc, deleteDoc } from "firebase/firestore";
|
|
23
|
+
|
|
24
|
+
// Import utility functions
|
|
25
|
+
import { searchCalendarEventsUtil } from "./utils/calendar-event.utils";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Calendar Service V3
|
|
29
|
+
* Focused on blocking event management and calendar event search
|
|
30
|
+
* Appointment logic is handled by AppointmentService and BookingAdmin
|
|
31
|
+
* External calendar sync is handled by dedicated cloud functions
|
|
32
|
+
*/
|
|
33
|
+
export class CalendarServiceV3 extends BaseService {
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new CalendarServiceV3 instance
|
|
36
|
+
* @param db - Firestore instance
|
|
37
|
+
* @param auth - Firebase Auth instance
|
|
38
|
+
* @param app - Firebase App instance
|
|
39
|
+
*/
|
|
40
|
+
constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
|
|
41
|
+
super(db, auth, app);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// #region Blocking Event CRUD Operations
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates a blocking event for a practitioner or clinic
|
|
48
|
+
* @param params - Blocking event creation parameters
|
|
49
|
+
* @returns Created calendar event
|
|
50
|
+
*/
|
|
51
|
+
async createBlockingEvent(
|
|
52
|
+
params: CreateBlockingEventParams
|
|
53
|
+
): Promise<CalendarEvent> {
|
|
54
|
+
// Validate input parameters
|
|
55
|
+
this.validateBlockingEventParams(params);
|
|
56
|
+
|
|
57
|
+
// Generate a unique ID for the event
|
|
58
|
+
const eventId = this.generateId();
|
|
59
|
+
|
|
60
|
+
// Determine the collection path based on entity type
|
|
61
|
+
const collectionPath = this.getEntityCalendarPath(
|
|
62
|
+
params.entityType,
|
|
63
|
+
params.entityId
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Create the event document reference
|
|
67
|
+
const eventRef = doc(this.db, collectionPath, eventId);
|
|
68
|
+
|
|
69
|
+
// Prepare the event data
|
|
70
|
+
const eventData: CreateCalendarEventData = {
|
|
71
|
+
id: eventId,
|
|
72
|
+
eventName: params.eventName,
|
|
73
|
+
eventTime: params.eventTime,
|
|
74
|
+
eventType: params.eventType,
|
|
75
|
+
description: params.description || "",
|
|
76
|
+
status: CalendarEventStatus.CONFIRMED, // Blocking events are always confirmed
|
|
77
|
+
syncStatus: CalendarSyncStatus.INTERNAL,
|
|
78
|
+
createdAt: serverTimestamp(),
|
|
79
|
+
updatedAt: serverTimestamp(),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Set entity-specific fields
|
|
83
|
+
if (params.entityType === "practitioner") {
|
|
84
|
+
eventData.practitionerProfileId = params.entityId;
|
|
85
|
+
} else {
|
|
86
|
+
eventData.clinicBranchId = params.entityId;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Create the event
|
|
90
|
+
await setDoc(eventRef, eventData);
|
|
91
|
+
|
|
92
|
+
// Return the created event
|
|
93
|
+
return {
|
|
94
|
+
...eventData,
|
|
95
|
+
createdAt: Timestamp.now(),
|
|
96
|
+
updatedAt: Timestamp.now(),
|
|
97
|
+
} as CalendarEvent;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Updates a blocking event
|
|
102
|
+
* @param params - Blocking event update parameters
|
|
103
|
+
* @returns Updated calendar event
|
|
104
|
+
*/
|
|
105
|
+
async updateBlockingEvent(
|
|
106
|
+
params: UpdateBlockingEventParams
|
|
107
|
+
): Promise<CalendarEvent> {
|
|
108
|
+
// Determine the collection path
|
|
109
|
+
const collectionPath = this.getEntityCalendarPath(
|
|
110
|
+
params.entityType,
|
|
111
|
+
params.entityId
|
|
112
|
+
);
|
|
113
|
+
const eventRef = doc(this.db, collectionPath, params.eventId);
|
|
114
|
+
|
|
115
|
+
// Check if event exists
|
|
116
|
+
const eventDoc = await getDoc(eventRef);
|
|
117
|
+
if (!eventDoc.exists()) {
|
|
118
|
+
throw new Error(`Blocking event with ID ${params.eventId} not found`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Prepare update data
|
|
122
|
+
const updateData: Partial<UpdateCalendarEventData> = {
|
|
123
|
+
updatedAt: serverTimestamp(),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
if (params.eventName !== undefined) {
|
|
127
|
+
updateData.eventName = params.eventName;
|
|
128
|
+
}
|
|
129
|
+
if (params.eventTime !== undefined) {
|
|
130
|
+
updateData.eventTime = params.eventTime;
|
|
131
|
+
}
|
|
132
|
+
if (params.description !== undefined) {
|
|
133
|
+
updateData.description = params.description;
|
|
134
|
+
}
|
|
135
|
+
if (params.status !== undefined) {
|
|
136
|
+
updateData.status = params.status;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Update the event
|
|
140
|
+
await updateDoc(eventRef, updateData);
|
|
141
|
+
|
|
142
|
+
// Get and return the updated event
|
|
143
|
+
const updatedEventDoc = await getDoc(eventRef);
|
|
144
|
+
return updatedEventDoc.data() as CalendarEvent;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Deletes a blocking event
|
|
149
|
+
* @param entityType - Type of entity (practitioner or clinic)
|
|
150
|
+
* @param entityId - ID of the entity
|
|
151
|
+
* @param eventId - ID of the event to delete
|
|
152
|
+
*/
|
|
153
|
+
async deleteBlockingEvent(
|
|
154
|
+
entityType: "practitioner" | "clinic",
|
|
155
|
+
entityId: string,
|
|
156
|
+
eventId: string
|
|
157
|
+
): Promise<void> {
|
|
158
|
+
// Determine the collection path
|
|
159
|
+
const collectionPath = this.getEntityCalendarPath(entityType, entityId);
|
|
160
|
+
const eventRef = doc(this.db, collectionPath, eventId);
|
|
161
|
+
|
|
162
|
+
// Check if event exists
|
|
163
|
+
const eventDoc = await getDoc(eventRef);
|
|
164
|
+
if (!eventDoc.exists()) {
|
|
165
|
+
throw new Error(`Blocking event with ID ${eventId} not found`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Delete the event
|
|
169
|
+
await deleteDoc(eventRef);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Gets a specific blocking event
|
|
174
|
+
* @param entityType - Type of entity (practitioner or clinic)
|
|
175
|
+
* @param entityId - ID of the entity
|
|
176
|
+
* @param eventId - ID of the event to retrieve
|
|
177
|
+
* @returns Calendar event or null if not found
|
|
178
|
+
*/
|
|
179
|
+
async getBlockingEvent(
|
|
180
|
+
entityType: "practitioner" | "clinic",
|
|
181
|
+
entityId: string,
|
|
182
|
+
eventId: string
|
|
183
|
+
): Promise<CalendarEvent | null> {
|
|
184
|
+
// Determine the collection path
|
|
185
|
+
const collectionPath = this.getEntityCalendarPath(entityType, entityId);
|
|
186
|
+
const eventRef = doc(this.db, collectionPath, eventId);
|
|
187
|
+
|
|
188
|
+
// Get the event
|
|
189
|
+
const eventDoc = await getDoc(eventRef);
|
|
190
|
+
if (!eventDoc.exists()) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return eventDoc.data() as CalendarEvent;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Gets blocking events for a specific entity
|
|
199
|
+
* @param entityType - Type of entity (practitioner or clinic)
|
|
200
|
+
* @param entityId - ID of the entity
|
|
201
|
+
* @param dateRange - Optional date range filter
|
|
202
|
+
* @param eventType - Optional event type filter
|
|
203
|
+
* @returns Array of calendar events
|
|
204
|
+
*/
|
|
205
|
+
async getEntityBlockingEvents(
|
|
206
|
+
entityType: "practitioner" | "clinic",
|
|
207
|
+
entityId: string,
|
|
208
|
+
dateRange?: DateRange,
|
|
209
|
+
eventType?: CalendarEventType
|
|
210
|
+
): Promise<CalendarEvent[]> {
|
|
211
|
+
// Use the existing search functionality
|
|
212
|
+
const searchParams: SearchCalendarEventsParams = {
|
|
213
|
+
searchLocation:
|
|
214
|
+
entityType === "practitioner"
|
|
215
|
+
? SearchLocationEnum.PRACTITIONER
|
|
216
|
+
: SearchLocationEnum.CLINIC,
|
|
217
|
+
entityId,
|
|
218
|
+
dateRange,
|
|
219
|
+
eventType,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Filter to only blocking-type events if no specific eventType is provided
|
|
223
|
+
if (!eventType) {
|
|
224
|
+
// We'll need to filter the results to only include blocking-type events
|
|
225
|
+
const allEvents = await searchCalendarEventsUtil(this.db, searchParams);
|
|
226
|
+
return allEvents.filter(
|
|
227
|
+
(event) =>
|
|
228
|
+
event.eventType === CalendarEventType.BLOCKING ||
|
|
229
|
+
event.eventType === CalendarEventType.BREAK ||
|
|
230
|
+
event.eventType === CalendarEventType.FREE_DAY ||
|
|
231
|
+
event.eventType === CalendarEventType.OTHER
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return searchCalendarEventsUtil(this.db, searchParams);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// #endregion
|
|
239
|
+
|
|
240
|
+
// #region Calendar Event Search
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Searches for calendar events based on specified criteria.
|
|
244
|
+
* This method supports searching for ALL event types (appointments, blocking events, etc.)
|
|
245
|
+
*
|
|
246
|
+
* @param params - The search parameters
|
|
247
|
+
* @returns A promise that resolves to an array of matching calendar events
|
|
248
|
+
*/
|
|
249
|
+
async searchCalendarEvents(
|
|
250
|
+
params: SearchCalendarEventsParams
|
|
251
|
+
): Promise<CalendarEvent[]> {
|
|
252
|
+
// Use the utility function to perform the search
|
|
253
|
+
return searchCalendarEventsUtil(this.db, params);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// #endregion
|
|
257
|
+
|
|
258
|
+
// #region Private Helper Methods
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Gets the calendar collection path for a specific entity
|
|
262
|
+
* @param entityType - Type of entity (practitioner or clinic)
|
|
263
|
+
* @param entityId - ID of the entity
|
|
264
|
+
* @returns Collection path string
|
|
265
|
+
*/
|
|
266
|
+
private getEntityCalendarPath(
|
|
267
|
+
entityType: "practitioner" | "clinic",
|
|
268
|
+
entityId: string
|
|
269
|
+
): string {
|
|
270
|
+
if (entityType === "practitioner") {
|
|
271
|
+
return `${PRACTITIONERS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
|
|
272
|
+
} else {
|
|
273
|
+
return `${CLINICS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Validates blocking event creation parameters
|
|
279
|
+
* @param params - Parameters to validate
|
|
280
|
+
* @throws Error if validation fails
|
|
281
|
+
*/
|
|
282
|
+
private validateBlockingEventParams(params: CreateBlockingEventParams): void {
|
|
283
|
+
if (!params.entityType || !params.entityId) {
|
|
284
|
+
throw new Error("Entity type and ID are required");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!params.eventName || params.eventName.trim() === "") {
|
|
288
|
+
throw new Error("Event name is required");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!params.eventTime || !params.eventTime.start || !params.eventTime.end) {
|
|
292
|
+
throw new Error("Event time with start and end is required");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Validate that end time is after start time
|
|
296
|
+
if (params.eventTime.end.toMillis() <= params.eventTime.start.toMillis()) {
|
|
297
|
+
throw new Error("Event end time must be after start time");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Validate event type is one of the blocking types
|
|
301
|
+
const validTypes = [
|
|
302
|
+
CalendarEventType.BLOCKING,
|
|
303
|
+
CalendarEventType.BREAK,
|
|
304
|
+
CalendarEventType.FREE_DAY,
|
|
305
|
+
CalendarEventType.OTHER,
|
|
306
|
+
];
|
|
307
|
+
if (!validTypes.includes(params.eventType)) {
|
|
308
|
+
throw new Error("Invalid event type for blocking events");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// #endregion
|
|
313
|
+
}
|
|
@@ -227,3 +227,32 @@ export interface SearchCalendarEventsParams {
|
|
|
227
227
|
/** Optional filter for event type. */
|
|
228
228
|
eventType?: CalendarEventType;
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Interface for creating blocking events
|
|
233
|
+
*/
|
|
234
|
+
export interface CreateBlockingEventParams {
|
|
235
|
+
entityType: "practitioner" | "clinic";
|
|
236
|
+
entityId: string;
|
|
237
|
+
eventName: string;
|
|
238
|
+
eventTime: CalendarEventTime;
|
|
239
|
+
eventType:
|
|
240
|
+
| CalendarEventType.BLOCKING
|
|
241
|
+
| CalendarEventType.BREAK
|
|
242
|
+
| CalendarEventType.FREE_DAY
|
|
243
|
+
| CalendarEventType.OTHER;
|
|
244
|
+
description?: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Interface for updating blocking events
|
|
249
|
+
*/
|
|
250
|
+
export interface UpdateBlockingEventParams {
|
|
251
|
+
entityType: "practitioner" | "clinic";
|
|
252
|
+
entityId: string;
|
|
253
|
+
eventId: string;
|
|
254
|
+
eventName?: string;
|
|
255
|
+
eventTime?: CalendarEventTime;
|
|
256
|
+
description?: string;
|
|
257
|
+
status?: CalendarEventStatus;
|
|
258
|
+
}
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
CalendarEventStatus,
|
|
5
5
|
CalendarEventType,
|
|
6
6
|
CalendarSyncStatus,
|
|
7
|
+
CreateBlockingEventParams,
|
|
8
|
+
UpdateBlockingEventParams,
|
|
7
9
|
} from "../types/calendar";
|
|
8
10
|
import { SyncedCalendarProvider } from "../types/calendar/synced-calendar.types";
|
|
9
11
|
import {
|
|
@@ -221,3 +223,42 @@ export const calendarEventSchema = z.object({
|
|
|
221
223
|
createdAt: z.instanceof(Date).or(z.instanceof(Timestamp)),
|
|
222
224
|
updatedAt: z.instanceof(Date).or(z.instanceof(Timestamp)),
|
|
223
225
|
});
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Validation schema for creating blocking events
|
|
229
|
+
* Based on CreateBlockingEventParams interface
|
|
230
|
+
*/
|
|
231
|
+
export const createBlockingEventSchema = z.object({
|
|
232
|
+
entityType: z.enum(["practitioner", "clinic"]),
|
|
233
|
+
entityId: z.string().min(1, "Entity ID is required"),
|
|
234
|
+
eventName: z
|
|
235
|
+
.string()
|
|
236
|
+
.min(1, "Event name is required")
|
|
237
|
+
.max(200, "Event name too long"),
|
|
238
|
+
eventTime: calendarEventTimeSchema,
|
|
239
|
+
eventType: z.enum([
|
|
240
|
+
CalendarEventType.BLOCKING,
|
|
241
|
+
CalendarEventType.BREAK,
|
|
242
|
+
CalendarEventType.FREE_DAY,
|
|
243
|
+
CalendarEventType.OTHER,
|
|
244
|
+
]),
|
|
245
|
+
description: z.string().max(1000, "Description too long").optional(),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Validation schema for updating blocking events
|
|
250
|
+
* Based on UpdateBlockingEventParams interface
|
|
251
|
+
*/
|
|
252
|
+
export const updateBlockingEventSchema = z.object({
|
|
253
|
+
entityType: z.enum(["practitioner", "clinic"]),
|
|
254
|
+
entityId: z.string().min(1, "Entity ID is required"),
|
|
255
|
+
eventId: z.string().min(1, "Event ID is required"),
|
|
256
|
+
eventName: z
|
|
257
|
+
.string()
|
|
258
|
+
.min(1, "Event name is required")
|
|
259
|
+
.max(200, "Event name too long")
|
|
260
|
+
.optional(),
|
|
261
|
+
eventTime: calendarEventTimeSchema.optional(),
|
|
262
|
+
description: z.string().max(1000, "Description too long").optional(),
|
|
263
|
+
status: z.nativeEnum(CalendarEventStatus).optional(),
|
|
264
|
+
});
|