import { Types } from "mongoose"; import { RoleEnum } from "src/Types&Enums/role.enum"; import { iranMobileLookupVariants } from "./iran-mobile"; /** ObjectId + string variants for one or more user ids (legacy duplicate accounts). */ export function collectUserIdVariants( ...ids: (string | Types.ObjectId | undefined | null)[] ): unknown[] { const seen = new Set(); const out: unknown[] = []; for (const id of ids) { if (id == null || id === "") continue; const s = String(id); if (seen.has(s)) continue; seen.add(s); out.push(s); if (Types.ObjectId.isValid(s)) { out.push(new Types.ObjectId(s)); } } return out; } export function buildUserIdFieldOrFilter( fieldPath: string, idVariants: unknown[], ): Record | null { if (!idVariants.length) return null; if (idVariants.length === 1) { return { [fieldPath]: idVariants[0] }; } return { $or: idVariants.map((id) => ({ [fieldPath]: id })) }; } /** * Mongo filter matching a user id field stored as ObjectId or legacy string. */ export function mongoUserIdEqualityFilter( fieldPath: string, userSub: string, ): Record | null { return buildUserIdFieldOrFilter(fieldPath, collectUserIdVariants(userSub)); } /** `$or` branches: user is a party on a blame case (by userId and/or phone). */ export function buildBlamePartyAccessOrConditions( user: { sub?: string; username?: string; }, linkedUserIdStrings: string[] = [], ): Record[] { const or: Record[] = []; const idVariants = collectUserIdVariants(user.sub, ...linkedUserIdStrings); const userIdCond = buildUserIdFieldOrFilter( "parties.person.userId", idVariants, ); if (userIdCond) or.push(userIdCond); const phoneVariants = user?.username ? iranMobileLookupVariants(user.username) : []; if (phoneVariants.length > 0) { or.push({ "parties.person.phoneNumber": { $in: phoneVariants } }); } return or; } /** `$or` branches: user owns a claim or is tied via blame party membership. */ export function buildClaimOwnerAccessOrConditions( userSub: string, username?: string, ): Record[] { const or: Record[] = []; const ownerIdCond = mongoUserIdEqualityFilter("owner.userId", userSub); if (ownerIdCond) or.push(ownerIdCond); return or; } export function buildFieldExpertInitiatedBlameFilter( expertSub: string, ): Record { const expertOid = Types.ObjectId.isValid(expertSub) ? new Types.ObjectId(expertSub) : expertSub; return { expertInitiated: true, initiatedByFieldExpertId: expertOid, }; } export function buildFieldExpertClaimAccessOrConditions( expertSub: string, expertBlameIds: unknown[], ): Record[] { const expertOid = Types.ObjectId.isValid(expertSub) ? new Types.ObjectId(expertSub) : expertSub; const or: Record[] = [ { initiatedByFieldExpertId: expertOid }, ]; if (expertBlameIds.length > 0) { or.push({ blameRequestId: { $in: expertBlameIds } }); } return or; } /** True when actor is the field expert who initiated this blame (any JWT shape). */ export function actorIsFieldExpertInitiator( actor: { sub?: string; role?: string } | null | undefined, ): boolean { return actor?.role === RoleEnum.FIELD_EXPERT && !!actor?.sub; }