@blackcode_sa/metaestetics-api 1.13.5 → 1.13.6
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 +20 -1
- package/dist/admin/index.d.ts +20 -1
- package/dist/admin/index.js +217 -1
- package/dist/admin/index.mjs +217 -1
- package/package.json +121 -121
- package/src/__mocks__/firstore.ts +10 -10
- package/src/admin/aggregation/README.md +79 -79
- package/src/admin/aggregation/appointment/README.md +128 -128
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +1984 -1984
- package/src/admin/aggregation/appointment/index.ts +1 -1
- package/src/admin/aggregation/clinic/README.md +52 -52
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +966 -703
- package/src/admin/aggregation/clinic/index.ts +1 -1
- package/src/admin/aggregation/forms/README.md +13 -13
- package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
- package/src/admin/aggregation/forms/index.ts +1 -1
- package/src/admin/aggregation/index.ts +8 -8
- package/src/admin/aggregation/patient/README.md +27 -27
- package/src/admin/aggregation/patient/index.ts +1 -1
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
- package/src/admin/aggregation/practitioner/README.md +42 -42
- package/src/admin/aggregation/practitioner/index.ts +1 -1
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
- package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
- package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
- package/src/admin/aggregation/procedure/README.md +43 -43
- package/src/admin/aggregation/procedure/index.ts +1 -1
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
- package/src/admin/aggregation/reviews/index.ts +1 -1
- package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +689 -689
- package/src/admin/analytics/analytics.admin.service.ts +278 -278
- package/src/admin/analytics/index.ts +2 -2
- package/src/admin/booking/README.md +125 -125
- package/src/admin/booking/booking.admin.ts +1037 -1037
- package/src/admin/booking/booking.calculator.ts +712 -712
- package/src/admin/booking/booking.types.ts +59 -59
- package/src/admin/booking/index.ts +3 -3
- package/src/admin/booking/timezones-problem.md +185 -185
- package/src/admin/calendar/README.md +7 -7
- package/src/admin/calendar/calendar.admin.service.ts +345 -345
- package/src/admin/calendar/index.ts +1 -1
- package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
- package/src/admin/documentation-templates/index.ts +1 -1
- package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
- package/src/admin/free-consultation/index.ts +1 -1
- package/src/admin/index.ts +81 -81
- package/src/admin/logger/index.ts +78 -78
- package/src/admin/mailing/README.md +95 -95
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
- package/src/admin/mailing/appointment/index.ts +1 -1
- package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
- package/src/admin/mailing/base.mailing.service.ts +208 -208
- package/src/admin/mailing/index.ts +3 -3
- package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
- package/src/admin/mailing/practitionerInvite/index.ts +2 -2
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
- package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
- package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
- package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
- package/src/admin/notifications/index.ts +1 -1
- package/src/admin/notifications/notifications.admin.ts +710 -710
- package/src/admin/requirements/README.md +128 -128
- package/src/admin/requirements/index.ts +1 -1
- package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
- package/src/admin/users/index.ts +1 -1
- package/src/admin/users/user-profile.admin.ts +405 -405
- package/src/backoffice/constants/certification.constants.ts +13 -13
- package/src/backoffice/constants/index.ts +1 -1
- package/src/backoffice/errors/backoffice.errors.ts +181 -181
- package/src/backoffice/errors/index.ts +1 -1
- package/src/backoffice/expo-safe/README.md +26 -26
- package/src/backoffice/expo-safe/index.ts +41 -41
- package/src/backoffice/index.ts +5 -5
- package/src/backoffice/services/FIXES_README.md +102 -102
- package/src/backoffice/services/README.md +57 -57
- package/src/backoffice/services/analytics.service.proposal.md +863 -863
- package/src/backoffice/services/analytics.service.summary.md +143 -143
- package/src/backoffice/services/brand.service.ts +256 -256
- package/src/backoffice/services/category.service.ts +384 -384
- package/src/backoffice/services/constants.service.ts +385 -385
- package/src/backoffice/services/documentation-template.service.ts +202 -202
- package/src/backoffice/services/index.ts +10 -10
- package/src/backoffice/services/migrate-products.ts +116 -116
- package/src/backoffice/services/product.service.ts +553 -553
- package/src/backoffice/services/requirement.service.ts +235 -235
- package/src/backoffice/services/subcategory.service.ts +461 -461
- package/src/backoffice/services/technology.service.ts +1151 -1151
- package/src/backoffice/types/README.md +12 -12
- package/src/backoffice/types/admin-constants.types.ts +69 -69
- package/src/backoffice/types/brand.types.ts +29 -29
- package/src/backoffice/types/category.types.ts +67 -67
- package/src/backoffice/types/documentation-templates.types.ts +28 -28
- package/src/backoffice/types/index.ts +10 -10
- package/src/backoffice/types/procedure-product.types.ts +38 -38
- package/src/backoffice/types/product.types.ts +240 -240
- package/src/backoffice/types/requirement.types.ts +63 -63
- package/src/backoffice/types/static/README.md +18 -18
- package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
- package/src/backoffice/types/static/certification.types.ts +37 -37
- package/src/backoffice/types/static/contraindication.types.ts +19 -19
- package/src/backoffice/types/static/index.ts +6 -6
- package/src/backoffice/types/static/pricing.types.ts +16 -16
- package/src/backoffice/types/static/procedure-family.types.ts +14 -14
- package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
- package/src/backoffice/types/subcategory.types.ts +34 -34
- package/src/backoffice/types/technology.types.ts +168 -168
- package/src/backoffice/validations/index.ts +1 -1
- package/src/backoffice/validations/schemas.ts +164 -164
- package/src/config/__mocks__/firebase.ts +99 -99
- package/src/config/firebase.ts +78 -78
- package/src/config/index.ts +9 -9
- package/src/errors/auth.error.ts +6 -6
- package/src/errors/auth.errors.ts +200 -200
- package/src/errors/clinic.errors.ts +32 -32
- package/src/errors/firebase.errors.ts +47 -47
- package/src/errors/user.errors.ts +99 -99
- package/src/index.backup.ts +407 -407
- package/src/index.ts +6 -6
- package/src/locales/en.ts +31 -31
- package/src/recommender/admin/index.ts +1 -1
- package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
- package/src/recommender/front/index.ts +1 -1
- package/src/recommender/front/services/onboarding.service.ts +5 -5
- package/src/recommender/front/services/recommender.service.ts +3 -3
- package/src/recommender/index.ts +1 -1
- package/src/services/PATIENTAUTH.MD +197 -197
- package/src/services/README.md +106 -106
- package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
- package/src/services/__tests__/auth/auth.setup.ts +293 -293
- package/src/services/__tests__/auth.service.test.ts +346 -346
- package/src/services/__tests__/base.service.test.ts +77 -77
- package/src/services/__tests__/user.service.test.ts +528 -528
- package/src/services/analytics/ARCHITECTURE.md +199 -199
- package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -225
- package/src/services/analytics/GROUPED_ANALYTICS.md +501 -501
- package/src/services/analytics/QUICK_START.md +393 -393
- package/src/services/analytics/README.md +304 -304
- package/src/services/analytics/SUMMARY.md +141 -141
- package/src/services/analytics/TRENDS.md +380 -380
- package/src/services/analytics/USAGE_GUIDE.md +518 -518
- package/src/services/analytics/analytics-cloud.service.ts +222 -222
- package/src/services/analytics/analytics.service.ts +2142 -2142
- package/src/services/analytics/index.ts +4 -4
- package/src/services/analytics/review-analytics.service.ts +941 -941
- package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -138
- package/src/services/analytics/utils/cost-calculation.utils.ts +182 -182
- package/src/services/analytics/utils/grouping.utils.ts +434 -434
- package/src/services/analytics/utils/stored-analytics.utils.ts +347 -347
- package/src/services/analytics/utils/time-calculation.utils.ts +186 -186
- package/src/services/analytics/utils/trend-calculation.utils.ts +200 -200
- package/src/services/appointment/README.md +17 -17
- package/src/services/appointment/appointment.service.ts +2558 -2558
- package/src/services/appointment/index.ts +1 -1
- package/src/services/appointment/utils/appointment.utils.ts +552 -552
- package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
- package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
- package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
- package/src/services/appointment/utils/zone-management.utils.ts +353 -353
- package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
- package/src/services/auth/auth.service.ts +989 -989
- package/src/services/auth/auth.v2.service.ts +961 -961
- package/src/services/auth/index.ts +7 -7
- package/src/services/auth/utils/error.utils.ts +90 -90
- package/src/services/auth/utils/firebase.utils.ts +49 -49
- package/src/services/auth/utils/index.ts +21 -21
- package/src/services/auth/utils/practitioner.utils.ts +125 -125
- package/src/services/base.service.ts +41 -41
- package/src/services/calendar/calendar.service.ts +1077 -1077
- package/src/services/calendar/calendar.v2.service.ts +1683 -1683
- package/src/services/calendar/calendar.v3.service.ts +313 -313
- package/src/services/calendar/externalCalendar.service.ts +178 -178
- package/src/services/calendar/index.ts +5 -5
- package/src/services/calendar/synced-calendars.service.ts +743 -743
- package/src/services/calendar/utils/appointment.utils.ts +265 -265
- package/src/services/calendar/utils/calendar-event.utils.ts +646 -646
- package/src/services/calendar/utils/clinic.utils.ts +237 -237
- package/src/services/calendar/utils/docs.utils.ts +157 -157
- package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
- package/src/services/calendar/utils/index.ts +8 -8
- package/src/services/calendar/utils/patient.utils.ts +198 -198
- package/src/services/calendar/utils/practitioner.utils.ts +221 -221
- package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
- package/src/services/clinic/README.md +204 -204
- package/src/services/clinic/__tests__/clinic-admin.service.test.ts +287 -287
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
- package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
- package/src/services/clinic/billing-transactions.service.ts +217 -217
- package/src/services/clinic/clinic-admin.service.ts +202 -202
- package/src/services/clinic/clinic-group.service.ts +310 -310
- package/src/services/clinic/clinic.service.ts +708 -708
- package/src/services/clinic/index.ts +5 -5
- package/src/services/clinic/practitioner-invite.service.ts +519 -519
- package/src/services/clinic/utils/admin.utils.ts +551 -551
- package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
- package/src/services/clinic/utils/clinic.utils.ts +949 -949
- package/src/services/clinic/utils/filter.utils.d.ts +23 -23
- package/src/services/clinic/utils/filter.utils.ts +446 -446
- package/src/services/clinic/utils/index.ts +11 -11
- package/src/services/clinic/utils/photos.utils.ts +188 -188
- package/src/services/clinic/utils/search.utils.ts +84 -84
- package/src/services/clinic/utils/tag.utils.ts +124 -124
- package/src/services/documentation-templates/documentation-template.service.ts +537 -537
- package/src/services/documentation-templates/filled-document.service.ts +587 -587
- package/src/services/documentation-templates/index.ts +2 -2
- package/src/services/index.ts +14 -14
- package/src/services/media/index.ts +1 -1
- package/src/services/media/media.service.ts +418 -418
- package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
- package/src/services/notifications/index.ts +1 -1
- package/src/services/notifications/notification.service.ts +215 -215
- package/src/services/patient/README.md +48 -48
- package/src/services/patient/To-Do.md +43 -43
- package/src/services/patient/__tests__/patient.service.test.ts +294 -294
- package/src/services/patient/index.ts +2 -2
- package/src/services/patient/patient.service.ts +883 -883
- package/src/services/patient/patientRequirements.service.ts +285 -285
- package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
- package/src/services/patient/utils/clinic.utils.ts +80 -80
- package/src/services/patient/utils/docs.utils.ts +142 -142
- package/src/services/patient/utils/index.ts +9 -9
- package/src/services/patient/utils/location.utils.ts +126 -126
- package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
- package/src/services/patient/utils/medical.utils.ts +458 -458
- package/src/services/patient/utils/practitioner.utils.ts +260 -260
- package/src/services/patient/utils/profile.utils.ts +510 -510
- package/src/services/patient/utils/sensitive.utils.ts +260 -260
- package/src/services/patient/utils/token.utils.ts +211 -211
- package/src/services/practitioner/README.md +145 -145
- package/src/services/practitioner/index.ts +1 -1
- package/src/services/practitioner/practitioner.service.ts +1742 -1742
- package/src/services/procedure/README.md +163 -163
- package/src/services/procedure/index.ts +1 -1
- package/src/services/procedure/procedure.service.ts +2200 -2200
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +734 -734
- package/src/services/user/index.ts +1 -1
- package/src/services/user/user.service.ts +489 -489
- package/src/services/user/user.v2.service.ts +466 -466
- package/src/types/analytics/analytics.types.ts +597 -597
- package/src/types/analytics/grouped-analytics.types.ts +173 -173
- package/src/types/analytics/index.ts +4 -4
- package/src/types/analytics/stored-analytics.types.ts +137 -137
- package/src/types/appointment/index.ts +480 -480
- package/src/types/calendar/index.ts +258 -258
- package/src/types/calendar/synced-calendar.types.ts +66 -66
- package/src/types/clinic/index.ts +498 -498
- package/src/types/clinic/practitioner-invite.types.ts +91 -91
- package/src/types/clinic/preferences.types.ts +159 -159
- package/src/types/clinic/to-do +3 -3
- package/src/types/documentation-templates/index.ts +308 -308
- package/src/types/index.ts +47 -47
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +286 -286
- package/src/types/patient/aesthetic-analysis.types.ts +66 -66
- package/src/types/patient/allergies.ts +58 -58
- package/src/types/patient/index.ts +275 -275
- package/src/types/patient/medical-info.types.ts +152 -152
- package/src/types/patient/patient-requirements.ts +92 -92
- package/src/types/patient/token.types.ts +61 -61
- package/src/types/practitioner/index.ts +206 -206
- package/src/types/procedure/index.ts +181 -181
- package/src/types/profile/index.ts +39 -39
- package/src/types/reviews/index.ts +132 -132
- package/src/types/tz-lookup.d.ts +4 -4
- package/src/types/user/index.ts +38 -38
- package/src/utils/TIMESTAMPS.md +176 -176
- package/src/utils/TimestampUtils.ts +241 -241
- package/src/utils/index.ts +1 -1
- package/src/validations/appointment.schema.ts +574 -574
- package/src/validations/calendar.schema.ts +225 -225
- package/src/validations/clinic.schema.ts +494 -494
- package/src/validations/common.schema.ts +25 -25
- package/src/validations/documentation-templates/index.ts +1 -1
- package/src/validations/documentation-templates/template.schema.ts +220 -220
- package/src/validations/documentation-templates.schema.ts +10 -10
- package/src/validations/index.ts +20 -20
- package/src/validations/media.schema.ts +10 -10
- package/src/validations/notification.schema.ts +90 -90
- package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
- package/src/validations/patient/medical-info.schema.ts +125 -125
- package/src/validations/patient/patient-requirements.schema.ts +84 -84
- package/src/validations/patient/token.schema.ts +29 -29
- package/src/validations/patient.schema.ts +217 -217
- package/src/validations/practitioner.schema.ts +222 -222
- package/src/validations/procedure-product.schema.ts +41 -41
- package/src/validations/procedure.schema.ts +124 -124
- package/src/validations/profile-info.schema.ts +41 -41
- package/src/validations/reviews.schema.ts +195 -195
- package/src/validations/schemas.ts +104 -104
- package/src/validations/shared.schema.ts +78 -78
|
@@ -1,537 +1,537 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collection,
|
|
3
|
-
doc,
|
|
4
|
-
getDoc,
|
|
5
|
-
getDocs,
|
|
6
|
-
setDoc,
|
|
7
|
-
updateDoc,
|
|
8
|
-
deleteDoc,
|
|
9
|
-
query,
|
|
10
|
-
where,
|
|
11
|
-
orderBy,
|
|
12
|
-
limit,
|
|
13
|
-
startAfter,
|
|
14
|
-
DocumentSnapshot,
|
|
15
|
-
QueryDocumentSnapshot,
|
|
16
|
-
serverTimestamp,
|
|
17
|
-
Timestamp,
|
|
18
|
-
QueryConstraint,
|
|
19
|
-
} from "firebase/firestore";
|
|
20
|
-
import { BaseService } from "../base.service";
|
|
21
|
-
import {
|
|
22
|
-
CreateDocumentTemplateData,
|
|
23
|
-
DocumentElement,
|
|
24
|
-
DocumentTemplate,
|
|
25
|
-
UpdateDocumentTemplateData,
|
|
26
|
-
} from "../../types";
|
|
27
|
-
import { DOCUMENTATION_TEMPLATES_COLLECTION } from "../../types";
|
|
28
|
-
import {
|
|
29
|
-
createDocumentTemplateSchema,
|
|
30
|
-
updateDocumentTemplateSchema,
|
|
31
|
-
} from "../../validations/documentation-templates.schema";
|
|
32
|
-
import { getCountFromServer } from "firebase/firestore";
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Service for managing documentation templates
|
|
36
|
-
*/
|
|
37
|
-
export class DocumentationTemplateService extends BaseService {
|
|
38
|
-
private readonly collectionRef = collection(
|
|
39
|
-
this.db,
|
|
40
|
-
DOCUMENTATION_TEMPLATES_COLLECTION
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
constructor(...args: ConstructorParameters<typeof BaseService>) {
|
|
44
|
-
super(...args);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Create a new document template
|
|
49
|
-
* @param data - Template data
|
|
50
|
-
* @param userId - ID of the user creating the template
|
|
51
|
-
* @returns The created template
|
|
52
|
-
*/
|
|
53
|
-
async createTemplate(
|
|
54
|
-
data: CreateDocumentTemplateData,
|
|
55
|
-
userId: string
|
|
56
|
-
): Promise<DocumentTemplate> {
|
|
57
|
-
// Validate data
|
|
58
|
-
const validatedData = createDocumentTemplateSchema.parse(data);
|
|
59
|
-
|
|
60
|
-
// Generate ID for the template
|
|
61
|
-
const templateId = this.generateId();
|
|
62
|
-
|
|
63
|
-
// Generate IDs for elements
|
|
64
|
-
const elementsWithIds = validatedData.elements.map((element) => ({
|
|
65
|
-
...element,
|
|
66
|
-
id: this.generateId(),
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
|
-
// Create template object
|
|
70
|
-
const now = Date.now();
|
|
71
|
-
const template: DocumentTemplate = {
|
|
72
|
-
id: templateId,
|
|
73
|
-
title: validatedData.title,
|
|
74
|
-
description: validatedData.description,
|
|
75
|
-
elements: elementsWithIds as DocumentElement[],
|
|
76
|
-
createdAt: now,
|
|
77
|
-
updatedAt: now,
|
|
78
|
-
createdBy: userId,
|
|
79
|
-
version: 1,
|
|
80
|
-
isActive: true,
|
|
81
|
-
tags: validatedData.tags || [],
|
|
82
|
-
isUserForm: validatedData.isUserForm || false,
|
|
83
|
-
isRequired: validatedData.isRequired || false,
|
|
84
|
-
sortingOrder: validatedData.sortingOrder || 0,
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// Save to Firestore
|
|
88
|
-
const docRef = doc(this.collectionRef, templateId);
|
|
89
|
-
await setDoc(docRef, template);
|
|
90
|
-
|
|
91
|
-
return template;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Get a document template by ID
|
|
96
|
-
* @param templateId - ID of the template to retrieve
|
|
97
|
-
* @param version - Optional version number to retrieve (defaults to latest version)
|
|
98
|
-
* @returns The template or null if not found
|
|
99
|
-
*/
|
|
100
|
-
async getTemplateById(
|
|
101
|
-
templateId: string,
|
|
102
|
-
version?: number
|
|
103
|
-
): Promise<DocumentTemplate | null> {
|
|
104
|
-
// First, check if the template exists at all
|
|
105
|
-
const docRef = doc(this.collectionRef, templateId);
|
|
106
|
-
const docSnap = await getDoc(docRef);
|
|
107
|
-
|
|
108
|
-
if (!docSnap.exists()) {
|
|
109
|
-
return null; // Template doesn't exist
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const currentTemplate = docSnap.data() as DocumentTemplate;
|
|
113
|
-
|
|
114
|
-
// If no specific version is requested, simply return the current template
|
|
115
|
-
if (version === undefined) {
|
|
116
|
-
return currentTemplate;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// If the requested version matches the current version, return it
|
|
120
|
-
if (currentTemplate.version === version) {
|
|
121
|
-
return currentTemplate;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Otherwise, try to find the requested version in the versions subcollection
|
|
125
|
-
try {
|
|
126
|
-
const versionTemplate = await this.getTemplateVersion(
|
|
127
|
-
templateId,
|
|
128
|
-
version
|
|
129
|
-
);
|
|
130
|
-
if (versionTemplate) {
|
|
131
|
-
return versionTemplate;
|
|
132
|
-
}
|
|
133
|
-
} catch (error) {
|
|
134
|
-
console.error(`Error getting template version ${version}:`, error);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// If we get here, the requested version doesn't exist
|
|
138
|
-
// Option 1: Return null (strict approach)
|
|
139
|
-
return null;
|
|
140
|
-
|
|
141
|
-
// Option 2: Return current version but indicate it's not the requested version (uncomment to use)
|
|
142
|
-
// return {
|
|
143
|
-
// ...currentTemplate,
|
|
144
|
-
// _versionRequested: version,
|
|
145
|
-
// _versionNotFound: true
|
|
146
|
-
// } as DocumentTemplate;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Update an existing document template
|
|
151
|
-
* @param templateId - ID of the template to update
|
|
152
|
-
* @param data - Updated template data
|
|
153
|
-
* @returns The updated template
|
|
154
|
-
*/
|
|
155
|
-
async updateTemplate(
|
|
156
|
-
templateId: string,
|
|
157
|
-
data: UpdateDocumentTemplateData
|
|
158
|
-
): Promise<DocumentTemplate> {
|
|
159
|
-
// Validate data
|
|
160
|
-
const validatedData = updateDocumentTemplateSchema.parse(data);
|
|
161
|
-
console.log("Validated data", validatedData);
|
|
162
|
-
|
|
163
|
-
// Get existing template
|
|
164
|
-
const template = await this.getTemplateById(templateId);
|
|
165
|
-
if (!template) {
|
|
166
|
-
throw new Error(`Template with ID ${templateId} not found`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Store the current version in the versions subcollection
|
|
170
|
-
const versionsCollectionRef = collection(
|
|
171
|
-
this.db,
|
|
172
|
-
`${DOCUMENTATION_TEMPLATES_COLLECTION}/${templateId}/versions`
|
|
173
|
-
);
|
|
174
|
-
const versionDocRef = doc(
|
|
175
|
-
versionsCollectionRef,
|
|
176
|
-
template.version.toString()
|
|
177
|
-
);
|
|
178
|
-
await setDoc(versionDocRef, template);
|
|
179
|
-
|
|
180
|
-
// Process elements if provided
|
|
181
|
-
let updatedElements = template.elements;
|
|
182
|
-
if (validatedData.elements) {
|
|
183
|
-
updatedElements = validatedData.elements.map((element) => ({
|
|
184
|
-
...element,
|
|
185
|
-
id: (element as any).id || this.generateId(),
|
|
186
|
-
})) as DocumentElement[];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const updatePayload: Partial<DocumentTemplate> = {
|
|
190
|
-
elements: updatedElements,
|
|
191
|
-
updatedAt: Date.now(),
|
|
192
|
-
version: template.version + 1,
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
// Conditionally add fields from validatedData to avoid overwriting with undefined
|
|
196
|
-
if (validatedData.title !== undefined)
|
|
197
|
-
updatePayload.title = validatedData.title;
|
|
198
|
-
if (validatedData.description !== undefined)
|
|
199
|
-
updatePayload.description = validatedData.description;
|
|
200
|
-
if (validatedData.isActive !== undefined)
|
|
201
|
-
updatePayload.isActive = validatedData.isActive;
|
|
202
|
-
if (validatedData.tags !== undefined)
|
|
203
|
-
updatePayload.tags = validatedData.tags;
|
|
204
|
-
|
|
205
|
-
// Always include these properties with defaults if they're missing
|
|
206
|
-
updatePayload.isUserForm = validatedData.isUserForm ?? false;
|
|
207
|
-
updatePayload.isRequired = validatedData.isRequired ?? false;
|
|
208
|
-
updatePayload.sortingOrder = validatedData.sortingOrder ?? 0;
|
|
209
|
-
|
|
210
|
-
const docRef = doc(this.collectionRef, templateId);
|
|
211
|
-
console.log("Update payload", updatePayload);
|
|
212
|
-
await updateDoc(docRef, updatePayload);
|
|
213
|
-
|
|
214
|
-
return { ...template, ...updatePayload } as DocumentTemplate;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Get a specific version of a template
|
|
219
|
-
* @param templateId - ID of the template
|
|
220
|
-
* @param versionNumber - Version number to retrieve
|
|
221
|
-
* @returns The template version or null if not found
|
|
222
|
-
*/
|
|
223
|
-
async getTemplateVersion(
|
|
224
|
-
templateId: string,
|
|
225
|
-
versionNumber: number
|
|
226
|
-
): Promise<DocumentTemplate | null> {
|
|
227
|
-
const versionDocRef = doc(
|
|
228
|
-
this.db,
|
|
229
|
-
`${DOCUMENTATION_TEMPLATES_COLLECTION}/${templateId}/versions/${versionNumber}`
|
|
230
|
-
);
|
|
231
|
-
const versionDocSnap = await getDoc(versionDocRef);
|
|
232
|
-
|
|
233
|
-
if (!versionDocSnap.exists()) {
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return versionDocSnap.data() as DocumentTemplate;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Get all versions of a template
|
|
242
|
-
* @param templateId - ID of the template
|
|
243
|
-
* @returns Array of template versions
|
|
244
|
-
*/
|
|
245
|
-
async getTemplateOldVersions(
|
|
246
|
-
templateId: string
|
|
247
|
-
): Promise<DocumentTemplate[]> {
|
|
248
|
-
const versionsCollectionRef = collection(
|
|
249
|
-
this.db,
|
|
250
|
-
`${DOCUMENTATION_TEMPLATES_COLLECTION}/${templateId}/versions`
|
|
251
|
-
);
|
|
252
|
-
const q = query(versionsCollectionRef, orderBy("version", "desc"));
|
|
253
|
-
|
|
254
|
-
const querySnapshot = await getDocs(q);
|
|
255
|
-
const versions: DocumentTemplate[] = [];
|
|
256
|
-
|
|
257
|
-
querySnapshot.forEach((doc) => {
|
|
258
|
-
versions.push(doc.data() as DocumentTemplate);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
return versions;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Delete a document template
|
|
266
|
-
* @param templateId - ID of the template to delete
|
|
267
|
-
*/
|
|
268
|
-
async deleteTemplate(templateId: string): Promise<void> {
|
|
269
|
-
const docRef = doc(this.collectionRef, templateId);
|
|
270
|
-
await deleteDoc(docRef);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Get all active templates
|
|
275
|
-
* @param pageSize - Number of templates to retrieve
|
|
276
|
-
* @param lastDoc - Last document from previous page for pagination
|
|
277
|
-
* @returns Array of templates and the last document for pagination
|
|
278
|
-
*/
|
|
279
|
-
async getActiveTemplates(
|
|
280
|
-
pageSize = 20,
|
|
281
|
-
lastDoc?: QueryDocumentSnapshot<DocumentTemplate>
|
|
282
|
-
): Promise<{
|
|
283
|
-
templates: DocumentTemplate[];
|
|
284
|
-
lastDoc: QueryDocumentSnapshot<DocumentTemplate> | null;
|
|
285
|
-
}> {
|
|
286
|
-
let q = query(
|
|
287
|
-
this.collectionRef,
|
|
288
|
-
where("isActive", "==", true),
|
|
289
|
-
orderBy("updatedAt", "desc"),
|
|
290
|
-
limit(pageSize)
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
if (lastDoc) {
|
|
294
|
-
q = query(q, startAfter(lastDoc));
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const querySnapshot = await getDocs(q);
|
|
298
|
-
const templates: DocumentTemplate[] = [];
|
|
299
|
-
let lastVisible: QueryDocumentSnapshot<DocumentTemplate> | null = null;
|
|
300
|
-
|
|
301
|
-
querySnapshot.forEach((doc) => {
|
|
302
|
-
templates.push(doc.data() as DocumentTemplate);
|
|
303
|
-
lastVisible = doc as QueryDocumentSnapshot<DocumentTemplate>;
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
templates,
|
|
308
|
-
lastDoc: lastVisible,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Get all active templates with optional filters and pagination.
|
|
314
|
-
* @param options - Options for filtering and pagination.
|
|
315
|
-
* @returns A promise that resolves to the templates and the last visible document.
|
|
316
|
-
*/
|
|
317
|
-
async getTemplates(options: {
|
|
318
|
-
pageSize?: number;
|
|
319
|
-
lastDoc?: QueryDocumentSnapshot<DocumentTemplate>;
|
|
320
|
-
isUserForm?: boolean;
|
|
321
|
-
isRequired?: boolean;
|
|
322
|
-
sortingOrder?: number;
|
|
323
|
-
}): Promise<{
|
|
324
|
-
templates: DocumentTemplate[];
|
|
325
|
-
lastDoc: QueryDocumentSnapshot<DocumentTemplate> | null;
|
|
326
|
-
}> {
|
|
327
|
-
const {
|
|
328
|
-
pageSize = 20,
|
|
329
|
-
lastDoc,
|
|
330
|
-
isUserForm,
|
|
331
|
-
isRequired,
|
|
332
|
-
sortingOrder,
|
|
333
|
-
} = options;
|
|
334
|
-
const constraints: QueryConstraint[] = [
|
|
335
|
-
where("isActive", "==", true),
|
|
336
|
-
orderBy("sortingOrder", "asc"),
|
|
337
|
-
orderBy("title", "asc"),
|
|
338
|
-
limit(pageSize),
|
|
339
|
-
];
|
|
340
|
-
|
|
341
|
-
if (isUserForm !== undefined) {
|
|
342
|
-
constraints.push(where("isUserForm", "==", isUserForm));
|
|
343
|
-
}
|
|
344
|
-
if (isRequired !== undefined) {
|
|
345
|
-
constraints.push(where("isRequired", "==", isRequired));
|
|
346
|
-
}
|
|
347
|
-
if (sortingOrder !== undefined) {
|
|
348
|
-
constraints.push(where("sortingOrder", "==", sortingOrder));
|
|
349
|
-
}
|
|
350
|
-
if (lastDoc) {
|
|
351
|
-
constraints.push(startAfter(lastDoc));
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const q = query(this.collectionRef, ...constraints.filter((c) => c));
|
|
355
|
-
|
|
356
|
-
const querySnapshot = await getDocs(q);
|
|
357
|
-
const templates: DocumentTemplate[] = [];
|
|
358
|
-
let lastVisible: QueryDocumentSnapshot<DocumentTemplate> | null = null;
|
|
359
|
-
|
|
360
|
-
querySnapshot.forEach((doc) => {
|
|
361
|
-
templates.push(doc.data() as DocumentTemplate);
|
|
362
|
-
lastVisible = doc as QueryDocumentSnapshot<DocumentTemplate>;
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
return {
|
|
366
|
-
templates,
|
|
367
|
-
lastDoc: lastVisible,
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Get the total count of active templates with optional filters.
|
|
373
|
-
* @param options - Options for filtering.
|
|
374
|
-
* @returns A promise that resolves to the total count of templates.
|
|
375
|
-
*/
|
|
376
|
-
async getTemplatesCount(options: {
|
|
377
|
-
isUserForm?: boolean;
|
|
378
|
-
isRequired?: boolean;
|
|
379
|
-
sortingOrder?: number;
|
|
380
|
-
search?: string; // Search will be applied in-memory for now
|
|
381
|
-
}): Promise<number> {
|
|
382
|
-
const { isUserForm, isRequired, sortingOrder } = options;
|
|
383
|
-
const constraints = [where("isActive", "==", true)];
|
|
384
|
-
|
|
385
|
-
if (isUserForm !== undefined) {
|
|
386
|
-
constraints.push(where("isUserForm", "==", isUserForm));
|
|
387
|
-
}
|
|
388
|
-
if (isRequired !== undefined) {
|
|
389
|
-
constraints.push(where("isRequired", "==", isRequired));
|
|
390
|
-
}
|
|
391
|
-
if (sortingOrder !== undefined) {
|
|
392
|
-
constraints.push(where("sortingOrder", "==", sortingOrder));
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const q = query(this.collectionRef, ...constraints.filter((c) => c));
|
|
396
|
-
const snapshot = await getCountFromServer(q);
|
|
397
|
-
|
|
398
|
-
return snapshot.data().count;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Get all active templates without pagination for filtering purposes.
|
|
403
|
-
* @returns A promise that resolves to an array of all active templates.
|
|
404
|
-
*/
|
|
405
|
-
async getAllActiveTemplates(): Promise<DocumentTemplate[]> {
|
|
406
|
-
const q = query(
|
|
407
|
-
this.collectionRef,
|
|
408
|
-
where("isActive", "==", true),
|
|
409
|
-
orderBy("title", "asc")
|
|
410
|
-
);
|
|
411
|
-
|
|
412
|
-
const querySnapshot = await getDocs(q);
|
|
413
|
-
const templates: DocumentTemplate[] = [];
|
|
414
|
-
|
|
415
|
-
querySnapshot.forEach((doc) => {
|
|
416
|
-
templates.push(doc.data() as DocumentTemplate);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
return templates;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Get templates by tags
|
|
424
|
-
* @param tags - Tags to filter by
|
|
425
|
-
* @param pageSize - Number of templates to retrieve
|
|
426
|
-
* @param lastDoc - Last document from previous page for pagination
|
|
427
|
-
* @returns Array of templates and the last document for pagination
|
|
428
|
-
*/
|
|
429
|
-
async getTemplatesByTags(
|
|
430
|
-
tags: string[],
|
|
431
|
-
pageSize = 20,
|
|
432
|
-
lastDoc?: QueryDocumentSnapshot<DocumentTemplate>
|
|
433
|
-
): Promise<{
|
|
434
|
-
templates: DocumentTemplate[];
|
|
435
|
-
lastDoc: QueryDocumentSnapshot<DocumentTemplate> | null;
|
|
436
|
-
}> {
|
|
437
|
-
let q = query(
|
|
438
|
-
this.collectionRef,
|
|
439
|
-
where("isActive", "==", true),
|
|
440
|
-
where("tags", "array-contains-any", tags),
|
|
441
|
-
orderBy("updatedAt", "desc"),
|
|
442
|
-
limit(pageSize)
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
if (lastDoc) {
|
|
446
|
-
q = query(q, startAfter(lastDoc));
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const querySnapshot = await getDocs(q);
|
|
450
|
-
const templates: DocumentTemplate[] = [];
|
|
451
|
-
let lastVisible: QueryDocumentSnapshot<DocumentTemplate> | null = null;
|
|
452
|
-
|
|
453
|
-
querySnapshot.forEach((doc) => {
|
|
454
|
-
templates.push(doc.data() as DocumentTemplate);
|
|
455
|
-
lastVisible = doc as QueryDocumentSnapshot<DocumentTemplate>;
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
return {
|
|
459
|
-
templates,
|
|
460
|
-
lastDoc: lastVisible,
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Get templates created by a specific user
|
|
466
|
-
* @param userId - ID of the user who created the templates
|
|
467
|
-
* @param pageSize - Number of templates to retrieve
|
|
468
|
-
* @param lastDoc - Last document from previous page for pagination
|
|
469
|
-
* @returns Array of templates and the last document for pagination
|
|
470
|
-
*/
|
|
471
|
-
async getTemplatesByCreator(
|
|
472
|
-
userId: string,
|
|
473
|
-
pageSize = 20,
|
|
474
|
-
lastDoc?: QueryDocumentSnapshot<DocumentTemplate>
|
|
475
|
-
): Promise<{
|
|
476
|
-
templates: DocumentTemplate[];
|
|
477
|
-
lastDoc: QueryDocumentSnapshot<DocumentTemplate> | null;
|
|
478
|
-
}> {
|
|
479
|
-
let q = query(
|
|
480
|
-
this.collectionRef,
|
|
481
|
-
where("createdBy", "==", userId),
|
|
482
|
-
orderBy("updatedAt", "desc"),
|
|
483
|
-
limit(pageSize)
|
|
484
|
-
);
|
|
485
|
-
|
|
486
|
-
if (lastDoc) {
|
|
487
|
-
q = query(q, startAfter(lastDoc));
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
const querySnapshot = await getDocs(q);
|
|
491
|
-
const templates: DocumentTemplate[] = [];
|
|
492
|
-
let lastVisible: QueryDocumentSnapshot<DocumentTemplate> | null = null;
|
|
493
|
-
|
|
494
|
-
querySnapshot.forEach((doc) => {
|
|
495
|
-
templates.push(doc.data() as DocumentTemplate);
|
|
496
|
-
lastVisible = doc as QueryDocumentSnapshot<DocumentTemplate>;
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
return {
|
|
500
|
-
templates,
|
|
501
|
-
lastDoc: lastVisible,
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Get all templates for selection with optional filtering
|
|
507
|
-
* @param options - Filtering options
|
|
508
|
-
* @returns Array of templates
|
|
509
|
-
*/
|
|
510
|
-
async getAllTemplatesForSelection(options?: {
|
|
511
|
-
isUserForm?: boolean;
|
|
512
|
-
isRequired?: boolean;
|
|
513
|
-
}): Promise<DocumentTemplate[]> {
|
|
514
|
-
let q = query(
|
|
515
|
-
this.collectionRef,
|
|
516
|
-
where("isActive", "==", true),
|
|
517
|
-
orderBy("updatedAt", "desc")
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
if (options?.isUserForm !== undefined) {
|
|
521
|
-
q = query(q, where("isUserForm", "==", options.isUserForm));
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (options?.isRequired !== undefined) {
|
|
525
|
-
q = query(q, where("isRequired", "==", options.isRequired));
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
const querySnapshot = await getDocs(q);
|
|
529
|
-
const templates: DocumentTemplate[] = [];
|
|
530
|
-
|
|
531
|
-
querySnapshot.forEach((doc) => {
|
|
532
|
-
templates.push(doc.data() as DocumentTemplate);
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
return templates;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
collection,
|
|
3
|
+
doc,
|
|
4
|
+
getDoc,
|
|
5
|
+
getDocs,
|
|
6
|
+
setDoc,
|
|
7
|
+
updateDoc,
|
|
8
|
+
deleteDoc,
|
|
9
|
+
query,
|
|
10
|
+
where,
|
|
11
|
+
orderBy,
|
|
12
|
+
limit,
|
|
13
|
+
startAfter,
|
|
14
|
+
DocumentSnapshot,
|
|
15
|
+
QueryDocumentSnapshot,
|
|
16
|
+
serverTimestamp,
|
|
17
|
+
Timestamp,
|
|
18
|
+
QueryConstraint,
|
|
19
|
+
} from "firebase/firestore";
|
|
20
|
+
import { BaseService } from "../base.service";
|
|
21
|
+
import {
|
|
22
|
+
CreateDocumentTemplateData,
|
|
23
|
+
DocumentElement,
|
|
24
|
+
DocumentTemplate,
|
|
25
|
+
UpdateDocumentTemplateData,
|
|
26
|
+
} from "../../types";
|
|
27
|
+
import { DOCUMENTATION_TEMPLATES_COLLECTION } from "../../types";
|
|
28
|
+
import {
|
|
29
|
+
createDocumentTemplateSchema,
|
|
30
|
+
updateDocumentTemplateSchema,
|
|
31
|
+
} from "../../validations/documentation-templates.schema";
|
|
32
|
+
import { getCountFromServer } from "firebase/firestore";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Service for managing documentation templates
|
|
36
|
+
*/
|
|
37
|
+
export class DocumentationTemplateService extends BaseService {
|
|
38
|
+
private readonly collectionRef = collection(
|
|
39
|
+
this.db,
|
|
40
|
+
DOCUMENTATION_TEMPLATES_COLLECTION
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
constructor(...args: ConstructorParameters<typeof BaseService>) {
|
|
44
|
+
super(...args);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a new document template
|
|
49
|
+
* @param data - Template data
|
|
50
|
+
* @param userId - ID of the user creating the template
|
|
51
|
+
* @returns The created template
|
|
52
|
+
*/
|
|
53
|
+
async createTemplate(
|
|
54
|
+
data: CreateDocumentTemplateData,
|
|
55
|
+
userId: string
|
|
56
|
+
): Promise<DocumentTemplate> {
|
|
57
|
+
// Validate data
|
|
58
|
+
const validatedData = createDocumentTemplateSchema.parse(data);
|
|
59
|
+
|
|
60
|
+
// Generate ID for the template
|
|
61
|
+
const templateId = this.generateId();
|
|
62
|
+
|
|
63
|
+
// Generate IDs for elements
|
|
64
|
+
const elementsWithIds = validatedData.elements.map((element) => ({
|
|
65
|
+
...element,
|
|
66
|
+
id: this.generateId(),
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
// Create template object
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const template: DocumentTemplate = {
|
|
72
|
+
id: templateId,
|
|
73
|
+
title: validatedData.title,
|
|
74
|
+
description: validatedData.description,
|
|
75
|
+
elements: elementsWithIds as DocumentElement[],
|
|
76
|
+
createdAt: now,
|
|
77
|
+
updatedAt: now,
|
|
78
|
+
createdBy: userId,
|
|
79
|
+
version: 1,
|
|
80
|
+
isActive: true,
|
|
81
|
+
tags: validatedData.tags || [],
|
|
82
|
+
isUserForm: validatedData.isUserForm || false,
|
|
83
|
+
isRequired: validatedData.isRequired || false,
|
|
84
|
+
sortingOrder: validatedData.sortingOrder || 0,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Save to Firestore
|
|
88
|
+
const docRef = doc(this.collectionRef, templateId);
|
|
89
|
+
await setDoc(docRef, template);
|
|
90
|
+
|
|
91
|
+
return template;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get a document template by ID
|
|
96
|
+
* @param templateId - ID of the template to retrieve
|
|
97
|
+
* @param version - Optional version number to retrieve (defaults to latest version)
|
|
98
|
+
* @returns The template or null if not found
|
|
99
|
+
*/
|
|
100
|
+
async getTemplateById(
|
|
101
|
+
templateId: string,
|
|
102
|
+
version?: number
|
|
103
|
+
): Promise<DocumentTemplate | null> {
|
|
104
|
+
// First, check if the template exists at all
|
|
105
|
+
const docRef = doc(this.collectionRef, templateId);
|
|
106
|
+
const docSnap = await getDoc(docRef);
|
|
107
|
+
|
|
108
|
+
if (!docSnap.exists()) {
|
|
109
|
+
return null; // Template doesn't exist
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const currentTemplate = docSnap.data() as DocumentTemplate;
|
|
113
|
+
|
|
114
|
+
// If no specific version is requested, simply return the current template
|
|
115
|
+
if (version === undefined) {
|
|
116
|
+
return currentTemplate;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// If the requested version matches the current version, return it
|
|
120
|
+
if (currentTemplate.version === version) {
|
|
121
|
+
return currentTemplate;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Otherwise, try to find the requested version in the versions subcollection
|
|
125
|
+
try {
|
|
126
|
+
const versionTemplate = await this.getTemplateVersion(
|
|
127
|
+
templateId,
|
|
128
|
+
version
|
|
129
|
+
);
|
|
130
|
+
if (versionTemplate) {
|
|
131
|
+
return versionTemplate;
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error(`Error getting template version ${version}:`, error);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// If we get here, the requested version doesn't exist
|
|
138
|
+
// Option 1: Return null (strict approach)
|
|
139
|
+
return null;
|
|
140
|
+
|
|
141
|
+
// Option 2: Return current version but indicate it's not the requested version (uncomment to use)
|
|
142
|
+
// return {
|
|
143
|
+
// ...currentTemplate,
|
|
144
|
+
// _versionRequested: version,
|
|
145
|
+
// _versionNotFound: true
|
|
146
|
+
// } as DocumentTemplate;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Update an existing document template
|
|
151
|
+
* @param templateId - ID of the template to update
|
|
152
|
+
* @param data - Updated template data
|
|
153
|
+
* @returns The updated template
|
|
154
|
+
*/
|
|
155
|
+
async updateTemplate(
|
|
156
|
+
templateId: string,
|
|
157
|
+
data: UpdateDocumentTemplateData
|
|
158
|
+
): Promise<DocumentTemplate> {
|
|
159
|
+
// Validate data
|
|
160
|
+
const validatedData = updateDocumentTemplateSchema.parse(data);
|
|
161
|
+
console.log("Validated data", validatedData);
|
|
162
|
+
|
|
163
|
+
// Get existing template
|
|
164
|
+
const template = await this.getTemplateById(templateId);
|
|
165
|
+
if (!template) {
|
|
166
|
+
throw new Error(`Template with ID ${templateId} not found`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Store the current version in the versions subcollection
|
|
170
|
+
const versionsCollectionRef = collection(
|
|
171
|
+
this.db,
|
|
172
|
+
`${DOCUMENTATION_TEMPLATES_COLLECTION}/${templateId}/versions`
|
|
173
|
+
);
|
|
174
|
+
const versionDocRef = doc(
|
|
175
|
+
versionsCollectionRef,
|
|
176
|
+
template.version.toString()
|
|
177
|
+
);
|
|
178
|
+
await setDoc(versionDocRef, template);
|
|
179
|
+
|
|
180
|
+
// Process elements if provided
|
|
181
|
+
let updatedElements = template.elements;
|
|
182
|
+
if (validatedData.elements) {
|
|
183
|
+
updatedElements = validatedData.elements.map((element) => ({
|
|
184
|
+
...element,
|
|
185
|
+
id: (element as any).id || this.generateId(),
|
|
186
|
+
})) as DocumentElement[];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const updatePayload: Partial<DocumentTemplate> = {
|
|
190
|
+
elements: updatedElements,
|
|
191
|
+
updatedAt: Date.now(),
|
|
192
|
+
version: template.version + 1,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Conditionally add fields from validatedData to avoid overwriting with undefined
|
|
196
|
+
if (validatedData.title !== undefined)
|
|
197
|
+
updatePayload.title = validatedData.title;
|
|
198
|
+
if (validatedData.description !== undefined)
|
|
199
|
+
updatePayload.description = validatedData.description;
|
|
200
|
+
if (validatedData.isActive !== undefined)
|
|
201
|
+
updatePayload.isActive = validatedData.isActive;
|
|
202
|
+
if (validatedData.tags !== undefined)
|
|
203
|
+
updatePayload.tags = validatedData.tags;
|
|
204
|
+
|
|
205
|
+
// Always include these properties with defaults if they're missing
|
|
206
|
+
updatePayload.isUserForm = validatedData.isUserForm ?? false;
|
|
207
|
+
updatePayload.isRequired = validatedData.isRequired ?? false;
|
|
208
|
+
updatePayload.sortingOrder = validatedData.sortingOrder ?? 0;
|
|
209
|
+
|
|
210
|
+
const docRef = doc(this.collectionRef, templateId);
|
|
211
|
+
console.log("Update payload", updatePayload);
|
|
212
|
+
await updateDoc(docRef, updatePayload);
|
|
213
|
+
|
|
214
|
+
return { ...template, ...updatePayload } as DocumentTemplate;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get a specific version of a template
|
|
219
|
+
* @param templateId - ID of the template
|
|
220
|
+
* @param versionNumber - Version number to retrieve
|
|
221
|
+
* @returns The template version or null if not found
|
|
222
|
+
*/
|
|
223
|
+
async getTemplateVersion(
|
|
224
|
+
templateId: string,
|
|
225
|
+
versionNumber: number
|
|
226
|
+
): Promise<DocumentTemplate | null> {
|
|
227
|
+
const versionDocRef = doc(
|
|
228
|
+
this.db,
|
|
229
|
+
`${DOCUMENTATION_TEMPLATES_COLLECTION}/${templateId}/versions/${versionNumber}`
|
|
230
|
+
);
|
|
231
|
+
const versionDocSnap = await getDoc(versionDocRef);
|
|
232
|
+
|
|
233
|
+
if (!versionDocSnap.exists()) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return versionDocSnap.data() as DocumentTemplate;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get all versions of a template
|
|
242
|
+
* @param templateId - ID of the template
|
|
243
|
+
* @returns Array of template versions
|
|
244
|
+
*/
|
|
245
|
+
async getTemplateOldVersions(
|
|
246
|
+
templateId: string
|
|
247
|
+
): Promise<DocumentTemplate[]> {
|
|
248
|
+
const versionsCollectionRef = collection(
|
|
249
|
+
this.db,
|
|
250
|
+
`${DOCUMENTATION_TEMPLATES_COLLECTION}/${templateId}/versions`
|
|
251
|
+
);
|
|
252
|
+
const q = query(versionsCollectionRef, orderBy("version", "desc"));
|
|
253
|
+
|
|
254
|
+
const querySnapshot = await getDocs(q);
|
|
255
|
+
const versions: DocumentTemplate[] = [];
|
|
256
|
+
|
|
257
|
+
querySnapshot.forEach((doc) => {
|
|
258
|
+
versions.push(doc.data() as DocumentTemplate);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return versions;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Delete a document template
|
|
266
|
+
* @param templateId - ID of the template to delete
|
|
267
|
+
*/
|
|
268
|
+
async deleteTemplate(templateId: string): Promise<void> {
|
|
269
|
+
const docRef = doc(this.collectionRef, templateId);
|
|
270
|
+
await deleteDoc(docRef);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get all active templates
|
|
275
|
+
* @param pageSize - Number of templates to retrieve
|
|
276
|
+
* @param lastDoc - Last document from previous page for pagination
|
|
277
|
+
* @returns Array of templates and the last document for pagination
|
|
278
|
+
*/
|
|
279
|
+
async getActiveTemplates(
|
|
280
|
+
pageSize = 20,
|
|
281
|
+
lastDoc?: QueryDocumentSnapshot<DocumentTemplate>
|
|
282
|
+
): Promise<{
|
|
283
|
+
templates: DocumentTemplate[];
|
|
284
|
+
lastDoc: QueryDocumentSnapshot<DocumentTemplate> | null;
|
|
285
|
+
}> {
|
|
286
|
+
let q = query(
|
|
287
|
+
this.collectionRef,
|
|
288
|
+
where("isActive", "==", true),
|
|
289
|
+
orderBy("updatedAt", "desc"),
|
|
290
|
+
limit(pageSize)
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (lastDoc) {
|
|
294
|
+
q = query(q, startAfter(lastDoc));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const querySnapshot = await getDocs(q);
|
|
298
|
+
const templates: DocumentTemplate[] = [];
|
|
299
|
+
let lastVisible: QueryDocumentSnapshot<DocumentTemplate> | null = null;
|
|
300
|
+
|
|
301
|
+
querySnapshot.forEach((doc) => {
|
|
302
|
+
templates.push(doc.data() as DocumentTemplate);
|
|
303
|
+
lastVisible = doc as QueryDocumentSnapshot<DocumentTemplate>;
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
templates,
|
|
308
|
+
lastDoc: lastVisible,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Get all active templates with optional filters and pagination.
|
|
314
|
+
* @param options - Options for filtering and pagination.
|
|
315
|
+
* @returns A promise that resolves to the templates and the last visible document.
|
|
316
|
+
*/
|
|
317
|
+
async getTemplates(options: {
|
|
318
|
+
pageSize?: number;
|
|
319
|
+
lastDoc?: QueryDocumentSnapshot<DocumentTemplate>;
|
|
320
|
+
isUserForm?: boolean;
|
|
321
|
+
isRequired?: boolean;
|
|
322
|
+
sortingOrder?: number;
|
|
323
|
+
}): Promise<{
|
|
324
|
+
templates: DocumentTemplate[];
|
|
325
|
+
lastDoc: QueryDocumentSnapshot<DocumentTemplate> | null;
|
|
326
|
+
}> {
|
|
327
|
+
const {
|
|
328
|
+
pageSize = 20,
|
|
329
|
+
lastDoc,
|
|
330
|
+
isUserForm,
|
|
331
|
+
isRequired,
|
|
332
|
+
sortingOrder,
|
|
333
|
+
} = options;
|
|
334
|
+
const constraints: QueryConstraint[] = [
|
|
335
|
+
where("isActive", "==", true),
|
|
336
|
+
orderBy("sortingOrder", "asc"),
|
|
337
|
+
orderBy("title", "asc"),
|
|
338
|
+
limit(pageSize),
|
|
339
|
+
];
|
|
340
|
+
|
|
341
|
+
if (isUserForm !== undefined) {
|
|
342
|
+
constraints.push(where("isUserForm", "==", isUserForm));
|
|
343
|
+
}
|
|
344
|
+
if (isRequired !== undefined) {
|
|
345
|
+
constraints.push(where("isRequired", "==", isRequired));
|
|
346
|
+
}
|
|
347
|
+
if (sortingOrder !== undefined) {
|
|
348
|
+
constraints.push(where("sortingOrder", "==", sortingOrder));
|
|
349
|
+
}
|
|
350
|
+
if (lastDoc) {
|
|
351
|
+
constraints.push(startAfter(lastDoc));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const q = query(this.collectionRef, ...constraints.filter((c) => c));
|
|
355
|
+
|
|
356
|
+
const querySnapshot = await getDocs(q);
|
|
357
|
+
const templates: DocumentTemplate[] = [];
|
|
358
|
+
let lastVisible: QueryDocumentSnapshot<DocumentTemplate> | null = null;
|
|
359
|
+
|
|
360
|
+
querySnapshot.forEach((doc) => {
|
|
361
|
+
templates.push(doc.data() as DocumentTemplate);
|
|
362
|
+
lastVisible = doc as QueryDocumentSnapshot<DocumentTemplate>;
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
templates,
|
|
367
|
+
lastDoc: lastVisible,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Get the total count of active templates with optional filters.
|
|
373
|
+
* @param options - Options for filtering.
|
|
374
|
+
* @returns A promise that resolves to the total count of templates.
|
|
375
|
+
*/
|
|
376
|
+
async getTemplatesCount(options: {
|
|
377
|
+
isUserForm?: boolean;
|
|
378
|
+
isRequired?: boolean;
|
|
379
|
+
sortingOrder?: number;
|
|
380
|
+
search?: string; // Search will be applied in-memory for now
|
|
381
|
+
}): Promise<number> {
|
|
382
|
+
const { isUserForm, isRequired, sortingOrder } = options;
|
|
383
|
+
const constraints = [where("isActive", "==", true)];
|
|
384
|
+
|
|
385
|
+
if (isUserForm !== undefined) {
|
|
386
|
+
constraints.push(where("isUserForm", "==", isUserForm));
|
|
387
|
+
}
|
|
388
|
+
if (isRequired !== undefined) {
|
|
389
|
+
constraints.push(where("isRequired", "==", isRequired));
|
|
390
|
+
}
|
|
391
|
+
if (sortingOrder !== undefined) {
|
|
392
|
+
constraints.push(where("sortingOrder", "==", sortingOrder));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const q = query(this.collectionRef, ...constraints.filter((c) => c));
|
|
396
|
+
const snapshot = await getCountFromServer(q);
|
|
397
|
+
|
|
398
|
+
return snapshot.data().count;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get all active templates without pagination for filtering purposes.
|
|
403
|
+
* @returns A promise that resolves to an array of all active templates.
|
|
404
|
+
*/
|
|
405
|
+
async getAllActiveTemplates(): Promise<DocumentTemplate[]> {
|
|
406
|
+
const q = query(
|
|
407
|
+
this.collectionRef,
|
|
408
|
+
where("isActive", "==", true),
|
|
409
|
+
orderBy("title", "asc")
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
const querySnapshot = await getDocs(q);
|
|
413
|
+
const templates: DocumentTemplate[] = [];
|
|
414
|
+
|
|
415
|
+
querySnapshot.forEach((doc) => {
|
|
416
|
+
templates.push(doc.data() as DocumentTemplate);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
return templates;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Get templates by tags
|
|
424
|
+
* @param tags - Tags to filter by
|
|
425
|
+
* @param pageSize - Number of templates to retrieve
|
|
426
|
+
* @param lastDoc - Last document from previous page for pagination
|
|
427
|
+
* @returns Array of templates and the last document for pagination
|
|
428
|
+
*/
|
|
429
|
+
async getTemplatesByTags(
|
|
430
|
+
tags: string[],
|
|
431
|
+
pageSize = 20,
|
|
432
|
+
lastDoc?: QueryDocumentSnapshot<DocumentTemplate>
|
|
433
|
+
): Promise<{
|
|
434
|
+
templates: DocumentTemplate[];
|
|
435
|
+
lastDoc: QueryDocumentSnapshot<DocumentTemplate> | null;
|
|
436
|
+
}> {
|
|
437
|
+
let q = query(
|
|
438
|
+
this.collectionRef,
|
|
439
|
+
where("isActive", "==", true),
|
|
440
|
+
where("tags", "array-contains-any", tags),
|
|
441
|
+
orderBy("updatedAt", "desc"),
|
|
442
|
+
limit(pageSize)
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
if (lastDoc) {
|
|
446
|
+
q = query(q, startAfter(lastDoc));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const querySnapshot = await getDocs(q);
|
|
450
|
+
const templates: DocumentTemplate[] = [];
|
|
451
|
+
let lastVisible: QueryDocumentSnapshot<DocumentTemplate> | null = null;
|
|
452
|
+
|
|
453
|
+
querySnapshot.forEach((doc) => {
|
|
454
|
+
templates.push(doc.data() as DocumentTemplate);
|
|
455
|
+
lastVisible = doc as QueryDocumentSnapshot<DocumentTemplate>;
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
templates,
|
|
460
|
+
lastDoc: lastVisible,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Get templates created by a specific user
|
|
466
|
+
* @param userId - ID of the user who created the templates
|
|
467
|
+
* @param pageSize - Number of templates to retrieve
|
|
468
|
+
* @param lastDoc - Last document from previous page for pagination
|
|
469
|
+
* @returns Array of templates and the last document for pagination
|
|
470
|
+
*/
|
|
471
|
+
async getTemplatesByCreator(
|
|
472
|
+
userId: string,
|
|
473
|
+
pageSize = 20,
|
|
474
|
+
lastDoc?: QueryDocumentSnapshot<DocumentTemplate>
|
|
475
|
+
): Promise<{
|
|
476
|
+
templates: DocumentTemplate[];
|
|
477
|
+
lastDoc: QueryDocumentSnapshot<DocumentTemplate> | null;
|
|
478
|
+
}> {
|
|
479
|
+
let q = query(
|
|
480
|
+
this.collectionRef,
|
|
481
|
+
where("createdBy", "==", userId),
|
|
482
|
+
orderBy("updatedAt", "desc"),
|
|
483
|
+
limit(pageSize)
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
if (lastDoc) {
|
|
487
|
+
q = query(q, startAfter(lastDoc));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const querySnapshot = await getDocs(q);
|
|
491
|
+
const templates: DocumentTemplate[] = [];
|
|
492
|
+
let lastVisible: QueryDocumentSnapshot<DocumentTemplate> | null = null;
|
|
493
|
+
|
|
494
|
+
querySnapshot.forEach((doc) => {
|
|
495
|
+
templates.push(doc.data() as DocumentTemplate);
|
|
496
|
+
lastVisible = doc as QueryDocumentSnapshot<DocumentTemplate>;
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
templates,
|
|
501
|
+
lastDoc: lastVisible,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Get all templates for selection with optional filtering
|
|
507
|
+
* @param options - Filtering options
|
|
508
|
+
* @returns Array of templates
|
|
509
|
+
*/
|
|
510
|
+
async getAllTemplatesForSelection(options?: {
|
|
511
|
+
isUserForm?: boolean;
|
|
512
|
+
isRequired?: boolean;
|
|
513
|
+
}): Promise<DocumentTemplate[]> {
|
|
514
|
+
let q = query(
|
|
515
|
+
this.collectionRef,
|
|
516
|
+
where("isActive", "==", true),
|
|
517
|
+
orderBy("updatedAt", "desc")
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
if (options?.isUserForm !== undefined) {
|
|
521
|
+
q = query(q, where("isUserForm", "==", options.isUserForm));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (options?.isRequired !== undefined) {
|
|
525
|
+
q = query(q, where("isRequired", "==", options.isRequired));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const querySnapshot = await getDocs(q);
|
|
529
|
+
const templates: DocumentTemplate[] = [];
|
|
530
|
+
|
|
531
|
+
querySnapshot.forEach((doc) => {
|
|
532
|
+
templates.push(doc.data() as DocumentTemplate);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
return templates;
|
|
536
|
+
}
|
|
537
|
+
}
|