From 2f3dfe775e35ba8d37e36a813a9abfe220d6ff98 Mon Sep 17 00:00:00 2001
From: Matthieu Bollot
Date: Mon, 8 Apr 2024 00:33:29 +0200
Subject: [PATCH] feat: Slack bot notifications (#676)
---
docker-compose.yml | 19 ++++---
server/.env.sample | 19 ++++---
server/api/controllers/cards/delete.js | 1 +
server/api/helpers/actions/create-one.js | 28 ++++++++++
server/api/helpers/cards/delete-one.js | 12 +++++
.../api/helpers/notifications/create-one.js | 2 +
server/api/helpers/utils/send-email.js | 2 +-
.../api/helpers/utils/send-slack-message.js | 53 +++++++++++++++++++
server/config/custom.js | 17 +++---
9 files changed, 129 insertions(+), 24 deletions(-)
create mode 100644 server/api/helpers/utils/send-slack-message.js
diff --git a/docker-compose.yml b/docker-compose.yml
index 51ac55cd..f7935aa1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -31,14 +31,6 @@ services:
# - DEFAULT_ADMIN_NAME=Demo Demo
# - DEFAULT_ADMIN_USERNAME=demo
- # Email Notifications (https://nodemailer.com/smtp/)
- # - SMTP_HOST=
- # - SMTP_PORT=587
- # - SMTP_SECURE=true
- # - SMTP_USER=
- # - SMTP_PASSWORD=
- # - SMTP_FROM="Demo Demo"
-
# - OIDC_ISSUER=
# - OIDC_CLIENT_ID=
# - OIDC_CLIENT_SECRET=
@@ -51,6 +43,17 @@ services:
# - OIDC_IGNORE_USERNAME=true
# - OIDC_IGNORE_ROLES=true
# - OIDC_ENFORCED=true
+
+ # Email Notifications (https://nodemailer.com/smtp/)
+ # - SMTP_HOST=
+ # - SMTP_PORT=587
+ # - SMTP_SECURE=true
+ # - SMTP_USER=
+ # - SMTP_PASSWORD=
+ # - SMTP_FROM="Demo Demo"
+
+ # - SLACK_BOT_TOKEN=
+ # - SLACK_CHANNEL_ID=
depends_on:
postgres:
condition: service_healthy
diff --git a/server/.env.sample b/server/.env.sample
index fb5a8288..15d66ed7 100644
--- a/server/.env.sample
+++ b/server/.env.sample
@@ -22,14 +22,6 @@ SECRET_KEY=notsecretkey
# DEFAULT_ADMIN_NAME=Demo Demo
# DEFAULT_ADMIN_USERNAME=demo
-# Email Notifications (https://nodemailer.com/smtp/)
-# SMTP_HOST=
-# SMTP_PORT=587
-# SMTP_SECURE=true
-# SMTP_USER=
-# SMTP_PASSWORD=
-# SMTP_FROM="Demo Demo"
-
# OIDC_ISSUER=
# OIDC_CLIENT_ID=
# OIDC_CLIENT_SECRET=
@@ -43,6 +35,17 @@ SECRET_KEY=notsecretkey
# OIDC_IGNORE_ROLES=true
# OIDC_ENFORCED=true
+# Email Notifications (https://nodemailer.com/smtp/)
+# SMTP_HOST=
+# SMTP_PORT=587
+# SMTP_SECURE=true
+# SMTP_USER=
+# SMTP_PASSWORD=
+# SMTP_FROM="Demo Demo"
+
+# SLACK_BOT_TOKEN=
+# SLACK_CHANNEL_ID=
+
## Do not edit this
TZ=UTC
diff --git a/server/api/controllers/cards/delete.js b/server/api/controllers/cards/delete.js
index e2b6d00b..dad82de1 100755
--- a/server/api/controllers/cards/delete.js
+++ b/server/api/controllers/cards/delete.js
@@ -47,6 +47,7 @@ module.exports = {
card = await sails.helpers.cards.deleteOne.with({
record: card,
+ user: currentUser,
request: this.req,
});
diff --git a/server/api/helpers/actions/create-one.js b/server/api/helpers/actions/create-one.js
index 78d5e0c5..b03adfdc 100644
--- a/server/api/helpers/actions/create-one.js
+++ b/server/api/helpers/actions/create-one.js
@@ -14,6 +14,30 @@ const valuesValidator = (value) => {
return true;
};
+const buildAndSendSlackMessage = async (user, card, action) => {
+ const cardLink = `<${sails.config.custom.baseUrl}/cards/${card.id}|${card.name}>`;
+
+ let markdown;
+ switch (action.type) {
+ case Action.Types.CREATE_CARD:
+ markdown = `${cardLink} was created by ${user.name} in *${action.data.list.name}*`;
+
+ break;
+ case Action.Types.MOVE_CARD:
+ markdown = `${cardLink} was moved by ${user.name} to *${action.data.toList.name}*`;
+
+ break;
+ case Action.Types.COMMENT_CARD:
+ markdown = `*${user.name}* commented on ${cardLink}:\n>${action.data.text}`;
+
+ break;
+ default:
+ return;
+ }
+
+ await sails.helpers.utils.sendSlackMessage(markdown);
+};
+
module.exports = {
inputs: {
values: {
@@ -67,6 +91,10 @@ module.exports = {
),
);
+ if (sails.config.custom.slackBotToken) {
+ buildAndSendSlackMessage(values.user, values.card, action);
+ }
+
return action;
},
};
diff --git a/server/api/helpers/cards/delete-one.js b/server/api/helpers/cards/delete-one.js
index fb72787f..a947f738 100644
--- a/server/api/helpers/cards/delete-one.js
+++ b/server/api/helpers/cards/delete-one.js
@@ -1,9 +1,17 @@
+const buildAndSendSlackMessage = async (user, card) => {
+ await sails.helpers.utils.sendSlackMessage(`*${card.name}* was deleted by ${user.name}`);
+};
+
module.exports = {
inputs: {
record: {
type: 'ref',
required: true,
},
+ user: {
+ type: 'ref',
+ required: true,
+ },
request: {
type: 'ref',
},
@@ -21,6 +29,10 @@ module.exports = {
},
inputs.request,
);
+
+ if (sails.config.custom.slackBotToken) {
+ buildAndSendSlackMessage(inputs.user, card);
+ }
}
return card;
diff --git a/server/api/helpers/notifications/create-one.js b/server/api/helpers/notifications/create-one.js
index 271a9179..f24e4bfa 100644
--- a/server/api/helpers/notifications/create-one.js
+++ b/server/api/helpers/notifications/create-one.js
@@ -27,6 +27,7 @@ const buildAndSendEmail = async (user, board, card, action, notifiableUser) => {
`from ${action.data.fromList.name} to ${action.data.toList.name} ` +
`on ${board.name}
`,
};
+
break;
case Action.Types.COMMENT_CARD:
emailData = {
@@ -37,6 +38,7 @@ const buildAndSendEmail = async (user, board, card, action, notifiableUser) => {
`on ${board.name}` +
`${action.data.text}
`,
};
+
break;
default:
return;
diff --git a/server/api/helpers/utils/send-email.js b/server/api/helpers/utils/send-email.js
index 5d9be724..7b8d08bf 100644
--- a/server/api/helpers/utils/send-email.js
+++ b/server/api/helpers/utils/send-email.js
@@ -25,7 +25,7 @@ module.exports = {
sails.log.info('Email sent: %s', info.messageId);
} catch (error) {
- sails.log.error(error);
+ sails.log.error(error); // TODO: provide description text?
}
},
};
diff --git a/server/api/helpers/utils/send-slack-message.js b/server/api/helpers/utils/send-slack-message.js
new file mode 100644
index 00000000..573fcf87
--- /dev/null
+++ b/server/api/helpers/utils/send-slack-message.js
@@ -0,0 +1,53 @@
+const POST_MESSAGE_API_URL = 'https://slack.com/api/chat.postMessage';
+
+module.exports = {
+ inputs: {
+ markdown: {
+ type: 'string',
+ required: true,
+ },
+ },
+
+ async fn(inputs) {
+ const headers = {
+ Authorization: `Bearer ${sails.config.custom.slackBotToken}`,
+ 'Content-Type': 'application/json; charset=utf-8',
+ };
+
+ const body = {
+ blocks: [
+ {
+ type: 'section',
+ text: {
+ type: 'mrkdwn',
+ text: inputs.markdown,
+ },
+ },
+ ],
+ channel: sails.config.custom.slackChannelId,
+ };
+
+ let response;
+ try {
+ response = await fetch(POST_MESSAGE_API_URL, {
+ headers,
+ method: 'POST',
+ body: JSON.stringify(body),
+ });
+ } catch (error) {
+ sails.log.error(error); // TODO: provide description text?
+ return;
+ }
+
+ if (!response.ok) {
+ sails.log.error('Error sending to Slack: %s', response.error);
+ return;
+ }
+
+ const responseJson = await response.json();
+
+ if (!responseJson.ok) {
+ sails.log.error('Error sending to Slack: %s', responseJson.error);
+ }
+ },
+};
diff --git a/server/config/custom.js b/server/config/custom.js
index 76473245..ac344d05 100644
--- a/server/config/custom.js
+++ b/server/config/custom.js
@@ -34,13 +34,6 @@ module.exports.custom = {
defaultAdminEmail:
process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase(),
- smtpHost: process.env.SMTP_HOST,
- smtpPort: process.env.SMTP_PORT || 587,
- smtpSecure: process.env.SMTP_SECURE === 'true',
- smtpUser: process.env.SMTP_USER,
- smtpPassword: process.env.SMTP_PASSWORD,
- smtpFrom: process.env.SMTP_FROM,
-
oidcIssuer: process.env.OIDC_ISSUER,
oidcClientId: process.env.OIDC_CLIENT_ID,
oidcClientSecret: process.env.OIDC_CLIENT_SECRET,
@@ -58,4 +51,14 @@ module.exports.custom = {
oidcRedirectUri: `${
sails.config.environment === 'production' ? process.env.BASE_URL : 'http://localhost:3000'
}/oidc-callback`,
+
+ smtpHost: process.env.SMTP_HOST,
+ smtpPort: process.env.SMTP_PORT || 587,
+ smtpSecure: process.env.SMTP_SECURE === 'true',
+ smtpUser: process.env.SMTP_USER,
+ smtpPassword: process.env.SMTP_PASSWORD,
+ smtpFrom: process.env.SMTP_FROM,
+
+ slackBotToken: process.env.SLACK_BOT_TOKEN,
+ slackChannelId: process.env.SLACK_CHANNEL_ID,
};