@blackcode_sa/metaestetics-api 1.7.18 → 1.7.20
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 +1 -1
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/backoffice/index.d.mts +6 -1
- package/dist/backoffice/index.d.ts +6 -1
- package/dist/index.d.mts +63 -60
- package/dist/index.d.ts +63 -60
- package/dist/index.js +1954 -1967
- package/dist/index.mjs +1341 -1354
- package/package.json +1 -1
- package/src/admin/booking/booking.admin.ts +4 -1
- package/src/services/documentation-templates/filled-document.service.ts +49 -153
- package/src/services/practitioner/practitioner.service.ts +92 -3
- package/src/services/procedure/procedure.service.ts +8 -2
- package/src/types/practitioner/index.ts +2 -1
- package/src/validations/media.schema.ts +1 -1
- package/src/validations/practitioner.schema.ts +3 -2
package/package.json
CHANGED
|
@@ -613,7 +613,10 @@ export class BookingAdmin {
|
|
|
613
613
|
};
|
|
614
614
|
const practitionerInfo: PractitionerProfileInfo = {
|
|
615
615
|
id: practitionerSnap.id,
|
|
616
|
-
practitionerPhoto:
|
|
616
|
+
practitionerPhoto:
|
|
617
|
+
typeof practitionerData.basicInfo.profileImageUrl === "string"
|
|
618
|
+
? practitionerData.basicInfo.profileImageUrl
|
|
619
|
+
: null,
|
|
617
620
|
name: `${practitionerData.basicInfo.firstName} ${practitionerData.basicInfo.lastName}`,
|
|
618
621
|
email: practitionerData.basicInfo.email,
|
|
619
622
|
phone: practitionerData.basicInfo.phoneNumber || null,
|
|
@@ -478,55 +478,45 @@ export class FilledDocumentService extends BaseService {
|
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
/**
|
|
481
|
-
* Upload a file
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
* @param
|
|
485
|
-
* @param
|
|
486
|
-
* @param
|
|
487
|
-
* @param
|
|
488
|
-
* @returns The
|
|
481
|
+
* Upload a file for a filled document field without updating the document.
|
|
482
|
+
* This method only handles the upload and returns the file value to be used by the UI.
|
|
483
|
+
*
|
|
484
|
+
* @param appointmentId - ID of the appointment
|
|
485
|
+
* @param formId - ID of the filled document
|
|
486
|
+
* @param isUserForm - Boolean indicating if it's a user form or doctor form
|
|
487
|
+
* @param file - The file to upload
|
|
488
|
+
* @returns The file value object to be stored in the document
|
|
489
489
|
*/
|
|
490
490
|
async uploadFileForFilledDocument(
|
|
491
491
|
appointmentId: string,
|
|
492
492
|
formId: string,
|
|
493
493
|
isUserForm: boolean,
|
|
494
|
-
file: File | Blob
|
|
495
|
-
|
|
496
|
-
accessLevel: MediaAccessLevel = MediaAccessLevel.PRIVATE
|
|
497
|
-
): Promise<FilledDocument> {
|
|
494
|
+
file: File | Blob
|
|
495
|
+
): Promise<FilledDocumentFileValue> {
|
|
498
496
|
console.log(
|
|
499
|
-
`[FilledDocumentService] Uploading file for
|
|
497
|
+
`[FilledDocumentService] Uploading file for form ${formId} in appointment ${appointmentId}`
|
|
500
498
|
);
|
|
501
499
|
|
|
502
|
-
//
|
|
503
|
-
const
|
|
504
|
-
appointmentId,
|
|
505
|
-
formId,
|
|
506
|
-
isUserForm
|
|
507
|
-
);
|
|
500
|
+
// Generate a unique file ID
|
|
501
|
+
const fileId = this.generateId();
|
|
508
502
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
);
|
|
513
|
-
}
|
|
503
|
+
// Set the path according to the specified structure
|
|
504
|
+
const formType = isUserForm ? "user-form" : "doctor-form";
|
|
505
|
+
const collectionName = `${formType}/${formId}`;
|
|
514
506
|
|
|
515
|
-
//
|
|
516
|
-
const
|
|
517
|
-
const collectionName = isUserForm
|
|
518
|
-
? "patient_forms_files"
|
|
519
|
-
: "doctor_forms_files";
|
|
507
|
+
// Always use CONFIDENTIAL access level
|
|
508
|
+
const accessLevel = MediaAccessLevel.CONFIDENTIAL;
|
|
520
509
|
|
|
510
|
+
// Upload the file using MediaService
|
|
521
511
|
const mediaMetadata = await this.mediaService.uploadMedia(
|
|
522
512
|
file,
|
|
523
|
-
ownerId
|
|
513
|
+
appointmentId, // Using appointmentId as ownerId
|
|
524
514
|
accessLevel,
|
|
525
515
|
collectionName,
|
|
526
|
-
file instanceof File ? file.name :
|
|
516
|
+
file instanceof File ? file.name : `file_${fileId}`
|
|
527
517
|
);
|
|
528
518
|
|
|
529
|
-
//
|
|
519
|
+
// Create and return a file value object
|
|
530
520
|
const fileValue: FilledDocumentFileValue = {
|
|
531
521
|
mediaId: mediaMetadata.id,
|
|
532
522
|
url: mediaMetadata.url,
|
|
@@ -536,156 +526,62 @@ export class FilledDocumentService extends BaseService {
|
|
|
536
526
|
uploadedAt: Date.now(),
|
|
537
527
|
};
|
|
538
528
|
|
|
539
|
-
|
|
540
|
-
const values = {
|
|
541
|
-
[fieldId]: fileValue,
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
// 5. Use the existing update method to save the file reference
|
|
545
|
-
return this.updateFilledDocumentInAppointment(
|
|
546
|
-
appointmentId,
|
|
547
|
-
formId,
|
|
548
|
-
isUserForm,
|
|
549
|
-
values
|
|
550
|
-
);
|
|
529
|
+
return fileValue;
|
|
551
530
|
}
|
|
552
531
|
|
|
553
532
|
/**
|
|
554
|
-
* Upload a signature image for a filled document
|
|
533
|
+
* Upload a signature image for a filled document.
|
|
555
534
|
* This is a specialized version of uploadFileForFilledDocument specifically for signatures.
|
|
556
|
-
*
|
|
557
|
-
* @param
|
|
558
|
-
* @param
|
|
559
|
-
* @param
|
|
560
|
-
* @param
|
|
561
|
-
* @returns The
|
|
535
|
+
*
|
|
536
|
+
* @param appointmentId - ID of the appointment
|
|
537
|
+
* @param formId - ID of the filled document
|
|
538
|
+
* @param isUserForm - Boolean indicating if it's a user form or doctor form
|
|
539
|
+
* @param signatureBlob - The signature image as a Blob
|
|
540
|
+
* @returns The file value object to be stored in the document
|
|
562
541
|
*/
|
|
563
542
|
async uploadSignatureForFilledDocument(
|
|
564
543
|
appointmentId: string,
|
|
565
544
|
formId: string,
|
|
566
545
|
isUserForm: boolean,
|
|
567
|
-
signatureBlob: Blob
|
|
568
|
-
|
|
569
|
-
): Promise<FilledDocument> {
|
|
546
|
+
signatureBlob: Blob
|
|
547
|
+
): Promise<FilledDocumentFileValue> {
|
|
570
548
|
console.log(
|
|
571
|
-
`[FilledDocumentService] Uploading signature for
|
|
549
|
+
`[FilledDocumentService] Uploading signature for form ${formId}`
|
|
572
550
|
);
|
|
573
551
|
|
|
574
|
-
//
|
|
552
|
+
// Generate a filename for the signature
|
|
553
|
+
const signatureId = this.generateId();
|
|
575
554
|
const signatureFile = new File(
|
|
576
555
|
[signatureBlob],
|
|
577
|
-
`signature_${
|
|
578
|
-
{
|
|
579
|
-
type: "image/png",
|
|
580
|
-
}
|
|
556
|
+
`signature_${signatureId}.png`,
|
|
557
|
+
{ type: "image/png" }
|
|
581
558
|
);
|
|
582
559
|
|
|
560
|
+
// Use the general file upload method
|
|
583
561
|
return this.uploadFileForFilledDocument(
|
|
584
562
|
appointmentId,
|
|
585
563
|
formId,
|
|
586
564
|
isUserForm,
|
|
587
|
-
signatureFile
|
|
588
|
-
fieldId,
|
|
589
|
-
MediaAccessLevel.CONFIDENTIAL // Signatures should be confidential
|
|
590
|
-
);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
/**
|
|
594
|
-
* Remove a file from a filled document field.
|
|
595
|
-
* This will both update the document and delete the media file.
|
|
596
|
-
* @param appointmentId - ID of the appointment.
|
|
597
|
-
* @param formId - ID of the filled document.
|
|
598
|
-
* @param isUserForm - Boolean indicating if it's a user form or doctor form.
|
|
599
|
-
* @param fieldId - The ID of the field containing the file.
|
|
600
|
-
* @returns The updated filled document with the file removed.
|
|
601
|
-
*/
|
|
602
|
-
async removeFileFromFilledDocument(
|
|
603
|
-
appointmentId: string,
|
|
604
|
-
formId: string,
|
|
605
|
-
isUserForm: boolean,
|
|
606
|
-
fieldId: string
|
|
607
|
-
): Promise<FilledDocument> {
|
|
608
|
-
console.log(
|
|
609
|
-
`[FilledDocumentService] Removing file from field ${fieldId} in form ${formId}`
|
|
610
|
-
);
|
|
611
|
-
|
|
612
|
-
// 1. Get the existing document to verify it exists and to get the file info
|
|
613
|
-
const existingDoc = await this.getFilledDocumentFromAppointmentById(
|
|
614
|
-
appointmentId,
|
|
615
|
-
formId,
|
|
616
|
-
isUserForm
|
|
617
|
-
);
|
|
618
|
-
|
|
619
|
-
if (!existingDoc) {
|
|
620
|
-
throw new Error(
|
|
621
|
-
`Filled document with ID ${formId} not found in appointment ${appointmentId}`
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// 2. Check if the field has a file value
|
|
626
|
-
const fileValue = existingDoc.values?.[fieldId] as FilledDocumentFileValue;
|
|
627
|
-
if (fileValue && fileValue.mediaId) {
|
|
628
|
-
// 3. Delete the file using MediaService
|
|
629
|
-
try {
|
|
630
|
-
await this.mediaService.deleteMedia(fileValue.mediaId);
|
|
631
|
-
} catch (error) {
|
|
632
|
-
console.error(
|
|
633
|
-
`[FilledDocumentService] Error deleting media ${fileValue.mediaId}:`,
|
|
634
|
-
error
|
|
635
|
-
);
|
|
636
|
-
// Continue with document update even if media deletion fails
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// 4. Update the document to remove the file reference
|
|
641
|
-
const values = {
|
|
642
|
-
[fieldId]: null,
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
// 5. Use the existing update method to save the changes
|
|
646
|
-
return this.updateFilledDocumentInAppointment(
|
|
647
|
-
appointmentId,
|
|
648
|
-
formId,
|
|
649
|
-
isUserForm,
|
|
650
|
-
values
|
|
565
|
+
signatureFile
|
|
651
566
|
);
|
|
652
567
|
}
|
|
653
568
|
|
|
654
569
|
/**
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
* @param
|
|
658
|
-
* @
|
|
659
|
-
* @param fieldId - The ID of the field containing the file.
|
|
660
|
-
* @returns The download URL for the file, or null if not found.
|
|
570
|
+
* Delete a file using its mediaId.
|
|
571
|
+
*
|
|
572
|
+
* @param mediaId - ID of the media to delete
|
|
573
|
+
* @returns Promise resolving when the deletion is complete
|
|
661
574
|
*/
|
|
662
|
-
async
|
|
663
|
-
appointmentId: string,
|
|
664
|
-
formId: string,
|
|
665
|
-
isUserForm: boolean,
|
|
666
|
-
fieldId: string
|
|
667
|
-
): Promise<string | null> {
|
|
575
|
+
async deleteFile(mediaId: string): Promise<void> {
|
|
668
576
|
console.log(
|
|
669
|
-
`[FilledDocumentService]
|
|
577
|
+
`[FilledDocumentService] Deleting file with mediaId ${mediaId}`
|
|
670
578
|
);
|
|
671
579
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
appointmentId,
|
|
675
|
-
formId,
|
|
676
|
-
isUserForm
|
|
677
|
-
);
|
|
678
|
-
|
|
679
|
-
if (!doc) {
|
|
680
|
-
return null;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// 2. Check if the field has a file value
|
|
684
|
-
const fileValue = doc.values?.[fieldId] as FilledDocumentFileValue;
|
|
685
|
-
if (fileValue && fileValue.url) {
|
|
686
|
-
return fileValue.url;
|
|
580
|
+
if (!mediaId) {
|
|
581
|
+
throw new Error("MediaId is required to delete a file");
|
|
687
582
|
}
|
|
688
583
|
|
|
689
|
-
|
|
584
|
+
// Delete the file using MediaService
|
|
585
|
+
await this.mediaService.deleteMedia(mediaId);
|
|
690
586
|
}
|
|
691
587
|
}
|
|
@@ -34,6 +34,11 @@ import {
|
|
|
34
34
|
} from "../../types/practitioner";
|
|
35
35
|
import { ProcedureSummaryInfo } from "../../types/procedure";
|
|
36
36
|
import { ClinicService } from "../clinic/clinic.service";
|
|
37
|
+
import {
|
|
38
|
+
MediaService,
|
|
39
|
+
MediaAccessLevel,
|
|
40
|
+
MediaResource,
|
|
41
|
+
} from "../media/media.service";
|
|
37
42
|
import {
|
|
38
43
|
practitionerSchema,
|
|
39
44
|
createPractitionerSchema,
|
|
@@ -53,6 +58,7 @@ import { ClinicInfo } from "../../types/profile";
|
|
|
53
58
|
|
|
54
59
|
export class PractitionerService extends BaseService {
|
|
55
60
|
private clinicService?: ClinicService;
|
|
61
|
+
private mediaService: MediaService;
|
|
56
62
|
|
|
57
63
|
constructor(
|
|
58
64
|
db: Firestore,
|
|
@@ -62,6 +68,7 @@ export class PractitionerService extends BaseService {
|
|
|
62
68
|
) {
|
|
63
69
|
super(db, auth, app);
|
|
64
70
|
this.clinicService = clinicService;
|
|
71
|
+
this.mediaService = new MediaService(db, auth, app);
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
private getClinicService(): ClinicService {
|
|
@@ -75,6 +82,71 @@ export class PractitionerService extends BaseService {
|
|
|
75
82
|
this.clinicService = clinicService;
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Handles profile photo upload for practitioners
|
|
87
|
+
* @param profilePhoto - MediaResource (File, Blob, or URL string)
|
|
88
|
+
* @param practitionerId - ID of the practitioner
|
|
89
|
+
* @returns URL string of the uploaded or existing photo
|
|
90
|
+
*/
|
|
91
|
+
private async handleProfilePhotoUpload(
|
|
92
|
+
profilePhoto: MediaResource | undefined,
|
|
93
|
+
practitionerId: string
|
|
94
|
+
): Promise<string | undefined> {
|
|
95
|
+
if (!profilePhoto) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// If it's already a URL string, return it as is
|
|
100
|
+
if (typeof profilePhoto === "string") {
|
|
101
|
+
return profilePhoto;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// If it's a File or Blob, upload it
|
|
105
|
+
if (profilePhoto instanceof File || profilePhoto instanceof Blob) {
|
|
106
|
+
console.log(
|
|
107
|
+
`[PractitionerService] Uploading profile photo for practitioner ${practitionerId}`
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const mediaMetadata = await this.mediaService.uploadMedia(
|
|
111
|
+
profilePhoto,
|
|
112
|
+
practitionerId, // Using practitionerId as ownerId
|
|
113
|
+
MediaAccessLevel.PUBLIC, // Profile photos should be public
|
|
114
|
+
"practitioner_profile_photos",
|
|
115
|
+
profilePhoto instanceof File
|
|
116
|
+
? profilePhoto.name
|
|
117
|
+
: `profile_photo_${practitionerId}`
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return mediaMetadata.url;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Processes BasicPractitionerInfo to handle profile photo uploads
|
|
128
|
+
* @param basicInfo - The basic info containing potential MediaResource profile photo
|
|
129
|
+
* @param practitionerId - ID of the practitioner
|
|
130
|
+
* @returns Processed basic info with URL string for profileImageUrl
|
|
131
|
+
*/
|
|
132
|
+
private async processBasicInfo(
|
|
133
|
+
basicInfo: PractitionerBasicInfo & { profileImageUrl?: MediaResource },
|
|
134
|
+
practitionerId: string
|
|
135
|
+
): Promise<PractitionerBasicInfo> {
|
|
136
|
+
const processedBasicInfo = { ...basicInfo };
|
|
137
|
+
|
|
138
|
+
// Handle profile photo upload if needed
|
|
139
|
+
if (basicInfo.profileImageUrl) {
|
|
140
|
+
const uploadedUrl = await this.handleProfilePhotoUpload(
|
|
141
|
+
basicInfo.profileImageUrl,
|
|
142
|
+
practitionerId
|
|
143
|
+
);
|
|
144
|
+
processedBasicInfo.profileImageUrl = uploadedUrl;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return processedBasicInfo;
|
|
148
|
+
}
|
|
149
|
+
|
|
78
150
|
/**
|
|
79
151
|
* Creates a new practitioner
|
|
80
152
|
*/
|
|
@@ -104,7 +176,10 @@ export class PractitionerService extends BaseService {
|
|
|
104
176
|
} = {
|
|
105
177
|
id: practitionerId,
|
|
106
178
|
userRef: validData.userRef,
|
|
107
|
-
basicInfo:
|
|
179
|
+
basicInfo: await this.processBasicInfo(
|
|
180
|
+
validData.basicInfo,
|
|
181
|
+
practitionerId
|
|
182
|
+
),
|
|
108
183
|
certification: validData.certification,
|
|
109
184
|
clinics: validData.clinics || [],
|
|
110
185
|
clinicWorkingHours: validData.clinicWorkingHours || [],
|
|
@@ -254,7 +329,10 @@ export class PractitionerService extends BaseService {
|
|
|
254
329
|
} = {
|
|
255
330
|
id: practitionerId,
|
|
256
331
|
userRef: "", // Prazno - biće popunjeno kada korisnik kreira nalog
|
|
257
|
-
basicInfo:
|
|
332
|
+
basicInfo: await this.processBasicInfo(
|
|
333
|
+
validatedData.basicInfo,
|
|
334
|
+
practitionerId
|
|
335
|
+
),
|
|
258
336
|
certification: validatedData.certification,
|
|
259
337
|
clinics: clinics,
|
|
260
338
|
clinicWorkingHours: validatedData.clinicWorkingHours || [],
|
|
@@ -619,9 +697,20 @@ export class PractitionerService extends BaseService {
|
|
|
619
697
|
|
|
620
698
|
const currentPractitioner = practitionerDoc.data() as Practitioner;
|
|
621
699
|
|
|
700
|
+
// Process basicInfo if it's being updated to handle profile photo uploads
|
|
701
|
+
let processedData = { ...validData };
|
|
702
|
+
if (validData.basicInfo) {
|
|
703
|
+
processedData.basicInfo = await this.processBasicInfo(
|
|
704
|
+
validData.basicInfo as PractitionerBasicInfo & {
|
|
705
|
+
profileImageUrl?: MediaResource;
|
|
706
|
+
},
|
|
707
|
+
practitionerId
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
622
711
|
// Prepare update data
|
|
623
712
|
const updateData = {
|
|
624
|
-
...
|
|
713
|
+
...processedData,
|
|
625
714
|
updatedAt: serverTimestamp(),
|
|
626
715
|
};
|
|
627
716
|
|
|
@@ -251,7 +251,10 @@ export class ProcedureService extends BaseService {
|
|
|
251
251
|
id: practitionerSnapshot.id,
|
|
252
252
|
name: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
|
|
253
253
|
description: practitioner.basicInfo.bio || "",
|
|
254
|
-
photo:
|
|
254
|
+
photo:
|
|
255
|
+
typeof practitioner.basicInfo.profileImageUrl === "string"
|
|
256
|
+
? practitioner.basicInfo.profileImageUrl
|
|
257
|
+
: "", // Default to empty string if not a processed URL
|
|
255
258
|
rating: practitioner.reviewInfo?.averageRating || 0,
|
|
256
259
|
services: practitioner.procedures || [],
|
|
257
260
|
};
|
|
@@ -412,7 +415,10 @@ export class ProcedureService extends BaseService {
|
|
|
412
415
|
id: newPractitioner.id,
|
|
413
416
|
name: `${newPractitioner.basicInfo.firstName} ${newPractitioner.basicInfo.lastName}`,
|
|
414
417
|
description: newPractitioner.basicInfo.bio || "",
|
|
415
|
-
photo:
|
|
418
|
+
photo:
|
|
419
|
+
typeof newPractitioner.basicInfo.profileImageUrl === "string"
|
|
420
|
+
? newPractitioner.basicInfo.profileImageUrl
|
|
421
|
+
: "", // Default to empty string if not a processed URL
|
|
416
422
|
rating: newPractitioner.reviewInfo?.averageRating || 0,
|
|
417
423
|
services: newPractitioner.procedures || [],
|
|
418
424
|
};
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
PricingMeasure,
|
|
12
12
|
} from "../../backoffice/types/static/pricing.types";
|
|
13
13
|
import { ProcedureSummaryInfo } from "../procedure";
|
|
14
|
+
import type { MediaResource } from "../../services/media/media.service";
|
|
14
15
|
|
|
15
16
|
export const PRACTITIONERS_COLLECTION = "practitioners";
|
|
16
17
|
export const REGISTER_TOKENS_COLLECTION = "register_tokens";
|
|
@@ -26,7 +27,7 @@ export interface PractitionerBasicInfo {
|
|
|
26
27
|
phoneNumber: string;
|
|
27
28
|
dateOfBirth: Timestamp | Date;
|
|
28
29
|
gender: "male" | "female" | "other";
|
|
29
|
-
profileImageUrl?:
|
|
30
|
+
profileImageUrl?: MediaResource;
|
|
30
31
|
bio?: string;
|
|
31
32
|
languages: string[];
|
|
32
33
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
PricingMeasure,
|
|
16
16
|
} from "../backoffice/types/static/pricing.types";
|
|
17
17
|
import { clinicInfoSchema, procedureSummaryInfoSchema } from "./shared.schema";
|
|
18
|
+
import { mediaResourceSchema } from "./media.schema";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Šema za validaciju osnovnih informacija o zdravstvenom radniku
|
|
@@ -27,7 +28,7 @@ export const practitionerBasicInfoSchema = z.object({
|
|
|
27
28
|
phoneNumber: z.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number"),
|
|
28
29
|
dateOfBirth: z.instanceof(Timestamp).or(z.date()),
|
|
29
30
|
gender: z.enum(["male", "female", "other"]),
|
|
30
|
-
profileImageUrl:
|
|
31
|
+
profileImageUrl: mediaResourceSchema.optional(),
|
|
31
32
|
bio: z.string().max(1000).optional(),
|
|
32
33
|
languages: z.array(z.string()).min(1),
|
|
33
34
|
});
|
|
@@ -204,7 +205,7 @@ export const practitionerSignupSchema = z.object({
|
|
|
204
205
|
basicInfo: z
|
|
205
206
|
.object({
|
|
206
207
|
phoneNumber: z.string().optional(),
|
|
207
|
-
profileImageUrl:
|
|
208
|
+
profileImageUrl: mediaResourceSchema.optional(),
|
|
208
209
|
gender: z.enum(["male", "female", "other"]).optional(),
|
|
209
210
|
bio: z.string().optional(),
|
|
210
211
|
})
|