Unified search algorithm across all lists; per-section record visibility (contacts, deals, activities, leads, finance, tickets, notes, projects)

This commit is contained in:
2026-10-06 15:04:46 +03:30
parent 43717392b2
commit e08f1452ef
38 changed files with 699 additions and 420 deletions
+34 -22
View File
@@ -4,9 +4,13 @@ import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import type { DataVisibility } from '@mento/shared';
import { BillingService } from '../billing/billing.service';
import { normalizeDataVisibility } from '../common/record-scope';
import {
type Actor,
normalizeDataVisibility,
parseVisibilityScopes,
} from '../common/record-scope';
import { PrismaService } from '../prisma/prisma.service';
import { canViewAllRecords } from '@mento/shared';
import { DATA_SCOPES, canViewAllRecords } from '@mento/shared';
export type JwtPayload = {
sub: string;
@@ -53,7 +57,12 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
isActive: true,
organization: { isActive: true },
},
select: { organizationId: true, role: true, dataVisibility: true },
select: {
organizationId: true,
role: true,
dataVisibility: true,
dataVisibilityScopes: true,
},
});
if (!membership) {
throw new UnauthorizedException();
@@ -61,12 +70,26 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
await this.billing.requireActiveForOrganization(membership.organizationId);
const dataVisibility = normalizeDataVisibility(membership.dataVisibility);
const visibleUserIds = await this.resolveVisibleUserIds(
membership.organizationId,
user.id,
membership.role,
dataVisibility,
);
const scopeLevels = parseVisibilityScopes(membership.dataVisibilityScopes);
const elevatedRole = canViewAllRecords(membership.role);
let teamIds: string[] | undefined;
const resolve = async (level: DataVisibility): Promise<string[] | null> => {
if (elevatedRole || level === 'org') return null;
if (level === 'own') return [user.id];
teamIds ??= await this.teamPeerIds(membership.organizationId, user.id);
return teamIds;
};
const visibleUserIds = await resolve(dataVisibility);
const scopes: Actor['scopes'] = {};
if (!elevatedRole) {
for (const scope of DATA_SCOPES) {
const level = scopeLevels[scope];
if (!level || level === dataVisibility) continue;
scopes[scope] = { dataVisibility: level, visibleUserIds: await resolve(level) };
}
}
return {
userId: user.id,
@@ -74,22 +97,11 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
role: membership.role,
dataVisibility,
visibleUserIds,
scopes,
};
}
private async resolveVisibleUserIds(
organizationId: string,
userId: string,
role: string,
dataVisibility: DataVisibility,
): Promise<string[] | null> {
if (canViewAllRecords(role) || dataVisibility === 'org') {
return null;
}
if (dataVisibility === 'own') {
return [userId];
}
private async teamPeerIds(organizationId: string, userId: string): Promise<string[]> {
const memberships = await this.prisma.teamMember.findMany({
where: {
userId,