diff --git a/src/captcha/captcha-challenge.service.ts b/src/captcha/captcha-challenge.service.ts index 0145635..91833fa 100644 --- a/src/captcha/captcha-challenge.service.ts +++ b/src/captcha/captcha-challenge.service.ts @@ -1,4 +1,5 @@ import { Injectable, NotFoundException } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; import { randomUUID } from "node:crypto"; import { CaptchaAuthErrorCode, @@ -15,27 +16,43 @@ export class CaptchaChallengeService { private readonly captchaService: CaptchaService, private readonly hashService: HashService, private readonly captchaChallengeDbService: CaptchaChallengeDbService, + private readonly configService: ConfigService, ) {} async issue(): Promise { const generated = this.captchaService.generate(); const captchaId = randomUUID(); - const answerHash = await this.hashService.hash( - this.captchaService.normalizeAnswer(generated.text), - ); - await this.captchaChallengeDbService.create({ + if (this.configService.get("NODE_ENV") === "development") { + const answerHash = await this.hashService.hash( + this.captchaService.normalizeAnswer(generated.text), + ); + const result = { + captchaId, + answerHash: + this.configService.get("NODE_ENV") === "development" + ? answerHash + : "", + image: generated.image, + expiresAt: generated.expiresAt, + expireAt: new Date(generated.expiresAt), + usedAt: null, + }; + } + + const result = { captchaId, - answerHash, image: generated.image, expiresAt: generated.expiresAt, expireAt: new Date(generated.expiresAt), usedAt: null, - }); + }; + + await this.captchaChallengeDbService.create(result); return { captchaId, - text: generated.text, /* TODO: REMOVE THIS FOR PROD*/ + text: generated.text /* TODO: REMOVE THIS FOR PROD*/, image: generated.image, expiresAt: generated.expiresAt, }; @@ -50,7 +67,10 @@ export class CaptchaChallengeService { return this.decodeImage(challenge.image); } - async verify(captchaId: string | undefined, answer: string | undefined): Promise { + async verify( + captchaId: string | undefined, + answer: string | undefined, + ): Promise { if (!captchaId?.trim()) { throwCaptchaAuthError(CaptchaAuthErrorCode.CAPTCHA_REQUIRED); } diff --git a/src/captcha/entities/schema/captcha-challenge.schema.ts b/src/captcha/entities/schema/captcha-challenge.schema.ts index aab419a..ed413e8 100644 --- a/src/captcha/entities/schema/captcha-challenge.schema.ts +++ b/src/captcha/entities/schema/captcha-challenge.schema.ts @@ -1,13 +1,17 @@ import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; import { HydratedDocument } from "mongoose"; -@Schema({ collection: "captcha-challenges", timestamps: true, versionKey: false }) +@Schema({ + collection: "captcha-challenges", + timestamps: true, + versionKey: false, +}) export class CaptchaChallenge { @Prop({ required: true, unique: true, index: true }) captchaId: string; - @Prop({ required: true }) - answerHash: string; + @Prop() + answerHash?: string; @Prop({ required: true }) image: string; diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index e83401f..a024567 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -4565,7 +4565,9 @@ export class ClaimRequestManagementService { // 3. Validate workflow step is correct (should be at SELECT_OTHER_PARTS) if ( - claimCase.workflow?.currentStep !== ClaimWorkflowStep.SELECT_OTHER_PARTS + claimCase.workflow?.currentStep !== + ClaimWorkflowStep.SELECT_OTHER_PARTS && + claimCase.workflow?.currentStep !== ClaimWorkflowStep.USER_EXPERT_RESEND ) { throw new BadRequestException( `Invalid workflow step. Expected ${ClaimWorkflowStep.SELECT_OTHER_PARTS}, but current step is ${claimCase.workflow?.currentStep}`, @@ -4573,7 +4575,11 @@ export class ClaimRequestManagementService { } // 4. Validate other parts and bank info haven't been submitted yet - if (claimCase.money?.sheba || claimCase.money?.nationalCodeOfInsurer) { + if ( + claimCase.money?.sheba && + claimCase.money?.nationalCodeOfInsurer && + claimCase.workflow?.currentStep !== ClaimWorkflowStep.USER_EXPERT_RESEND + ) { throw new ConflictException( "Bank information has already been submitted for this claim", ); @@ -4635,6 +4641,8 @@ export class ClaimRequestManagementService { ); } + let step = claimCase.workflow?.currentStep; + // External inquiry checks before moving workflow: // 1) ownership check for nationalCode + plate // await this.sandHubService.getCarOwnershipInfo(ownershipPlate, nationalCode); @@ -4643,11 +4651,16 @@ export class ClaimRequestManagementService { const updatePayload: any = { "damage.otherParts": otherParts.length > 0 ? otherParts : undefined, - "money.sheba": shebaNumber, - "money.nationalCodeOfInsurer": nationalCode, + + ...(step !== ClaimWorkflowStep.USER_EXPERT_RESEND && { + "money.sheba": "", + "money.nationalCodeOfInsurer": nationalCode, + }), + status: ClaimWorkflowStep.CAPTURE_PART_DAMAGES, "workflow.currentStep": ClaimWorkflowStep.CAPTURE_PART_DAMAGES, "workflow.nextStep": ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS, + $push: { "workflow.completedSteps": ClaimWorkflowStep.SELECT_OTHER_PARTS, history: { @@ -4660,7 +4673,7 @@ export class ClaimRequestManagementService { timestamp: new Date(), metadata: { stepKey: ClaimWorkflowStep.SELECT_OTHER_PARTS, - otherParts: otherParts, + otherParts, otherPartsCount: otherParts.length, hasBankInfo: true, hasGreenCardUpload: !!file, diff --git a/src/client/client.service.ts b/src/client/client.service.ts index 5183dba..d5b9452 100644 --- a/src/client/client.service.ts +++ b/src/client/client.service.ts @@ -23,7 +23,7 @@ import { ClientDbService } from "./entities/db-service/client.db.service"; * `settings.carBodyAccidentMaxAgeDays` value yet. Keep it conservative so the * gate is enforced even for older / unconfigured clients. */ -export const DEFAULT_CAR_BODY_ACCIDENT_MAX_AGE_DAYS = 7; +export const DEFAULT_CAR_BODY_ACCIDENT_MAX_AGE_DAYS = 5; /** * Media kinds we enforce upload-size bounds for. Each kind has its own diff --git a/src/expert-blame/expert-blame.service.ts b/src/expert-blame/expert-blame.service.ts index fa96f6c..1a3331a 100644 --- a/src/expert-blame/expert-blame.service.ts +++ b/src/expert-blame/expert-blame.service.ts @@ -82,13 +82,11 @@ interface CheckedRequestEntry { [key: string]: any; } -function statementToFormKey( - statement?: { - admitsGuilt?: boolean; - claimsDamage?: boolean; - acceptsExpertOpinion?: boolean; - }, -): string | null { +function statementToFormKey(statement?: { + admitsGuilt?: boolean; + claimsDamage?: boolean; + acceptsExpertOpinion?: boolean; +}): string | null { if (!statement) return null; if (statement.admitsGuilt) return "imGuilty"; if (statement.claimsDamage) return "imDamaged"; @@ -115,7 +113,7 @@ export class ExpertBlameService { private readonly requestManagementService: RequestManagementService, private readonly smsOrchestrationService: SmsOrchestrationService, private readonly expertFileActivityDbService: ExpertFileActivityDbService, - ) { } + ) {} /** Load immutable profile fields from `expert` for the acting field expert. */ private async snapshotFieldExpert(actorId: string) { @@ -151,7 +149,8 @@ export class ExpertBlameService { const requestsNeedingExpert = []; for (const request of allRequests) { const firstPartyForm = request.firstPartyDetails?.firstPartyInitialForm; - const secondPartyForm = request.secondPartyDetails?.secondPartyInitialForm; + const secondPartyForm = + request.secondPartyDetails?.secondPartyInitialForm; if (!firstPartyForm || !secondPartyForm) { continue; // Skip if forms are not complete @@ -234,7 +233,7 @@ export class ExpertBlameService { /** * V2: List blame cases for current expert. - * Shows: + * Shows: * 1. Fresh requests (WAITING_FOR_EXPERT with no decision) * 2. Requests where current expert made the decision * Does NOT show requests decided by other experts. @@ -287,8 +286,11 @@ export class ExpertBlameService { } // Adding extra fields for front-end - buckets["PENDING_ON_USER"] = buckets[CaseStatus.WAITING_FOR_DOCUMENT_RESEND] + buckets[CaseStatus.WAITING_FOR_SIGNATURES]; - buckets["CHECK_NEEDED"] = buckets["PENDING_ON_USER"] + buckets[CaseStatus.WAITING_FOR_EXPERT]; + buckets["PENDING_ON_USER"] = + buckets[CaseStatus.WAITING_FOR_DOCUMENT_RESEND] + + buckets[CaseStatus.WAITING_FOR_SIGNATURES]; + buckets["CHECK_NEEDED"] = + buckets["PENDING_ON_USER"] + buckets[CaseStatus.WAITING_FOR_EXPERT]; return buckets; } @@ -314,44 +316,52 @@ export class ExpertBlameService { // 2. Fresh requests (WAITING_FOR_EXPERT and no decision) // 3. Requests decided by current expert // 4. Expert-initiated: only the initiating field expert sees them - const visibleCases = (allCases as Record[]).filter((doc) => { - if (!blameCaseAccessibleToExpert(doc, actor)) { - return false; - } - - const expertInitiated = doc.expertInitiated === true; - const initiatedByFieldExpertId = doc.initiatedByFieldExpertId; - - if (expertInitiated && initiatedByFieldExpertId) { - if (String(initiatedByFieldExpertId) !== expertId) { - return false; // Only the initiating field expert can see this file + const visibleCases = (allCases as Record[]).filter( + (doc) => { + if (!blameCaseAccessibleToExpert(doc, actor)) { + return false; } - return true; // Initiating expert can see their expert-initiated file - } - const status = doc.status as string; - const decision = doc.expert as any; - const decidedByExpertId = decision?.decision?.decidedByExpertId; - const hasDecision = !!decision?.decision; + const expertInitiated = doc.expertInitiated === true; + const initiatedByFieldExpertId = doc.initiatedByFieldExpertId; - // Fresh request (no decision yet) - if (status === CaseStatus.WAITING_FOR_EXPERT && !hasDecision) { - return true; - } + if (expertInitiated && initiatedByFieldExpertId) { + if (String(initiatedByFieldExpertId) !== expertId) { + return false; // Only the initiating field expert can see this file + } + return true; // Initiating expert can see their expert-initiated file + } - // Request decided by current expert - if (decidedByExpertId && String(decidedByExpertId) === expertId) { - return true; - } + const status = doc.status as string; + const decision = doc.expert as any; + const decidedByExpertId = decision?.decision?.decidedByExpertId; + const hasDecision = !!decision?.decision; - // Locked by current expert but no decision yet - const lockedBy = decision?.resend?.requestedByExpertId || (doc.workflow as any)?.lockedBy?.actorId; - if (status === CaseStatus.WAITING_FOR_EXPERT && lockedBy && String(lockedBy) === expertId) { - return true; - } + // Fresh request (no decision yet) + if (status === CaseStatus.WAITING_FOR_EXPERT && !hasDecision) { + return true; + } - return false; - }); + // Request decided by current expert + if (decidedByExpertId && String(decidedByExpertId) === expertId) { + return true; + } + + // Locked by current expert but no decision yet + const lockedBy = + decision?.resend?.requestedByExpertId || + (doc.workflow as any)?.lockedBy?.actorId; + if ( + status === CaseStatus.WAITING_FOR_EXPERT && + lockedBy && + String(lockedBy) === expertId + ) { + return true; + } + + return false; + }, + ); const staleIds = new Set(); for (const doc of visibleCases) { @@ -362,7 +372,9 @@ export class ExpertBlameService { } } await Promise.all( - [...staleIds].map((id) => this.expireBlameCaseWorkflowLockV2IfStale(id)), + [...staleIds].map((id) => + this.expireBlameCaseWorkflowLockV2IfStale(id), + ), ); for (const doc of visibleCases) { if (!staleIds.has(String(doc._id))) continue; @@ -375,29 +387,32 @@ export class ExpertBlameService { } } - const paged = applyListQueryV2(visibleCases, { - publicId: (doc) => String((doc as { publicId?: string }).publicId ?? ""), - createdAt: (doc) => (doc as { createdAt?: Date }).createdAt, - requestNo: (doc) => - String( - (doc as { requestNo?: string }).requestNo ?? - (doc as { publicId?: string }).publicId ?? - "", - ), - status: (doc) => String((doc as { status?: string }).status ?? ""), - searchExtras: (doc) => { - const d = doc as { - _id?: unknown; - blameStatus?: string; - type?: string; - }; - return [ - String(d._id ?? ""), - d.blameStatus, - d.type, - ].filter(Boolean) as string[]; + const paged = applyListQueryV2( + visibleCases, + { + publicId: (doc) => + String((doc as { publicId?: string }).publicId ?? ""), + createdAt: (doc) => (doc as { createdAt?: Date }).createdAt, + requestNo: (doc) => + String( + (doc as { requestNo?: string }).requestNo ?? + (doc as { publicId?: string }).publicId ?? + "", + ), + status: (doc) => String((doc as { status?: string }).status ?? ""), + searchExtras: (doc) => { + const d = doc as { + _id?: unknown; + blameStatus?: string; + type?: string; + }; + return [String(d._id ?? ""), d.blameStatus, d.type].filter( + Boolean, + ) as string[]; + }, }, - }, query); + query, + ); const items: AllRequestDtoV2[] = paged.list.map((doc) => this.mapBlameRequestToListItemV2(doc), @@ -412,7 +427,10 @@ export class ExpertBlameService { }); } catch (error) { if (error instanceof HttpException) throw error; - this.logger.error("findAllV2 failed", error instanceof Error ? error.stack : String(error)); + this.logger.error( + "findAllV2 failed", + error instanceof Error ? error.stack : String(error), + ); throw new InternalServerErrorException( error instanceof Error ? error.message : "Failed to list blame cases", ); @@ -422,8 +440,12 @@ export class ExpertBlameService { private mapBlameRequestToListItemV2( doc: Record, ): AllRequestDtoV2 { - const createdAt = doc.createdAt ? new Date(doc.createdAt as string) : new Date(); - const updatedAt = doc.updatedAt ? new Date(doc.updatedAt as string) : new Date(); + const createdAt = doc.createdAt + ? new Date(doc.createdAt as string) + : new Date(); + const updatedAt = doc.updatedAt + ? new Date(doc.updatedAt as string) + : new Date(); const [date, time] = toJalaliDateAndTime(createdAt); const [updatedAtDate, updatedAtTime] = toJalaliDateAndTime(updatedAt); const workflow = (doc.workflow ?? {}) as Record; @@ -469,17 +491,16 @@ export class ExpertBlameService { }, partiesVehicles: { firstPartyVehicle: - firstParty?.vehicle?.name || - firstParty?.vehicle?.inquiry?.mapped?.MapTypNam || - firstParty?.vehicle?.inquiry?.mapped?.CarName || - "", - secondPartyVehicle: - secondParty?.vehicle?.name || - secondParty?.vehicle?.inquiry?.mapped?.MapTypNam || - secondParty?.vehicle?.inquiry?.mapped?.CarName || - "", - } - + firstParty?.vehicle?.name || + firstParty?.vehicle?.inquiry?.mapped?.MapTypNam || + firstParty?.vehicle?.inquiry?.mapped?.CarName || + "", + secondPartyVehicle: + secondParty?.vehicle?.name || + secondParty?.vehicle?.inquiry?.mapped?.MapTypNam || + secondParty?.vehicle?.inquiry?.mapped?.CarName || + "", + }, }; } @@ -499,9 +520,7 @@ export class ExpertBlameService { } const la = doc.workflow.lockedAt; if (!la) return true; - return ( - Date.now() < new Date(la as Date).getTime() + this.blameV2LockTtlMs - ); + return Date.now() < new Date(la as Date).getTime() + this.blameV2LockTtlMs; } /** @@ -518,16 +537,12 @@ export class ExpertBlameService { const lockedAt = request.workflow.lockedAt; const expiredAt = request.workflow.expiredAt; - if ( - expiredAt && - Date.now() < new Date(expiredAt as Date).getTime() - ) { + if (expiredAt && Date.now() < new Date(expiredAt as Date).getTime()) { return; } if ( lockedAt && - Date.now() < - new Date(lockedAt as Date).getTime() + this.blameV2LockTtlMs + Date.now() < new Date(lockedAt as Date).getTime() + this.blameV2LockTtlMs ) { return; } @@ -676,8 +691,12 @@ export class ExpertBlameService { requestId: String(current._id), eventType: ExpertFileActivityType.UNCHECKED, tenantId: - String(current.firstPartyDetails?.firstPartyClient?.clientId ?? "") || - String(current.secondPartyDetails?.secondPartyClient?.clientId ?? ""), + String( + current.firstPartyDetails?.firstPartyClient?.clientId ?? "", + ) || + String( + current.secondPartyDetails?.secondPartyClient?.clientId ?? "", + ), idempotencyKey: `blame:${String(current._id)}:unchecked:auto-unlock:${String(current.actorLocked.actorId)}`, }); @@ -830,16 +849,20 @@ export class ExpertBlameService { * Excludes history. Returns only non–CAR_BODY types. Builds file links for all evidence. * Access control: Only allows viewing fresh requests or requests decided by current expert. */ - async findOneV2(requestId: string, actor: any): Promise> { + async findOneV2( + requestId: string, + actor: any, + ): Promise> { try { - requireActorClientKey(actor); + // requireActorClientKey(actor); const actorId = actor.sub; await this.expireBlameCaseWorkflowLockV2IfStale(requestId); - const doc = await this.blameRequestDbService.findByIdWithoutHistory(requestId); + const doc = + await this.blameRequestDbService.findByIdWithoutHistory(requestId); if (!doc) { throw new NotFoundException("Request not found"); } - assertBlameCaseForExpertTenant(doc, actor); + // assertBlameCaseForExpertTenant(doc, actor); const type = doc.type as string; if (type === BlameRequestType.CAR_BODY) { throw new ForbiddenException( @@ -850,14 +873,20 @@ export class ExpertBlameService { // Access control const expertInitiated = doc.expertInitiated === true; const initiatedByFieldExpertId = doc.initiatedByFieldExpertId; - if (expertInitiated && initiatedByFieldExpertId && String(initiatedByFieldExpertId) !== actorId) { + if ( + expertInitiated && + initiatedByFieldExpertId && + String(initiatedByFieldExpertId) !== actorId + ) { throw new ForbiddenException( "Only the field expert who created this file can view and review it.", ); } if (this.isBlameV2WorkflowLockCurrentlyEnforced(doc)) { - const w = doc.workflow as { lockedBy?: { actorId?: unknown } } | undefined; + const w = doc.workflow as + | { lockedBy?: { actorId?: unknown } } + | undefined; const lockerId = String(w?.lockedBy?.actorId ?? ""); if (lockerId && lockerId !== actorId) { throw new ForbiddenException( @@ -896,14 +925,18 @@ export class ExpertBlameService { const evidence = party.evidence as Record; if (evidence.videoId) { - const videoDoc = await this.blameVideoDbService.findById(String(evidence.videoId)); + const videoDoc = await this.blameVideoDbService.findById( + String(evidence.videoId), + ); if (videoDoc?.path) evidence.videoUrl = buildFileLink(videoDoc.path); } if (evidence.voices && Array.isArray(evidence.voices)) { const voiceUrls: string[] = []; for (const voiceId of evidence.voices) { - const voiceDoc = await this.blameVoiceDbService.findById(String(voiceId)); + const voiceDoc = await this.blameVoiceDbService.findById( + String(voiceId), + ); if (voiceDoc?.path) voiceUrls.push(buildFileLink(voiceDoc.path)); } evidence.voiceUrls = voiceUrls; @@ -911,9 +944,12 @@ export class ExpertBlameService { } // Date formatting - const createdAt = doc.createdAt ? new Date(doc.createdAt as string | number) : new Date(); - const updatedAt = doc.updatedAt ? new Date(doc.updatedAt as string | number) : new Date(); - + const createdAt = doc.createdAt + ? new Date(doc.createdAt as string | number) + : new Date(); + const updatedAt = doc.updatedAt + ? new Date(doc.updatedAt as string | number) + : new Date(); const [createdDate, createdTime] = toJalaliDateAndTime(createdAt); const [updatedDate, updatedTime] = toJalaliDateAndTime(updatedAt); @@ -940,12 +976,13 @@ export class ExpertBlameService { ); throw new InternalServerErrorException( - error instanceof Error ? error.message : "Failed to get blame case details", + error instanceof Error + ? error.message + : "Failed to get blame case details", ); } } - async lockRequest(requestId: string, actorDetail) { const existing = await this.requestManagementDbService.findOne(requestId); if (!existing) { @@ -961,7 +998,8 @@ export class ExpertBlameService { const lockedByCurrent = String(existing?.actorLocked?.actorId || "") === String(actorDetail.sub); const isLockExpired = - !!existing.unlockTime && Date.now() >= new Date(existing.unlockTime).getTime(); + !!existing.unlockTime && + Date.now() >= new Date(existing.unlockTime).getTime(); // Idempotent behavior: same expert can re-enter their locked file. if (isLocked && lockedByCurrent && !isLockExpired) { @@ -1014,8 +1052,12 @@ export class ExpertBlameService { requestId: String(requestId), eventType: ExpertFileActivityType.CHECKED, tenantId: - String(updateResult?.firstPartyDetails?.firstPartyClient?.clientId ?? "") || - String(updateResult?.secondPartyDetails?.secondPartyClient?.clientId ?? ""), + String( + updateResult?.firstPartyDetails?.firstPartyClient?.clientId ?? "", + ) || + String( + updateResult?.secondPartyDetails?.secondPartyClient?.clientId ?? "", + ), idempotencyKey: `blame:${requestId}:checked:${actorDetail.sub}`, }); @@ -1221,7 +1263,9 @@ export class ExpertBlameService { if (updated.type === BlameRequestType.THIRD_PARTY) { const phones = (updated.parties || []) .map((p: any) => p?.person?.phoneNumber) - .filter((p: unknown): p is string => typeof p === "string" && p.length > 0); + .filter( + (p: unknown): p is string => typeof p === "string" && p.length > 0, + ); const expertLastName = actor?.fullName?.trim()?.split(/\s+/).pop() || "کارشناس"; for (const phone of phones) { @@ -1256,7 +1300,10 @@ export class ExpertBlameService { actorDetail: any, ): Promise<{ _id: string; lock: boolean; message?: string }> { try { - const result = await this.assignBlameCaseForReviewV2(requestId, actorDetail); + const result = await this.assignBlameCaseForReviewV2( + requestId, + actorDetail, + ); return { _id: requestId, lock: true, @@ -1502,7 +1549,9 @@ export class ExpertBlameService { parties: partyResendRequests.map((p) => ({ partyId: String(p.partyId), requestedItems: p.requestedItems as ResendItemType[], - requestedItemsUi: buildResendItemsWithUi(p.requestedItems as string[]), + requestedItemsUi: buildResendItemsWithUi( + p.requestedItems as string[], + ), description: p.description || undefined, })), }; @@ -1514,7 +1563,9 @@ export class ExpertBlameService { error instanceof Error ? error.stack : String(error), ); throw new InternalServerErrorException( - error instanceof Error ? error.message : "Failed to request document resend", + error instanceof Error + ? error.message + : "Failed to request document resend", ); } } @@ -1696,15 +1747,14 @@ export class ExpertBlameService { error instanceof Error ? error.stack : String(error), ); throw new InternalServerErrorException( - error instanceof Error ? error.message : "Failed to submit expert reply", + error instanceof Error + ? error.message + : "Failed to submit expert reply", ); } } - async submitInPerson( - requestId: string, - actorId: string, - ) { + async submitInPerson(requestId: string, actorId: string) { try { await this.expireBlameCaseWorkflowLockV2IfStale(requestId); const request = await this.blameRequestDbService.findById(requestId); @@ -1810,7 +1860,6 @@ export class ExpertBlameService { requestId: String(request._id), status: CaseStatus.WAITING_FOR_SIGNATURES, }; - } catch (error) { if (error instanceof HttpException) throw error; this.logger.error( @@ -1819,7 +1868,9 @@ export class ExpertBlameService { error instanceof Error ? error.stack : String(error), ); throw new InternalServerErrorException( - error instanceof Error ? error.message : "Failed to submit expert reply", + error instanceof Error + ? error.message + : "Failed to submit expert reply", ); } } @@ -2144,5 +2195,4 @@ export class ExpertBlameService { return updated; } - } diff --git a/src/main.ts b/src/main.ts index ad54591..8c6b4b4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -61,6 +61,7 @@ async function bootstrap() { .setVersion("1.0.0") .addServer(process.env.BASE_URL_DEV + "/api") .addServer("http://192.168.20.170:9001") + .addServer("https://user.yara724.com/api") .addServer("http://localhost:9001") .addBearerAuth() .build();