feat(sse): scope timeline events to affected audiences
This commit is contained in:
@@ -68,6 +68,34 @@ function deliverEvent(event: SseEvent): void {
|
||||
export const userAudience = (userId: string): SseAudience => `user:${userId}`;
|
||||
export const roleAudience = (role: string): SseAudience => `role:${role}`;
|
||||
export const permissionAudience = (permission: string): SseAudience => `permission:${permission}`;
|
||||
export const resourceAudience = (resourceId: string): SseAudience => `resource:${resourceId}`;
|
||||
|
||||
function extractScopedResourceIds(payload: Record<string, unknown>): string[] {
|
||||
const resourceIds = new Set<string>();
|
||||
|
||||
const directResourceId = payload["resourceId"];
|
||||
if (typeof directResourceId === "string" && directResourceId.trim().length > 0) {
|
||||
resourceIds.add(directResourceId);
|
||||
}
|
||||
|
||||
const listedResourceIds = payload["resourceIds"];
|
||||
if (Array.isArray(listedResourceIds)) {
|
||||
for (const resourceId of listedResourceIds) {
|
||||
if (typeof resourceId === "string" && resourceId.trim().length > 0) {
|
||||
resourceIds.add(resourceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...resourceIds];
|
||||
}
|
||||
|
||||
function buildPlanningAudiences(payload: Record<string, unknown>): SseAudience[] {
|
||||
return normalizeAudiences([
|
||||
permissionAudience(PermissionKey.MANAGE_ALLOCATIONS),
|
||||
...extractScopedResourceIds(payload).map((resourceId) => resourceAudience(resourceId)),
|
||||
]);
|
||||
}
|
||||
|
||||
/** Flush a single event type from the buffer and deliver to subscribers. */
|
||||
function flushEventType(type: SseEventType, audience: readonly SseAudience[]): void {
|
||||
@@ -250,20 +278,20 @@ setupSubscriber();
|
||||
|
||||
// Helper emitters
|
||||
export const emitAllocationCreated = (allocation: Record<string, unknown>) =>
|
||||
eventBus.emit(SSE_EVENT_TYPES.ALLOCATION_CREATED, allocation, [permissionAudience(PermissionKey.MANAGE_ALLOCATIONS)]);
|
||||
eventBus.emit(SSE_EVENT_TYPES.ALLOCATION_CREATED, allocation, buildPlanningAudiences(allocation));
|
||||
|
||||
export const emitAllocationUpdated = (allocation: Record<string, unknown>) =>
|
||||
eventBus.emit(SSE_EVENT_TYPES.ALLOCATION_UPDATED, allocation, [permissionAudience(PermissionKey.MANAGE_ALLOCATIONS)]);
|
||||
eventBus.emit(SSE_EVENT_TYPES.ALLOCATION_UPDATED, allocation, buildPlanningAudiences(allocation));
|
||||
|
||||
export const emitAllocationDeleted = (allocationId: string, projectId: string) =>
|
||||
export const emitAllocationDeleted = (allocationId: string, projectId: string, resourceId?: string | null) =>
|
||||
eventBus.emit(
|
||||
SSE_EVENT_TYPES.ALLOCATION_DELETED,
|
||||
{ allocationId, projectId },
|
||||
[permissionAudience(PermissionKey.MANAGE_ALLOCATIONS)],
|
||||
{ allocationId, projectId, ...(resourceId ? { resourceId } : {}) },
|
||||
buildPlanningAudiences({ allocationId, projectId, ...(resourceId ? { resourceId } : {}) }),
|
||||
);
|
||||
|
||||
export const emitProjectShifted = (project: Record<string, unknown>) =>
|
||||
eventBus.emit(SSE_EVENT_TYPES.PROJECT_SHIFTED, project, [permissionAudience(PermissionKey.MANAGE_ALLOCATIONS)]);
|
||||
eventBus.emit(SSE_EVENT_TYPES.PROJECT_SHIFTED, project, buildPlanningAudiences(project));
|
||||
|
||||
export const emitBudgetWarning = (projectId: string, payload: Record<string, unknown>) =>
|
||||
eventBus.emit(
|
||||
@@ -273,16 +301,16 @@ export const emitBudgetWarning = (projectId: string, payload: Record<string, unk
|
||||
);
|
||||
|
||||
export const emitVacationCreated = (vacation: Record<string, unknown>) =>
|
||||
eventBus.emit(SSE_EVENT_TYPES.VACATION_CREATED, vacation, [permissionAudience(PermissionKey.MANAGE_ALLOCATIONS)]);
|
||||
eventBus.emit(SSE_EVENT_TYPES.VACATION_CREATED, vacation, buildPlanningAudiences(vacation));
|
||||
|
||||
export const emitVacationUpdated = (vacation: Record<string, unknown>) =>
|
||||
eventBus.emit(SSE_EVENT_TYPES.VACATION_UPDATED, vacation, [permissionAudience(PermissionKey.MANAGE_ALLOCATIONS)]);
|
||||
eventBus.emit(SSE_EVENT_TYPES.VACATION_UPDATED, vacation, buildPlanningAudiences(vacation));
|
||||
|
||||
export const emitVacationDeleted = (vacationId: string, resourceId: string) =>
|
||||
eventBus.emit(
|
||||
SSE_EVENT_TYPES.VACATION_DELETED,
|
||||
{ vacationId, resourceId },
|
||||
[permissionAudience(PermissionKey.MANAGE_ALLOCATIONS)],
|
||||
buildPlanningAudiences({ vacationId, resourceId }),
|
||||
);
|
||||
|
||||
export const emitRoleCreated = (role: Record<string, unknown>) =>
|
||||
|
||||
Reference in New Issue
Block a user