@blackcode_sa/metaestetics-api 1.5.51 → 1.6.0

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.
@@ -61,6 +61,16 @@ import { clinicAdminSignupSchema } from "../validations/clinic.schema";
61
61
  import { ClinicGroupService } from "./clinic/clinic-group.service";
62
62
  import { ClinicAdminService } from "./clinic/clinic-admin.service";
63
63
  import { ClinicService } from "./clinic/clinic.service";
64
+ import {
65
+ Practitioner,
66
+ CreatePractitionerData,
67
+ PractitionerStatus,
68
+ PractitionerBasicInfo,
69
+ PractitionerCertification,
70
+ } from "../types/practitioner";
71
+ import { PractitionerService } from "./practitioner/practitioner.service";
72
+ import { practitionerSignupSchema } from "../validations/practitioner.schema";
73
+ import { CertificationLevel } from "../backoffice/types/static/certification.types";
64
74
 
65
75
  export class AuthService extends BaseService {
66
76
  private googleProvider = new GoogleAuthProvider();
@@ -858,4 +868,289 @@ export class AuthService extends BaseService {
858
868
  throw error;
859
869
  }
860
870
  }
871
+
872
+ /**
873
+ * Registers a new practitioner user with email and password
874
+ * Can either create a new practitioner profile or claim an existing draft profile with a token
875
+ *
876
+ * @param data - Practitioner signup data containing either new profile details or token for claiming draft profile
877
+ * @returns Object containing the created user and practitioner profile
878
+ */
879
+ async signUpPractitioner(data: {
880
+ email: string;
881
+ password: string;
882
+ firstName: string;
883
+ lastName: string;
884
+ token?: string;
885
+ profileData?: Partial<CreatePractitionerData>;
886
+ }): Promise<{
887
+ user: User;
888
+ practitioner: Practitioner;
889
+ }> {
890
+ try {
891
+ console.log("[AUTH] Starting practitioner signup process", {
892
+ email: data.email,
893
+ hasToken: !!data.token,
894
+ });
895
+
896
+ // Validate data
897
+ try {
898
+ await practitionerSignupSchema.parseAsync(data);
899
+ console.log("[AUTH] Practitioner signup data validation passed");
900
+ } catch (validationError) {
901
+ console.error(
902
+ "[AUTH] Validation error in signUpPractitioner:",
903
+ validationError
904
+ );
905
+ throw validationError;
906
+ }
907
+
908
+ // Create Firebase user
909
+ console.log("[AUTH] Creating Firebase user");
910
+ let firebaseUser;
911
+ try {
912
+ const result = await createUserWithEmailAndPassword(
913
+ this.auth,
914
+ data.email,
915
+ data.password
916
+ );
917
+ firebaseUser = result.user;
918
+ console.log("[AUTH] Firebase user created successfully", {
919
+ uid: firebaseUser.uid,
920
+ });
921
+ } catch (firebaseError) {
922
+ console.error("[AUTH] Firebase user creation failed:", firebaseError);
923
+ throw firebaseError;
924
+ }
925
+
926
+ // Create user with PRACTITIONER role
927
+ console.log("[AUTH] Creating user with PRACTITIONER role");
928
+ let user;
929
+ try {
930
+ user = await this.userService.createUser(
931
+ firebaseUser,
932
+ [UserRole.PRACTITIONER],
933
+ {
934
+ skipProfileCreation: true, // We'll create the profile separately
935
+ }
936
+ );
937
+ console.log("[AUTH] User with PRACTITIONER role created successfully", {
938
+ userId: user.uid,
939
+ });
940
+ } catch (userCreationError) {
941
+ console.error("[AUTH] User creation failed:", userCreationError);
942
+ throw userCreationError;
943
+ }
944
+
945
+ // Initialize practitioner service
946
+ console.log("[AUTH] Initializing practitioner service");
947
+ const practitionerService = new PractitionerService(
948
+ this.db,
949
+ this.auth,
950
+ this.app
951
+ );
952
+
953
+ let practitioner: Practitioner | null = null;
954
+
955
+ // Check if we're claiming an existing draft profile with a token
956
+ if (data.token) {
957
+ console.log("[AUTH] Token provided, attempting to claim draft profile");
958
+
959
+ try {
960
+ // Validate token and claim the profile
961
+ practitioner = await practitionerService.validateTokenAndClaimProfile(
962
+ data.token,
963
+ firebaseUser.uid
964
+ );
965
+
966
+ if (!practitioner) {
967
+ throw new Error("Invalid or expired invitation token");
968
+ }
969
+
970
+ console.log("[AUTH] Successfully claimed draft profile", {
971
+ practitionerId: practitioner.id,
972
+ });
973
+
974
+ // Link the practitioner profile to the user
975
+ await this.userService.updateUser(firebaseUser.uid, {
976
+ practitionerProfile: practitioner.id,
977
+ });
978
+ console.log(
979
+ "[AUTH] User updated with practitioner profile reference"
980
+ );
981
+ } catch (tokenError) {
982
+ console.error("[AUTH] Failed to claim draft profile:", tokenError);
983
+ throw tokenError;
984
+ }
985
+ } else {
986
+ console.log("[AUTH] Creating new practitioner profile");
987
+
988
+ if (!data.profileData) {
989
+ data.profileData = {};
990
+ }
991
+
992
+ // We need to create a full PractitionerBasicInfo object
993
+ const basicInfo: PractitionerBasicInfo = {
994
+ firstName: data.firstName,
995
+ lastName: data.lastName,
996
+ email: data.email,
997
+ phoneNumber: data.profileData.basicInfo?.phoneNumber || "",
998
+ profileImageUrl: data.profileData.basicInfo?.profileImageUrl || "",
999
+ gender: data.profileData.basicInfo?.gender || "other", // Default to "other" if not provided
1000
+ bio: data.profileData.basicInfo?.bio || "",
1001
+ title: "Practitioner", // Default title
1002
+ dateOfBirth: new Date(), // Default to today
1003
+ languages: ["English"], // Default language
1004
+ };
1005
+
1006
+ // Basic certification information with placeholders
1007
+ const certification: PractitionerCertification = data.profileData
1008
+ .certification || {
1009
+ level: CertificationLevel.AESTHETICIAN,
1010
+ specialties: [],
1011
+ licenseNumber: "Pending",
1012
+ issuingAuthority: "Pending",
1013
+ issueDate: new Date(),
1014
+ verificationStatus: "pending",
1015
+ };
1016
+
1017
+ // Create basic profile data
1018
+ const createPractitionerData: CreatePractitionerData = {
1019
+ userRef: firebaseUser.uid,
1020
+ basicInfo,
1021
+ certification,
1022
+ status: PractitionerStatus.ACTIVE,
1023
+ isActive: true,
1024
+ isVerified: false,
1025
+ };
1026
+
1027
+ try {
1028
+ practitioner = await practitionerService.createPractitioner(
1029
+ createPractitionerData
1030
+ );
1031
+ console.log("[AUTH] Practitioner profile created successfully", {
1032
+ practitionerId: practitioner.id,
1033
+ });
1034
+
1035
+ // Link the practitioner profile to the user
1036
+ await this.userService.updateUser(firebaseUser.uid, {
1037
+ practitionerProfile: practitioner.id,
1038
+ });
1039
+ console.log(
1040
+ "[AUTH] User updated with practitioner profile reference"
1041
+ );
1042
+ } catch (createError) {
1043
+ console.error(
1044
+ "[AUTH] Failed to create practitioner profile:",
1045
+ createError
1046
+ );
1047
+ throw createError;
1048
+ }
1049
+ }
1050
+
1051
+ console.log("[AUTH] Practitioner signup completed successfully", {
1052
+ userId: user.uid,
1053
+ practitionerId: practitioner?.id || "unknown",
1054
+ });
1055
+
1056
+ return {
1057
+ user,
1058
+ practitioner,
1059
+ };
1060
+ } catch (error) {
1061
+ if (error instanceof z.ZodError) {
1062
+ console.error(
1063
+ "[AUTH] Zod validation error in signUpPractitioner:",
1064
+ JSON.stringify(error.errors, null, 2)
1065
+ );
1066
+ throw AUTH_ERRORS.VALIDATION_ERROR;
1067
+ }
1068
+
1069
+ const firebaseError = error as FirebaseError;
1070
+ if (firebaseError.code === FirebaseErrorCode.EMAIL_ALREADY_IN_USE) {
1071
+ console.error("[AUTH] Email already in use:", data.email);
1072
+ throw AUTH_ERRORS.EMAIL_ALREADY_EXISTS;
1073
+ }
1074
+
1075
+ console.error("[AUTH] Unhandled error in signUpPractitioner:", error);
1076
+ throw error;
1077
+ }
1078
+ }
1079
+
1080
+ /**
1081
+ * Signs in a user with email and password specifically for practitioner role
1082
+ * @param email - User's email
1083
+ * @param password - User's password
1084
+ * @returns Object containing the user and practitioner profile
1085
+ * @throws {AUTH_ERRORS.INVALID_ROLE} If user doesn't have practitioner role
1086
+ * @throws {AUTH_ERRORS.NOT_FOUND} If practitioner profile is not found
1087
+ */
1088
+ async signInPractitioner(
1089
+ email: string,
1090
+ password: string
1091
+ ): Promise<{
1092
+ user: User;
1093
+ practitioner: Practitioner;
1094
+ }> {
1095
+ try {
1096
+ console.log("[AUTH] Starting practitioner signin process", {
1097
+ email: email,
1098
+ });
1099
+
1100
+ // Initialize required service
1101
+ const practitionerService = new PractitionerService(
1102
+ this.db,
1103
+ this.auth,
1104
+ this.app
1105
+ );
1106
+
1107
+ // Sign in with email/password
1108
+ const { user: firebaseUser } = await signInWithEmailAndPassword(
1109
+ this.auth,
1110
+ email,
1111
+ password
1112
+ );
1113
+
1114
+ // Get or create user
1115
+ const user = await this.userService.getOrCreateUser(firebaseUser);
1116
+ console.log("[AUTH] User retrieved", { uid: user.uid });
1117
+
1118
+ // Check if user has practitioner role
1119
+ if (!user.roles?.includes(UserRole.PRACTITIONER)) {
1120
+ console.error("[AUTH] User is not a practitioner:", user.uid);
1121
+ throw AUTH_ERRORS.INVALID_ROLE;
1122
+ }
1123
+
1124
+ // Check and get practitioner profile
1125
+ if (!user.practitionerProfile) {
1126
+ console.error("[AUTH] User has no practitioner profile:", user.uid);
1127
+ throw AUTH_ERRORS.NOT_FOUND;
1128
+ }
1129
+
1130
+ // Get practitioner profile
1131
+ const practitioner = await practitionerService.getPractitioner(
1132
+ user.practitionerProfile
1133
+ );
1134
+ if (!practitioner) {
1135
+ console.error(
1136
+ "[AUTH] Practitioner profile not found:",
1137
+ user.practitionerProfile
1138
+ );
1139
+ throw AUTH_ERRORS.NOT_FOUND;
1140
+ }
1141
+
1142
+ console.log("[AUTH] Practitioner signin completed successfully", {
1143
+ userId: user.uid,
1144
+ practitionerId: practitioner.id,
1145
+ });
1146
+
1147
+ return {
1148
+ user,
1149
+ practitioner,
1150
+ };
1151
+ } catch (error) {
1152
+ console.error("[AUTH] Error in signInPractitioner:", error);
1153
+ throw error;
1154
+ }
1155
+ }
861
1156
  }
@@ -189,3 +189,27 @@ export const createPractitionerTokenSchema = z.object({
189
189
  clinicId: z.string().min(1),
190
190
  expiresAt: z.date().optional(),
191
191
  });
192
+
193
+ /**
194
+ * Simplified schema for practitioner signup
195
+ */
196
+ export const practitionerSignupSchema = z.object({
197
+ email: z.string().email(),
198
+ password: z.string().min(8),
199
+ firstName: z.string().min(2).max(50),
200
+ lastName: z.string().min(2).max(50),
201
+ token: z.string().optional(),
202
+ profileData: z
203
+ .object({
204
+ basicInfo: z
205
+ .object({
206
+ phoneNumber: z.string().optional(),
207
+ profileImageUrl: z.string().optional(),
208
+ gender: z.enum(["male", "female", "other"]).optional(),
209
+ bio: z.string().optional(),
210
+ })
211
+ .optional(),
212
+ certification: z.any().optional(),
213
+ })
214
+ .optional(),
215
+ });