Skip to content

Commit 77d1f32

Browse files
committed
refactor(webhook): enhance data projection by using specific DB schemes for project, workspace, user, event, and plan DTOs
1 parent da94019 commit 77d1f32

1 file changed

Lines changed: 87 additions & 76 deletions

File tree

workers/webhook/src/templates/generic.ts

Lines changed: 87 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,157 @@
1-
import { Notification } from 'hawk-worker-sender/types/template-variables';
1+
import { Notification, TemplateEventData } from 'hawk-worker-sender/types/template-variables';
2+
import {
3+
ProjectDBScheme,
4+
WorkspaceDBScheme,
5+
UserDBScheme,
6+
DecodedGroupedEvent,
7+
PlanDBScheme,
8+
} from '@hawk.so/types';
29
import { WebhookDelivery } from '../../types/template';
310

411
/**
5-
* Converts ObjectId (or any BSON value) to string, passes primitives through
12+
* Projects safe public fields from a project document
613
*
7-
* @param value - value to stringify
14+
* @param p - project DB record
815
*/
9-
function str(value: unknown): string {
10-
return String(value);
16+
function projectDTO(p: ProjectDBScheme): Record<string, unknown> {
17+
return {
18+
id: String(p._id),
19+
name: p.name,
20+
workspaceId: String(p.workspaceId),
21+
image: p.image ?? null,
22+
};
1123
}
1224

1325
/**
14-
* Safe property accessor — returns undefined for missing nested paths
26+
* Projects safe public fields from a workspace document
1527
*
16-
* @param obj - source object
17-
* @param key - property name
28+
* @param w - workspace DB record
1829
*/
19-
function get(obj: Record<string, unknown>, key: string): unknown {
20-
return obj?.[key];
21-
}
22-
23-
/* ---------- Shared DTO projectors ---------- */
24-
25-
function projectDTO(p: Record<string, unknown>): Record<string, unknown> {
26-
return {
27-
id: str(get(p, '_id')),
28-
name: get(p, 'name') ?? null,
29-
workspaceId: get(p, 'workspaceId') ? str(get(p, 'workspaceId')) : null,
30-
image: get(p, 'image') ?? null,
31-
};
32-
}
33-
34-
function workspaceDTO(w: Record<string, unknown>): Record<string, unknown> {
30+
function workspaceDTO(w: WorkspaceDBScheme): Record<string, unknown> {
3531
return {
36-
id: str(get(w, '_id')),
37-
name: get(w, 'name') ?? null,
38-
image: get(w, 'image') ?? null,
32+
id: String(w._id),
33+
name: w.name,
34+
image: w.image ?? null,
3935
};
4036
}
4137

42-
function userDTO(u: Record<string, unknown>): Record<string, unknown> {
38+
/**
39+
* Projects safe public fields from a user document (no password, no bank cards)
40+
*
41+
* @param u - user DB record
42+
*/
43+
function userDTO(u: UserDBScheme): Record<string, unknown> {
4344
return {
44-
id: str(get(u, '_id')),
45-
name: get(u, 'name') ?? null,
46-
email: get(u, 'email') ?? null,
47-
image: get(u, 'image') ?? null,
45+
id: String(u._id),
46+
name: u.name ?? null,
47+
email: u.email ?? null,
48+
image: u.image ?? null,
4849
};
4950
}
5051

51-
function eventDTO(e: Record<string, unknown>): Record<string, unknown> {
52-
const payload = (e.payload ?? {}) as Record<string, unknown>;
53-
const backtrace = (payload.backtrace ?? []) as Array<Record<string, unknown>>;
54-
52+
/**
53+
* Projects safe public fields from a grouped event (no sourceCode, no breadcrumbs, no addons)
54+
*
55+
* @param e - decoded grouped event
56+
*/
57+
function eventDTO(e: DecodedGroupedEvent): Record<string, unknown> {
5558
return {
56-
id: e._id ? str(e._id) : null,
57-
groupHash: e.groupHash ?? null,
58-
totalCount: e.totalCount ?? null,
59-
catcherType: e.catcherType ?? null,
60-
timestamp: e.timestamp ?? null,
61-
usersAffected: e.usersAffected ?? null,
62-
title: payload.title ?? null,
63-
type: payload.type ?? null,
64-
backtrace: backtrace.map((f) => ({
65-
file: f.file ?? null,
66-
line: f.line ?? null,
59+
id: e._id ? String(e._id) : null,
60+
groupHash: e.groupHash,
61+
totalCount: e.totalCount,
62+
catcherType: e.catcherType,
63+
timestamp: e.timestamp,
64+
usersAffected: e.usersAffected,
65+
title: e.payload.title,
66+
type: e.payload.type ?? null,
67+
backtrace: (e.payload.backtrace ?? []).map((f) => ({
68+
file: f.file,
69+
line: f.line,
6770
column: f.column ?? null,
6871
function: f.function ?? null,
6972
})),
7073
};
7174
}
7275

73-
function templateEventDataDTO(item: Record<string, unknown>): Record<string, unknown> {
74-
const event = (item.event ?? {}) as Record<string, unknown>;
75-
76+
/**
77+
* Projects event list item with its metadata (newCount, daysRepeated, etc.)
78+
*
79+
* @param item - template event data from sender worker
80+
*/
81+
function templateEventDataDTO(item: TemplateEventData): Record<string, unknown> {
7682
return {
77-
event: eventDTO(event),
78-
newCount: item.newCount ?? null,
79-
daysRepeated: item.daysRepeated ?? null,
83+
event: eventDTO(item.event),
84+
newCount: item.newCount,
85+
daysRepeated: item.daysRepeated,
8086
usersAffected: item.usersAffected ?? null,
81-
repetitionId: item.repetitionId ? str(item.repetitionId) : null,
87+
repetitionId: item.repetitionId ? String(item.repetitionId) : null,
8288
};
8389
}
8490

85-
function planDTO(p: Record<string, unknown>): Record<string, unknown> {
91+
/**
92+
* Projects safe public fields from a plan document
93+
*
94+
* @param p - plan DB record
95+
*/
96+
function planDTO(p: PlanDBScheme): Record<string, unknown> {
8697
return {
87-
id: str(get(p, '_id')),
88-
name: get(p, 'name') ?? null,
89-
eventsLimit: get(p, 'eventsLimit') ?? null,
90-
monthlyCharge: get(p, 'monthlyCharge') ?? null,
98+
id: String(p._id),
99+
name: p.name,
100+
eventsLimit: p.eventsLimit,
101+
monthlyCharge: p.monthlyCharge,
91102
};
92103
}
93104

94105
type PayloadProjector = (payload: Record<string, unknown>) => Record<string, unknown>;
95106

96107
const projectors: Record<string, PayloadProjector> = {
97108
'event': (p) => ({
98-
project: projectDTO((p.project ?? {}) as Record<string, unknown>),
99-
events: ((p.events ?? []) as Array<Record<string, unknown>>).map(templateEventDataDTO),
109+
project: p.project ? projectDTO(p.project as ProjectDBScheme) : null,
110+
events: ((p.events ?? []) as TemplateEventData[]).map(templateEventDataDTO),
100111
period: p.period ?? null,
101112
}),
102113

103114
'several-events': (p) => ({
104-
project: projectDTO((p.project ?? {}) as Record<string, unknown>),
105-
events: ((p.events ?? []) as Array<Record<string, unknown>>).map(templateEventDataDTO),
115+
project: p.project ? projectDTO(p.project as ProjectDBScheme) : null,
116+
events: ((p.events ?? []) as TemplateEventData[]).map(templateEventDataDTO),
106117
period: p.period ?? null,
107118
}),
108119

109120
'assignee': (p) => ({
110-
project: projectDTO((p.project ?? {}) as Record<string, unknown>),
111-
event: eventDTO((p.event ?? {}) as Record<string, unknown>),
112-
whoAssigned: userDTO((p.whoAssigned ?? {}) as Record<string, unknown>),
121+
project: p.project ? projectDTO(p.project as ProjectDBScheme) : null,
122+
event: p.event ? eventDTO(p.event as DecodedGroupedEvent) : null,
123+
whoAssigned: p.whoAssigned ? userDTO(p.whoAssigned as UserDBScheme) : null,
113124
daysRepeated: p.daysRepeated ?? null,
114125
}),
115126

116127
'block-workspace': (p) => ({
117-
workspace: workspaceDTO((p.workspace ?? {}) as Record<string, unknown>),
128+
workspace: p.workspace ? workspaceDTO(p.workspace as WorkspaceDBScheme) : null,
118129
}),
119130

120131
'blocked-workspace-reminder': (p) => ({
121-
workspace: workspaceDTO((p.workspace ?? {}) as Record<string, unknown>),
132+
workspace: p.workspace ? workspaceDTO(p.workspace as WorkspaceDBScheme) : null,
122133
daysAfterBlock: p.daysAfterBlock ?? null,
123134
}),
124135

125136
'days-limit-almost-reached': (p) => ({
126-
workspace: workspaceDTO((p.workspace ?? {}) as Record<string, unknown>),
137+
workspace: p.workspace ? workspaceDTO(p.workspace as WorkspaceDBScheme) : null,
127138
daysLeft: p.daysLeft ?? null,
128139
}),
129140

130141
'events-limit-almost-reached': (p) => ({
131-
workspace: workspaceDTO((p.workspace ?? {}) as Record<string, unknown>),
142+
workspace: p.workspace ? workspaceDTO(p.workspace as WorkspaceDBScheme) : null,
132143
eventsCount: p.eventsCount ?? null,
133144
eventsLimit: p.eventsLimit ?? null,
134145
}),
135146

136147
'payment-failed': (p) => ({
137-
workspace: workspaceDTO((p.workspace ?? {}) as Record<string, unknown>),
148+
workspace: p.workspace ? workspaceDTO(p.workspace as WorkspaceDBScheme) : null,
138149
reason: p.reason ?? null,
139150
}),
140151

141152
'payment-success': (p) => ({
142-
workspace: workspaceDTO((p.workspace ?? {}) as Record<string, unknown>),
143-
plan: planDTO((p.plan ?? {}) as Record<string, unknown>),
153+
workspace: p.workspace ? workspaceDTO(p.workspace as WorkspaceDBScheme) : null,
154+
plan: p.plan ? planDTO(p.plan as PlanDBScheme) : null,
144155
}),
145156

146157
'sign-up': (p) => ({
@@ -163,9 +174,9 @@ const projectors: Record<string, PayloadProjector> = {
163174
* @param notification - notification with type and payload
164175
*/
165176
export default function render(notification: Notification): WebhookDelivery {
166-
const project = projectors[notification.type];
167-
const payload = project
168-
? project(notification.payload as unknown as Record<string, unknown>)
177+
const projector = projectors[notification.type];
178+
const payload = projector
179+
? projector(notification.payload as unknown as Record<string, unknown>)
169180
: {};
170181

171182
return {

0 commit comments

Comments
 (0)