Skip to content

Commit 5b68bb2

Browse files
committed
Add backend Slack alert support
1 parent 97e52de commit 5b68bb2

File tree

15 files changed

+857
-2
lines changed

15 files changed

+857
-2
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
3+
const Archetype = require('archetype');
4+
const authorize = require('../../authorize');
5+
6+
const CreateAlertParams = new Archetype({
7+
workspaceId: {
8+
$type: 'string'
9+
},
10+
name: {
11+
$type: 'string'
12+
},
13+
eventType: {
14+
$type: 'string',
15+
$required: true
16+
},
17+
database: {
18+
$type: 'string'
19+
},
20+
collection: {
21+
$type: 'string'
22+
},
23+
slackChannel: {
24+
$type: 'string',
25+
$required: true
26+
},
27+
templateText: {
28+
$type: 'string',
29+
$required: true
30+
},
31+
enabled: {
32+
$type: 'boolean'
33+
},
34+
roles: {
35+
$type: ['string']
36+
}
37+
}).compile('CreateAlertParams');
38+
39+
module.exports = ({ studioConnection }) => async function createAlert(params) {
40+
const {
41+
workspaceId,
42+
name,
43+
eventType,
44+
database,
45+
collection,
46+
slackChannel,
47+
templateText,
48+
enabled,
49+
roles
50+
} = new CreateAlertParams(params);
51+
52+
await authorize('Alert.createAlert', roles);
53+
54+
const Alert = studioConnection.model('__Studio_Alert');
55+
const alert = await Alert.create({
56+
workspaceId,
57+
name,
58+
eventType,
59+
database,
60+
collection,
61+
slackChannel,
62+
templateText,
63+
enabled: !!enabled
64+
});
65+
66+
return { alert };
67+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
const Archetype = require('archetype');
4+
const authorize = require('../../authorize');
5+
6+
const DeleteAlertParams = new Archetype({
7+
alertId: {
8+
$type: 'string',
9+
$required: true
10+
},
11+
roles: {
12+
$type: ['string']
13+
}
14+
}).compile('DeleteAlertParams');
15+
16+
module.exports = ({ studioConnection }) => async function deleteAlert(params) {
17+
const { alertId, roles } = new DeleteAlertParams(params);
18+
19+
await authorize('Alert.deleteAlert', roles);
20+
21+
const Alert = studioConnection.model('__Studio_Alert');
22+
await Alert.findByIdAndDelete(alertId);
23+
24+
return { success: true };
25+
};

backend/actions/Alert/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
exports.createAlert = require('./createAlert');
4+
exports.deleteAlert = require('./deleteAlert');
5+
exports.listAlerts = require('./listAlerts');
6+
exports.sendTestAlert = require('./sendTestAlert');
7+
exports.updateAlert = require('./updateAlert');
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
const Archetype = require('archetype');
4+
const authorize = require('../../authorize');
5+
6+
const ListAlertsParams = new Archetype({
7+
workspaceId: {
8+
$type: 'string'
9+
},
10+
roles: {
11+
$type: ['string']
12+
}
13+
}).compile('ListAlertsParams');
14+
15+
module.exports = ({ studioConnection }) => async function listAlerts(params = {}) {
16+
const { workspaceId, roles } = new ListAlertsParams(params);
17+
18+
await authorize('Alert.listAlerts', roles);
19+
20+
const Alert = studioConnection.model('__Studio_Alert');
21+
const query = workspaceId ? { workspaceId } : {};
22+
const alerts = await Alert.find(query).sort({ createdAt: -1 }).lean();
23+
24+
return { alerts };
25+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
const Archetype = require('archetype');
4+
const authorize = require('../../authorize');
5+
const { renderTemplate, notifySlack } = require('../../alerts/alertUtils');
6+
7+
const SendTestAlertParams = new Archetype({
8+
workspaceId: {
9+
$type: 'string'
10+
},
11+
slackChannel: {
12+
$type: 'string',
13+
$required: true
14+
},
15+
templateText: {
16+
$type: 'string',
17+
$required: true
18+
},
19+
sampleDocument: {
20+
$type: 'object',
21+
$required: true
22+
},
23+
roles: {
24+
$type: ['string']
25+
}
26+
}).compile('SendTestAlertParams');
27+
28+
module.exports = ({ options }) => async function sendTestAlert(params) {
29+
const { workspaceId, slackChannel, templateText, sampleDocument, roles } = new SendTestAlertParams(params);
30+
31+
await authorize('Alert.sendTestAlert', roles);
32+
33+
const mothershipUrl = options?._mothershipUrl || 'https://mongoose-js.netlify.app/.netlify/functions';
34+
const text = renderTemplate(templateText, sampleDocument);
35+
await notifySlack({
36+
mothershipUrl,
37+
payload: {
38+
workspaceId,
39+
channel: slackChannel,
40+
template: templateText,
41+
text,
42+
sampleDocument
43+
}
44+
});
45+
46+
return { success: true };
47+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use strict';
2+
3+
const Archetype = require('archetype');
4+
const authorize = require('../../authorize');
5+
6+
const UpdateAlertParams = new Archetype({
7+
alertId: {
8+
$type: 'string',
9+
$required: true
10+
},
11+
workspaceId: {
12+
$type: 'string'
13+
},
14+
name: {
15+
$type: 'string'
16+
},
17+
eventType: {
18+
$type: 'string'
19+
},
20+
database: {
21+
$type: 'string'
22+
},
23+
collection: {
24+
$type: 'string'
25+
},
26+
slackChannel: {
27+
$type: 'string'
28+
},
29+
templateText: {
30+
$type: 'string'
31+
},
32+
enabled: {
33+
$type: 'boolean'
34+
},
35+
roles: {
36+
$type: ['string']
37+
}
38+
}).compile('UpdateAlertParams');
39+
40+
module.exports = ({ studioConnection }) => async function updateAlert(params) {
41+
const {
42+
alertId,
43+
workspaceId,
44+
name,
45+
eventType,
46+
database,
47+
collection,
48+
slackChannel,
49+
templateText,
50+
enabled,
51+
roles
52+
} = new UpdateAlertParams(params);
53+
54+
await authorize('Alert.updateAlert', roles);
55+
56+
const Alert = studioConnection.model('__Studio_Alert');
57+
const alert = await Alert.findByIdAndUpdate(
58+
alertId,
59+
{
60+
workspaceId,
61+
name,
62+
eventType,
63+
database,
64+
collection,
65+
slackChannel,
66+
templateText,
67+
enabled
68+
},
69+
{ new: true }
70+
);
71+
72+
return { alert };
73+
};

backend/actions/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
exports.ChatMessage = require('./ChatMessage');
44
exports.ChatThread = require('./ChatThread');
55
exports.Dashboard = require('./Dashboard');
6+
exports.Alert = require('./Alert');
67
exports.Model = require('./Model');
78
exports.Script = require('./Script');
89
exports.status = require('./status');

backend/alerts/alertUtils.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
3+
function getValueByPath(object, path) {
4+
return path.split('.').reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : null), object);
5+
}
6+
7+
function renderTemplate(template, doc) {
8+
if (!template) {
9+
return '';
10+
}
11+
return template.replace(/{{\s*([^}]+)\s*}}/g, (_match, path) => {
12+
const value = getValueByPath(doc, path.trim());
13+
return value === null ? '—' : String(value);
14+
});
15+
}
16+
17+
async function notifySlack({ mothershipUrl, payload }) {
18+
const response = await fetch(`${mothershipUrl}/notifySlack`, {
19+
method: 'POST',
20+
headers: { 'Content-Type': 'application/json' },
21+
body: JSON.stringify(payload)
22+
});
23+
24+
if (!response.ok) {
25+
const text = await response.text();
26+
throw new Error(`Slack notify failed (${response.status}): ${text}`);
27+
}
28+
29+
return response.json().catch(() => ({}));
30+
}
31+
32+
module.exports = {
33+
getValueByPath,
34+
renderTemplate,
35+
notifySlack
36+
};

0 commit comments

Comments
 (0)