@blackcode_sa/metaestetics-api 1.12.16 → 1.12.19

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.
@@ -3388,7 +3388,7 @@ declare class DocumentManagerAdminService {
3388
3388
 
3389
3389
  /**
3390
3390
  * Ensures that the free consultation infrastructure exists in the database
3391
- * Creates category, subcategory, and technology if they don't exist
3391
+ * Creates category, subcategory, technology, and product if they don't exist
3392
3392
  * @param db - Firestore database instance (optional, defaults to admin.firestore())
3393
3393
  * @returns Promise<boolean> - Always returns true after ensuring infrastructure exists
3394
3394
  */
@@ -3388,7 +3388,7 @@ declare class DocumentManagerAdminService {
3388
3388
 
3389
3389
  /**
3390
3390
  * Ensures that the free consultation infrastructure exists in the database
3391
- * Creates category, subcategory, and technology if they don't exist
3391
+ * Creates category, subcategory, technology, and product if they don't exist
3392
3392
  * @param db - Firestore database instance (optional, defaults to admin.firestore())
3393
3393
  * @returns Promise<boolean> - Always returns true after ensuring infrastructure exists
3394
3394
  */
@@ -7383,13 +7383,14 @@ var SUBCATEGORIES_COLLECTION = "subcategories";
7383
7383
  // src/backoffice/types/technology.types.ts
7384
7384
  var TECHNOLOGIES_COLLECTION = "technologies";
7385
7385
 
7386
+ // src/backoffice/types/product.types.ts
7387
+ var PRODUCTS_COLLECTION = "products";
7388
+
7386
7389
  // src/admin/free-consultation/free-consultation-utils.admin.ts
7387
7390
  async function freeConsultationInfrastructure(db) {
7388
7391
  const firestore18 = db || admin16.firestore();
7389
7392
  try {
7390
- console.log(
7391
- "[freeConsultationInfrastructure] Checking free consultation infrastructure..."
7392
- );
7393
+ console.log("[freeConsultationInfrastructure] Checking free consultation infrastructure...");
7393
7394
  const technologyRef = firestore18.collection(TECHNOLOGIES_COLLECTION).doc("free-consultation-tech");
7394
7395
  const technologyDoc = await technologyRef.get();
7395
7396
  if (technologyDoc.exists) {
@@ -7398,19 +7399,14 @@ async function freeConsultationInfrastructure(db) {
7398
7399
  );
7399
7400
  return true;
7400
7401
  }
7401
- console.log(
7402
- "[freeConsultationInfrastructure] Creating free consultation infrastructure..."
7403
- );
7402
+ console.log("[freeConsultationInfrastructure] Creating free consultation infrastructure...");
7404
7403
  await createFreeConsultationInfrastructure(firestore18);
7405
7404
  console.log(
7406
7405
  "[freeConsultationInfrastructure] Successfully created free consultation infrastructure"
7407
7406
  );
7408
7407
  return true;
7409
7408
  } catch (error) {
7410
- console.error(
7411
- "[freeConsultationInfrastructure] Error ensuring infrastructure:",
7412
- error
7413
- );
7409
+ console.error("[freeConsultationInfrastructure] Error ensuring infrastructure:", error);
7414
7410
  throw error;
7415
7411
  }
7416
7412
  }
@@ -7474,11 +7470,32 @@ async function createFreeConsultationInfrastructure(db) {
7474
7470
  updatedAt: now
7475
7471
  };
7476
7472
  batch.set(technologyRef, technologyData);
7473
+ const productRef = db.collection(TECHNOLOGIES_COLLECTION).doc("free-consultation-tech").collection(PRODUCTS_COLLECTION).doc("free-consultation-product");
7474
+ const productData = {
7475
+ id: "free-consultation-product",
7476
+ name: "Free Consultation Service",
7477
+ description: "No physical product required for consultation services",
7478
+ brandId: "consultation-brand",
7479
+ brandName: "Consultation Services",
7480
+ technologyId: "free-consultation-tech",
7481
+ technologyName: "Free Consultation Technology",
7482
+ categoryId: "consultation",
7483
+ subcategoryId: "free-consultation",
7484
+ isActive: true,
7485
+ createdAt: now,
7486
+ updatedAt: now,
7487
+ technicalDetails: "Virtual consultation service - no physical product required",
7488
+ warnings: [],
7489
+ indications: ["Initial patient assessment", "Treatment planning", "Patient education"],
7490
+ contraindications: []
7491
+ };
7492
+ batch.set(productRef, productData);
7477
7493
  await batch.commit();
7478
7494
  console.log("[freeConsultationInfrastructure] Successfully created:");
7479
7495
  console.log(" \u2713 Category: consultation");
7480
7496
  console.log(" \u2713 Subcategory: free-consultation");
7481
7497
  console.log(" \u2713 Technology: free-consultation-tech");
7498
+ console.log(" \u2713 Product: free-consultation-product");
7482
7499
  }
7483
7500
 
7484
7501
  // src/admin/mailing/practitionerInvite/templates/invitation.template.ts
@@ -7321,13 +7321,14 @@ var SUBCATEGORIES_COLLECTION = "subcategories";
7321
7321
  // src/backoffice/types/technology.types.ts
7322
7322
  var TECHNOLOGIES_COLLECTION = "technologies";
7323
7323
 
7324
+ // src/backoffice/types/product.types.ts
7325
+ var PRODUCTS_COLLECTION = "products";
7326
+
7324
7327
  // src/admin/free-consultation/free-consultation-utils.admin.ts
7325
7328
  async function freeConsultationInfrastructure(db) {
7326
7329
  const firestore18 = db || admin16.firestore();
7327
7330
  try {
7328
- console.log(
7329
- "[freeConsultationInfrastructure] Checking free consultation infrastructure..."
7330
- );
7331
+ console.log("[freeConsultationInfrastructure] Checking free consultation infrastructure...");
7331
7332
  const technologyRef = firestore18.collection(TECHNOLOGIES_COLLECTION).doc("free-consultation-tech");
7332
7333
  const technologyDoc = await technologyRef.get();
7333
7334
  if (technologyDoc.exists) {
@@ -7336,19 +7337,14 @@ async function freeConsultationInfrastructure(db) {
7336
7337
  );
7337
7338
  return true;
7338
7339
  }
7339
- console.log(
7340
- "[freeConsultationInfrastructure] Creating free consultation infrastructure..."
7341
- );
7340
+ console.log("[freeConsultationInfrastructure] Creating free consultation infrastructure...");
7342
7341
  await createFreeConsultationInfrastructure(firestore18);
7343
7342
  console.log(
7344
7343
  "[freeConsultationInfrastructure] Successfully created free consultation infrastructure"
7345
7344
  );
7346
7345
  return true;
7347
7346
  } catch (error) {
7348
- console.error(
7349
- "[freeConsultationInfrastructure] Error ensuring infrastructure:",
7350
- error
7351
- );
7347
+ console.error("[freeConsultationInfrastructure] Error ensuring infrastructure:", error);
7352
7348
  throw error;
7353
7349
  }
7354
7350
  }
@@ -7412,11 +7408,32 @@ async function createFreeConsultationInfrastructure(db) {
7412
7408
  updatedAt: now
7413
7409
  };
7414
7410
  batch.set(technologyRef, technologyData);
7411
+ const productRef = db.collection(TECHNOLOGIES_COLLECTION).doc("free-consultation-tech").collection(PRODUCTS_COLLECTION).doc("free-consultation-product");
7412
+ const productData = {
7413
+ id: "free-consultation-product",
7414
+ name: "Free Consultation Service",
7415
+ description: "No physical product required for consultation services",
7416
+ brandId: "consultation-brand",
7417
+ brandName: "Consultation Services",
7418
+ technologyId: "free-consultation-tech",
7419
+ technologyName: "Free Consultation Technology",
7420
+ categoryId: "consultation",
7421
+ subcategoryId: "free-consultation",
7422
+ isActive: true,
7423
+ createdAt: now,
7424
+ updatedAt: now,
7425
+ technicalDetails: "Virtual consultation service - no physical product required",
7426
+ warnings: [],
7427
+ indications: ["Initial patient assessment", "Treatment planning", "Patient education"],
7428
+ contraindications: []
7429
+ };
7430
+ batch.set(productRef, productData);
7415
7431
  await batch.commit();
7416
7432
  console.log("[freeConsultationInfrastructure] Successfully created:");
7417
7433
  console.log(" \u2713 Category: consultation");
7418
7434
  console.log(" \u2713 Subcategory: free-consultation");
7419
7435
  console.log(" \u2713 Technology: free-consultation-tech");
7436
+ console.log(" \u2713 Product: free-consultation-product");
7420
7437
  }
7421
7438
 
7422
7439
  // src/admin/mailing/practitionerInvite/templates/invitation.template.ts
@@ -2276,8 +2276,8 @@ var TechnologyService = class extends BaseService {
2276
2276
  * console.log(allowedTechnologies.subcategories); // ["subcategory1", "subcategory2"]
2277
2277
  */
2278
2278
  async getAllowedTechnologies(practitioner) {
2279
- const allTechnologies = await this.getAll();
2280
- const allowedTechnologies = allTechnologies.technologies.filter(
2279
+ const allTechnologies = await this.getAllForFilter();
2280
+ const allowedTechnologies = allTechnologies.filter(
2281
2281
  (technology) => this.validateCertification(technology.certificationRequirement, practitioner.certification)
2282
2282
  );
2283
2283
  const families = [...new Set(allowedTechnologies.map((t) => t.family))];
@@ -2292,8 +2292,8 @@ var TechnologyService = class extends BaseService {
2292
2292
  * console.log(allowedTechnologies.subcategories); // ["subcategory1", "subcategory2"]
2293
2293
  */
2294
2294
  async getAllowedTechnologies(practitioner) {
2295
- const allTechnologies = await this.getAll();
2296
- const allowedTechnologies = allTechnologies.technologies.filter(
2295
+ const allTechnologies = await this.getAllForFilter();
2296
+ const allowedTechnologies = allTechnologies.filter(
2297
2297
  (technology) => this.validateCertification(technology.certificationRequirement, practitioner.certification)
2298
2298
  );
2299
2299
  const families = [...new Set(allowedTechnologies.map((t) => t.family))];
package/dist/index.js CHANGED
@@ -7406,9 +7406,12 @@ var UserService = class extends BaseService {
7406
7406
  if ((await this.getUserById(userId)).patientProfile || patientProfile2.userRef) {
7407
7407
  throw new Error("User already has a patient profile.");
7408
7408
  }
7409
+ const sensitiveInfo = await patientService.getSensitiveInfo(patientProfile2.id, userId);
7410
+ const fullDisplayName = sensitiveInfo ? `${sensitiveInfo.firstName} ${sensitiveInfo.lastName}` : patientProfile2.displayName;
7409
7411
  await patientService.updatePatientProfile(patientProfile2.id, {
7410
7412
  userRef: userId,
7411
- isManual: false
7413
+ isManual: false,
7414
+ displayName: fullDisplayName
7412
7415
  });
7413
7416
  await patientService.markPatientTokenAsUsed(token.id, token.patientId, userId);
7414
7417
  profiles.patientProfile = patientProfile2.id;
@@ -10352,6 +10355,34 @@ var AuthService = class extends BaseService {
10352
10355
  async signInWithGoogleIdToken(idToken, initialRole = "patient" /* PATIENT */) {
10353
10356
  try {
10354
10357
  console.log("[AUTH] Signing in with Google ID Token");
10358
+ let email;
10359
+ try {
10360
+ const payloadBase64 = idToken.split(".")[1];
10361
+ const payloadJson = globalThis.atob ? globalThis.atob(payloadBase64) : Buffer.from(payloadBase64, "base64").toString("utf8");
10362
+ const payload = JSON.parse(payloadJson);
10363
+ email = payload.email;
10364
+ } catch (decodeError) {
10365
+ console.warn("[AUTH] Failed to decode email from Google ID token:", decodeError);
10366
+ }
10367
+ if (!email) {
10368
+ throw new AuthError(
10369
+ "Unable to read email from Google token. Please try again or use another sign-in method.",
10370
+ "AUTH/INVALID_GOOGLE_TOKEN",
10371
+ 400
10372
+ );
10373
+ }
10374
+ const methods = await (0, import_auth7.fetchSignInMethodsForEmail)(this.auth, email);
10375
+ console.log("[AUTH] Fetch sign in methods for email:", email, methods);
10376
+ const hasGoogleMethod = methods.includes(import_auth7.GoogleAuthProvider.GOOGLE_SIGN_IN_METHOD);
10377
+ console.log("[AUTH] Has Google method:", hasGoogleMethod);
10378
+ if (!hasGoogleMethod) {
10379
+ console.log("[AUTH] No existing Google credential for email, aborting login:", email);
10380
+ throw new AuthError(
10381
+ "No account found for this Google user. Please complete registration first.",
10382
+ "AUTH/USER_NOT_FOUND",
10383
+ 404
10384
+ );
10385
+ }
10355
10386
  const credential = import_auth7.GoogleAuthProvider.credential(idToken);
10356
10387
  const { user: firebaseUser } = await (0, import_auth7.signInWithCredential)(this.auth, credential);
10357
10388
  console.log("[AUTH] Firebase user signed in:", firebaseUser.uid);
@@ -10360,7 +10391,7 @@ var AuthService = class extends BaseService {
10360
10391
  console.log("[AUTH] Existing user found, returning profile:", existingUser.uid);
10361
10392
  return existingUser;
10362
10393
  }
10363
- console.log("[AUTH] No existing user found for Google account:", firebaseUser.email);
10394
+ console.log("[AUTH] No existing MetaEstetics user for Google account \u2013 signing out.");
10364
10395
  await (0, import_auth7.signOut)(this.auth);
10365
10396
  throw new AuthError(
10366
10397
  'No account found. Please complete registration by starting with "Get Started".',
@@ -17270,8 +17301,8 @@ var TechnologyService = class extends BaseService {
17270
17301
  * console.log(allowedTechnologies.subcategories); // ["subcategory1", "subcategory2"]
17271
17302
  */
17272
17303
  async getAllowedTechnologies(practitioner) {
17273
- const allTechnologies = await this.getAll();
17274
- const allowedTechnologies = allTechnologies.technologies.filter(
17304
+ const allTechnologies = await this.getAllForFilter();
17305
+ const allowedTechnologies = allTechnologies.filter(
17275
17306
  (technology) => this.validateCertification(technology.certificationRequirement, practitioner.certification)
17276
17307
  );
17277
17308
  const families = [...new Set(allowedTechnologies.map((t) => t.family))];
package/dist/index.mjs CHANGED
@@ -1798,6 +1798,7 @@ import {
1798
1798
  sendPasswordResetEmail,
1799
1799
  verifyPasswordResetCode,
1800
1800
  confirmPasswordReset,
1801
+ fetchSignInMethodsForEmail as fetchSignInMethodsForEmail2,
1801
1802
  signInWithCredential
1802
1803
  } from "firebase/auth";
1803
1804
  import {
@@ -7446,9 +7447,12 @@ var UserService = class extends BaseService {
7446
7447
  if ((await this.getUserById(userId)).patientProfile || patientProfile2.userRef) {
7447
7448
  throw new Error("User already has a patient profile.");
7448
7449
  }
7450
+ const sensitiveInfo = await patientService.getSensitiveInfo(patientProfile2.id, userId);
7451
+ const fullDisplayName = sensitiveInfo ? `${sensitiveInfo.firstName} ${sensitiveInfo.lastName}` : patientProfile2.displayName;
7449
7452
  await patientService.updatePatientProfile(patientProfile2.id, {
7450
7453
  userRef: userId,
7451
- isManual: false
7454
+ isManual: false,
7455
+ displayName: fullDisplayName
7452
7456
  });
7453
7457
  await patientService.markPatientTokenAsUsed(token.id, token.patientId, userId);
7454
7458
  profiles.patientProfile = patientProfile2.id;
@@ -10456,6 +10460,34 @@ var AuthService = class extends BaseService {
10456
10460
  async signInWithGoogleIdToken(idToken, initialRole = "patient" /* PATIENT */) {
10457
10461
  try {
10458
10462
  console.log("[AUTH] Signing in with Google ID Token");
10463
+ let email;
10464
+ try {
10465
+ const payloadBase64 = idToken.split(".")[1];
10466
+ const payloadJson = globalThis.atob ? globalThis.atob(payloadBase64) : Buffer.from(payloadBase64, "base64").toString("utf8");
10467
+ const payload = JSON.parse(payloadJson);
10468
+ email = payload.email;
10469
+ } catch (decodeError) {
10470
+ console.warn("[AUTH] Failed to decode email from Google ID token:", decodeError);
10471
+ }
10472
+ if (!email) {
10473
+ throw new AuthError(
10474
+ "Unable to read email from Google token. Please try again or use another sign-in method.",
10475
+ "AUTH/INVALID_GOOGLE_TOKEN",
10476
+ 400
10477
+ );
10478
+ }
10479
+ const methods = await fetchSignInMethodsForEmail2(this.auth, email);
10480
+ console.log("[AUTH] Fetch sign in methods for email:", email, methods);
10481
+ const hasGoogleMethod = methods.includes(GoogleAuthProvider.GOOGLE_SIGN_IN_METHOD);
10482
+ console.log("[AUTH] Has Google method:", hasGoogleMethod);
10483
+ if (!hasGoogleMethod) {
10484
+ console.log("[AUTH] No existing Google credential for email, aborting login:", email);
10485
+ throw new AuthError(
10486
+ "No account found for this Google user. Please complete registration first.",
10487
+ "AUTH/USER_NOT_FOUND",
10488
+ 404
10489
+ );
10490
+ }
10459
10491
  const credential = GoogleAuthProvider.credential(idToken);
10460
10492
  const { user: firebaseUser } = await signInWithCredential(this.auth, credential);
10461
10493
  console.log("[AUTH] Firebase user signed in:", firebaseUser.uid);
@@ -10464,7 +10496,7 @@ var AuthService = class extends BaseService {
10464
10496
  console.log("[AUTH] Existing user found, returning profile:", existingUser.uid);
10465
10497
  return existingUser;
10466
10498
  }
10467
- console.log("[AUTH] No existing user found for Google account:", firebaseUser.email);
10499
+ console.log("[AUTH] No existing MetaEstetics user for Google account \u2013 signing out.");
10468
10500
  await firebaseSignOut(this.auth);
10469
10501
  throw new AuthError(
10470
10502
  'No account found. Please complete registration by starting with "Get Started".',
@@ -17589,8 +17621,8 @@ var TechnologyService = class extends BaseService {
17589
17621
  * console.log(allowedTechnologies.subcategories); // ["subcategory1", "subcategory2"]
17590
17622
  */
17591
17623
  async getAllowedTechnologies(practitioner) {
17592
- const allTechnologies = await this.getAll();
17593
- const allowedTechnologies = allTechnologies.technologies.filter(
17624
+ const allTechnologies = await this.getAllForFilter();
17625
+ const allowedTechnologies = allTechnologies.filter(
17594
17626
  (technology) => this.validateCertification(technology.certificationRequirement, practitioner.certification)
17595
17627
  );
17596
17628
  const families = [...new Set(allowedTechnologies.map((t) => t.family))];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.12.16",
4
+ "version": "1.12.19",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -0,0 +1,82 @@
1
+ # Free Consultation Infrastructure
2
+
3
+ This module manages the infrastructure required for free consultation procedures in the MetaEstetics system.
4
+
5
+ ## Overview
6
+
7
+ The free consultation feature allows practitioners to offer complimentary initial consultations to patients. This requires specific infrastructure components to be present in the database.
8
+
9
+ ## Infrastructure Components
10
+
11
+ The system creates the following components when `freeConsultationInfrastructure()` is called:
12
+
13
+ ### 1. Category: "consultation"
14
+ - **ID**: `consultation`
15
+ - **Name**: Consultation
16
+ - **Description**: Professional consultation services for treatment planning and assessment
17
+ - **Family**: AESTHETICS
18
+
19
+ ### 2. Subcategory: "free-consultation"
20
+ - **ID**: `free-consultation`
21
+ - **Name**: Free Consultation
22
+ - **Description**: Complimentary initial consultation to discuss treatment options and assess patient needs
23
+ - **Parent Category**: consultation
24
+
25
+ ### 3. Technology: "free-consultation-tech"
26
+ - **ID**: `free-consultation-tech`
27
+ - **Name**: Free Consultation Technology
28
+ - **Description**: Technology framework for providing free initial consultations to patients
29
+ - **Requirements**: No pre/post requirements
30
+ - **Certification**: Minimum level AESTHETICIAN
31
+
32
+ ### 4. Product: "free-consultation-product"
33
+ - **ID**: `free-consultation-product`
34
+ - **Name**: Free Consultation Service
35
+ - **Description**: No physical product required for consultation services
36
+ - **Brand**: Consultation Services
37
+ - **Type**: Virtual service
38
+
39
+ ## Usage
40
+
41
+ ### Ensuring Infrastructure Exists
42
+
43
+ ```typescript
44
+ import { freeConsultationInfrastructure } from './free-consultation-utils.admin';
45
+
46
+ // Ensure infrastructure exists before creating consultation procedures
47
+ const infrastructureExists = await freeConsultationInfrastructure();
48
+ ```
49
+
50
+ ### Creating Free Consultation Procedures
51
+
52
+ The infrastructure is automatically checked and created when practitioners enable free consultations through the `PractitionerService.EnableFreeConsultation()` method.
53
+
54
+ ## Error Handling
55
+
56
+ If you encounter the error:
57
+ ```
58
+ Product with ID free-consultation-product not found for technology free-consultation-tech
59
+ ```
60
+
61
+ This indicates that the infrastructure was not properly created. The system will automatically attempt to create the missing infrastructure when the error occurs.
62
+
63
+ ## Database Structure
64
+
65
+ ```
66
+ categories/
67
+ consultation/
68
+ subcategories/
69
+ free-consultation/
70
+
71
+ technologies/
72
+ free-consultation-tech/
73
+ products/
74
+ free-consultation-product/
75
+ ```
76
+
77
+ ## Notes
78
+
79
+ - The infrastructure is created atomically using Firestore batch operations
80
+ - All components are created with `isActive: true`
81
+ - The system checks for existing infrastructure before attempting to create new components
82
+ - No physical products are required for consultation procedures
@@ -1,80 +1,72 @@
1
- import * as admin from "firebase-admin";
2
- import { ProcedureFamily } from "../../backoffice/types/static/procedure-family.types";
1
+ import * as admin from 'firebase-admin';
2
+ import { ProcedureFamily } from '../../backoffice/types/static/procedure-family.types';
3
3
  import {
4
4
  CertificationLevel,
5
5
  CertificationSpecialty,
6
- } from "../../backoffice/types/static/certification.types";
7
- import { CATEGORIES_COLLECTION } from "../../backoffice/types/category.types";
8
- import { SUBCATEGORIES_COLLECTION } from "../../backoffice/types/subcategory.types";
9
- import { TECHNOLOGIES_COLLECTION } from "../../backoffice/types/technology.types";
6
+ } from '../../backoffice/types/static/certification.types';
7
+ import { CATEGORIES_COLLECTION } from '../../backoffice/types/category.types';
8
+ import { SUBCATEGORIES_COLLECTION } from '../../backoffice/types/subcategory.types';
9
+ import { TECHNOLOGIES_COLLECTION } from '../../backoffice/types/technology.types';
10
+ import { PRODUCTS_COLLECTION } from '../../backoffice/types/product.types';
10
11
 
11
12
  /**
12
13
  * Ensures that the free consultation infrastructure exists in the database
13
- * Creates category, subcategory, and technology if they don't exist
14
+ * Creates category, subcategory, technology, and product if they don't exist
14
15
  * @param db - Firestore database instance (optional, defaults to admin.firestore())
15
16
  * @returns Promise<boolean> - Always returns true after ensuring infrastructure exists
16
17
  */
17
18
  export async function freeConsultationInfrastructure(
18
- db?: admin.firestore.Firestore
19
+ db?: admin.firestore.Firestore,
19
20
  ): Promise<boolean> {
20
21
  const firestore = db || admin.firestore();
21
22
 
22
23
  try {
23
- console.log(
24
- "[freeConsultationInfrastructure] Checking free consultation infrastructure..."
25
- );
24
+ console.log('[freeConsultationInfrastructure] Checking free consultation infrastructure...');
26
25
 
27
26
  // Check if the technology already exists
28
27
  const technologyRef = firestore
29
28
  .collection(TECHNOLOGIES_COLLECTION)
30
- .doc("free-consultation-tech");
29
+ .doc('free-consultation-tech');
31
30
 
32
31
  const technologyDoc = await technologyRef.get();
33
32
 
34
33
  if (technologyDoc.exists) {
35
34
  console.log(
36
- "[freeConsultationInfrastructure] Free consultation infrastructure already exists"
35
+ '[freeConsultationInfrastructure] Free consultation infrastructure already exists',
37
36
  );
38
37
  return true;
39
38
  }
40
39
 
41
- console.log(
42
- "[freeConsultationInfrastructure] Creating free consultation infrastructure..."
43
- );
40
+ console.log('[freeConsultationInfrastructure] Creating free consultation infrastructure...');
44
41
 
45
- // Create the infrastructure in the correct order: Category → Subcategory → Technology
42
+ // Create the infrastructure in the correct order: Category → Subcategory → Technology → Product
46
43
  await createFreeConsultationInfrastructure(firestore);
47
44
 
48
45
  console.log(
49
- "[freeConsultationInfrastructure] Successfully created free consultation infrastructure"
46
+ '[freeConsultationInfrastructure] Successfully created free consultation infrastructure',
50
47
  );
51
48
  return true;
52
49
  } catch (error) {
53
- console.error(
54
- "[freeConsultationInfrastructure] Error ensuring infrastructure:",
55
- error
56
- );
50
+ console.error('[freeConsultationInfrastructure] Error ensuring infrastructure:', error);
57
51
  throw error;
58
52
  }
59
53
  }
60
54
 
61
55
  /**
62
56
  * Creates the complete free consultation infrastructure
57
+ * Creates category, subcategory, technology, and product in the correct order
63
58
  * @param db - Firestore database instance
64
59
  */
65
- async function createFreeConsultationInfrastructure(
66
- db: admin.firestore.Firestore
67
- ): Promise<void> {
60
+ async function createFreeConsultationInfrastructure(db: admin.firestore.Firestore): Promise<void> {
68
61
  const batch = db.batch();
69
62
  const now = new Date();
70
63
 
71
64
  // 1. Create Category: "consultation"
72
- const categoryRef = db.collection(CATEGORIES_COLLECTION).doc("consultation");
65
+ const categoryRef = db.collection(CATEGORIES_COLLECTION).doc('consultation');
73
66
  const categoryData = {
74
- id: "consultation",
75
- name: "Consultation",
76
- description:
77
- "Professional consultation services for treatment planning and assessment",
67
+ id: 'consultation',
68
+ name: 'Consultation',
69
+ description: 'Professional consultation services for treatment planning and assessment',
78
70
  family: ProcedureFamily.AESTHETICS,
79
71
  isActive: true,
80
72
  createdAt: now,
@@ -85,16 +77,16 @@ async function createFreeConsultationInfrastructure(
85
77
  // 2. Create Subcategory: "free-consultation"
86
78
  const subcategoryRef = db
87
79
  .collection(CATEGORIES_COLLECTION)
88
- .doc("consultation")
80
+ .doc('consultation')
89
81
  .collection(SUBCATEGORIES_COLLECTION)
90
- .doc("free-consultation");
82
+ .doc('free-consultation');
91
83
 
92
84
  const subcategoryData = {
93
- id: "free-consultation",
94
- name: "Free Consultation",
85
+ id: 'free-consultation',
86
+ name: 'Free Consultation',
95
87
  description:
96
- "Complimentary initial consultation to discuss treatment options and assess patient needs",
97
- categoryId: "consultation",
88
+ 'Complimentary initial consultation to discuss treatment options and assess patient needs',
89
+ categoryId: 'consultation',
98
90
  isActive: true,
99
91
  createdAt: now,
100
92
  updatedAt: now,
@@ -102,19 +94,15 @@ async function createFreeConsultationInfrastructure(
102
94
  batch.set(subcategoryRef, subcategoryData);
103
95
 
104
96
  // 3. Create Technology: "free-consultation-tech"
105
- const technologyRef = db
106
- .collection(TECHNOLOGIES_COLLECTION)
107
- .doc("free-consultation-tech");
97
+ const technologyRef = db.collection(TECHNOLOGIES_COLLECTION).doc('free-consultation-tech');
108
98
  const technologyData = {
109
- id: "free-consultation-tech",
110
- name: "Free Consultation Technology",
111
- description:
112
- "Technology framework for providing free initial consultations to patients",
99
+ id: 'free-consultation-tech',
100
+ name: 'Free Consultation Technology',
101
+ description: 'Technology framework for providing free initial consultations to patients',
113
102
  family: ProcedureFamily.AESTHETICS,
114
- categoryId: "consultation",
115
- subcategoryId: "free-consultation",
116
- technicalDetails:
117
- "Standard consultation protocol for initial patient assessment",
103
+ categoryId: 'consultation',
104
+ subcategoryId: 'free-consultation',
105
+ technicalDetails: 'Standard consultation protocol for initial patient assessment',
118
106
  requirements: {
119
107
  pre: [], // No pre-requirements for consultation
120
108
  post: [], // No post-requirements for consultation
@@ -122,9 +110,9 @@ async function createFreeConsultationInfrastructure(
122
110
  blockingConditions: [], // No blocking conditions for consultation
123
111
  contraindications: [], // No contraindications for consultation
124
112
  benefits: [
125
- "IMPROVED_PATIENT_UNDERSTANDING",
126
- "BETTER_TREATMENT_PLANNING",
127
- "ENHANCED_PATIENT_CONFIDENCE",
113
+ 'IMPROVED_PATIENT_UNDERSTANDING',
114
+ 'BETTER_TREATMENT_PLANNING',
115
+ 'ENHANCED_PATIENT_CONFIDENCE',
128
116
  ],
129
117
  certificationRequirement: {
130
118
  minimumLevel: CertificationLevel.AESTHETICIAN,
@@ -137,11 +125,39 @@ async function createFreeConsultationInfrastructure(
137
125
  };
138
126
  batch.set(technologyRef, technologyData);
139
127
 
128
+ // 4. Create Product: "free-consultation-product"
129
+ const productRef = db
130
+ .collection(TECHNOLOGIES_COLLECTION)
131
+ .doc('free-consultation-tech')
132
+ .collection(PRODUCTS_COLLECTION)
133
+ .doc('free-consultation-product');
134
+
135
+ const productData = {
136
+ id: 'free-consultation-product',
137
+ name: 'Free Consultation Service',
138
+ description: 'No physical product required for consultation services',
139
+ brandId: 'consultation-brand',
140
+ brandName: 'Consultation Services',
141
+ technologyId: 'free-consultation-tech',
142
+ technologyName: 'Free Consultation Technology',
143
+ categoryId: 'consultation',
144
+ subcategoryId: 'free-consultation',
145
+ isActive: true,
146
+ createdAt: now,
147
+ updatedAt: now,
148
+ technicalDetails: 'Virtual consultation service - no physical product required',
149
+ warnings: [],
150
+ indications: ['Initial patient assessment', 'Treatment planning', 'Patient education'],
151
+ contraindications: [],
152
+ };
153
+ batch.set(productRef, productData);
154
+
140
155
  // Commit all changes atomically
141
156
  await batch.commit();
142
157
 
143
- console.log("[freeConsultationInfrastructure] Successfully created:");
144
- console.log(" ✓ Category: consultation");
145
- console.log(" ✓ Subcategory: free-consultation");
146
- console.log(" ✓ Technology: free-consultation-tech");
158
+ console.log('[freeConsultationInfrastructure] Successfully created:');
159
+ console.log(' ✓ Category: consultation');
160
+ console.log(' ✓ Subcategory: free-consultation');
161
+ console.log(' ✓ Technology: free-consultation-tech');
162
+ console.log(' ✓ Product: free-consultation-product');
147
163
  }
@@ -693,10 +693,10 @@ export class TechnologyService extends BaseService implements ITechnologyService
693
693
  subcategories: string[];
694
694
  }> {
695
695
  // Get all active technologies
696
- const allTechnologies = await this.getAll();
696
+ const allTechnologies = await this.getAllForFilter();
697
697
 
698
698
  // Filter technologies based on certification requirements
699
- const allowedTechnologies = allTechnologies.technologies.filter(technology =>
699
+ const allowedTechnologies = allTechnologies.filter(technology =>
700
700
  this.validateCertification(technology.certificationRequirement, practitioner.certification),
701
701
  );
702
702
 
@@ -883,23 +883,58 @@ export class AuthService extends BaseService {
883
883
  ): Promise<User> {
884
884
  try {
885
885
  console.log('[AUTH] Signing in with Google ID Token');
886
+
887
+ // 1) Extract the email claim from the raw JWT so we can check Auth records *before* sign-in
888
+ let email: string | undefined;
889
+ try {
890
+ const payloadBase64 = idToken.split('.')[1];
891
+ const payloadJson = globalThis.atob
892
+ ? globalThis.atob(payloadBase64)
893
+ : Buffer.from(payloadBase64, 'base64').toString('utf8');
894
+ const payload = JSON.parse(payloadJson);
895
+ email = payload.email as string | undefined;
896
+ } catch (decodeError) {
897
+ console.warn('[AUTH] Failed to decode email from Google ID token:', decodeError);
898
+ }
899
+
900
+ if (!email) {
901
+ throw new AuthError(
902
+ 'Unable to read email from Google token. Please try again or use another sign-in method.',
903
+ 'AUTH/INVALID_GOOGLE_TOKEN',
904
+ 400,
905
+ );
906
+ }
907
+
908
+ // 2) Check if this email already has a Google credential in Firebase Auth.
909
+ // If not, abort early so we *never* create an unwanted Auth user.
910
+ const methods = await fetchSignInMethodsForEmail(this.auth, email);
911
+ console.log('[AUTH] Fetch sign in methods for email:', email, methods);
912
+ const hasGoogleMethod = methods.includes(GoogleAuthProvider.GOOGLE_SIGN_IN_METHOD);
913
+ console.log('[AUTH] Has Google method:', hasGoogleMethod);
914
+ if (!hasGoogleMethod) {
915
+ console.log('[AUTH] No existing Google credential for email, aborting login:', email);
916
+ throw new AuthError(
917
+ 'No account found for this Google user. Please complete registration first.',
918
+ 'AUTH/USER_NOT_FOUND',
919
+ 404,
920
+ );
921
+ }
922
+
923
+ // 3) Safe to sign-in – we know the credential belongs to an existing Auth account.
886
924
  const credential = GoogleAuthProvider.credential(idToken);
887
925
  const { user: firebaseUser } = await signInWithCredential(this.auth, credential);
888
926
  console.log('[AUTH] Firebase user signed in:', firebaseUser.uid);
889
927
 
890
- // Check if the user already has a profile in our database
928
+ // 4) Load our domain user document.
891
929
  const existingUser = await this.userService.getUserById(firebaseUser.uid);
892
930
  if (existingUser) {
893
931
  console.log('[AUTH] Existing user found, returning profile:', existingUser.uid);
894
932
  return existingUser;
895
933
  }
896
934
 
897
- // If no profile exists, reject the login - user must complete onboarding first
898
- console.log('[AUTH] No existing user found for Google account:', firebaseUser.email);
899
-
900
- // Sign out the Firebase user since we don't allow auto-registration
935
+ // 5) If no profile exists we sign out immediately and error but crucially no phantom user was created.
936
+ console.log('[AUTH] No existing MetaEstetics user for Google account – signing out.');
901
937
  await firebaseSignOut(this.auth);
902
-
903
938
  throw new AuthError(
904
939
  'No account found. Please complete registration by starting with "Get Started".',
905
940
  'AUTH/USER_NOT_FOUND',
@@ -178,10 +178,17 @@ export class UserService extends BaseService {
178
178
  throw new Error('User already has a patient profile.');
179
179
  }
180
180
 
181
- // Claim the profile: link userRef and set isManual to false
181
+ // Claim the profile: link userRef, set isManual to false, and update displayName
182
+ // Get sensitive info to construct full display name
183
+ const sensitiveInfo = await patientService.getSensitiveInfo(patientProfile.id, userId);
184
+ const fullDisplayName = sensitiveInfo
185
+ ? `${sensitiveInfo.firstName} ${sensitiveInfo.lastName}`
186
+ : patientProfile.displayName;
187
+
182
188
  await patientService.updatePatientProfile(patientProfile.id, {
183
189
  userRef: userId,
184
190
  isManual: false,
191
+ displayName: fullDisplayName,
185
192
  });
186
193
 
187
194
  // Mark the token as used