forked from Yara724/api
Fixed Captcha
This commit is contained in:
@@ -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<string, unknown>[]).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<string, unknown>[]).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<string>();
|
||||
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<string, unknown>,
|
||||
): 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<string, unknown>;
|
||||
@@ -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<Record<string, unknown>> {
|
||||
async findOneV2(
|
||||
requestId: string,
|
||||
actor: any,
|
||||
): Promise<Record<string, unknown>> {
|
||||
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<string, unknown>;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user