Files
CRM_Montzar/apps/api/src/memberships/memberships.service.ts
T
Mahdi 09a60b3b5a feat(acl): deep data access by section for roles and users
Role default + user override with OR rules; covers customers, leads, sales, activities, notes, routines, projects, and tickets.
2026-10-22 11:53:26 +03:30

174 lines
4.6 KiB
TypeScript

import {
ForbiddenException,
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import type { OrganizationMembershipDto, UserRole } from '@mento/shared';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
export type MembershipActor = {
userId: string;
organizationId: string;
role: UserRole;
};
@Injectable()
export class MembershipsService {
constructor(private readonly prisma: PrismaService) {}
async listActive(userId: string) {
return this.prisma.membership.findMany({
where: { userId, isActive: true, organization: { isActive: true } },
include: {
organization: { select: { id: true, name: true } },
orgRole: { select: { name: true } },
},
orderBy: { createdAt: 'asc' },
});
}
async requireActive(
userId: string,
organizationId: string,
): Promise<MembershipActor> {
const row = await this.prisma.membership.findFirst({
where: { userId, organizationId, isActive: true },
select: {
userId: true,
organizationId: true,
role: true,
organization: { select: { isActive: true } },
},
});
if (!row) {
throw new ForbiddenException('به این محیط دسترسی ندارید');
}
if (row.organization.isActive === false) {
throw new UnauthorizedException('این محیط غیرفعال است');
}
return {
userId: row.userId,
organizationId: row.organizationId,
role: row.role,
};
}
toChoices(
rows: Array<{
organization: { id: string; name: string };
role: UserRole;
orgRole: { name: string } | null;
}>,
): OrganizationMembershipDto[] {
return rows.map((row) => ({
id: row.organization.id,
name: row.organization.name,
role: row.role,
orgRoleName: row.orgRole?.name,
}));
}
async createMembership(data: {
userId: string;
organizationId: string;
role: UserRole;
orgRoleId?: string | null;
commissionPercent?: number;
isActive?: boolean;
}) {
return this.prisma.membership.create({
data: {
userId: data.userId,
organizationId: data.organizationId,
role: data.role,
orgRoleId: data.orgRoleId ?? null,
isActive: data.isActive ?? true,
commissionPercent:
data.commissionPercent == null
? undefined
: new Prisma.Decimal(data.commissionPercent),
},
});
}
async findInOrg(organizationId: string, userId: string) {
return this.prisma.membership.findFirst({
where: { organizationId, userId },
include: {
user: true,
orgRole: { select: { id: true, name: true, color: true, dataAccessPolicy: true } },
},
});
}
async listInOrg(organizationId: string) {
return this.prisma.membership.findMany({
where: { organizationId },
include: {
user: true,
orgRole: { select: { id: true, name: true, color: true, dataAccessPolicy: true } },
},
orderBy: [{ user: { lastLoginAt: 'desc' } }, { user: { fullName: 'asc' } }],
});
}
async countActiveOwners(organizationId: string, exceptUserId?: string) {
return this.prisma.membership.count({
where: {
organizationId,
role: 'owner',
isActive: true,
...(exceptUserId ? { userId: { not: exceptUserId } } : {}),
},
});
}
async syncUserLastOrg(
userId: string,
organizationId: string,
role: UserRole,
orgRoleId?: string | null,
) {
await this.prisma.user.update({
where: { id: userId },
data: {
organizationId,
role,
...(orgRoleId !== undefined ? { orgRoleId } : {}),
},
});
}
async findUserInOrgById(organizationId: string, userId: string) {
const row = await this.findInOrg(organizationId, userId);
if (!row) throw new NotFoundException('کاربر پیدا نشد');
return row;
}
async findUserInOrgByIdentity(
organizationId: string,
where: { id?: string; email?: string; mobile?: string },
) {
const user = await this.prisma.user.findFirst({
where: {
...(where.id ? { id: where.id } : {}),
...(where.email ? { email: where.email } : {}),
...(where.mobile ? { mobile: where.mobile } : {}),
},
select: { id: true },
});
if (!user) return null;
return this.prisma.membership.findFirst({
where: { organizationId, userId: user.id },
include: { user: true },
});
}
}
export function unauthorizedIfMissing<T>(value: T | null | undefined): T {
if (!value) throw new UnauthorizedException();
return value;
}