Skip to content

Commit 4419429

Browse files
Codacy quality fixes
Signed-off-by: Steve Springett <steve@springett.us>
1 parent 5769c76 commit 4419429

20 files changed

+1282
-1554
lines changed

backend/src/events/channels/chat-formatters.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -70,46 +70,64 @@ export function getEventNature(eventType: string): EventNature {
7070
}
7171

7272
/**
73-
* Build a human-readable summary line from the event type and data.
73+
* Handlers for different entity types in getSummary.
7474
*/
75-
export function getSummary(eventType: string, data: Record<string, unknown>): string {
76-
const name = (data.name || data.title || data.entityName) as string | undefined;
77-
const state = (data.state || data.newState) as string | undefined;
75+
type SummaryHandler = (eventType: string, name: string | undefined, state: string | undefined) => string | null;
7876

79-
if (eventType.includes('assessment')) {
77+
const summaryHandlers: Record<string, SummaryHandler> = {
78+
assessment: (eventType, name, state) => {
8079
if (eventType.includes('created')) return `Assessment Created: ${name || 'New Assessment'}`;
8180
if (eventType.includes('state_changed')) return `Assessment ${state || 'Updated'}`;
8281
if (eventType.includes('assigned')) return `Assessment Assigned: ${name || 'Assessment'}`;
8382
if (eventType.includes('deleted')) return `Assessment Deleted: ${name || 'Assessment'}`;
84-
}
85-
86-
if (eventType.includes('evidence')) {
83+
return null;
84+
},
85+
evidence: (eventType, name, state) => {
8786
if (eventType.includes('created')) return `Evidence Created: ${name || 'New Evidence'}`;
8887
if (eventType.includes('state_changed')) return `Evidence ${state || 'Updated'}`;
8988
if (eventType.includes('attachment_added')) return `Evidence Attachment Added: ${name || 'Evidence'}`;
9089
if (eventType.includes('attachment_removed')) return `Evidence Attachment Removed: ${name || 'Evidence'}`;
91-
}
92-
93-
if (eventType.includes('attestation')) {
90+
return null;
91+
},
92+
attestation: (eventType, name) => {
9493
if (eventType.includes('created')) return `Attestation Created: ${name || 'New Attestation'}`;
9594
if (eventType.includes('signed')) return `Attestation Signed: ${name || 'Attestation'}`;
9695
if (eventType.includes('exported')) return `Attestation Exported: ${name || 'Attestation'}`;
97-
}
98-
99-
if (eventType.includes('claim')) {
96+
return null;
97+
},
98+
claim: (eventType, name) => {
10099
if (eventType.includes('created')) return `Claim Created: ${name || 'New Claim'}`;
101100
if (eventType.includes('updated')) return `Claim Updated: ${name || 'Claim'}`;
102-
}
103-
104-
if (eventType.includes('project')) {
101+
return null;
102+
},
103+
project: (eventType, name, state) => {
105104
if (eventType.includes('created')) return `Project Created: ${name || 'New Project'}`;
106105
if (eventType.includes('state_changed')) return `Project ${state || 'Updated'}`;
107106
if (eventType.includes('archived')) return `Project Archived: ${name || 'Project'}`;
108-
}
109-
110-
if (eventType.includes('standard')) {
107+
return null;
108+
},
109+
standard: (eventType, name, state) => {
111110
if (eventType.includes('imported')) return `Standard Imported: ${name || 'New Standard'}`;
112111
if (eventType.includes('state_changed')) return `Standard ${state || 'Updated'}`;
112+
return null;
113+
},
114+
};
115+
116+
/**
117+
* Build a human-readable summary line from the event type and data.
118+
*/
119+
export function getSummary(eventType: string, data: Record<string, unknown>): string {
120+
const name = (data.name || data.title || data.entityName) as string | undefined;
121+
const state = (data.state || data.newState) as string | undefined;
122+
123+
// Try each entity-type handler
124+
for (const [entityType, handler] of Object.entries(summaryHandlers)) {
125+
if (eventType.includes(entityType)) {
126+
const result = handler(eventType, name, state);
127+
if (result) {
128+
return result;
129+
}
130+
}
113131
}
114132

115133
// Fallback: convert dot-separated type to readable string

backend/src/events/in-app-channel.ts

Lines changed: 2 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -5,99 +5,13 @@
55
* handles recipient resolution and calls deliverToUser() directly.
66
*/
77

8-
import { v4 as uuidv4 } from 'uuid';
98
import type { Kysely } from 'kysely';
109
import type { Database } from '../db/types.js';
1110
import { logger } from '../utils/logger.js';
1211
import type { NotificationChannel } from './channel.js';
1312
import type { EventEnvelope } from './types.js';
1413
import { resolveRecipients } from './recipients.js';
15-
16-
/**
17-
* Build a human readable notification title from an event envelope.
18-
*/
19-
function buildTitle(envelope: EventEnvelope): string {
20-
const { type, data } = envelope;
21-
22-
switch (type) {
23-
case 'evidence.state_changed': {
24-
const state = data.newState as string | undefined;
25-
if (state === 'in_review') return 'Evidence Submitted for Review';
26-
if (state === 'approved' || state === 'claimed') return 'Evidence Approved';
27-
if (state === 'in_progress') return 'Evidence Rejected';
28-
return 'Evidence State Changed';
29-
}
30-
case 'assessment.state_changed': {
31-
const state = data.newState as string | undefined;
32-
if (state === 'in_progress') return 'Assessment Started';
33-
if (state === 'completed') return 'Assessment Completed';
34-
return 'Assessment State Changed';
35-
}
36-
case 'assessment.created':
37-
return 'New Assessment Created';
38-
case 'attestation.created':
39-
return 'Attestation Created';
40-
case 'attestation.signed':
41-
return 'Attestation Signed';
42-
default:
43-
return type.replace(/\./g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
44-
}
45-
}
46-
47-
/**
48-
* Build a human readable notification message from an event envelope.
49-
*/
50-
function buildMessage(envelope: EventEnvelope): string {
51-
const { type, data } = envelope;
52-
const name = (data.evidenceName || data.assessmentTitle || data.name || '') as string;
53-
54-
switch (type) {
55-
case 'evidence.state_changed': {
56-
const state = data.newState as string | undefined;
57-
if (state === 'in_review') return `Evidence "${name}" has been submitted for your review`;
58-
if (state === 'approved' || state === 'claimed') return `Your evidence "${name}" has been approved`;
59-
if (state === 'in_progress') {
60-
const reason = data.rejectionReason as string | undefined;
61-
return reason
62-
? `Your evidence "${name}" has been rejected. Reason: ${reason}`
63-
: `Your evidence "${name}" has been rejected`;
64-
}
65-
return `Evidence "${name}" state changed to ${state}`;
66-
}
67-
case 'assessment.state_changed': {
68-
const state = data.newState as string | undefined;
69-
const title = (data.assessmentTitle || '') as string;
70-
if (state === 'in_progress') return `Assessment "${title}" has been started`;
71-
if (state === 'completed') return `Assessment "${title}" has been completed`;
72-
return `Assessment "${title}" state changed to ${state}`;
73-
}
74-
case 'assessment.created':
75-
return `Assessment "${(data.assessmentTitle || '') as string}" has been created`;
76-
default:
77-
return `${buildTitle(envelope)}: ${name || envelope.type}`;
78-
}
79-
}
80-
81-
/**
82-
* Build a link for the notification.
83-
*/
84-
function buildLink(envelope: EventEnvelope): string | null {
85-
const { type, data } = envelope;
86-
87-
if (type.startsWith('evidence.') && data.evidenceId) {
88-
return `/evidence/${String(data.evidenceId)}`;
89-
}
90-
if (type.startsWith('assessment.') && data.assessmentId) {
91-
return `/assessments/${String(data.assessmentId)}`;
92-
}
93-
if (type.startsWith('attestation.') && data.attestationId) {
94-
return `/attestations/${String(data.attestationId)}`;
95-
}
96-
if (type.startsWith('project.') && data.projectId) {
97-
return `/projects/${String(data.projectId)}`;
98-
}
99-
return null;
100-
}
14+
import { buildTitle, buildMessage, buildLink, insertNotification } from './notification-utils.js';
10115

10216
export class InAppChannel implements NotificationChannel {
10317
name = 'in_app';
@@ -144,26 +58,7 @@ export class InAppChannel implements NotificationChannel {
14458
const message = buildMessage(envelope);
14559
const link = buildLink(envelope);
14660

147-
try {
148-
await db
149-
.insertInto('notification')
150-
.values({
151-
id: uuidv4(),
152-
user_id: userId,
153-
type: envelope.type.replace(/\./g, '_'),
154-
title,
155-
message,
156-
link: link || undefined,
157-
is_read: false,
158-
})
159-
.execute();
160-
} catch (error) {
161-
logger.error('Failed to create in-app notification', {
162-
eventId: envelope.id,
163-
userId,
164-
error: error instanceof Error ? error.message : String(error),
165-
});
166-
}
61+
await insertNotification(db, envelope, userId, title, message, link);
16762
}
16863

16964
async shutdown(): Promise<void> {

backend/src/events/rules-engine.ts

Lines changed: 5 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { logger } from '../utils/logger.js';
1212
import type { EventEnvelope } from './types.js';
1313
import type { NotificationRule } from '../db/types.js';
1414
import { ChannelRegistry } from './channel-registry.js';
15-
import { v4 as uuidv4 } from 'uuid';
15+
import { buildTitle, buildMessage, buildLink, insertNotification } from './notification-utils.js';
1616

1717
/**
1818
* Result of evaluating a rule against an event.
@@ -275,122 +275,17 @@ export class RulesEngine {
275275
*/
276276
private async deliverInApp(envelope: EventEnvelope, matches: RuleMatch[]): Promise<void> {
277277
const db = this.getDb();
278-
const title = this.buildTitle(envelope);
279-
const message = this.buildMessage(envelope);
280-
const link = this.buildLink(envelope);
278+
const title = buildTitle(envelope);
279+
const message = buildMessage(envelope);
280+
const link = buildLink(envelope);
281281

282282
for (const match of matches) {
283283
const userId = match.rule.user_id || match.userId;
284284
if (!userId) {
285285
continue;
286286
}
287287

288-
try {
289-
await db
290-
.insertInto('notification')
291-
.values({
292-
id: uuidv4(),
293-
user_id: userId,
294-
type: envelope.type.replace(/\./g, '_'),
295-
title,
296-
message,
297-
link: link || undefined,
298-
is_read: false,
299-
})
300-
.execute();
301-
} catch (error) {
302-
logger.error('Failed to create in-app notification', {
303-
eventId: envelope.id,
304-
userId,
305-
error: error instanceof Error ? error.message : String(error),
306-
});
307-
}
308-
}
309-
}
310-
311-
/**
312-
* Build a human readable notification title from an event envelope.
313-
*/
314-
private buildTitle(envelope: EventEnvelope): string {
315-
const { type, data } = envelope;
316-
317-
switch (type) {
318-
case 'evidence.state_changed': {
319-
const state = data.newState as string | undefined;
320-
if (state === 'in_review') return 'Evidence Submitted for Review';
321-
if (state === 'approved' || state === 'claimed') return 'Evidence Approved';
322-
if (state === 'in_progress') return 'Evidence Rejected';
323-
return 'Evidence State Changed';
324-
}
325-
case 'assessment.state_changed': {
326-
const state = data.newState as string | undefined;
327-
if (state === 'in_progress') return 'Assessment Started';
328-
if (state === 'completed') return 'Assessment Completed';
329-
return 'Assessment State Changed';
330-
}
331-
case 'assessment.created':
332-
return 'New Assessment Created';
333-
case 'attestation.created':
334-
return 'Attestation Created';
335-
case 'attestation.signed':
336-
return 'Attestation Signed';
337-
default:
338-
return type.replace(/\./g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
339-
}
340-
}
341-
342-
/**
343-
* Build a human readable notification message from an event envelope.
344-
*/
345-
private buildMessage(envelope: EventEnvelope): string {
346-
const { type, data } = envelope;
347-
const name = (data.evidenceName || data.assessmentTitle || data.name || '') as string;
348-
349-
switch (type) {
350-
case 'evidence.state_changed': {
351-
const state = data.newState as string | undefined;
352-
if (state === 'in_review') return `Evidence "${name}" has been submitted for your review`;
353-
if (state === 'approved' || state === 'claimed') return `Your evidence "${name}" has been approved`;
354-
if (state === 'in_progress') {
355-
const reason = data.rejectionReason as string | undefined;
356-
return reason
357-
? `Your evidence "${name}" has been rejected. Reason: ${reason}`
358-
: `Your evidence "${name}" has been rejected`;
359-
}
360-
return `Evidence "${name}" state changed to ${state}`;
361-
}
362-
case 'assessment.state_changed': {
363-
const state = data.newState as string | undefined;
364-
const title = (data.assessmentTitle || '') as string;
365-
if (state === 'in_progress') return `Assessment "${title}" has been started`;
366-
if (state === 'completed') return `Assessment "${title}" has been completed`;
367-
return `Assessment "${title}" state changed to ${state}`;
368-
}
369-
case 'assessment.created':
370-
return `Assessment "${(data.assessmentTitle || '') as string}" has been created`;
371-
default:
372-
return `${this.buildTitle(envelope)}: ${name || type}`;
373-
}
374-
}
375-
376-
/**
377-
* Build a link for the notification.
378-
*/
379-
private buildLink(envelope: EventEnvelope): string | null {
380-
const { type, data } = envelope;
381-
382-
if (type.startsWith('evidence.') && data.evidenceId) {
383-
return `/evidence/${String(data.evidenceId)}`;
384-
}
385-
if (type.startsWith('assessment.') && data.assessmentId) {
386-
return `/assessments/${String(data.assessmentId)}`;
387-
}
388-
if (type.startsWith('attestation.') && data.attestationId) {
389-
return `/attestations/${String(data.attestationId)}`;
390-
}
391-
if (type.startsWith('project.') && data.projectId) {
392-
return `/projects/${String(data.projectId)}`;
288+
await insertNotification(db, envelope, userId, title, message, link);
393289
}
394-
return null;
395290
}
396291
}

0 commit comments

Comments
 (0)