@blackcode_sa/metaestetics-api 1.5.0 → 1.5.1
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/index.d.mts +571 -7
- package/dist/index.d.ts +571 -7
- package/dist/index.js +293 -2
- package/dist/index.mjs +287 -2
- package/package.json +1 -1
- package/src/index.ts +9 -1
- package/src/services/practitioner/practitioner.service.ts +345 -1
- package/src/types/practitioner/index.ts +60 -0
- package/src/validations/practitioner.schema.ts +45 -0
package/dist/index.mjs
CHANGED
|
@@ -3053,6 +3053,19 @@ import {
|
|
|
3053
3053
|
|
|
3054
3054
|
// src/types/practitioner/index.ts
|
|
3055
3055
|
var PRACTITIONERS_COLLECTION = "practitioners";
|
|
3056
|
+
var REGISTER_TOKENS_COLLECTION = "register_tokens";
|
|
3057
|
+
var PractitionerStatus = /* @__PURE__ */ ((PractitionerStatus2) => {
|
|
3058
|
+
PractitionerStatus2["DRAFT"] = "draft";
|
|
3059
|
+
PractitionerStatus2["ACTIVE"] = "active";
|
|
3060
|
+
return PractitionerStatus2;
|
|
3061
|
+
})(PractitionerStatus || {});
|
|
3062
|
+
var PractitionerTokenStatus = /* @__PURE__ */ ((PractitionerTokenStatus2) => {
|
|
3063
|
+
PractitionerTokenStatus2["ACTIVE"] = "active";
|
|
3064
|
+
PractitionerTokenStatus2["USED"] = "used";
|
|
3065
|
+
PractitionerTokenStatus2["EXPIRED"] = "expired";
|
|
3066
|
+
PractitionerTokenStatus2["REVOKED"] = "revoked";
|
|
3067
|
+
return PractitionerTokenStatus2;
|
|
3068
|
+
})(PractitionerTokenStatus || {});
|
|
3056
3069
|
|
|
3057
3070
|
// src/validations/practitioner.schema.ts
|
|
3058
3071
|
import { z as z10 } from "zod";
|
|
@@ -3164,6 +3177,7 @@ var practitionerSchema = z10.object({
|
|
|
3164
3177
|
clinicWorkingHours: z10.array(practitionerClinicWorkingHoursSchema),
|
|
3165
3178
|
isActive: z10.boolean(),
|
|
3166
3179
|
isVerified: z10.boolean(),
|
|
3180
|
+
status: z10.nativeEnum(PractitionerStatus),
|
|
3167
3181
|
createdAt: z10.instanceof(Timestamp7),
|
|
3168
3182
|
updatedAt: z10.instanceof(Timestamp7)
|
|
3169
3183
|
});
|
|
@@ -3174,7 +3188,35 @@ var createPractitionerSchema = z10.object({
|
|
|
3174
3188
|
clinics: z10.array(z10.string()).optional(),
|
|
3175
3189
|
clinicWorkingHours: z10.array(practitionerClinicWorkingHoursSchema).optional(),
|
|
3176
3190
|
isActive: z10.boolean(),
|
|
3177
|
-
isVerified: z10.boolean()
|
|
3191
|
+
isVerified: z10.boolean(),
|
|
3192
|
+
status: z10.nativeEnum(PractitionerStatus).optional()
|
|
3193
|
+
});
|
|
3194
|
+
var createDraftPractitionerSchema = z10.object({
|
|
3195
|
+
basicInfo: practitionerBasicInfoSchema,
|
|
3196
|
+
certification: practitionerCertificationSchema,
|
|
3197
|
+
clinics: z10.array(z10.string()).optional(),
|
|
3198
|
+
clinicWorkingHours: z10.array(practitionerClinicWorkingHoursSchema).optional(),
|
|
3199
|
+
isActive: z10.boolean().optional().default(false),
|
|
3200
|
+
isVerified: z10.boolean().optional().default(false)
|
|
3201
|
+
});
|
|
3202
|
+
var practitionerTokenSchema = z10.object({
|
|
3203
|
+
id: z10.string().min(1),
|
|
3204
|
+
token: z10.string().min(6),
|
|
3205
|
+
practitionerId: z10.string().min(1),
|
|
3206
|
+
email: z10.string().email(),
|
|
3207
|
+
clinicId: z10.string().min(1),
|
|
3208
|
+
status: z10.nativeEnum(PractitionerTokenStatus),
|
|
3209
|
+
createdBy: z10.string().min(1),
|
|
3210
|
+
createdAt: z10.instanceof(Timestamp7),
|
|
3211
|
+
expiresAt: z10.instanceof(Timestamp7),
|
|
3212
|
+
usedBy: z10.string().optional(),
|
|
3213
|
+
usedAt: z10.instanceof(Timestamp7).optional()
|
|
3214
|
+
});
|
|
3215
|
+
var createPractitionerTokenSchema = z10.object({
|
|
3216
|
+
practitionerId: z10.string().min(1),
|
|
3217
|
+
email: z10.string().email(),
|
|
3218
|
+
clinicId: z10.string().min(1),
|
|
3219
|
+
expiresAt: z10.date().optional()
|
|
3178
3220
|
});
|
|
3179
3221
|
|
|
3180
3222
|
// src/services/practitioner/practitioner.service.ts
|
|
@@ -3225,6 +3267,7 @@ var PractitionerService = class extends BaseService {
|
|
|
3225
3267
|
clinicWorkingHours: validatedData.clinicWorkingHours || [],
|
|
3226
3268
|
isActive: validatedData.isActive,
|
|
3227
3269
|
isVerified: validatedData.isVerified,
|
|
3270
|
+
status: validatedData.status || "active" /* ACTIVE */,
|
|
3228
3271
|
createdAt: serverTimestamp9(),
|
|
3229
3272
|
updatedAt: serverTimestamp9()
|
|
3230
3273
|
};
|
|
@@ -3249,6 +3292,200 @@ var PractitionerService = class extends BaseService {
|
|
|
3249
3292
|
throw error;
|
|
3250
3293
|
}
|
|
3251
3294
|
}
|
|
3295
|
+
/**
|
|
3296
|
+
* Kreira novi draft profil zdravstvenog radnika bez povezanog korisnika
|
|
3297
|
+
* Koristi se od strane administratora klinike za kreiranje profila i kasnije pozivanje
|
|
3298
|
+
* @param data Podaci za kreiranje draft profila
|
|
3299
|
+
* @param createdBy ID administratora koji kreira profil
|
|
3300
|
+
* @param clinicId ID klinike za koju se kreira profil
|
|
3301
|
+
* @returns Objekt koji sadrži kreirani draft profil i token za registraciju
|
|
3302
|
+
*/
|
|
3303
|
+
async createDraftPractitioner(data, createdBy, clinicId) {
|
|
3304
|
+
try {
|
|
3305
|
+
const validatedData = createDraftPractitionerSchema.parse(data);
|
|
3306
|
+
const clinic = await this.getClinicService().getClinic(clinicId);
|
|
3307
|
+
if (!clinic) {
|
|
3308
|
+
throw new Error(`Clinic ${clinicId} not found`);
|
|
3309
|
+
}
|
|
3310
|
+
const clinics = data.clinics || [clinicId];
|
|
3311
|
+
if (data.clinics) {
|
|
3312
|
+
for (const cId of data.clinics) {
|
|
3313
|
+
if (cId !== clinicId) {
|
|
3314
|
+
const otherClinic = await this.getClinicService().getClinic(cId);
|
|
3315
|
+
if (!otherClinic) {
|
|
3316
|
+
throw new Error(`Clinic ${cId} not found`);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
const practitionerId = this.generateId();
|
|
3322
|
+
const practitionerData = {
|
|
3323
|
+
id: practitionerId,
|
|
3324
|
+
userRef: "",
|
|
3325
|
+
// Prazno - biće popunjeno kada korisnik kreira nalog
|
|
3326
|
+
basicInfo: validatedData.basicInfo,
|
|
3327
|
+
certification: validatedData.certification,
|
|
3328
|
+
clinics,
|
|
3329
|
+
clinicWorkingHours: validatedData.clinicWorkingHours || [],
|
|
3330
|
+
isActive: validatedData.isActive !== void 0 ? validatedData.isActive : false,
|
|
3331
|
+
isVerified: validatedData.isVerified !== void 0 ? validatedData.isVerified : false,
|
|
3332
|
+
status: "draft" /* DRAFT */,
|
|
3333
|
+
createdAt: serverTimestamp9(),
|
|
3334
|
+
updatedAt: serverTimestamp9()
|
|
3335
|
+
};
|
|
3336
|
+
practitionerSchema.parse({
|
|
3337
|
+
...practitionerData,
|
|
3338
|
+
userRef: "temp-for-validation",
|
|
3339
|
+
createdAt: Timestamp8.now(),
|
|
3340
|
+
updatedAt: Timestamp8.now()
|
|
3341
|
+
});
|
|
3342
|
+
await setDoc7(
|
|
3343
|
+
doc5(this.db, PRACTITIONERS_COLLECTION, practitionerData.id),
|
|
3344
|
+
practitionerData
|
|
3345
|
+
);
|
|
3346
|
+
const savedPractitioner = await this.getPractitioner(practitionerData.id);
|
|
3347
|
+
if (!savedPractitioner) {
|
|
3348
|
+
throw new Error("Failed to create draft practitioner profile");
|
|
3349
|
+
}
|
|
3350
|
+
const tokenString = this.generateId().slice(0, 6).toUpperCase();
|
|
3351
|
+
const expiration = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
|
|
3352
|
+
const token = {
|
|
3353
|
+
id: this.generateId(),
|
|
3354
|
+
token: tokenString,
|
|
3355
|
+
practitionerId,
|
|
3356
|
+
email: practitionerData.basicInfo.email,
|
|
3357
|
+
clinicId,
|
|
3358
|
+
status: "active" /* ACTIVE */,
|
|
3359
|
+
createdBy,
|
|
3360
|
+
createdAt: Timestamp8.now(),
|
|
3361
|
+
expiresAt: Timestamp8.fromDate(expiration)
|
|
3362
|
+
};
|
|
3363
|
+
practitionerTokenSchema.parse(token);
|
|
3364
|
+
const tokenPath = `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
|
|
3365
|
+
await setDoc7(doc5(this.db, tokenPath), token);
|
|
3366
|
+
return { practitioner: savedPractitioner, token };
|
|
3367
|
+
} catch (error) {
|
|
3368
|
+
if (error instanceof z11.ZodError) {
|
|
3369
|
+
throw new Error("Invalid practitioner data: " + error.message);
|
|
3370
|
+
}
|
|
3371
|
+
throw error;
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
/**
|
|
3375
|
+
* Creates a token for inviting practitioner to claim their profile
|
|
3376
|
+
* @param data Data for creating token
|
|
3377
|
+
* @param createdBy ID of the user creating the token
|
|
3378
|
+
* @returns Created token
|
|
3379
|
+
*/
|
|
3380
|
+
async createPractitionerToken(data, createdBy) {
|
|
3381
|
+
try {
|
|
3382
|
+
const validatedData = createPractitionerTokenSchema.parse(data);
|
|
3383
|
+
const practitioner = await this.getPractitioner(
|
|
3384
|
+
validatedData.practitionerId
|
|
3385
|
+
);
|
|
3386
|
+
if (!practitioner) {
|
|
3387
|
+
throw new Error("Practitioner not found");
|
|
3388
|
+
}
|
|
3389
|
+
if (practitioner.status !== "draft" /* DRAFT */) {
|
|
3390
|
+
throw new Error(
|
|
3391
|
+
"Can only create tokens for practitioners in DRAFT status"
|
|
3392
|
+
);
|
|
3393
|
+
}
|
|
3394
|
+
const clinic = await this.getClinicService().getClinic(
|
|
3395
|
+
validatedData.clinicId
|
|
3396
|
+
);
|
|
3397
|
+
if (!clinic) {
|
|
3398
|
+
throw new Error(`Clinic ${validatedData.clinicId} not found`);
|
|
3399
|
+
}
|
|
3400
|
+
if (!practitioner.clinics.includes(validatedData.clinicId)) {
|
|
3401
|
+
throw new Error("Practitioner is not associated with this clinic");
|
|
3402
|
+
}
|
|
3403
|
+
const expiration = validatedData.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
|
|
3404
|
+
const tokenString = this.generateId().slice(0, 6).toUpperCase();
|
|
3405
|
+
const token = {
|
|
3406
|
+
id: this.generateId(),
|
|
3407
|
+
token: tokenString,
|
|
3408
|
+
practitionerId: validatedData.practitionerId,
|
|
3409
|
+
email: validatedData.email,
|
|
3410
|
+
clinicId: validatedData.clinicId,
|
|
3411
|
+
status: "active" /* ACTIVE */,
|
|
3412
|
+
createdBy,
|
|
3413
|
+
createdAt: Timestamp8.now(),
|
|
3414
|
+
expiresAt: Timestamp8.fromDate(expiration)
|
|
3415
|
+
};
|
|
3416
|
+
practitionerTokenSchema.parse(token);
|
|
3417
|
+
const tokenPath = `${PRACTITIONERS_COLLECTION}/${validatedData.practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
|
|
3418
|
+
await setDoc7(doc5(this.db, tokenPath), token);
|
|
3419
|
+
return token;
|
|
3420
|
+
} catch (error) {
|
|
3421
|
+
if (error instanceof z11.ZodError) {
|
|
3422
|
+
throw new Error("Invalid token data: " + error.message);
|
|
3423
|
+
}
|
|
3424
|
+
throw error;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
* Gets active tokens for a practitioner
|
|
3429
|
+
* @param practitionerId ID of the practitioner
|
|
3430
|
+
* @returns Array of active tokens
|
|
3431
|
+
*/
|
|
3432
|
+
async getPractitionerActiveTokens(practitionerId) {
|
|
3433
|
+
const tokensRef = collection3(
|
|
3434
|
+
this.db,
|
|
3435
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
|
|
3436
|
+
);
|
|
3437
|
+
const q = query3(
|
|
3438
|
+
tokensRef,
|
|
3439
|
+
where3("status", "==", "active" /* ACTIVE */),
|
|
3440
|
+
where3("expiresAt", ">", Timestamp8.now())
|
|
3441
|
+
);
|
|
3442
|
+
const querySnapshot = await getDocs3(q);
|
|
3443
|
+
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
3444
|
+
}
|
|
3445
|
+
/**
|
|
3446
|
+
* Gets a token by its string value and validates it
|
|
3447
|
+
* @param tokenString The token string to find
|
|
3448
|
+
* @returns The token if found and valid, null otherwise
|
|
3449
|
+
*/
|
|
3450
|
+
async validateToken(tokenString) {
|
|
3451
|
+
const practitionersRef = collection3(this.db, PRACTITIONERS_COLLECTION);
|
|
3452
|
+
const practitionersSnapshot = await getDocs3(practitionersRef);
|
|
3453
|
+
for (const practitionerDoc of practitionersSnapshot.docs) {
|
|
3454
|
+
const practitionerId = practitionerDoc.id;
|
|
3455
|
+
const tokensRef = collection3(
|
|
3456
|
+
this.db,
|
|
3457
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
|
|
3458
|
+
);
|
|
3459
|
+
const q = query3(
|
|
3460
|
+
tokensRef,
|
|
3461
|
+
where3("token", "==", tokenString),
|
|
3462
|
+
where3("status", "==", "active" /* ACTIVE */),
|
|
3463
|
+
where3("expiresAt", ">", Timestamp8.now())
|
|
3464
|
+
);
|
|
3465
|
+
const tokenSnapshot = await getDocs3(q);
|
|
3466
|
+
if (!tokenSnapshot.empty) {
|
|
3467
|
+
return tokenSnapshot.docs[0].data();
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
return null;
|
|
3471
|
+
}
|
|
3472
|
+
/**
|
|
3473
|
+
* Marks a token as used
|
|
3474
|
+
* @param tokenId ID of the token
|
|
3475
|
+
* @param practitionerId ID of the practitioner
|
|
3476
|
+
* @param userId ID of the user using the token
|
|
3477
|
+
*/
|
|
3478
|
+
async markTokenAsUsed(tokenId, practitionerId, userId) {
|
|
3479
|
+
const tokenRef = doc5(
|
|
3480
|
+
this.db,
|
|
3481
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${tokenId}`
|
|
3482
|
+
);
|
|
3483
|
+
await updateDoc8(tokenRef, {
|
|
3484
|
+
status: "used" /* USED */,
|
|
3485
|
+
usedBy: userId,
|
|
3486
|
+
usedAt: Timestamp8.now()
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3252
3489
|
/**
|
|
3253
3490
|
* Dohvata zdravstvenog radnika po ID-u
|
|
3254
3491
|
*/
|
|
@@ -3282,7 +3519,20 @@ var PractitionerService = class extends BaseService {
|
|
|
3282
3519
|
const q = query3(
|
|
3283
3520
|
collection3(this.db, PRACTITIONERS_COLLECTION),
|
|
3284
3521
|
where3("clinics", "array-contains", clinicId),
|
|
3285
|
-
where3("isActive", "==", true)
|
|
3522
|
+
where3("isActive", "==", true),
|
|
3523
|
+
where3("status", "==", "active" /* ACTIVE */)
|
|
3524
|
+
);
|
|
3525
|
+
const querySnapshot = await getDocs3(q);
|
|
3526
|
+
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
3527
|
+
}
|
|
3528
|
+
/**
|
|
3529
|
+
* Dohvata sve draft zdravstvene radnike za određenu kliniku
|
|
3530
|
+
*/
|
|
3531
|
+
async getDraftPractitionersByClinic(clinicId) {
|
|
3532
|
+
const q = query3(
|
|
3533
|
+
collection3(this.db, PRACTITIONERS_COLLECTION),
|
|
3534
|
+
where3("clinics", "array-contains", clinicId),
|
|
3535
|
+
where3("status", "==", "draft" /* DRAFT */)
|
|
3286
3536
|
);
|
|
3287
3537
|
const querySnapshot = await getDocs3(q);
|
|
3288
3538
|
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
@@ -3391,6 +3641,35 @@ var PractitionerService = class extends BaseService {
|
|
|
3391
3641
|
}
|
|
3392
3642
|
await deleteDoc2(doc5(this.db, PRACTITIONERS_COLLECTION, practitionerId));
|
|
3393
3643
|
}
|
|
3644
|
+
/**
|
|
3645
|
+
* Validates a registration token and claims the associated draft practitioner profile
|
|
3646
|
+
* @param tokenString The token provided by the practitioner
|
|
3647
|
+
* @param userId The ID of the user claiming the profile
|
|
3648
|
+
* @returns The claimed practitioner profile or null if token is invalid
|
|
3649
|
+
*/
|
|
3650
|
+
async validateTokenAndClaimProfile(tokenString, userId) {
|
|
3651
|
+
const token = await this.validateToken(tokenString);
|
|
3652
|
+
if (!token) {
|
|
3653
|
+
return null;
|
|
3654
|
+
}
|
|
3655
|
+
const practitioner = await this.getPractitioner(token.practitionerId);
|
|
3656
|
+
if (!practitioner) {
|
|
3657
|
+
return null;
|
|
3658
|
+
}
|
|
3659
|
+
if (practitioner.status !== "draft" /* DRAFT */) {
|
|
3660
|
+
throw new Error("This practitioner profile has already been claimed");
|
|
3661
|
+
}
|
|
3662
|
+
const existingPractitioner = await this.getPractitionerByUserRef(userId);
|
|
3663
|
+
if (existingPractitioner) {
|
|
3664
|
+
throw new Error("User already has a practitioner profile");
|
|
3665
|
+
}
|
|
3666
|
+
const updatedPractitioner = await this.updatePractitioner(practitioner.id, {
|
|
3667
|
+
userRef: userId,
|
|
3668
|
+
status: "active" /* ACTIVE */
|
|
3669
|
+
});
|
|
3670
|
+
await this.markTokenAsUsed(token.id, token.practitionerId, userId);
|
|
3671
|
+
return updatedPractitioner;
|
|
3672
|
+
}
|
|
3394
3673
|
};
|
|
3395
3674
|
|
|
3396
3675
|
// src/services/user.service.ts
|
|
@@ -9129,8 +9408,11 @@ export {
|
|
|
9129
9408
|
PatientService,
|
|
9130
9409
|
PracticeType,
|
|
9131
9410
|
PractitionerService,
|
|
9411
|
+
PractitionerStatus,
|
|
9412
|
+
PractitionerTokenStatus,
|
|
9132
9413
|
PricingMeasure,
|
|
9133
9414
|
ProcedureFamily,
|
|
9415
|
+
REGISTER_TOKENS_COLLECTION,
|
|
9134
9416
|
SYNCED_CALENDARS_COLLECTION,
|
|
9135
9417
|
SubscriptionModel,
|
|
9136
9418
|
SyncedCalendarProvider,
|
|
@@ -9175,11 +9457,13 @@ export {
|
|
|
9175
9457
|
createClinicSchema,
|
|
9176
9458
|
createDefaultClinicGroupSchema,
|
|
9177
9459
|
createDocumentTemplateSchema,
|
|
9460
|
+
createDraftPractitionerSchema,
|
|
9178
9461
|
createPatientLocationInfoSchema,
|
|
9179
9462
|
createPatientMedicalInfoSchema,
|
|
9180
9463
|
createPatientProfileSchema,
|
|
9181
9464
|
createPatientSensitiveInfoSchema,
|
|
9182
9465
|
createPractitionerSchema,
|
|
9466
|
+
createPractitionerTokenSchema,
|
|
9183
9467
|
createUserOptionsSchema,
|
|
9184
9468
|
doctorInfoSchema,
|
|
9185
9469
|
documentElementSchema,
|
|
@@ -9212,6 +9496,7 @@ export {
|
|
|
9212
9496
|
practitionerProfileInfoSchema,
|
|
9213
9497
|
practitionerReviewSchema,
|
|
9214
9498
|
practitionerSchema,
|
|
9499
|
+
practitionerTokenSchema,
|
|
9215
9500
|
practitionerWorkingHoursSchema,
|
|
9216
9501
|
preRequirementNotificationSchema,
|
|
9217
9502
|
procedureCategorizationSchema,
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -123,8 +123,16 @@ export type {
|
|
|
123
123
|
PractitionerReview,
|
|
124
124
|
PractitionerWorkingHours,
|
|
125
125
|
PractitionerClinicWorkingHours,
|
|
126
|
+
CreateDraftPractitionerData,
|
|
127
|
+
PractitionerToken,
|
|
128
|
+
CreatePractitionerTokenData,
|
|
129
|
+
} from "./types/practitioner";
|
|
130
|
+
export {
|
|
131
|
+
PRACTITIONERS_COLLECTION,
|
|
132
|
+
REGISTER_TOKENS_COLLECTION,
|
|
133
|
+
PractitionerStatus,
|
|
134
|
+
PractitionerTokenStatus,
|
|
126
135
|
} from "./types/practitioner";
|
|
127
|
-
export { PRACTITIONERS_COLLECTION } from "./types/practitioner";
|
|
128
136
|
|
|
129
137
|
// Clinic types
|
|
130
138
|
export type {
|