Compare commits

...
Author SHA1 Message Date
Stan M ea1b3b7f92 feat: Ability to run with read-only root filesystem in Helm (#695) 2024-04-10 12:56:21 +02:00
Maksim Eltyshev 4e2863faa7 feat: Automatic logout when session expires
Closes #693
2024-04-09 15:12:46 +02:00
Maksim Eltyshev b46fb43e6f chore: Cleanup 2024-04-08 01:47:24 +02:00
Maksim Eltyshev bcacfa8fe2 Merge branch 'GlitchSecure-slack-bot-integration' 2024-04-08 01:29:28 +02:00
Niccolò Pedrini 4df2becd6f fix: Update Italian translation (#688) 2024-04-08 01:27:19 +02:00
Matthieu Bollot 2990ea593a feat: Slack bot notifications (#676) 2024-04-08 01:27:10 +02:00
Blyamur b976673826 fix: Update Russian translation (#686) 2024-04-08 01:24:50 +02:00
leroyloren 16e034efcb fix: Update Czech translation (#679) 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 6712625c3f chore: Update version 2024-04-08 01:24:50 +02:00
Matthieu Bollot b5bbf6a6a4 feat: Add ability to duplicate card (#668) 2024-04-08 01:24:50 +02:00
Brian Stinson 3d3e7761f5 fix: Allow specifying external dburl in Helm (#672) 2024-04-08 01:24:50 +02:00
piero fa56b9c01f fix: Update HorizontalPodAutoscaler version and spec in chart (#666)
Closes #667
2024-04-08 01:24:50 +02:00
Maksim Eltyshev 882213784f chore: Update version 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 251bd793f3 fix: Fix OIDC authentication error when redirecting from another tab
Closes #650
2024-04-08 01:24:50 +02:00
Maksim Eltyshev 088b0e760b fix: Fix scrollbar styles 2024-04-08 01:24:50 +02:00
Maksim Eltyshev cc1fa1cc3b chore: Update version 2024-04-08 01:24:50 +02:00
Edouard bcd3ea86e8 feat: SMTP integration and email notifications (#631) 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 0176650f67 fix: Add date-fns locale to Portuguese translation 2024-04-08 01:24:50 +02:00
Jeffrey f3963aded3 feat: Add Dutch translation (#644) 2024-04-08 01:24:50 +02:00
Reuben Hu bbe720481a feat: Add Indonesian translation (#643) 2024-04-08 01:24:50 +02:00
IV16SL 04f9030474 fix: Fix Chinese translation (#641) 2024-04-08 01:24:50 +02:00
Chris 6d135be859 feat: Add Portuguese translation (#640) 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 63cbe7063e docs: Update features, add contributors 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 7985bea2b8 chore: Update version 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 733abccaa9 fix: Fix case sensitivity of default admin environment variables 2024-04-08 01:24:50 +02:00
Daniel Hiller 119cce82b4 fix: Container build windows 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 988e70ec40 fix: Fix nullable boolean inputs 2024-04-08 01:24:50 +02:00
Daniel Hiller 5e3fca3771 Update build-and-push-docker-image-dev.yml 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 6d9ce56f4a fix: Include due date when importing from Trello
Closes #598
2024-04-08 01:24:50 +02:00
Daniel Hiller 3c8c04fd37 ci: update workflow 2024-04-08 01:24:50 +02:00
Daniel Hiller 790a1d5cc9 feat: Add healthcheck for docker deployments
fix: Missing healthcheck.js

fix: missing healthcheck.js

fix: HEALTHCHECK command
2024-04-08 01:24:50 +02:00
Maksim Eltyshev 4b352fd878 fix: Remove overflow of attachment dropzone 2024-04-08 01:24:50 +02:00
Maksim Eltyshev a196f2ef11 chore: Update version 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 6c65d135f7 feat: Add ability to enforce SSO
Closes #543, closes #545
2024-04-08 01:24:10 +02:00
Maksim Eltyshev a1a1e9a86a chore: Update version 2024-04-08 01:18:26 +02:00
Maksim Eltyshev 0d39a7567f feat: Add ability to map OIDC attributes and ignore username
Closes #554
2024-04-08 01:18:26 +02:00
Maksim Eltyshev 76a18d06d6 chore: Update version 2024-04-08 01:18:26 +02:00
Maksim Eltyshev ce52fc5af7 fix: Fix images becoming black and white when resizing
Closes #574, closes #585
2024-04-08 01:18:26 +02:00
Maksim Eltyshev 0a2863a79c chore: Bump sharp version 2024-04-08 01:18:26 +02:00
Niccolò Pedrini 39e320fe4b fix: Update Italian translation (#688) 2024-04-08 00:50:52 +02:00
Matthieu Bollot 2f3dfe775e feat: Slack bot notifications (#676) 2024-04-08 00:33:29 +02:00
Blyamur 22964cb375 fix: Update Russian translation (#686) 2024-04-07 22:05:22 +02:00
leroyloren fd89cf7f2b fix: Update Czech translation (#679) 2024-04-07 21:58:27 +02:00
GlitchWitch 273d994750 Remove unused code and comments 2023-12-28 12:01:38 -06:00
Brad Bahls 86d21d6abd updated to use currentUser.name for messages 2023-12-28 09:03:41 -07:00
GlitchWitch a9c200cc3b Merge branch 'master' into slack-bot-integration 2023-12-23 14:41:54 -06:00
GlitchWitch 3fedc14062 Rename custom.js -> slack.js 2023-12-23 10:52:15 -06:00
GlitchWitch c42e1fb5dd Add slack variables to docker-compose 2023-12-23 10:49:02 -06:00
GlitchWitch 9961340118 Replace plankaProdUrl with one set by environment. Remove hardcoded channel and unused variables. 2023-12-23 10:48:50 -06:00
Brad Bahls f54bd22757 removed test webhook url 2023-12-23 08:58:22 -07:00
Brad Bahls 1043dacd67 added new custom service with slack integration helper functions; added axios package; added notifications for card create, delete, and update (move); added notifications for comment create 2023-12-23 08:52:07 -07:00
29 changed files with 292 additions and 79 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.23
version: 0.1.24
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
+9
View File
@@ -55,6 +55,11 @@ spec:
- mountPath: /app/private/attachments
subPath: attachments
name: planka
{{- if .Values.securityContext.readOnlyRootFilesystem }}
- mountPath: /app/logs
subPath: app-logs
name: emptydir
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
env:
@@ -139,3 +144,7 @@ spec:
{{- else }}
emptyDir: {}
{{- end }}
{{- if .Values.securityContext.readOnlyRootFilesystem }}
- name: emptydir
emptyDir: {}
{{- end }}
+4 -2
View File
@@ -47,9 +47,11 @@ initializeCore.fetchConfig = (config) => ({
},
});
const logout = () => ({
const logout = (invalidateAccessToken) => ({
type: ActionTypes.LOGOUT,
payload: {},
payload: {
invalidateAccessToken,
},
});
logout.invalidateAccessToken = () => ({
+4 -4
View File
@@ -38,7 +38,7 @@ const handleCardUpdate = (card) => ({
},
});
const moveCard = (id, listId, index = 0) => ({
const moveCard = (id, listId, index) => ({
type: EntryActionTypes.CARD_MOVE,
payload: {
id,
@@ -47,7 +47,7 @@ const moveCard = (id, listId, index = 0) => ({
},
});
const moveCurrentCard = (listId, index = 0) => ({
const moveCurrentCard = (listId, index) => ({
type: EntryActionTypes.CURRENT_CARD_MOVE,
payload: {
listId,
@@ -55,7 +55,7 @@ const moveCurrentCard = (listId, index = 0) => ({
},
});
const transferCard = (id, boardId, listId, index = 0) => ({
const transferCard = (id, boardId, listId, index) => ({
type: EntryActionTypes.CARD_TRANSFER,
payload: {
id,
@@ -65,7 +65,7 @@ const transferCard = (id, boardId, listId, index = 0) => ({
},
});
const transferCurrentCard = (boardId, listId, index = 0) => ({
const transferCurrentCard = (boardId, listId, index) => ({
type: EntryActionTypes.CURRENT_CARD_TRANSFER,
payload: {
boardId,
+4 -2
View File
@@ -1,8 +1,10 @@
import EntryActionTypes from '../constants/EntryActionTypes';
const logout = () => ({
const logout = (invalidateAccessToken) => ({
type: EntryActionTypes.LOGOUT,
payload: {},
payload: {
invalidateAccessToken,
},
});
export default {
+8 -5
View File
@@ -39,9 +39,9 @@ export default {
areYouSureYouWantToLeaveBoard: 'Opravdu chcete opustit tuto tabuli?',
areYouSureYouWantToLeaveProject: 'Opravdu chcete opustit projekt?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Are you sure you want to remove this manager from the project?',
'Jste si jisti, že chcete tohoto správce z projektu odebrat?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'Opravdu chcete odstranit tohoto člena z tabule?',
'Jste si jisti, že chcete tohoto člena odebrat z tabule?',
attachment: 'Příloha',
attachments: 'Přílohy',
authentication: 'Ověření',
@@ -55,6 +55,7 @@ export default {
cardNotFound_title: 'Karta nenalezena',
cardOrActionAreDeleted: 'Karta nebo akce je smazána.',
color: 'Barva',
copy_inline: 'copy',
createBoard_title: 'Vytvořit tabuli',
createLabel_title: 'Vytvořit štítek',
createNewOneOrSelectExistingOne: 'Vytvořit nový nebo vyberte<br />již existující.',
@@ -130,7 +131,7 @@ export default {
phone: 'Telefon',
preferences: 'Volby',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: dejte Ctrl-V (Cmd-V na Mac) pro vložení přílohy ze schránky.',
'Tip: stisknutím Ctrl-V (Cmd-V na Mac) přidáte přílohu ze schránky.',
project: 'Projekt',
projectNotFound_title: 'Projekt nenalezen',
removeManager_title: 'Odstranit vedoucího',
@@ -199,6 +200,8 @@ export default {
deleteTask: 'Smazat úkol',
deleteTask_title: 'Smazat úkol',
deleteUser: 'Smazat uživatele',
duplicate: 'Duplikovat',
duplicateCard_title: 'Duplikovat kartu',
edit: 'Upravit',
editDueDate_title: 'Upravit termín do',
editDescription_title: 'Upravit popis',
@@ -226,13 +229,13 @@ export default {
removeMember: 'Odstranit člena',
save: 'Uložit',
showAllAttachments: 'Zozbrazit všechny přílohy ({{hidden}} skryté)',
showFewerAttachments: 'Zobrazit méně příloh',
showDetails: 'Zobrazit detaily',
showFewerAttachments: 'Zobrazit méně příloh',
start: 'Start',
stop: 'Stop',
subscribe: 'Odebírat',
unsubscribe: 'Neodebírat',
uploadNewAvatar: 'Nahrát nového avatara',
uploadNewAvatar: 'Nahrát nový avatar',
uploadNewImage: 'Nahrát nový obrázek',
},
},
+13 -7
View File
@@ -3,10 +3,10 @@ export default {
date: 'd/M/yyyy',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'MMM d',
longDateTime: "MMMM d 'at' p",
fullDate: 'MMM d, y',
fullDateTime: "MMMM d, y 'at' p",
longDate: 'd MMM',
longDateTime: "d MMMM 'alle' p",
fullDate: 'd MMM, y',
fullDateTime: "d MMMM, y 'alle' p",
},
translation: {
@@ -21,7 +21,7 @@ export default {
administrator: 'Amministratore',
all: 'tutto',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'All changes will be automatically saved<br />after connection restored.',
'Tutte le modifiche verranno salvate<br />al ripristino della connessione.',
areYouSureYouWantToDeleteThisAttachment: 'Sei sicuro di voler eliminare questo allegato?',
areYouSureYouWantToDeleteThisBoard: 'Sei sicuro di voler eliminare questa bacheca?',
areYouSureYouWantToDeleteThisCard: 'Sei sicuro di voler eliminare questa card?',
@@ -50,6 +50,7 @@ export default {
cardNotFound_title: 'Card non trovata',
cardOrActionAreDeleted: "La card o l'azione vengono eliminate.",
color: 'Colore',
copy_inline: 'copia',
createBoard_title: 'Crea Bacheca',
createLabel_title: 'Crea Etichetta',
createNewOneOrSelectExistingOne: 'Crea nuovo o seleziona<br />esistente.',
@@ -94,8 +95,10 @@ export default {
filterByLabels_title: 'Filtra per Etichetta',
filterByMembers_title: 'Filtra per Membro',
fromComputer_title: 'Dal Computer',
fromTrello: 'Da Trello',
general: 'Generale',
hours: 'Ore',
importBoard_title: 'Importa Board',
invalidCurrentPassword: 'Password corrente non valida',
labels: 'Etichette',
language: 'Lingua',
@@ -123,7 +126,7 @@ export default {
phone: 'Telefono',
preferences: 'Preferenze',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: prmi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard.',
'Consiglio: premi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard.',
project: 'Progetto',
projectNotFound_title: 'Progetto non trovato',
removeManager_title: 'Rimuovi Manager',
@@ -193,6 +196,8 @@ export default {
deleteTask: 'Elimina task',
deleteTask_title: 'Elimina Task',
deleteUser: 'Elimina utente',
duplicate: 'Duplica',
duplicateCard_title: 'Duplica Card',
edit: 'Modifica',
editDueDate_title: 'Modifica data di scadenza',
editDescription_title: 'Modifica Descrizione',
@@ -204,9 +209,10 @@ export default {
editTitle_title: 'Modifica Titolo',
editUsername_title: 'Modifica Username',
hideDetails: 'Nascondi dettagli',
import: 'Importa',
leaveBoard: 'Lascia bacheca',
leaveProject: 'Lascia progetto',
logOut_title: 'Log Out',
logOut_title: 'Disconnettiti',
makeCover_title: 'Crea Cover',
move: 'Muovi',
moveCard_title: 'Muovi Card',
+2
View File
@@ -199,6 +199,8 @@ export default {
deleteProject: 'Удалить проект',
deleteTask: 'Удалить задачу',
deleteUser: 'Удалить пользователя',
duplicate: 'Дублировать',
duplicateCard_title: 'Дублировать карточку',
edit: 'Изменить',
editBackground: 'Изменить фон',
editDueDate: 'Изменить срок',
+2 -4
View File
@@ -1,8 +1,7 @@
import { call, fork, join, put, select, take } from 'redux-saga/effects';
import selectors from '../../selectors';
import actions from '../../actions';
import { removeAccessToken } from '../../utils/access-token-storage';
import entryActions from '../../entry-actions';
import ErrorCodes from '../../constants/ErrorCodes';
let lastRequestTask;
@@ -22,8 +21,7 @@ function* queueRequest(method, ...args) {
});
} catch (error) {
if (error.code === ErrorCodes.UNAUTHORIZED) {
yield call(removeAccessToken);
yield put(actions.logout()); // TODO: next url
yield put(entryActions.logout(false));
yield take();
}
+2 -2
View File
@@ -86,7 +86,7 @@ export function* handleCardUpdate(card) {
yield put(actions.handleCardUpdate(card));
}
export function* moveCard(id, listId, index) {
export function* moveCard(id, listId, index = 0) {
const position = yield select(selectors.selectNextCardPosition, listId, index, id);
yield call(updateCard, id, {
@@ -101,7 +101,7 @@ export function* moveCurrentCard(listId, index) {
yield call(moveCard, cardId, listId, index);
}
export function* transferCard(id, boardId, listId, index) {
export function* transferCard(id, boardId, listId, index = 0) {
const { cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath);
const position = yield select(selectors.selectNextCardPosition, listId, index, id);
+2 -3
View File
@@ -1,4 +1,4 @@
import { call, put, select, take } from 'redux-saga/effects';
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
import requests from '../requests';
@@ -84,8 +84,7 @@ export function* logout(invalidateAccessToken = true) {
} catch (error) {} // eslint-disable-line no-empty
}
yield put(actions.logout());
yield take();
yield put(actions.logout()); // TODO: next url
}
export default {
+9
View File
@@ -1,10 +1,12 @@
import { call, put, select, take } from 'redux-saga/effects';
import { push } from '../../../lib/redux-router';
import { logout } from './core';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { getAccessToken } from '../../../utils/access-token-storage';
import ActionTypes from '../../../constants/ActionTypes';
import Paths from '../../../constants/Paths';
@@ -25,6 +27,13 @@ export function* goToCard(cardId) {
}
export function* handleLocationChange() {
const accessToken = yield call(getAccessToken);
if (!accessToken) {
yield call(logout, false);
return;
}
const pathsMatch = yield select(selectors.selectPathsMatch);
if (!pathsMatch) {
+1
View File
@@ -218,6 +218,7 @@ export function* handleUserDelete(user) {
if (user.id === currentUserId) {
yield call(logout, false);
return;
}
yield put(actions.handleUserDelete(user));
+5 -1
View File
@@ -4,5 +4,9 @@ import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* coreWatchers() {
yield all([takeEvery(EntryActionTypes.LOGOUT, () => services.logout())]);
yield all([
takeEvery(EntryActionTypes.LOGOUT, ({ payload: { invalidateAccessToken } }) =>
services.logout(invalidateAccessToken),
),
]);
}
+8
View File
@@ -16,6 +16,10 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleSocketReconnect());
};
const handleLogout = () => {
emit(entryActions.logout(false));
};
const handleUserCreate = api.makeHandleUserCreate(({ item }) => {
emit(entryActions.handleUserCreate(item));
});
@@ -171,6 +175,8 @@ const createSocketEventsChannel = () =>
socket.on('disconnect', handleDisconnect);
socket.on('reconnect', handleReconnect);
socket.on('logout', handleLogout);
socket.on('userCreate', handleUserCreate);
socket.on('userUpdate', handleUserUpdate);
socket.on('userDelete', handleUserDelete);
@@ -227,6 +233,8 @@ const createSocketEventsChannel = () =>
socket.off('disconnect', handleDisconnect);
socket.off('reconnect', handleReconnect);
socket.off('logout', handleLogout);
socket.off('userCreate', handleUserCreate);
socket.off('userUpdate', handleUserUpdate);
socket.off('userDelete', handleUserDelete);
+11 -8
View File
@@ -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" <demo@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" <demo@demo.demo>
# - SLACK_BOT_TOKEN=
# - SLACK_CHANNEL_ID=
depends_on:
postgres:
condition: service_healthy
+11 -8
View File
@@ -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" <demo@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" <demo@demo.demo>
# SLACK_BOT_TOKEN=
# SLACK_CHANNEL_ID=
## Do not edit this
TZ=UTC
@@ -9,6 +9,10 @@ module.exports = {
deletedAt: new Date().toISOString(),
});
if (this.req.isSocket) {
sails.sockets.leaveAll(`@accessToken:${accessToken}`);
}
return {
item: accessToken,
};
+1
View File
@@ -47,6 +47,7 @@ module.exports = {
card = await sails.helpers.cards.deleteOne.with({
record: card,
user: currentUser,
request: this.req,
});
+28
View File
@@ -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;
},
};
+12
View File
@@ -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;
@@ -27,6 +27,7 @@ const buildAndSendEmail = async (user, board, card, action, notifiableUser) => {
`from ${action.data.fromList.name} to ${action.data.toList.name} ` +
`on <a href="${process.env.BASE_URL}/boards/${board.id}">${board.name}</a></p>`,
};
break;
case Action.Types.COMMENT_CARD:
emailData = {
@@ -37,6 +38,7 @@ const buildAndSendEmail = async (user, board, card, action, notifiableUser) => {
`on <a href="${process.env.BASE_URL}/boards/${board.id}">${board.name}</a></p>` +
`<p>${action.data.text}</p>`,
};
break;
default:
return;
+1 -1
View File
@@ -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?
}
},
};
@@ -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);
}
},
};
+1
View File
@@ -61,6 +61,7 @@ module.exports = function defineCurrentUserHook(sails) {
});
if (req.isSocket) {
sails.sockets.join(req, `@accessToken:${accessToken}`);
sails.sockets.join(req, `@user:${currentUser.id}`);
}
}
+22 -11
View File
@@ -1,6 +1,14 @@
const openidClient = require('openid-client');
module.exports = function oidcServiceHook(sails) {
/**
* oidc hook
*
* @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,
* and/or initialization logic.
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
module.exports = function defineOidcHook(sails) {
let client = null;
return {
@@ -9,17 +17,20 @@ module.exports = function oidcServiceHook(sails) {
*/
async initialize() {
if (sails.config.custom.oidcIssuer) {
const issuer = await openidClient.Issuer.discover(sails.config.custom.oidcIssuer);
client = new issuer.Client({
client_id: sails.config.custom.oidcClientId,
client_secret: sails.config.custom.oidcClientSecret,
redirect_uris: [sails.config.custom.oidcRedirectUri],
response_types: ['code'],
});
sails.log.info('OIDC hook has been loaded successfully');
if (!sails.config.custom.oidcIssuer) {
return;
}
sails.log.info('Initializing custom hook (`oidc`)');
const issuer = await openidClient.Issuer.discover(sails.config.custom.oidcIssuer);
client = new issuer.Client({
client_id: sails.config.custom.oidcClientId,
client_secret: sails.config.custom.oidcClientSecret,
redirect_uris: [sails.config.custom.oidcRedirectUri],
response_types: ['code'],
});
},
getClient() {
+24 -13
View File
@@ -1,6 +1,14 @@
const nodemailer = require('nodemailer');
module.exports = function smtpServiceHook(sails) {
/**
* smtp hook
*
* @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,
* and/or initialization logic.
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
module.exports = function defineSmtpHook(sails) {
let transporter = null;
return {
@@ -9,19 +17,22 @@ module.exports = function smtpServiceHook(sails) {
*/
async initialize() {
if (sails.config.custom.smtpHost) {
transporter = nodemailer.createTransport({
pool: true,
host: sails.config.custom.smtpHost,
port: sails.config.custom.smtpPort,
secure: sails.config.custom.smtpSecure,
auth: sails.config.custom.smtpUser && {
user: sails.config.custom.smtpUser,
pass: sails.config.custom.smtpPassword,
},
});
sails.log.info('SMTP hook has been loaded successfully');
if (!sails.config.custom.smtpHost) {
return;
}
sails.log.info('Initializing custom hook (`smtp`)');
transporter = nodemailer.createTransport({
pool: true,
host: sails.config.custom.smtpHost,
port: sails.config.custom.smtpPort,
secure: sails.config.custom.smtpSecure,
auth: sails.config.custom.smtpUser && {
user: sails.config.custom.smtpUser,
pass: sails.config.custom.smtpPassword,
},
});
},
getTransporter() {
+38
View File
@@ -0,0 +1,38 @@
/**
* watcher hook
*
* @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,
* and/or initialization logic.
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
module.exports = function defineWatcherHook(sails) {
const checkSocketConnectionsToLogout = () => {
Object.keys(sails.io.sockets.adapter.rooms).forEach((room) => {
if (!room.startsWith('@accessToken:')) {
return;
}
const accessToken = room.split(':')[1];
try {
sails.helpers.utils.verifyToken(accessToken);
} catch (error) {
sails.sockets.broadcast(room, 'logout');
sails.sockets.leaveAll(room);
}
});
};
return {
/**
* Runs when this Sails app loads/lifts.
*/
async initialize() {
sails.log.info('Initializing custom hook (`watcher`)');
setInterval(checkSocketConnectionsToLogout, 60 * 1000);
},
};
};
+10 -7
View File
@@ -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,
};