@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.
- package/dist/admin/index.d.mts +35 -37
- package/dist/admin/index.d.ts +35 -37
- package/dist/admin/index.js +109 -222
- package/dist/admin/index.mjs +109 -222
- package/dist/index.d.mts +106 -1
- package/dist/index.d.ts +106 -1
- package/dist/index.js +246 -0
- package/dist/index.mjs +245 -0
- package/package.json +1 -1
- package/src/admin/mailing/base.mailing.service.ts +99 -273
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +64 -30
- package/src/services/auth.service.ts +295 -0
- package/src/validations/practitioner.schema.ts +24 -0
|
@@ -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
|
+
});
|