mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 14:08:11 +00:00
feat: scope notification to permission actions
This commit is contained in:
@@ -46,7 +46,8 @@ export class BackofficeService {
|
||||
|
||||
/**
|
||||
* IAM user ids of every current employee across all organizations — used by
|
||||
* the notification recipients resolver's `allBackoffice` selector.
|
||||
* support chat for staff room membership. Notifications deliberately do NOT
|
||||
* use this: they target a desk via `getEmployeeUserIdsByPermission`.
|
||||
*/
|
||||
async getAllCurrentEmployeeUserIds(): Promise<string[]> {
|
||||
const employees = await this.employeeRepository.find({
|
||||
@@ -63,24 +64,67 @@ export class BackofficeService {
|
||||
* IAM user ids of current employees (any org) holding ANY of the given
|
||||
* permission keys — used by the notification recipients resolver's
|
||||
* `permissionKeys` selector for department/role-scoped targeting.
|
||||
*
|
||||
* This MUST agree with the request-time guard (`hasFreightPermission`,
|
||||
* common/freight-permission.util.ts), which counts four grant carriers plus
|
||||
* the super_admin bypass. Counting fewer silently drops legitimate
|
||||
* recipients: an earlier version joined only direct position permissions, on
|
||||
* which `bookings:view` resolved to 2 users — against 21 through position
|
||||
* TYPES, which is where admin-created positions actually keep their grants.
|
||||
*
|
||||
* Raw SQL rather than QueryBuilder because `Position.positionTypePermissions`
|
||||
* declares its inverse side against PositionType, so a relation join emits
|
||||
* `ptp.position_type_id = position.id` and silently matches nothing. Same
|
||||
* approach as FreightMeService's position-type lookups.
|
||||
*/
|
||||
async getEmployeeUserIdsByPermission(
|
||||
permissionKeys: string[],
|
||||
): Promise<string[]> {
|
||||
if (!permissionKeys.length) return [];
|
||||
const rows: { userId: string | null }[] = await this.employeeRepository
|
||||
.createQueryBuilder("employee")
|
||||
.innerJoin("employee.employeePositions", "employeePosition")
|
||||
.innerJoin("employeePosition.position", "position")
|
||||
.innerJoin("position.positionPermission", "positionPermission")
|
||||
.innerJoin("positionPermission.permission", "permission")
|
||||
.where("employee.isCurrent = :isCurrent", { isCurrent: true })
|
||||
.andWhere("permission.key IN (:...permissionKeys)", { permissionKeys })
|
||||
.select("DISTINCT employee.user_id", "userId")
|
||||
.getRawMany();
|
||||
return rows
|
||||
.map((r) => r.userId)
|
||||
.filter((id): id is string => Boolean(id));
|
||||
const rows: { userId: string }[] = await this.dataSource.query(
|
||||
`WITH target AS (SELECT id FROM iam.permissions WHERE key = ANY($1))
|
||||
-- 1. IAM role grants (user_roles -> role_permissions).
|
||||
SELECT e.user_id AS "userId"
|
||||
FROM iam.employees e
|
||||
JOIN iam.user_roles ur ON ur.user_id = e.user_id
|
||||
JOIN iam.role_permissions rp ON rp.role_id = ur.role_id
|
||||
WHERE e.is_current AND e.user_id IS NOT NULL
|
||||
AND rp.permission_id IN (SELECT id FROM target)
|
||||
UNION
|
||||
-- 2. Direct position grants. A delegate keeps their own position AND
|
||||
-- gains the one they stand in for, so both columns count.
|
||||
SELECT e.user_id
|
||||
FROM iam.employees e
|
||||
JOIN iam.employee_positions ep
|
||||
ON ep.employee_id = e.id AND ep.is_current
|
||||
JOIN iam.position_permissions pp
|
||||
ON pp.position_id IN (ep.position_id, ep.delegatee_position_id)
|
||||
WHERE e.is_current AND e.user_id IS NOT NULL
|
||||
AND pp.permission_id IN (SELECT id FROM target)
|
||||
UNION
|
||||
-- 3. Position TYPE grants — where admin-created positions keep theirs.
|
||||
SELECT e.user_id
|
||||
FROM iam.employees e
|
||||
JOIN iam.employee_positions ep
|
||||
ON ep.employee_id = e.id AND ep.is_current
|
||||
JOIN iam.positions p
|
||||
ON p.id IN (ep.position_id, ep.delegatee_position_id)
|
||||
JOIN iam.position_type_permissions ptp
|
||||
ON ptp.position_type_id = p.position_type_id
|
||||
WHERE e.is_current AND e.user_id IS NOT NULL
|
||||
AND ptp.permission_id IN (SELECT id FROM target)
|
||||
UNION
|
||||
-- 4. super_admin passes every freight permission check, so mirror that
|
||||
-- here or admins go blind on desks nobody else has been granted yet.
|
||||
SELECT e.user_id
|
||||
FROM iam.employees e
|
||||
JOIN iam.user_roles ur ON ur.user_id = e.user_id
|
||||
JOIN iam.roles r ON r.id = ur.role_id
|
||||
WHERE e.is_current AND e.user_id IS NOT NULL
|
||||
AND r.key = 'super_admin'`,
|
||||
[permissionKeys],
|
||||
);
|
||||
return rows.map((r) => r.userId);
|
||||
}
|
||||
|
||||
async createOrganizationUser(
|
||||
|
||||
Reference in New Issue
Block a user