Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | 1x 1x 1x 40x 1x 8x | /**
* Registrul de reguli de notificare (spec ยง11.2).
*
* Alegere deliberata: regulile sunt in cod, nu in baza cu `condition_expression`
* evaluat dinamic. Un evaluator de expresii din DB e o suprafata de atac si un
* limbaj de programat in plus, fara ca cineva sa administreze inca reguli per
* tenant. Contractul catre restul sistemului e acelasi cand vor migra in DB.
*/
export type Severity =
| 'CRITICAL'
| 'ACTION_REQUIRED'
| 'WARNING'
| 'INFORMATION'
| 'INTELLIGENCE'
| 'SYSTEM';
/** Cui i se trimite. Rezolvat server-side, niciodata din payload-ul clientului. */
export type RecipientStrategy = 'actor' | 'owners_and_admins' | 'all_members';
export interface NotificationEvent {
id: string;
tenantId: string;
workspaceId: string | null;
eventType: string;
actorId: string | null;
subjectId: string | null;
aggregateType: string | null;
payload: Record<string, unknown>;
createdAt: Date;
}
export interface NotificationRule {
id: string;
eventType: string;
category: string;
severity: Severity;
recipientStrategy: RecipientStrategy;
channels: string[];
/** Fereastra in care o notificare cu aceeasi cheie nu se retrimite. */
deduplicationWindowMinutes: number;
/** Daca e setat, notificarile din fereastra se contopesc intr-una singura. */
aggregationWindowMinutes?: number;
/** Nu notifica actorul despre propria actiune, decat daca e explicit dorit. */
notifyActor: boolean;
isActive: boolean;
title(event: NotificationEvent): string;
body?(event: NotificationEvent): string | undefined;
actionUrl?(event: NotificationEvent): string | undefined;
/** Quick action: ce command real executa butonul din inbox. */
actionType?: string;
}
function str(payload: Record<string, unknown>, key: string, fallback = ''): string {
const value = payload[key];
return typeof value === 'string' ? value : fallback;
}
export const NOTIFICATION_RULES: NotificationRule[] = [
{
id: 'task-created@v1',
eventType: 'task.created',
category: 'tasks',
severity: 'INFORMATION',
recipientStrategy: 'owners_and_admins',
channels: ['in_app'],
deduplicationWindowMinutes: 60,
// Multe taskuri create intr-o ora devin o singura notificare, nu 47.
aggregationWindowMinutes: 60,
notifyActor: false,
isActive: true,
title: (event) => `Task nou: ${str(event.payload, 'title', 'fara titlu')}`,
actionUrl: () => '/dashboard/tasks',
},
{
id: 'organization-created@v1',
eventType: 'organization.created',
category: 'business',
severity: 'INFORMATION',
recipientStrategy: 'owners_and_admins',
channels: ['in_app'],
deduplicationWindowMinutes: 60,
aggregationWindowMinutes: 60,
notifyActor: false,
isActive: true,
title: (event) => `Companie adaugata: ${str(event.payload, 'name', 'fara nume')}`,
actionUrl: () => '/dashboard/organizations',
},
{
id: 'research-brief-created@v1',
eventType: 'research_brief.created',
category: 'intelligence',
severity: 'INTELLIGENCE',
recipientStrategy: 'owners_and_admins',
channels: ['in_app'],
deduplicationWindowMinutes: 60,
notifyActor: false,
isActive: true,
title: (event) => `Research brief nou: ${str(event.payload, 'title', '')}`,
actionUrl: () => '/dashboard/research',
},
{
id: 'user-onboarded@v1',
eventType: 'user.onboarded',
category: 'system',
severity: 'SYSTEM',
recipientStrategy: 'owners_and_admins',
channels: ['in_app'],
deduplicationWindowMinutes: 1440,
notifyActor: true,
isActive: true,
title: (event) => `Workspace creat: ${str(event.payload, 'tenantName', '')}`,
actionUrl: () => '/dashboard',
},
];
export const SEVERITY_ORDER: Record<Severity, number> = {
SYSTEM: 0,
INFORMATION: 1,
INTELLIGENCE: 2,
WARNING: 3,
ACTION_REQUIRED: 4,
CRITICAL: 5,
};
export function rulesFor(eventType: string): NotificationRule[] {
return NOTIFICATION_RULES.filter((rule) => rule.isActive && rule.eventType === eventType);
}
export function subscribedEventTypes(): string[] {
return [...new Set(NOTIFICATION_RULES.filter((r) => r.isActive).map((r) => r.eventType))];
}
|