@blackcode_sa/metaestetics-api 1.8.17 → 1.10.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.8.17",
4
+ "version": "1.10.0",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -16,8 +16,8 @@ import {
16
16
  writeBatch,
17
17
  arrayUnion,
18
18
  arrayRemove,
19
- } from "firebase/firestore";
20
- import { BaseService } from "../base.service";
19
+ } from 'firebase/firestore';
20
+ import { BaseService } from '../base.service';
21
21
  import {
22
22
  Clinic,
23
23
  CreateClinicData,
@@ -29,35 +29,31 @@ import {
29
29
  ClinicBranchSetupData,
30
30
  CLINIC_ADMINS_COLLECTION,
31
31
  DoctorInfo,
32
- } from "../../types/clinic";
32
+ } from '../../types/clinic';
33
33
  // Correct imports
34
- import { ProcedureSummaryInfo } from "../../types/procedure";
35
- import { ClinicInfo } from "../../types/profile";
36
- import { ClinicGroupService } from "./clinic-group.service";
37
- import { ClinicAdminService } from "./clinic-admin.service";
38
- import {
39
- geohashForLocation,
40
- geohashQueryBounds,
41
- distanceBetween,
42
- } from "geofire-common";
34
+ import { ProcedureSummaryInfo } from '../../types/procedure';
35
+ import { ClinicInfo } from '../../types/profile';
36
+ import { ClinicGroupService } from './clinic-group.service';
37
+ import { ClinicAdminService } from './clinic-admin.service';
38
+ import { geohashForLocation, geohashQueryBounds, distanceBetween } from 'geofire-common';
43
39
  import {
44
40
  clinicSchema,
45
41
  createClinicSchema,
46
42
  updateClinicSchema,
47
43
  clinicBranchSetupSchema,
48
- } from "../../validations/clinic.schema";
49
- import { z } from "zod";
50
- import { Auth } from "firebase/auth";
51
- import { Firestore } from "firebase/firestore";
52
- import { FirebaseApp } from "firebase/app";
53
- import * as ClinicUtils from "./utils/clinic.utils";
54
- import * as TagUtils from "./utils/tag.utils";
55
- import * as SearchUtils from "./utils/search.utils";
56
- import * as AdminUtils from "./utils/admin.utils";
57
- import * as FilterUtils from "./utils/filter.utils";
58
- import { ClinicReviewInfo } from "../../types/reviews";
59
- import { PRACTITIONERS_COLLECTION } from "../../types/practitioner";
60
- import { MediaService, MediaAccessLevel } from "../media/media.service";
44
+ } from '../../validations/clinic.schema';
45
+ import { z } from 'zod';
46
+ import { Auth } from 'firebase/auth';
47
+ import { Firestore } from 'firebase/firestore';
48
+ import { FirebaseApp } from 'firebase/app';
49
+ import * as ClinicUtils from './utils/clinic.utils';
50
+ import * as TagUtils from './utils/tag.utils';
51
+ import * as SearchUtils from './utils/search.utils';
52
+ import * as AdminUtils from './utils/admin.utils';
53
+ import * as FilterUtils from './utils/filter.utils';
54
+ import { ClinicReviewInfo } from '../../types/reviews';
55
+ import { PRACTITIONERS_COLLECTION } from '../../types/practitioner';
56
+ import { MediaService, MediaAccessLevel } from '../media/media.service';
61
57
 
62
58
  export class ClinicService extends BaseService {
63
59
  private clinicGroupService: ClinicGroupService;
@@ -70,7 +66,7 @@ export class ClinicService extends BaseService {
70
66
  app: FirebaseApp,
71
67
  clinicGroupService: ClinicGroupService,
72
68
  clinicAdminService: ClinicAdminService,
73
- mediaService: MediaService
69
+ mediaService: MediaService,
74
70
  ) {
75
71
  super(db, auth, app);
76
72
  this.clinicAdminService = clinicAdminService;
@@ -88,25 +84,23 @@ export class ClinicService extends BaseService {
88
84
  private async processMedia(
89
85
  media: string | File | Blob | null | undefined,
90
86
  ownerId: string,
91
- collectionName: string
87
+ collectionName: string,
92
88
  ): Promise<string | null> {
93
89
  if (!media) return null;
94
90
 
95
91
  // If already a string URL, return it directly
96
- if (typeof media === "string") {
92
+ if (typeof media === 'string') {
97
93
  return media;
98
94
  }
99
95
 
100
96
  // If it's a File, upload it using MediaService
101
97
  if (media instanceof File || media instanceof Blob) {
102
- console.log(
103
- `[ClinicService] Uploading ${collectionName} media for ${ownerId}`
104
- );
98
+ console.log(`[ClinicService] Uploading ${collectionName} media for ${ownerId}`);
105
99
  const metadata = await this.mediaService.uploadMedia(
106
100
  media,
107
101
  ownerId,
108
102
  MediaAccessLevel.PUBLIC,
109
- collectionName
103
+ collectionName,
110
104
  );
111
105
  return metadata.url;
112
106
  }
@@ -124,18 +118,14 @@ export class ClinicService extends BaseService {
124
118
  private async processMediaArray(
125
119
  mediaArray: (string | File | Blob)[] | undefined,
126
120
  ownerId: string,
127
- collectionName: string
121
+ collectionName: string,
128
122
  ): Promise<string[]> {
129
123
  if (!mediaArray || mediaArray.length === 0) return [];
130
124
 
131
125
  const result: string[] = [];
132
126
 
133
127
  for (const media of mediaArray) {
134
- const processedUrl = await this.processMedia(
135
- media,
136
- ownerId,
137
- collectionName
138
- );
128
+ const processedUrl = await this.processMedia(media, ownerId, collectionName);
139
129
  if (processedUrl) {
140
130
  result.push(processedUrl);
141
131
  }
@@ -154,18 +144,14 @@ export class ClinicService extends BaseService {
154
144
  private async processPhotosWithTags(
155
145
  photosWithTags: { url: string | File | Blob; tag: string }[] | undefined,
156
146
  ownerId: string,
157
- collectionName: string
147
+ collectionName: string,
158
148
  ): Promise<{ url: string; tag: string }[]> {
159
149
  if (!photosWithTags || photosWithTags.length === 0) return [];
160
150
 
161
151
  const result: { url: string; tag: string }[] = [];
162
152
 
163
153
  for (const item of photosWithTags) {
164
- const processedUrl = await this.processMedia(
165
- item.url,
166
- ownerId,
167
- collectionName
168
- );
154
+ const processedUrl = await this.processMedia(item.url, ownerId, collectionName);
169
155
  if (processedUrl) {
170
156
  result.push({
171
157
  url: processedUrl,
@@ -181,10 +167,7 @@ export class ClinicService extends BaseService {
181
167
  * Creates a new clinic.
182
168
  * Handles both URL strings and File uploads for media fields.
183
169
  */
184
- async createClinic(
185
- data: CreateClinicData,
186
- creatorAdminId: string
187
- ): Promise<Clinic> {
170
+ async createClinic(data: CreateClinicData, creatorAdminId: string): Promise<Clinic> {
188
171
  try {
189
172
  // Generate ID first so we can use it for media uploads
190
173
  const clinicId = this.generateId();
@@ -192,35 +175,27 @@ export class ClinicService extends BaseService {
192
175
  // Validate data - this now works because mediaResourceSchema has been updated to support Files/Blobs
193
176
  const validatedData = createClinicSchema.parse(data);
194
177
 
195
- const group = await this.clinicGroupService.getClinicGroup(
196
- validatedData.clinicGroupId
197
- );
178
+ const group = await this.clinicGroupService.getClinicGroup(validatedData.clinicGroupId);
198
179
  if (!group) {
199
- throw new Error(
200
- `Clinic group ${validatedData.clinicGroupId} not found`
201
- );
180
+ throw new Error(`Clinic group ${validatedData.clinicGroupId} not found`);
202
181
  }
203
182
 
204
183
  // Process media fields - convert File/Blob objects to URLs
205
- const logoUrl = await this.processMedia(
206
- validatedData.logo,
207
- clinicId,
208
- "clinic-logos"
209
- );
184
+ const logoUrl = await this.processMedia(validatedData.logo, clinicId, 'clinic-logos');
210
185
  const coverPhotoUrl = await this.processMedia(
211
186
  validatedData.coverPhoto,
212
187
  clinicId,
213
- "clinic-cover-photos"
188
+ 'clinic-cover-photos',
214
189
  );
215
190
  const featuredPhotos = await this.processMediaArray(
216
191
  validatedData.featuredPhotos,
217
192
  clinicId,
218
- "clinic-featured-photos"
193
+ 'clinic-featured-photos',
219
194
  );
220
195
  const photosWithTags = await this.processPhotosWithTags(
221
196
  validatedData.photosWithTags,
222
197
  clinicId,
223
- "clinic-gallery"
198
+ 'clinic-gallery',
224
199
  );
225
200
 
226
201
  const location = validatedData.location;
@@ -237,7 +212,7 @@ export class ClinicService extends BaseService {
237
212
  recommendationPercentage: 0,
238
213
  };
239
214
 
240
- const clinicData: Omit<Clinic, "createdAt" | "updatedAt"> & {
215
+ const clinicData: Omit<Clinic, 'createdAt' | 'updatedAt'> & {
241
216
  createdAt: FieldValue;
242
217
  updatedAt: FieldValue;
243
218
  } = {
@@ -259,12 +234,8 @@ export class ClinicService extends BaseService {
259
234
  proceduresInfo: validatedData.proceduresInfo || [],
260
235
  reviewInfo: defaultReviewInfo,
261
236
  admins: [creatorAdminId],
262
- isActive:
263
- validatedData.isActive !== undefined ? validatedData.isActive : true,
264
- isVerified:
265
- validatedData.isVerified !== undefined
266
- ? validatedData.isVerified
267
- : false,
237
+ isActive: validatedData.isActive !== undefined ? validatedData.isActive : true,
238
+ isVerified: validatedData.isVerified !== undefined ? validatedData.isVerified : false,
268
239
  logo: logoUrl,
269
240
  createdAt: serverTimestamp(),
270
241
  updatedAt: serverTimestamp(),
@@ -295,13 +266,13 @@ export class ClinicService extends BaseService {
295
266
  console.log(`[ClinicService] Clinic created successfully: ${clinicId}`);
296
267
 
297
268
  const savedClinic = await this.getClinic(clinicId);
298
- if (!savedClinic) throw new Error("Failed to retrieve created clinic");
269
+ if (!savedClinic) throw new Error('Failed to retrieve created clinic');
299
270
  return savedClinic;
300
271
  } catch (error) {
301
272
  if (error instanceof z.ZodError) {
302
- throw new Error("Invalid clinic data: " + error.message);
273
+ throw new Error('Invalid clinic data: ' + error.message);
303
274
  }
304
- console.error("Error creating clinic:", error);
275
+ console.error('Error creating clinic:', error);
305
276
  throw error;
306
277
  }
307
278
  }
@@ -313,7 +284,7 @@ export class ClinicService extends BaseService {
313
284
  async updateClinic(
314
285
  clinicId: string,
315
286
  data: Partial<CreateClinicData>,
316
- adminId: string
287
+ adminId: string,
317
288
  ): Promise<Clinic> {
318
289
  try {
319
290
  // First check if clinic exists
@@ -333,18 +304,14 @@ export class ClinicService extends BaseService {
333
304
 
334
305
  // Process media fields if provided
335
306
  if (validatedData.logo !== undefined) {
336
- updatePayload.logo = await this.processMedia(
337
- validatedData.logo,
338
- clinicId,
339
- "clinic-logos"
340
- );
307
+ updatePayload.logo = await this.processMedia(validatedData.logo, clinicId, 'clinic-logos');
341
308
  }
342
309
 
343
310
  if (validatedData.coverPhoto !== undefined) {
344
311
  updatePayload.coverPhoto = await this.processMedia(
345
312
  validatedData.coverPhoto,
346
313
  clinicId,
347
- "clinic-cover-photos"
314
+ 'clinic-cover-photos',
348
315
  );
349
316
  }
350
317
 
@@ -352,7 +319,7 @@ export class ClinicService extends BaseService {
352
319
  updatePayload.featuredPhotos = await this.processMediaArray(
353
320
  validatedData.featuredPhotos,
354
321
  clinicId,
355
- "clinic-featured-photos"
322
+ 'clinic-featured-photos',
356
323
  );
357
324
  }
358
325
 
@@ -360,28 +327,27 @@ export class ClinicService extends BaseService {
360
327
  updatePayload.photosWithTags = await this.processPhotosWithTags(
361
328
  validatedData.photosWithTags,
362
329
  clinicId,
363
- "clinic-gallery"
330
+ 'clinic-gallery',
364
331
  );
365
332
  }
366
333
 
367
334
  // Process non-media fields
368
335
  const fieldsToUpdate = [
369
- "name",
370
- "description",
371
- "contactInfo",
372
- "workingHours",
373
- "tags",
374
- "doctors",
375
- "procedures",
376
- "proceduresInfo",
377
- "isActive",
378
- "isVerified",
336
+ 'name',
337
+ 'description',
338
+ 'contactInfo',
339
+ 'workingHours',
340
+ 'tags',
341
+ 'doctors',
342
+ 'procedures',
343
+ 'proceduresInfo',
344
+ 'isActive',
345
+ 'isVerified',
379
346
  ];
380
347
 
381
348
  for (const field of fieldsToUpdate) {
382
349
  if (validatedData[field as keyof typeof validatedData] !== undefined) {
383
- updatePayload[field] =
384
- validatedData[field as keyof typeof validatedData];
350
+ updatePayload[field] = validatedData[field as keyof typeof validatedData];
385
351
  }
386
352
  }
387
353
 
@@ -408,11 +374,11 @@ export class ClinicService extends BaseService {
408
374
 
409
375
  // Return the updated clinic
410
376
  const updatedClinic = await this.getClinic(clinicId);
411
- if (!updatedClinic) throw new Error("Failed to retrieve updated clinic");
377
+ if (!updatedClinic) throw new Error('Failed to retrieve updated clinic');
412
378
  return updatedClinic;
413
379
  } catch (error) {
414
380
  if (error instanceof z.ZodError) {
415
- throw new Error("Invalid clinic update data: " + error.message);
381
+ throw new Error('Invalid clinic update data: ' + error.message);
416
382
  }
417
383
  console.error(`Error updating clinic ${clinicId}:`, error);
418
384
  throw error;
@@ -467,14 +433,9 @@ export class ClinicService extends BaseService {
467
433
  procedures?: string[];
468
434
  tags?: ClinicTag[];
469
435
  // Add other relevant filters based on Clinic/ProcedureSummaryInfo fields
470
- }
436
+ },
471
437
  ): Promise<Clinic[]> {
472
- return SearchUtils.findClinicsInRadius(
473
- this.db,
474
- center,
475
- radiusInKm,
476
- filters
477
- );
438
+ return SearchUtils.findClinicsInRadius(this.db, center, radiusInKm, filters);
478
439
  }
479
440
 
480
441
  async addTags(
@@ -482,16 +443,9 @@ export class ClinicService extends BaseService {
482
443
  adminId: string,
483
444
  newTags: {
484
445
  tags?: ClinicTag[];
485
- }
446
+ },
486
447
  ): Promise<Clinic> {
487
- return TagUtils.addTags(
488
- this.db,
489
- clinicId,
490
- adminId,
491
- newTags,
492
- this.clinicAdminService,
493
- this.app
494
- );
448
+ return TagUtils.addTags(this.db, clinicId, adminId, newTags, this.clinicAdminService, this.app);
495
449
  }
496
450
 
497
451
  async removeTags(
@@ -499,7 +453,7 @@ export class ClinicService extends BaseService {
499
453
  adminId: string,
500
454
  tagsToRemove: {
501
455
  tags?: ClinicTag[];
502
- }
456
+ },
503
457
  ): Promise<Clinic> {
504
458
  return TagUtils.removeTags(
505
459
  this.db,
@@ -507,7 +461,7 @@ export class ClinicService extends BaseService {
507
461
  adminId,
508
462
  tagsToRemove,
509
463
  this.clinicAdminService,
510
- this.app
464
+ this.app,
511
465
  );
512
466
  }
513
467
 
@@ -516,14 +470,14 @@ export class ClinicService extends BaseService {
516
470
  options?: {
517
471
  isActive?: boolean;
518
472
  includeGroupClinics?: boolean; // ako je true i admin je owner, uključuje sve klinike grupe
519
- }
473
+ },
520
474
  ): Promise<Clinic[]> {
521
475
  return ClinicUtils.getClinicsByAdmin(
522
476
  this.db,
523
477
  adminId,
524
478
  options,
525
479
  this.clinicAdminService,
526
- this.clinicGroupService
480
+ this.clinicGroupService,
527
481
  );
528
482
  }
529
483
 
@@ -532,7 +486,7 @@ export class ClinicService extends BaseService {
532
486
  this.db,
533
487
  adminId,
534
488
  this.clinicAdminService,
535
- this.clinicGroupService
489
+ this.clinicGroupService,
536
490
  );
537
491
  }
538
492
 
@@ -543,24 +497,22 @@ export class ClinicService extends BaseService {
543
497
  async createClinicBranch(
544
498
  clinicGroupId: string,
545
499
  setupData: ClinicBranchSetupData,
546
- adminId: string
500
+ adminId: string,
547
501
  ): Promise<Clinic> {
548
- console.log("[CLINIC_SERVICE] Starting clinic branch creation", {
502
+ console.log('[CLINIC_SERVICE] Starting clinic branch creation', {
549
503
  clinicGroupId,
550
504
  adminId,
551
505
  });
552
506
 
553
507
  // Verify clinic group exists
554
- const clinicGroup = await this.clinicGroupService.getClinicGroup(
555
- clinicGroupId
556
- );
508
+ const clinicGroup = await this.clinicGroupService.getClinicGroup(clinicGroupId);
557
509
  if (!clinicGroup) {
558
- console.error("[CLINIC_SERVICE] Clinic group not found", {
510
+ console.error('[CLINIC_SERVICE] Clinic group not found', {
559
511
  clinicGroupId,
560
512
  });
561
513
  throw new Error(`Clinic group with ID ${clinicGroupId} not found`);
562
514
  }
563
- console.log("[CLINIC_SERVICE] Clinic group verified");
515
+ console.log('[CLINIC_SERVICE] Clinic group verified');
564
516
 
565
517
  // Validate branch setup data first
566
518
  const validatedSetupData = clinicBranchSetupSchema.parse(setupData);
@@ -586,7 +538,7 @@ export class ClinicService extends BaseService {
586
538
  isVerified: false,
587
539
  };
588
540
 
589
- console.log("[CLINIC_SERVICE] Creating clinic branch", {
541
+ console.log('[CLINIC_SERVICE] Creating clinic branch', {
590
542
  name: createClinicData.name,
591
543
  hasLogo: !!createClinicData.logo,
592
544
  hasCoverPhoto: !!createClinicData.coverPhoto,
@@ -595,7 +547,7 @@ export class ClinicService extends BaseService {
595
547
  // Use createClinic which now handles validation and media uploads
596
548
  const clinic = await this.createClinic(createClinicData, adminId);
597
549
 
598
- console.log("[CLINIC_SERVICE] Clinic branch created successfully", {
550
+ console.log('[CLINIC_SERVICE] Clinic branch created successfully', {
599
551
  clinicId: clinic.id,
600
552
  });
601
553
  return clinic;
@@ -607,7 +559,7 @@ export class ClinicService extends BaseService {
607
559
 
608
560
  async getAllClinics(
609
561
  pagination?: number,
610
- lastDoc?: any
562
+ lastDoc?: any,
611
563
  ): Promise<{ clinics: Clinic[]; lastDoc: any }> {
612
564
  return ClinicUtils.getAllClinics(this.db, pagination, lastDoc);
613
565
  }
@@ -616,15 +568,9 @@ export class ClinicService extends BaseService {
616
568
  center: { latitude: number; longitude: number },
617
569
  rangeInKm: number,
618
570
  pagination?: number,
619
- lastDoc?: any
571
+ lastDoc?: any,
620
572
  ): Promise<{ clinics: (Clinic & { distance: number })[]; lastDoc: any }> {
621
- return ClinicUtils.getAllClinicsInRange(
622
- this.db,
623
- center,
624
- rangeInKm,
625
- pagination,
626
- lastDoc
627
- );
573
+ return ClinicUtils.getAllClinicsInRange(this.db, center, rangeInKm, pagination, lastDoc);
628
574
  }
629
575
 
630
576
  /**
@@ -643,6 +589,7 @@ export class ClinicService extends BaseService {
643
589
  procedureTechnology?: string;
644
590
  minRating?: number;
645
591
  maxRating?: number;
592
+ nameSearch?: string;
646
593
  pagination?: number;
647
594
  lastDoc?: any;
648
595
  isActive?: boolean;
@@ -658,16 +605,18 @@ export class ClinicService extends BaseService {
658
605
  * This is optimized for mobile map usage to reduce payload size.
659
606
  * @returns Array of minimal clinic info for map
660
607
  */
661
- async getClinicsForMap(): Promise<{
662
- id: string;
663
- name: string;
664
- address: string;
665
- latitude: number | undefined;
666
- longitude: number | undefined;
667
- }[]> {
608
+ async getClinicsForMap(): Promise<
609
+ {
610
+ id: string;
611
+ name: string;
612
+ address: string;
613
+ latitude: number | undefined;
614
+ longitude: number | undefined;
615
+ }[]
616
+ > {
668
617
  const clinicsRef = collection(this.db, CLINICS_COLLECTION);
669
618
  const snapshot = await getDocs(clinicsRef);
670
- const clinicsForMap = snapshot.docs.map(doc => {
619
+ const clinicsForMap = snapshot.docs.map((doc) => {
671
620
  const data = doc.data();
672
621
  return {
673
622
  id: doc.id,