From 1043dacd67d036e68f6bd5eb7b38ba85b74fbbc8 Mon Sep 17 00:00:00 2001 From: Brad Bahls Date: Sat, 23 Dec 2023 08:52:07 -0700 Subject: [PATCH 01/83] 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 --- package.json | 1 + server/api/controllers/cards/create.js | 7 ++ server/api/controllers/cards/delete.js | 7 ++ server/api/controllers/cards/update.js | 14 ++++ .../api/controllers/comment-actions/create.js | 8 ++ server/api/services/custom.js | 73 +++++++++++++++++++ 6 files changed, 110 insertions(+) create mode 100644 server/api/services/custom.js diff --git a/package.json b/package.json index 9616e44..d329db8 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ } }, "dependencies": { + "axios": "^1.6.2", "concurrently": "^8.2.2", "husky": "^8.0.3", "lint-staged": "^15.1.0" diff --git a/server/api/controllers/cards/create.js b/server/api/controllers/cards/create.js index ab0c444..ead9275 100755 --- a/server/api/controllers/cards/create.js +++ b/server/api/controllers/cards/create.js @@ -1,4 +1,5 @@ const moment = require('moment'); +const services = require('../../services/custom'); const Errors = { NOT_ENOUGH_RIGHTS: { @@ -108,6 +109,12 @@ module.exports = { }) .intercept('positionMustBeInValues', () => Errors.POSITION_MUST_BE_PRESENT); + const cardUrl = services.buildCardUrl(card); + const messageText = cardUrl + ' was created by ' + currentUser.username + ' in *' + list.name + '*'; + services.sendSlackMessage(messageText) + .then(() => { console.log('Slack message sent successfully.'); }) + .catch((error) => { console.error('Failed to send Slack message:', error.message); }); + return { item: card, }; diff --git a/server/api/controllers/cards/delete.js b/server/api/controllers/cards/delete.js index e2b6d00..865cd2e 100755 --- a/server/api/controllers/cards/delete.js +++ b/server/api/controllers/cards/delete.js @@ -1,3 +1,5 @@ +const services = require('../../services/custom'); + const Errors = { NOT_ENOUGH_RIGHTS: { notEnoughRights: 'Not enough rights', @@ -54,6 +56,11 @@ module.exports = { throw Errors.CARD_NOT_FOUND; } + const messageText = '*' + card.name + '* was deleted by ' + currentUser.username; + services.sendSlackMessage(messageText) + .then(() => { console.log('Slack message sent successfully.'); }) + .catch((error) => { console.error('Failed to send Slack message:', error.message); }); + return { item: card, }; diff --git a/server/api/controllers/cards/update.js b/server/api/controllers/cards/update.js index 8ee3752..df69f57 100755 --- a/server/api/controllers/cards/update.js +++ b/server/api/controllers/cards/update.js @@ -1,4 +1,5 @@ const moment = require('moment'); +const services = require('../../services/custom'); const Errors = { NOT_ENOUGH_RIGHTS: { @@ -175,6 +176,8 @@ module.exports = { 'isSubscribed', ]); + const cardPositionBefore = card.position; + card = await sails.helpers.cards.updateOne .with({ board, @@ -195,6 +198,17 @@ module.exports = { throw Errors.CARD_NOT_FOUND; } + const cardPositionAfter = card.position; + const cardMoved = cardPositionBefore !== cardPositionAfter; + + if (cardMoved) { + const cardUrl = services.buildCardUrl(card); + const messageText = cardUrl + ' was moved by ' + currentUser.username + ' to *' + nextList.name + '*'; + services.sendSlackMessage(messageText) + .then(() => { console.log('Slack message sent successfully.'); }) + .catch((error) => { console.error('Failed to send Slack message:', error.message); }); + } + return { item: card, }; diff --git a/server/api/controllers/comment-actions/create.js b/server/api/controllers/comment-actions/create.js index ddf4b99..7e5d97e 100755 --- a/server/api/controllers/comment-actions/create.js +++ b/server/api/controllers/comment-actions/create.js @@ -1,3 +1,5 @@ +const services = require('../../services/custom'); + const Errors = { NOT_ENOUGH_RIGHTS: { notEnoughRights: 'Not enough rights', @@ -63,6 +65,12 @@ module.exports = { request: this.req, }); + const cardUrl = services.buildCardUrl(card); + const messageText = '*' + currentUser.username + '* commented on ' + cardUrl + ':\n>' + inputs.text; + services.sendSlackMessage(messageText) + .then(() => { console.log('Slack message sent successfully.'); }) + .catch((error) => { console.error('Failed to send Slack message:', error.message); }); + return { item: action, }; diff --git a/server/api/services/custom.js b/server/api/services/custom.js new file mode 100644 index 0000000..1166934 --- /dev/null +++ b/server/api/services/custom.js @@ -0,0 +1,73 @@ +const axios = require('axios'); +const slackPostUrl = 'https://slack.com/api/chat.postMessage'; +const channelId = 'C06B6F4R9RT'; + +const plankaProdUrl = 'https://kanban.glitchsecure.com'; +const plankaTestUrl = 'http://localhost:3000'; +const plankaTestWebhookUrl = 'https://hooks.slack.com/services/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'; +const slackAPIToken = process.env.SLACK_BOT_TOKEN; + +async function sendSlackMessage(messageText) { + if (!slackAPIToken) { + throw new Error('No Slack BOT token found'); + } + + console.log('Sending to Slack'); + + + const postData = { + blocks: [ { + type: 'section', + text: { + type: 'mrkdwn', + text: messageText, + }, + }] + }; + + try { + // Prod path + const config = { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${slackAPIToken}`, + }, + }; + + axios.post(slackPostUrl, { ...postData, channel: channelId }, config) + .then(response => { + console.log('Slack response:', response.data); + }) + .catch(error => { + console.error('Error sending to Slack:', error.message); + }); + + + // Testing in dev environment (Brad) + /* + const response = await axios.post(plankaTestWebhookUrl, postData, { + headers: { + 'Content-Type': 'application/json' + } + }); + */ + + console.log('Slack response:', response.data); + return response.data; + } catch (error) { + console.error('Error sending to Slack:', error.message); + throw error; + } + } + + function buildCardUrl(card) { + const url = plankaProdUrl + '/cards/' + card.id; + const cardUrl = '<' + url + '|' + card.name + '>'; + console.log(cardUrl); + return cardUrl; + } + + module.exports = { + sendSlackMessage, + buildCardUrl + }; From f54bd227571bf869997004f304ad7a2a3d25c205 Mon Sep 17 00:00:00 2001 From: Brad Bahls Date: Sat, 23 Dec 2023 08:58:22 -0700 Subject: [PATCH 02/83] removed test webhook url --- server/api/services/custom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/services/custom.js b/server/api/services/custom.js index 1166934..3e2dd9b 100644 --- a/server/api/services/custom.js +++ b/server/api/services/custom.js @@ -4,7 +4,7 @@ const channelId = 'C06B6F4R9RT'; const plankaProdUrl = 'https://kanban.glitchsecure.com'; const plankaTestUrl = 'http://localhost:3000'; -const plankaTestWebhookUrl = 'https://hooks.slack.com/services/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'; +const plankaTestWebhookUrl = ''; const slackAPIToken = process.env.SLACK_BOT_TOKEN; async function sendSlackMessage(messageText) { From 99613401183026d056184677e3b5efdab8df73ed Mon Sep 17 00:00:00 2001 From: GlitchWitch Date: Sat, 23 Dec 2023 10:48:50 -0600 Subject: [PATCH 03/83] Replace plankaProdUrl with one set by environment. Remove hardcoded channel and unused variables. --- server/api/services/custom.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/server/api/services/custom.js b/server/api/services/custom.js index 3e2dd9b..6f6c5d1 100644 --- a/server/api/services/custom.js +++ b/server/api/services/custom.js @@ -1,11 +1,8 @@ const axios = require('axios'); const slackPostUrl = 'https://slack.com/api/chat.postMessage'; -const channelId = 'C06B6F4R9RT'; - -const plankaProdUrl = 'https://kanban.glitchsecure.com'; -const plankaTestUrl = 'http://localhost:3000'; -const plankaTestWebhookUrl = ''; +const channelId = process.env.SLACK_CHANNEL_ID; const slackAPIToken = process.env.SLACK_BOT_TOKEN; +const plankaProdUrl = process.env.BASE_URL; async function sendSlackMessage(messageText) { if (!slackAPIToken) { From c42e1fb5dd8858f2b5a7b771d8798f1b5cb7a6f4 Mon Sep 17 00:00:00 2001 From: GlitchWitch Date: Sat, 23 Dec 2023 10:49:02 -0600 Subject: [PATCH 04/83] Add slack variables to docker-compose --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 9d8fef1..6fac280 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,6 +46,9 @@ services: # - OIDC_ADMIN_ROLES=admin # - OIDC_ROLES_ATTRIBUTE=groups # - OIDC_IGNORE_ROLES=true + + # - SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx + # - SLACK_CHANNEL_ID=xxxxxxxxxx depends_on: - postgres From 3fedc140621550c6ba63ec2155ebd0948609f422 Mon Sep 17 00:00:00 2001 From: GlitchWitch Date: Sat, 23 Dec 2023 10:52:15 -0600 Subject: [PATCH 05/83] Rename custom.js -> slack.js --- server/api/controllers/cards/create.js | 2 +- server/api/controllers/cards/delete.js | 2 +- server/api/controllers/cards/update.js | 2 +- server/api/controllers/comment-actions/create.js | 2 +- server/api/services/{custom.js => slack.js} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename server/api/services/{custom.js => slack.js} (100%) diff --git a/server/api/controllers/cards/create.js b/server/api/controllers/cards/create.js index ead9275..9acfc74 100755 --- a/server/api/controllers/cards/create.js +++ b/server/api/controllers/cards/create.js @@ -1,5 +1,5 @@ const moment = require('moment'); -const services = require('../../services/custom'); +const services = require('../../services/slack'); const Errors = { NOT_ENOUGH_RIGHTS: { diff --git a/server/api/controllers/cards/delete.js b/server/api/controllers/cards/delete.js index 865cd2e..32d4500 100755 --- a/server/api/controllers/cards/delete.js +++ b/server/api/controllers/cards/delete.js @@ -1,4 +1,4 @@ -const services = require('../../services/custom'); +const services = require('../../services/slack'); const Errors = { NOT_ENOUGH_RIGHTS: { diff --git a/server/api/controllers/cards/update.js b/server/api/controllers/cards/update.js index df69f57..4e3a844 100755 --- a/server/api/controllers/cards/update.js +++ b/server/api/controllers/cards/update.js @@ -1,5 +1,5 @@ const moment = require('moment'); -const services = require('../../services/custom'); +const services = require('../../services/slack'); const Errors = { NOT_ENOUGH_RIGHTS: { diff --git a/server/api/controllers/comment-actions/create.js b/server/api/controllers/comment-actions/create.js index 7e5d97e..950effd 100755 --- a/server/api/controllers/comment-actions/create.js +++ b/server/api/controllers/comment-actions/create.js @@ -1,4 +1,4 @@ -const services = require('../../services/custom'); +const services = require('../../services/slack'); const Errors = { NOT_ENOUGH_RIGHTS: { diff --git a/server/api/services/custom.js b/server/api/services/slack.js similarity index 100% rename from server/api/services/custom.js rename to server/api/services/slack.js From 86d21d6abda8eb268a320392d9c2f2caff91fbad Mon Sep 17 00:00:00 2001 From: Brad Bahls Date: Thu, 28 Dec 2023 09:03:41 -0700 Subject: [PATCH 06/83] updated to use currentUser.name for messages --- server/api/controllers/cards/create.js | 2 +- server/api/controllers/cards/delete.js | 2 +- server/api/controllers/cards/update.js | 2 +- server/api/controllers/comment-actions/create.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/api/controllers/cards/create.js b/server/api/controllers/cards/create.js index 9acfc74..d858058 100755 --- a/server/api/controllers/cards/create.js +++ b/server/api/controllers/cards/create.js @@ -110,7 +110,7 @@ module.exports = { .intercept('positionMustBeInValues', () => Errors.POSITION_MUST_BE_PRESENT); const cardUrl = services.buildCardUrl(card); - const messageText = cardUrl + ' was created by ' + currentUser.username + ' in *' + list.name + '*'; + const messageText = cardUrl + ' was created by ' + currentUser.name + ' in *' + list.name + '*'; services.sendSlackMessage(messageText) .then(() => { console.log('Slack message sent successfully.'); }) .catch((error) => { console.error('Failed to send Slack message:', error.message); }); diff --git a/server/api/controllers/cards/delete.js b/server/api/controllers/cards/delete.js index 32d4500..84d188d 100755 --- a/server/api/controllers/cards/delete.js +++ b/server/api/controllers/cards/delete.js @@ -56,7 +56,7 @@ module.exports = { throw Errors.CARD_NOT_FOUND; } - const messageText = '*' + card.name + '* was deleted by ' + currentUser.username; + const messageText = '*' + card.name + '* was deleted by ' + currentUser.name; services.sendSlackMessage(messageText) .then(() => { console.log('Slack message sent successfully.'); }) .catch((error) => { console.error('Failed to send Slack message:', error.message); }); diff --git a/server/api/controllers/cards/update.js b/server/api/controllers/cards/update.js index 4e3a844..2c0a2c2 100755 --- a/server/api/controllers/cards/update.js +++ b/server/api/controllers/cards/update.js @@ -203,7 +203,7 @@ module.exports = { if (cardMoved) { const cardUrl = services.buildCardUrl(card); - const messageText = cardUrl + ' was moved by ' + currentUser.username + ' to *' + nextList.name + '*'; + const messageText = cardUrl + ' was moved by ' + currentUser.name + ' to *' + nextList.name + '*'; services.sendSlackMessage(messageText) .then(() => { console.log('Slack message sent successfully.'); }) .catch((error) => { console.error('Failed to send Slack message:', error.message); }); diff --git a/server/api/controllers/comment-actions/create.js b/server/api/controllers/comment-actions/create.js index 950effd..81bc420 100755 --- a/server/api/controllers/comment-actions/create.js +++ b/server/api/controllers/comment-actions/create.js @@ -66,7 +66,7 @@ module.exports = { }); const cardUrl = services.buildCardUrl(card); - const messageText = '*' + currentUser.username + '* commented on ' + cardUrl + ':\n>' + inputs.text; + const messageText = '*' + currentUser.name + '* commented on ' + cardUrl + ':\n>' + inputs.text; services.sendSlackMessage(messageText) .then(() => { console.log('Slack message sent successfully.'); }) .catch((error) => { console.error('Failed to send Slack message:', error.message); }); From 273d994750ed3fb5c8ae33078ee84452f7222576 Mon Sep 17 00:00:00 2001 From: GlitchWitch Date: Thu, 28 Dec 2023 12:01:38 -0600 Subject: [PATCH 07/83] Remove unused code and comments --- server/api/services/slack.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/server/api/services/slack.js b/server/api/services/slack.js index 6f6c5d1..c06aa5f 100644 --- a/server/api/services/slack.js +++ b/server/api/services/slack.js @@ -11,7 +11,6 @@ async function sendSlackMessage(messageText) { console.log('Sending to Slack'); - const postData = { blocks: [ { type: 'section', @@ -23,7 +22,6 @@ async function sendSlackMessage(messageText) { }; try { - // Prod path const config = { headers: { 'Content-Type': 'application/json', @@ -39,16 +37,6 @@ async function sendSlackMessage(messageText) { console.error('Error sending to Slack:', error.message); }); - - // Testing in dev environment (Brad) - /* - const response = await axios.post(plankaTestWebhookUrl, postData, { - headers: { - 'Content-Type': 'application/json' - } - }); - */ - console.log('Slack response:', response.data); return response.data; } catch (error) { From 634d6ceab1bf0d7954a5a9fbc8cbb2dc77fa8c6a Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Thu, 25 Jan 2024 23:01:59 +0100 Subject: [PATCH 08/83] feat: Add ability to map OIDC attributes and ignore username Closes #554 --- .../AccountPane/AccountPane.jsx | 110 ++++++++++-------- .../UserSettingsModal/UserSettingsModal.jsx | 3 + .../UsersModal/Item/ActionsStep.jsx | 12 +- .../src/components/UsersModal/Item/Item.jsx | 3 + .../src/components/UsersModal/UsersModal.jsx | 1 + .../containers/UserSettingsModalContainer.js | 2 + client/src/models/User.js | 1 + docker-compose.yml | 4 + server/.env.sample | 4 + .../api/controllers/users/update-username.js | 21 ++-- .../users/get-or-create-one-using-oidc.js | 18 ++- server/api/models/User.js | 1 + server/config/custom.js | 4 + 13 files changed, 112 insertions(+), 72 deletions(-) diff --git a/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx b/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx index 7333cf5..eed1326 100644 --- a/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx +++ b/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx @@ -24,6 +24,7 @@ const AccountPane = React.memo( organization, language, isLocked, + isUsernameLocked, isAvatarUpdating, usernameUpdateForm, emailUpdateForm, @@ -104,7 +105,7 @@ const AccountPane = React.memo( value={language || 'auto'} onChange={handleLanguageChange} /> - {!isLocked && ( + {(!isLocked || !isUsernameLocked) && ( <>
@@ -113,56 +114,62 @@ const AccountPane = React.memo( })}
-
- - - -
-
- - - -
-
- - - -
+ {!isUsernameLocked && ( +
+ + + +
+ )} + {!isLocked && ( + <> +
+ + + +
+
+ + + +
+ + )} )} @@ -179,6 +186,7 @@ AccountPane.propTypes = { organization: PropTypes.string, language: PropTypes.string, isLocked: PropTypes.bool.isRequired, + isUsernameLocked: PropTypes.bool.isRequired, isAvatarUpdating: PropTypes.bool.isRequired, /* eslint-disable react/forbid-prop-types */ usernameUpdateForm: PropTypes.object.isRequired, diff --git a/client/src/components/UserSettingsModal/UserSettingsModal.jsx b/client/src/components/UserSettingsModal/UserSettingsModal.jsx index fc8ac9c..8767ac4 100644 --- a/client/src/components/UserSettingsModal/UserSettingsModal.jsx +++ b/client/src/components/UserSettingsModal/UserSettingsModal.jsx @@ -18,6 +18,7 @@ const UserSettingsModal = React.memo( language, subscribeToOwnCards, isLocked, + isUsernameLocked, isAvatarUpdating, usernameUpdateForm, emailUpdateForm, @@ -50,6 +51,7 @@ const UserSettingsModal = React.memo( organization={organization} language={language} isLocked={isLocked} + isUsernameLocked={isUsernameLocked} isAvatarUpdating={isAvatarUpdating} usernameUpdateForm={usernameUpdateForm} emailUpdateForm={emailUpdateForm} @@ -108,6 +110,7 @@ UserSettingsModal.propTypes = { language: PropTypes.string, subscribeToOwnCards: PropTypes.bool.isRequired, isLocked: PropTypes.bool.isRequired, + isUsernameLocked: PropTypes.bool.isRequired, isAvatarUpdating: PropTypes.bool.isRequired, /* eslint-disable react/forbid-prop-types */ usernameUpdateForm: PropTypes.object.isRequired, diff --git a/client/src/components/UsersModal/Item/ActionsStep.jsx b/client/src/components/UsersModal/Item/ActionsStep.jsx index f8a2959..7b0eaed 100644 --- a/client/src/components/UsersModal/Item/ActionsStep.jsx +++ b/client/src/components/UsersModal/Item/ActionsStep.jsx @@ -136,13 +136,15 @@ const ActionsStep = React.memo( context: 'title', })} + {!user.isUsernameLocked && ( + + {t('action.editUsername', { + context: 'title', + })} + + )} {!user.isLocked && ( <> - - {t('action.editUsername', { - context: 'title', - })} - {t('action.editEmail', { context: 'title', diff --git a/client/src/components/UsersModal/Item/Item.jsx b/client/src/components/UsersModal/Item/Item.jsx index 80db356..ed68da3 100755 --- a/client/src/components/UsersModal/Item/Item.jsx +++ b/client/src/components/UsersModal/Item/Item.jsx @@ -19,6 +19,7 @@ const Item = React.memo( isAdmin, isLocked, isRoleLocked, + isUsernameLocked, isDeletionLocked, emailUpdateForm, passwordUpdateForm, @@ -61,6 +62,7 @@ const Item = React.memo( phone, isAdmin, isLocked, + isUsernameLocked, isDeletionLocked, emailUpdateForm, passwordUpdateForm, @@ -95,6 +97,7 @@ Item.propTypes = { isAdmin: PropTypes.bool.isRequired, isLocked: PropTypes.bool.isRequired, isRoleLocked: PropTypes.bool.isRequired, + isUsernameLocked: PropTypes.bool.isRequired, isDeletionLocked: PropTypes.bool.isRequired, /* eslint-disable react/forbid-prop-types */ emailUpdateForm: PropTypes.object.isRequired, diff --git a/client/src/components/UsersModal/UsersModal.jsx b/client/src/components/UsersModal/UsersModal.jsx index 0b55778..280cd08 100755 --- a/client/src/components/UsersModal/UsersModal.jsx +++ b/client/src/components/UsersModal/UsersModal.jsx @@ -112,6 +112,7 @@ const UsersModal = React.memo( isAdmin={item.isAdmin} isLocked={item.isLocked} isRoleLocked={item.isRoleLocked} + isUsernameLocked={item.isUsernameLocked} isDeletionLocked={item.isDeletionLocked} emailUpdateForm={item.emailUpdateForm} passwordUpdateForm={item.passwordUpdateForm} diff --git a/client/src/containers/UserSettingsModalContainer.js b/client/src/containers/UserSettingsModalContainer.js index 620632e..8c3b1c3 100644 --- a/client/src/containers/UserSettingsModalContainer.js +++ b/client/src/containers/UserSettingsModalContainer.js @@ -16,6 +16,7 @@ const mapStateToProps = (state) => { language, subscribeToOwnCards, isLocked, + isUsernameLocked, isAvatarUpdating, emailUpdateForm, passwordUpdateForm, @@ -32,6 +33,7 @@ const mapStateToProps = (state) => { language, subscribeToOwnCards, isLocked, + isUsernameLocked, isAvatarUpdating, emailUpdateForm, passwordUpdateForm, diff --git a/client/src/models/User.js b/client/src/models/User.js index 8455530..9bf8208 100755 --- a/client/src/models/User.js +++ b/client/src/models/User.js @@ -46,6 +46,7 @@ export default class extends BaseModel { isAdmin: attr(), isLocked: attr(), isRoleLocked: attr(), + isUsernameLocked: attr(), isDeletionLocked: attr(), deletedAt: attr(), createdAt: attr({ diff --git a/docker-compose.yml b/docker-compose.yml index 9d8fef1..01016dc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,7 +44,11 @@ services: # - OIDC_CLIENT_SECRET= # - OIDC_SCOPES=openid email profile # - OIDC_ADMIN_ROLES=admin + # - OIDC_EMAIL_ATTRIBUTE=email + # - OIDC_NAME_ATTRIBUTE=name + # - OIDC_USERNAME_ATTRIBUTE=preferred_username # - OIDC_ROLES_ATTRIBUTE=groups + # - OIDC_IGNORE_USERNAME=true # - OIDC_IGNORE_ROLES=true depends_on: - postgres diff --git a/server/.env.sample b/server/.env.sample index f06b647..31f2342 100644 --- a/server/.env.sample +++ b/server/.env.sample @@ -27,7 +27,11 @@ SECRET_KEY=notsecretkey # OIDC_CLIENT_SECRET= # OIDC_SCOPES=openid email profile # OIDC_ADMIN_ROLES=admin +# OIDC_EMAIL_ATTRIBUTE=email +# OIDC_NAME_ATTRIBUTE=name +# OIDC_USERNAME_ATTRIBUTE=preferred_username # OIDC_ROLES_ATTRIBUTE=groups +# OIDC_IGNORE_USERNAME=true # OIDC_IGNORE_ROLES=true ## Do not edit this diff --git a/server/api/controllers/users/update-username.js b/server/api/controllers/users/update-username.js index 5805946..b55529b 100644 --- a/server/api/controllers/users/update-username.js +++ b/server/api/controllers/users/update-username.js @@ -53,11 +53,7 @@ module.exports = { async fn(inputs) { const { currentUser } = this.req; - if (inputs.id === currentUser.id) { - if (!inputs.currentPassword) { - throw Errors.INVALID_CURRENT_PASSWORD; - } - } else if (!currentUser.isAdmin) { + if (inputs.id !== currentUser.id && !currentUser.isAdmin) { throw Errors.USER_NOT_FOUND; // Forbidden } @@ -67,15 +63,18 @@ module.exports = { throw Errors.USER_NOT_FOUND; } - if (user.email === sails.config.custom.defaultAdminEmail || user.isSso) { + if (user.email === sails.config.custom.defaultAdminEmail) { throw Errors.NOT_ENOUGH_RIGHTS; } - if ( - inputs.id === currentUser.id && - !bcrypt.compareSync(inputs.currentPassword, user.password) - ) { - throw Errors.INVALID_CURRENT_PASSWORD; + if (user.isSso) { + if (!sails.config.custom.oidcIgnoreUsername) { + throw Errors.NOT_ENOUGH_RIGHTS; + } + } else if (inputs.id === currentUser.id) { + if (!inputs.currentPassword || !bcrypt.compareSync(inputs.currentPassword, user.password)) { + throw Errors.INVALID_CURRENT_PASSWORD; + } } const values = _.pick(inputs, ['username']); diff --git a/server/api/helpers/users/get-or-create-one-using-oidc.js b/server/api/helpers/users/get-or-create-one-using-oidc.js index 6d1c49f..2186c09 100644 --- a/server/api/helpers/users/get-or-create-one-using-oidc.js +++ b/server/api/helpers/users/get-or-create-one-using-oidc.js @@ -38,7 +38,10 @@ module.exports = { throw 'invalidCodeOrNonce'; } - if (!userInfo.email || !userInfo.name) { + if ( + !userInfo[sails.config.custom.oidcEmailAttribute] || + !userInfo[sails.config.custom.oidcNameAttribute] + ) { throw 'missingValues'; } @@ -56,12 +59,14 @@ module.exports = { const values = { isAdmin, - email: userInfo.email, + email: userInfo[sails.config.custom.oidcEmailAttribute], isSso: true, - name: userInfo.name, - username: userInfo.preferred_username, + name: userInfo[sails.config.custom.oidcNameAttribute], subscribeToOwnCards: false, }; + if (!sails.config.custom.oidcIgnoreUsername) { + values.username = userInfo[sails.config.custom.oidcUsernameAttribute]; + } let user; // This whole block technically needs to be executed in a transaction @@ -95,7 +100,10 @@ module.exports = { }); } - const updateFieldKeys = ['email', 'isSso', 'name', 'username']; + const updateFieldKeys = ['email', 'isSso', 'name']; + if (!sails.config.custom.oidcIgnoreUsername) { + updateFieldKeys.push('username'); + } if (!sails.config.custom.oidcIgnoreRoles) { updateFieldKeys.push('isAdmin'); } diff --git a/server/api/models/User.js b/server/api/models/User.js index 9bf8a29..1d87531 100755 --- a/server/api/models/User.js +++ b/server/api/models/User.js @@ -116,6 +116,7 @@ module.exports = { ..._.omit(this, ['password', 'isSso', 'avatar', 'passwordChangedAt']), isLocked: this.isSso || isDefaultAdmin, isRoleLocked: (this.isSso && !sails.config.custom.oidcIgnoreRoles) || isDefaultAdmin, + isUsernameLocked: (this.isSso && !sails.config.custom.oidcIgnoreUsername) || isDefaultAdmin, isDeletionLocked: isDefaultAdmin, avatarUrl: this.avatar && diff --git a/server/config/custom.js b/server/config/custom.js index cbbc89b..afd60ec 100644 --- a/server/config/custom.js +++ b/server/config/custom.js @@ -38,7 +38,11 @@ module.exports.custom = { oidcClientSecret: process.env.OIDC_CLIENT_SECRET, oidcScopes: process.env.OIDC_SCOPES || 'openid email profile', oidcAdminRoles: process.env.OIDC_ADMIN_ROLES ? process.env.OIDC_ADMIN_ROLES.split(',') : [], + oidcEmailAttribute: process.env.OIDC_EMAIL_ATTRIBUTE || 'email', + oidcNameAttribute: process.env.OIDC_NAME_ATTRIBUTE || 'name', + oidcUsernameAttribute: process.env.OIDC_USERNAME_ATTRIBUTE || 'preferred_username', oidcRolesAttribute: process.env.OIDC_ROLES_ATTRIBUTE || 'groups', + oidcIgnoreUsername: process.env.OIDC_IGNORE_USERNAME === 'true', oidcIgnoreRoles: process.env.OIDC_IGNORE_ROLES === 'true', // TODO: move client base url to environment variable? From a1fd6942482f2fae77686d6a364b3bc023348d9f Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Wed, 31 Jan 2024 15:58:57 +0100 Subject: [PATCH 09/83] chore: Update version --- charts/planka/Chart.yaml | 4 ++-- client/.env | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/planka/Chart.yaml b/charts/planka/Chart.yaml index 1c32bf7..cf4fe0c 100644 --- a/charts/planka/Chart.yaml +++ b/charts/planka/Chart.yaml @@ -15,13 +15,13 @@ 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.15 +version: 0.1.16 # 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 # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.15.4" +appVersion: "1.15.5" dependencies: - alias: postgresql diff --git a/client/.env b/client/.env index 4d4ab73..b54a394 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.15.4 +REACT_APP_VERSION=1.15.5 diff --git a/package-lock.json b/package-lock.json index 51e7931..ed45ee0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "planka", - "version": "1.15.4", + "version": "1.15.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "planka", - "version": "1.15.4", + "version": "1.15.5", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 87a41e4..31ba8fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "planka", - "version": "1.15.4", + "version": "1.15.5", "private": true, "homepage": "https://plankanban.github.io/planka", "repository": { From b8d262f7452ae802f5f1ccbd19e8482727076c27 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Thu, 1 Feb 2024 00:31:15 +0100 Subject: [PATCH 10/83] feat: Add ability to enforce SSO Closes #543, closes #545 --- client/src/components/Login/Login.jsx | 90 ++++++++++--------- .../src/components/UsersModal/UsersModal.jsx | 14 +-- client/src/containers/LoginContainer.js | 1 + client/src/containers/UsersModalContainer.js | 2 + docker-compose.yml | 1 + server/.env.sample | 1 + .../api/controllers/access-tokens/create.js | 5 +- server/api/controllers/show-config.js | 1 + server/api/controllers/users/create.js | 10 +++ server/config/custom.js | 1 + 10 files changed, 80 insertions(+), 46 deletions(-) diff --git a/client/src/components/Login/Login.jsx b/client/src/components/Login/Login.jsx index 84e6a9d..6f547ee 100755 --- a/client/src/components/Login/Login.jsx +++ b/client/src/components/Login/Login.jsx @@ -68,6 +68,7 @@ const Login = React.memo( isSubmittingUsingOidc, error, withOidc, + isOidcEnforced, onAuthenticate, onAuthenticateUsingOidc, onMessageDismiss, @@ -107,8 +108,10 @@ const Login = React.memo( }, [onAuthenticate, data]); useEffect(() => { - emailOrUsernameField.current.focus(); - }, []); + if (!isOidcEnforced) { + emailOrUsernameField.current.focus(); + } + }, [isOidcEnforced]); useEffect(() => { if (wasSubmitting && !isSubmitting && error) { @@ -159,51 +162,57 @@ const Login = React.memo( onDismiss={onMessageDismiss} /> )} -
-
-
{t('common.emailOrUsername')}
- -
-
-
{t('common.password')}
- +
+
{t('common.emailOrUsername')}
+ +
+
+
{t('common.password')}
+ +
+ -
- - + + )} {withOidc && ( + /> )} @@ -242,6 +251,7 @@ Login.propTypes = { isSubmittingUsingOidc: PropTypes.bool.isRequired, error: PropTypes.object, // eslint-disable-line react/forbid-prop-types withOidc: PropTypes.bool.isRequired, + isOidcEnforced: PropTypes.bool.isRequired, onAuthenticate: PropTypes.func.isRequired, onAuthenticateUsingOidc: PropTypes.func.isRequired, onMessageDismiss: PropTypes.func.isRequired, diff --git a/client/src/components/UsersModal/UsersModal.jsx b/client/src/components/UsersModal/UsersModal.jsx index 280cd08..68f0f82 100755 --- a/client/src/components/UsersModal/UsersModal.jsx +++ b/client/src/components/UsersModal/UsersModal.jsx @@ -10,6 +10,7 @@ import Item from './Item'; const UsersModal = React.memo( ({ items, + canAdd, onUpdate, onUsernameUpdate, onUsernameUpdateMessageDismiss, @@ -130,11 +131,13 @@ const UsersModal = React.memo( - - - + onUpdate: (data) => entryActions.updateCard(id, data), onMove: (listId, index) => entryActions.moveCard(id, listId, index), onTransfer: (boardId, listId) => entryActions.transferCard(id, boardId, listId), + onDuplicate: () => entryActions.duplicateCard(id), onDelete: () => entryActions.deleteCard(id), onUserAdd: (userId) => entryActions.addUserToCard(userId, id), onUserRemove: (userId) => entryActions.removeUserFromCard(userId, id), diff --git a/client/src/containers/CardModalContainer.js b/client/src/containers/CardModalContainer.js index ceae984..a8df6ea 100755 --- a/client/src/containers/CardModalContainer.js +++ b/client/src/containers/CardModalContainer.js @@ -78,6 +78,7 @@ const mapDispatchToProps = (dispatch) => onUpdate: entryActions.updateCurrentCard, onMove: entryActions.moveCurrentCard, onTransfer: entryActions.transferCurrentCard, + onDuplicate: entryActions.duplicateCurrentCard, onDelete: entryActions.deleteCurrentCard, onUserAdd: entryActions.addUserToCurrentCard, onUserRemove: entryActions.removeUserFromCurrentCard, diff --git a/client/src/entry-actions/cards.js b/client/src/entry-actions/cards.js index d73b247..346fc8d 100755 --- a/client/src/entry-actions/cards.js +++ b/client/src/entry-actions/cards.js @@ -74,6 +74,18 @@ const transferCurrentCard = (boardId, listId, index = 0) => ({ }, }); +const duplicateCard = (id) => ({ + type: EntryActionTypes.CARD_DUPLICATE, + payload: { + id, + }, +}); + +const duplicateCurrentCard = () => ({ + type: EntryActionTypes.CURRENT_CARD_DUPLICATE, + payload: {}, +}); + const deleteCard = (id) => ({ type: EntryActionTypes.CARD_DELETE, payload: { @@ -103,6 +115,8 @@ export default { moveCurrentCard, transferCard, transferCurrentCard, + duplicateCard, + duplicateCurrentCard, deleteCard, deleteCurrentCard, handleCardDelete, diff --git a/client/src/locales/en/core.js b/client/src/locales/en/core.js index 83b0116..41e4494 100644 --- a/client/src/locales/en/core.js +++ b/client/src/locales/en/core.js @@ -51,6 +51,7 @@ export default { cardNotFound_title: 'Card Not Found', cardOrActionAreDeleted: 'Card or action are deleted.', color: 'Color', + copy_inline: 'copy', createBoard_title: 'Create Board', createLabel_title: 'Create Label', createNewOneOrSelectExistingOne: 'Create a new one or select
an existing one.', @@ -196,6 +197,8 @@ export default { deleteTask: 'Delete task', deleteTask_title: 'Delete Task', deleteUser: 'Delete user', + duplicate: 'Duplicate', + duplicateCard_title: 'Duplicate Card', edit: 'Edit', editDueDate_title: 'Edit Due Date', editDescription_title: 'Edit Description', diff --git a/client/src/locales/fr/core.js b/client/src/locales/fr/core.js index e20e291..1b9296f 100644 --- a/client/src/locales/fr/core.js +++ b/client/src/locales/fr/core.js @@ -169,6 +169,7 @@ export default { deleteTask: 'Supprimer la tâche', deleteTask_title: 'Supprimer la tâche', deleteUser: "Supprimer l'utilisateur", + duplicate: 'Dupliquer', edit: 'Modifier', editDueDate_title: "Modifier la date d'échéance", editDescription_title: 'Éditer la description', diff --git a/client/src/models/Card.js b/client/src/models/Card.js index 78c682c..3c45256 100755 --- a/client/src/models/Card.js +++ b/client/src/models/Card.js @@ -1,3 +1,4 @@ +import pick from 'lodash/pick'; import { attr, fk, many, oneToOne } from 'redux-orm'; import BaseModel from './BaseModel'; @@ -165,7 +166,6 @@ export default class extends BaseModel { break; case ActionTypes.CARD_CREATE: - case ActionTypes.CARD_CREATE_HANDLE: case ActionTypes.CARD_UPDATE__SUCCESS: case ActionTypes.CARD_UPDATE_HANDLE: Card.upsert(payload.card); @@ -176,10 +176,63 @@ export default class extends BaseModel { Card.upsert(payload.card); break; + case ActionTypes.CARD_CREATE_HANDLE: { + const cardModel = Card.upsert(payload.card); + + payload.cardMemberships.forEach(({ userId }) => { + cardModel.users.add(userId); + }); + + payload.cardLabels.forEach(({ labelId }) => { + cardModel.labels.add(labelId); + }); + + break; + } case ActionTypes.CARD_UPDATE: Card.withId(payload.id).update(payload.data); break; + case ActionTypes.CARD_DUPLICATE: { + const cardModel = Card.withId(payload.id); + + const nextCardModel = Card.upsert({ + ...pick(cardModel.ref, [ + 'boardId', + 'listId', + 'position', + 'name', + 'description', + 'dueDate', + 'stopwatch', + ]), + ...payload.card, + }); + + cardModel.users.toRefArray().forEach(({ id }) => { + nextCardModel.users.add(id); + }); + + cardModel.labels.toRefArray().forEach(({ id }) => { + nextCardModel.labels.add(id); + }); + + break; + } + case ActionTypes.CARD_DUPLICATE__SUCCESS: { + Card.withId(payload.localId).deleteWithRelated(); + const cardModel = Card.upsert(payload.card); + + payload.cardMemberships.forEach(({ userId }) => { + cardModel.users.add(userId); + }); + + payload.cardLabels.forEach(({ labelId }) => { + cardModel.labels.add(labelId); + }); + + break; + } case ActionTypes.CARD_DELETE: Card.withId(payload.id).deleteWithRelated(); diff --git a/client/src/models/Task.js b/client/src/models/Task.js index 1a47271..b990fe2 100755 --- a/client/src/models/Task.js +++ b/client/src/models/Task.js @@ -1,5 +1,6 @@ import { attr, fk } from 'redux-orm'; +import { createLocalId } from '../utils/local-id'; import BaseModel from './BaseModel'; import ActionTypes from '../constants/ActionTypes'; @@ -44,10 +45,24 @@ export default class extends BaseModel { break; case ActionTypes.BOARD_FETCH__SUCCESS: + case ActionTypes.CARD_CREATE_HANDLE: + case ActionTypes.CARD_DUPLICATE__SUCCESS: payload.tasks.forEach((task) => { Task.upsert(task); }); + break; + case ActionTypes.CARD_DUPLICATE: + payload.taskIds.forEach((taskId, index) => { + const taskModel = Task.withId(taskId); + + Task.upsert({ + ...taskModel.ref, + id: `${createLocalId()}-${index}`, // TODO: hack? + cardId: payload.card.id, + }); + }); + break; case ActionTypes.TASK_CREATE: case ActionTypes.TASK_CREATE_HANDLE: diff --git a/client/src/sagas/core/services/cards.js b/client/src/sagas/core/services/cards.js index 40c3db8..eb2d1da 100644 --- a/client/src/sagas/core/services/cards.js +++ b/client/src/sagas/core/services/cards.js @@ -5,6 +5,7 @@ import request from '../request'; import selectors from '../../../selectors'; import actions from '../../../actions'; import api from '../../../api'; +import i18n from '../../../i18n'; import { createLocalId } from '../../../utils/local-id'; export function* createCard(listId, data, autoOpen) { @@ -41,8 +42,23 @@ export function* createCard(listId, data, autoOpen) { } } -export function* handleCardCreate(card) { - yield put(actions.handleCardCreate(card)); +export function* handleCardCreate({ id }) { + let card; + let cardMemberships; + let cardLabels; + let tasks; + let attachments; + + try { + ({ + item: card, + included: { cardMemberships, cardLabels, tasks, attachments }, + } = yield call(request, api.getCard, id)); + } catch (error) { + return; + } + + yield put(actions.handleCardCreate(card, cardMemberships, cardLabels, tasks, attachments)); } export function* updateCard(id, data) { @@ -106,6 +122,55 @@ export function* transferCurrentCard(boardId, listId, index) { yield call(transferCard, cardId, boardId, listId, index); } +export function* duplicateCard(id) { + const { listId, name } = yield select(selectors.selectCardById, id); + const index = yield select(selectors.selectCardIndexById, id); + + const nextData = { + position: yield select(selectors.selectNextCardPosition, listId, index + 1), + name: `${name} (${i18n.t('common.copy', { + context: 'inline', + })})`, + }; + + const localId = yield call(createLocalId); + const taskIds = yield select(selectors.selectTaskIdsByCardId, id); + + yield put( + actions.duplicateCard( + id, + { + ...nextData, + id: localId, + }, + taskIds, + ), + ); + + let card; + let cardMemberships; + let cardLabels; + let tasks; + + try { + ({ + item: card, + included: { cardMemberships, cardLabels, tasks }, + } = yield call(request, api.duplicateCard, id, nextData)); + } catch (error) { + yield put(actions.duplicateCard.failure(localId, error)); + return; + } + + yield put(actions.duplicateCard.success(localId, card, cardMemberships, cardLabels, tasks)); +} + +export function* duplicateCurrentCard() { + const { cardId } = yield select(selectors.selectPath); + + yield call(duplicateCard, cardId); +} + export function* deleteCard(id) { const { cardId, boardId } = yield select(selectors.selectPath); @@ -147,11 +212,13 @@ export default { handleCardCreate, updateCard, updateCurrentCard, + handleCardUpdate, moveCard, moveCurrentCard, transferCard, transferCurrentCard, - handleCardUpdate, + duplicateCard, + duplicateCurrentCard, deleteCard, deleteCurrentCard, handleCardDelete, diff --git a/client/src/sagas/core/watchers/cards.js b/client/src/sagas/core/watchers/cards.js index 0290515..3fcb599 100644 --- a/client/src/sagas/core/watchers/cards.js +++ b/client/src/sagas/core/watchers/cards.js @@ -32,6 +32,8 @@ export default function* cardsWatchers() { takeEvery(EntryActionTypes.CURRENT_CARD_TRANSFER, ({ payload: { boardId, listId, index } }) => services.transferCurrentCard(boardId, listId, index), ), + takeEvery(EntryActionTypes.CARD_DUPLICATE, ({ payload: { id } }) => services.duplicateCard(id)), + takeEvery(EntryActionTypes.CURRENT_CARD_DUPLICATE, () => services.duplicateCurrentCard()), takeEvery(EntryActionTypes.CARD_DELETE, ({ payload: { id } }) => services.deleteCard(id)), takeEvery(EntryActionTypes.CURRENT_CARD_DELETE, () => services.deleteCurrentCard()), takeEvery(EntryActionTypes.CARD_DELETE_HANDLE, ({ payload: { card } }) => diff --git a/client/src/selectors/cards.js b/client/src/selectors/cards.js index 8f100d5..d1137dc 100644 --- a/client/src/selectors/cards.js +++ b/client/src/selectors/cards.js @@ -26,6 +26,24 @@ export const makeSelectCardById = () => export const selectCardById = makeSelectCardById(); +export const makeSelectCardIndexById = () => + createSelector( + orm, + (_, id) => id, + ({ Card }, id) => { + const cardModel = Card.withId(id); + + if (!cardModel) { + return cardModel; + } + + const cardModels = cardModel.list.getFilteredOrderedCardsModelArray(); + return cardModels.findIndex((cardModelItem) => cardModelItem.id === cardModel.id); + }, + ); + +export const selectCardIndexById = makeSelectCardIndexById(); + export const makeSelectUsersByCardId = () => createSelector( orm, @@ -60,6 +78,26 @@ export const makeSelectLabelsByCardId = () => export const selectLabelsByCardId = makeSelectLabelsByCardId(); +export const makeSelectTaskIdsByCardId = () => + createSelector( + orm, + (_, id) => id, + ({ Card }, id) => { + const cardModel = Card.withId(id); + + if (!cardModel) { + return cardModel; + } + + return cardModel + .getOrderedTasksQuerySet() + .toRefArray() + .map((task) => task.id); + }, + ); + +export const selectTaskIdsByCardId = makeSelectTaskIdsByCardId(); + export const makeSelectTasksByCardId = () => createSelector( orm, @@ -286,10 +324,14 @@ export const selectNotificationIdsForCurrentCard = createSelector( export default { makeSelectCardById, selectCardById, + makeSelectCardIndexById, + selectCardIndexById, makeSelectUsersByCardId, selectUsersByCardId, makeSelectLabelsByCardId, selectLabelsByCardId, + makeSelectTaskIdsByCardId, + selectTaskIdsByCardId, makeSelectTasksByCardId, selectTasksByCardId, makeSelectLastActivityIdByCardId, diff --git a/server/api/controllers/cards/duplicate.js b/server/api/controllers/cards/duplicate.js new file mode 100755 index 0000000..918a8ec --- /dev/null +++ b/server/api/controllers/cards/duplicate.js @@ -0,0 +1,82 @@ +const Errors = { + NOT_ENOUGH_RIGHTS: { + notEnoughRights: 'Not enough rights', + }, + CARD_NOT_FOUND: { + cardNotFound: 'Card not found', + }, +}; + +module.exports = { + inputs: { + id: { + type: 'string', + regex: /^[0-9]+$/, + required: true, + }, + position: { + type: 'number', + required: true, + }, + name: { + type: 'string', + }, + }, + + exits: { + notEnoughRights: { + responseType: 'forbidden', + }, + cardNotFound: { + responseType: 'notFound', + }, + }, + + async fn(inputs) { + const { currentUser } = this.req; + + const { card, list, board } = await sails.helpers.cards + .getProjectPath(inputs.id) + .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); + + const boardMembership = await BoardMembership.findOne({ + boardId: card.boardId, + userId: currentUser.id, + }); + + if (!boardMembership) { + throw Errors.CARD_NOT_FOUND; // Forbidden + } + + if (boardMembership.role !== BoardMembership.Roles.EDITOR) { + throw Errors.NOT_ENOUGH_RIGHTS; + } + + const values = _.pick(inputs, ['position', 'name']); + + const { + card: nextCard, + cardMemberships, + cardLabels, + tasks, + } = await sails.helpers.cards.duplicateOne.with({ + board, + list, + record: card, + values: { + ...values, + creatorUser: currentUser, + }, + request: this.req, + }); + + return { + item: nextCard, + included: { + cardMemberships, + cardLabels, + tasks, + }, + }; + }, +}; diff --git a/server/api/helpers/boards/import-from-trello.js b/server/api/helpers/boards/import-from-trello.js index efc74dc..2ab6e0a 100644 --- a/server/api/helpers/boards/import-from-trello.js +++ b/server/api/helpers/boards/import-from-trello.js @@ -1,3 +1,5 @@ +const POSITION_GAP = 65535; // TODO: move to config + module.exports = { inputs: { user: { @@ -124,7 +126,7 @@ module.exports = { getUsedTrelloLabels().map(async (trelloLabel, index) => { const plankaLabel = await Label.create({ boardId: inputs.board.id, - position: 65535 * (index + 1), // TODO: move to config + position: POSITION_GAP * (index + 1), name: trelloLabel.name || null, color: getPlankaLabelColor(trelloLabel.color), }).fetch(); diff --git a/server/api/helpers/cards/duplicate-one.js b/server/api/helpers/cards/duplicate-one.js new file mode 100644 index 0000000..a63feaa --- /dev/null +++ b/server/api/helpers/cards/duplicate-one.js @@ -0,0 +1,144 @@ +const valuesValidator = (value) => { + if (!_.isPlainObject(value)) { + return false; + } + + if (!_.isUndefined(value.position) && !_.isFinite(value.position)) { + return false; + } + + if (!_.isPlainObject(value.creatorUser)) { + return false; + } + + return true; +}; + +module.exports = { + inputs: { + record: { + type: 'ref', + required: true, + }, + values: { + type: 'ref', + custom: valuesValidator, + required: true, + }, + board: { + type: 'ref', + required: true, + }, + list: { + type: 'ref', + required: true, + }, + request: { + type: 'ref', + }, + }, + + async fn(inputs) { + const { values } = inputs; + + const cards = await sails.helpers.lists.getCards(inputs.record.listId); + + const { position, repositions } = sails.helpers.utils.insertToPositionables( + values.position, + cards, + ); + + repositions.forEach(async ({ id, position: nextPosition }) => { + await Card.update({ + id, + listId: inputs.record.listId, + }).set({ + position: nextPosition, + }); + + sails.sockets.broadcast(`board:${inputs.record.boardId}`, 'cardUpdate', { + item: { + id, + position: nextPosition, + }, + }); + }); + + const card = await Card.create({ + ..._.pick(inputs.record, [ + 'boardId', + 'listId', + 'name', + 'description', + 'dueDate', + 'stopwatch', + ]), + ...values, + position, + creatorUserId: values.creatorUser.id, + }).fetch(); + + const cardMemberships = await sails.helpers.cards.getCardMemberships(inputs.record.id); + const cardMembershipsValues = cardMemberships.map((cardMembership) => ({ + ..._.pick(cardMembership, ['userId']), + cardId: card.id, + })); + const nextCardMemberships = await CardMembership.createEach(cardMembershipsValues).fetch(); + + const cardLabels = await sails.helpers.cards.getCardLabels(inputs.record.id); + const cardLabelsValues = cardLabels.map((cardLabel) => ({ + ..._.pick(cardLabel, ['labelId']), + cardId: card.id, + })); + const nextCardLabels = await CardLabel.createEach(cardLabelsValues).fetch(); + + const tasks = await sails.helpers.cards.getTasks(inputs.record.id); + const tasksValues = tasks.map((task) => ({ + ..._.pick(task, ['position', 'name', 'isCompleted']), + cardId: card.id, + })); + const nextTasks = await Task.createEach(tasksValues).fetch(); + + sails.sockets.broadcast( + `board:${card.boardId}`, + 'cardCreate', + { + item: card, + }, + inputs.request, + ); + + if (values.creatorUser.subscribeToOwnCards) { + await CardSubscription.create({ + cardId: card.id, + userId: card.creatorUserId, + }).tolerate('E_UNIQUE'); + + sails.sockets.broadcast(`user:${card.creatorUserId}`, 'cardUpdate', { + item: { + id: card.id, + isSubscribed: true, + }, + }); + } + + await sails.helpers.actions.createOne.with({ + values: { + card, + type: Action.Types.CREATE_CARD, // TODO: introduce separate type? + data: { + list: _.pick(inputs.list, ['id', 'name']), + }, + user: values.creatorUser, + }, + board: inputs.board, + }); + + return { + card, + cardMemberships: nextCardMemberships, + cardLabels: nextCardLabels, + tasks: nextTasks, + }; + }, +}; diff --git a/server/config/routes.js b/server/config/routes.js index 77f5c43..8b88df5 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -55,6 +55,7 @@ module.exports.routes = { 'POST /api/lists/:listId/cards': 'cards/create', 'GET /api/cards/:id': 'cards/show', 'PATCH /api/cards/:id': 'cards/update', + 'POST /api/cards/:id/duplicate': 'cards/duplicate', 'DELETE /api/cards/:id': 'cards/delete', 'POST /api/cards/:cardId/memberships': 'card-memberships/create', 'DELETE /api/cards/:cardId/memberships': 'card-memberships/delete', From 5acf84c20bb2610e0fbf5d93fe968f33c018f295 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Fri, 5 Apr 2024 22:49:02 +0200 Subject: [PATCH 35/83] chore: Update version --- charts/planka/Chart.yaml | 4 ++-- client/.env | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/planka/Chart.yaml b/charts/planka/Chart.yaml index 3f291a9..5a6debe 100644 --- a/charts/planka/Chart.yaml +++ b/charts/planka/Chart.yaml @@ -15,13 +15,13 @@ 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.22 +version: 0.1.23 # 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 # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.16.1" +appVersion: "1.16.2" dependencies: - alias: postgresql diff --git a/client/.env b/client/.env index 0718c00..e17aeb7 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.16.1 +REACT_APP_VERSION=1.16.2 diff --git a/package-lock.json b/package-lock.json index 1bd8b53..eee91c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "planka", - "version": "1.16.1", + "version": "1.16.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "planka", - "version": "1.16.1", + "version": "1.16.2", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index e880d0a..7e06e77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "planka", - "version": "1.16.1", + "version": "1.16.2", "private": true, "homepage": "https://plankanban.github.io/planka", "repository": { From fd89cf7f2bf875a4eec3d50ae1ee59eadadc1052 Mon Sep 17 00:00:00 2001 From: leroyloren <57643470+leroyloren@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:58:27 +0200 Subject: [PATCH 36/83] fix: Update Czech translation (#679) --- client/src/locales/cs/core.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/client/src/locales/cs/core.js b/client/src/locales/cs/core.js index 04dfc8d..4001513 100644 --- a/client/src/locales/cs/core.js +++ b/client/src/locales/cs/core.js @@ -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
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', }, }, From 22964cb375735e4e40a57c2c3d00312fdae89087 Mon Sep 17 00:00:00 2001 From: Blyamur <409434+blyamur@users.noreply.github.com> Date: Sun, 7 Apr 2024 23:05:22 +0300 Subject: [PATCH 37/83] fix: Update Russian translation (#686) --- client/src/locales/ru/core.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/locales/ru/core.js b/client/src/locales/ru/core.js index 138b893..06520c6 100644 --- a/client/src/locales/ru/core.js +++ b/client/src/locales/ru/core.js @@ -199,6 +199,8 @@ export default { deleteProject: 'Удалить проект', deleteTask: 'Удалить задачу', deleteUser: 'Удалить пользователя', + duplicate: 'Дублировать', + duplicateCard_title: 'Дублировать карточку', edit: 'Изменить', editBackground: 'Изменить фон', editDueDate: 'Изменить срок', From 2f3dfe775e35ba8d37e36a813a9abfe220d6ff98 Mon Sep 17 00:00:00 2001 From: Matthieu Bollot Date: Mon, 8 Apr 2024 00:33:29 +0200 Subject: [PATCH 38/83] 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 51ac55c..f7935aa 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 fb5a828..15d66ed 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 e2b6d00..dad82de 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 78d5e0c..b03adfd 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 fb72787..a947f73 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 271a917..f24e4bf 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 5d9be72..7b8d08b 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 0000000..573fcf8 --- /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 7647324..ac344d0 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, }; From 39e320fe4ba7e0304320cd0d3764139f91054f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Pedrini?= Date: Mon, 8 Apr 2024 00:50:52 +0200 Subject: [PATCH 39/83] fix: Update Italian translation (#688) --- client/src/locales/it/core.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/client/src/locales/it/core.js b/client/src/locales/it/core.js index f08a946..1f605b1 100644 --- a/client/src/locales/it/core.js +++ b/client/src/locales/it/core.js @@ -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
after connection restored.', + 'Tutte le modifiche verranno salvate
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
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', From 0a2863a79ccf6aecddee6d0372ae552ef4977db0 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Wed, 3 Jan 2024 13:59:14 +0500 Subject: [PATCH 40/83] chore: Bump sharp version --- server/package-lock.json | 190 +++++++++++++++++++-------------------- server/package.json | 2 +- 2 files changed, 96 insertions(+), 96 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 99e0e2a..1b07cb3 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -21,7 +21,7 @@ "sails-hook-orm": "^4.0.2", "sails-hook-sockets": "^2.0.4", "sails-postgresql-redacted": "^1.0.2-9", - "sharp": "^0.33.0", + "sharp": "^0.33.1", "stream-to-array": "^2.3.0", "uuid": "^9.0.1", "validator": "^13.11.0", @@ -167,9 +167,9 @@ "dev": true }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.0.tgz", - "integrity": "sha512-070tEheekI1LJWTGPC9WlQEa5UoKTXzzlORBHMX4TbfUxMiL336YHR8vBEUNsjse0RJCX8dZ4ZXwT595aEF1ug==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz", + "integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==", "cpu": [ "arm64" ], @@ -192,9 +192,9 @@ } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.0.tgz", - "integrity": "sha512-pu/nvn152F3qbPeUkr+4e9zVvEhD3jhwzF473veQfMPkOYo9aoWXSfdZH/E6F+nYC3qvFjbxbvdDbUtEbghLqw==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz", + "integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==", "cpu": [ "x64" ], @@ -385,9 +385,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.0.tgz", - "integrity": "sha512-4horD3wMFd5a0ddbDY8/dXU9CaOgHjEHALAddXgafoR5oWq5s8X61PDgsSeh4Qupsdo6ycfPPSSNBrfVQnwwrg==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz", + "integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==", "cpu": [ "arm" ], @@ -410,9 +410,9 @@ } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.0.tgz", - "integrity": "sha512-dcomVSrtgF70SyOr8RCOCQ8XGVThXwe71A1d8MGA+mXEVRJ/J6/TrCbBEJh9ddcEIIsrnrkolaEvYSHqVhswQw==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz", + "integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==", "cpu": [ "arm64" ], @@ -435,9 +435,9 @@ } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.0.tgz", - "integrity": "sha512-TiVJbx38J2rNVfA309ffSOB+3/7wOsZYQEOlKqOUdWD/nqkjNGrX+YQGz7nzcf5oy2lC+d37+w183iNXRZNngQ==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz", + "integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==", "cpu": [ "s390x" ], @@ -460,9 +460,9 @@ } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.0.tgz", - "integrity": "sha512-PaZM4Zi7/Ek71WgTdvR+KzTZpBqrQOFcPe7/8ZoPRlTYYRe43k6TWsf4GVH6XKRLMYeSp8J89RfAhBrSP4itNA==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz", + "integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==", "cpu": [ "x64" ], @@ -485,9 +485,9 @@ } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.0.tgz", - "integrity": "sha512-1QLbbN0zt+32eVrg7bb1lwtvEaZwlhEsY1OrijroMkwAqlHqFj6R33Y47s2XUv7P6Ie1PwCxK/uFnNqMnkd5kg==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz", + "integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==", "cpu": [ "arm64" ], @@ -510,9 +510,9 @@ } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.0.tgz", - "integrity": "sha512-CecqgB/CnkvCWFhmfN9ZhPGMLXaEBXl4o7WtA6U3Ztrlh/s7FUKX4vNxpMSYLIrWuuzjiaYdfU3+Tdqh1xaHfw==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz", + "integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==", "cpu": [ "x64" ], @@ -535,9 +535,9 @@ } }, "node_modules/@img/sharp-wasm32": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.0.tgz", - "integrity": "sha512-Hn4js32gUX9qkISlemZBUPuMs0k/xNJebUNl/L6djnU07B/HAA2KaxRVb3HvbU5fL242hLOcp0+tR+M8dvJUFw==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz", + "integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==", "cpu": [ "wasm32" ], @@ -556,9 +556,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.0.tgz", - "integrity": "sha512-5HfcsCZi3l5nPRF2q3bllMVMDXBqEWI3Q8KQONfzl0TferFE5lnsIG0A1YrntMAGqvkzdW6y1Ci1A2uTvxhfzg==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz", + "integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==", "cpu": [ "ia32" ], @@ -577,9 +577,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.0.tgz", - "integrity": "sha512-i3DtP/2ce1yKFj4OzOnOYltOEL/+dp4dc4dJXJBv6god1AFTcmkaA99H/7SwOmkCOBQkbVvA3lCGm3/5nDtf9Q==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz", + "integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==", "cpu": [ "x64" ], @@ -7269,9 +7269,9 @@ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "node_modules/sharp": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.0.tgz", - "integrity": "sha512-99DZKudjm/Rmz+M0/26t4DKpXyywAOJaayGS9boEn7FvgtG0RYBi46uPE2c+obcJRtA3AZa0QwJot63gJQ1F0Q==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz", + "integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==", "hasInstallScript": true, "dependencies": { "color": "^4.2.3", @@ -7286,8 +7286,8 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.0", - "@img/sharp-darwin-x64": "0.33.0", + "@img/sharp-darwin-arm64": "0.33.1", + "@img/sharp-darwin-x64": "0.33.1", "@img/sharp-libvips-darwin-arm64": "1.0.0", "@img/sharp-libvips-darwin-x64": "1.0.0", "@img/sharp-libvips-linux-arm": "1.0.0", @@ -7296,15 +7296,15 @@ "@img/sharp-libvips-linux-x64": "1.0.0", "@img/sharp-libvips-linuxmusl-arm64": "1.0.0", "@img/sharp-libvips-linuxmusl-x64": "1.0.0", - "@img/sharp-linux-arm": "0.33.0", - "@img/sharp-linux-arm64": "0.33.0", - "@img/sharp-linux-s390x": "0.33.0", - "@img/sharp-linux-x64": "0.33.0", - "@img/sharp-linuxmusl-arm64": "0.33.0", - "@img/sharp-linuxmusl-x64": "0.33.0", - "@img/sharp-wasm32": "0.33.0", - "@img/sharp-win32-ia32": "0.33.0", - "@img/sharp-win32-x64": "0.33.0" + "@img/sharp-linux-arm": "0.33.1", + "@img/sharp-linux-arm64": "0.33.1", + "@img/sharp-linux-s390x": "0.33.1", + "@img/sharp-linux-x64": "0.33.1", + "@img/sharp-linuxmusl-arm64": "0.33.1", + "@img/sharp-linuxmusl-x64": "0.33.1", + "@img/sharp-wasm32": "0.33.1", + "@img/sharp-win32-ia32": "0.33.1", + "@img/sharp-win32-x64": "0.33.1" } }, "node_modules/sharp/node_modules/semver": { @@ -9094,18 +9094,18 @@ "dev": true }, "@img/sharp-darwin-arm64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.0.tgz", - "integrity": "sha512-070tEheekI1LJWTGPC9WlQEa5UoKTXzzlORBHMX4TbfUxMiL336YHR8vBEUNsjse0RJCX8dZ4ZXwT595aEF1ug==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz", + "integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==", "optional": true, "requires": { "@img/sharp-libvips-darwin-arm64": "1.0.0" } }, "@img/sharp-darwin-x64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.0.tgz", - "integrity": "sha512-pu/nvn152F3qbPeUkr+4e9zVvEhD3jhwzF473veQfMPkOYo9aoWXSfdZH/E6F+nYC3qvFjbxbvdDbUtEbghLqw==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz", + "integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==", "optional": true, "requires": { "@img/sharp-libvips-darwin-x64": "1.0.0" @@ -9160,78 +9160,78 @@ "optional": true }, "@img/sharp-linux-arm": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.0.tgz", - "integrity": "sha512-4horD3wMFd5a0ddbDY8/dXU9CaOgHjEHALAddXgafoR5oWq5s8X61PDgsSeh4Qupsdo6ycfPPSSNBrfVQnwwrg==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz", + "integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==", "optional": true, "requires": { "@img/sharp-libvips-linux-arm": "1.0.0" } }, "@img/sharp-linux-arm64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.0.tgz", - "integrity": "sha512-dcomVSrtgF70SyOr8RCOCQ8XGVThXwe71A1d8MGA+mXEVRJ/J6/TrCbBEJh9ddcEIIsrnrkolaEvYSHqVhswQw==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz", + "integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==", "optional": true, "requires": { "@img/sharp-libvips-linux-arm64": "1.0.0" } }, "@img/sharp-linux-s390x": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.0.tgz", - "integrity": "sha512-TiVJbx38J2rNVfA309ffSOB+3/7wOsZYQEOlKqOUdWD/nqkjNGrX+YQGz7nzcf5oy2lC+d37+w183iNXRZNngQ==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz", + "integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==", "optional": true, "requires": { "@img/sharp-libvips-linux-s390x": "1.0.0" } }, "@img/sharp-linux-x64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.0.tgz", - "integrity": "sha512-PaZM4Zi7/Ek71WgTdvR+KzTZpBqrQOFcPe7/8ZoPRlTYYRe43k6TWsf4GVH6XKRLMYeSp8J89RfAhBrSP4itNA==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz", + "integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==", "optional": true, "requires": { "@img/sharp-libvips-linux-x64": "1.0.0" } }, "@img/sharp-linuxmusl-arm64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.0.tgz", - "integrity": "sha512-1QLbbN0zt+32eVrg7bb1lwtvEaZwlhEsY1OrijroMkwAqlHqFj6R33Y47s2XUv7P6Ie1PwCxK/uFnNqMnkd5kg==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz", + "integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==", "optional": true, "requires": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.0" } }, "@img/sharp-linuxmusl-x64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.0.tgz", - "integrity": "sha512-CecqgB/CnkvCWFhmfN9ZhPGMLXaEBXl4o7WtA6U3Ztrlh/s7FUKX4vNxpMSYLIrWuuzjiaYdfU3+Tdqh1xaHfw==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz", + "integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==", "optional": true, "requires": { "@img/sharp-libvips-linuxmusl-x64": "1.0.0" } }, "@img/sharp-wasm32": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.0.tgz", - "integrity": "sha512-Hn4js32gUX9qkISlemZBUPuMs0k/xNJebUNl/L6djnU07B/HAA2KaxRVb3HvbU5fL242hLOcp0+tR+M8dvJUFw==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz", + "integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==", "optional": true, "requires": { "@emnapi/runtime": "^0.44.0" } }, "@img/sharp-win32-ia32": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.0.tgz", - "integrity": "sha512-5HfcsCZi3l5nPRF2q3bllMVMDXBqEWI3Q8KQONfzl0TferFE5lnsIG0A1YrntMAGqvkzdW6y1Ci1A2uTvxhfzg==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz", + "integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==", "optional": true }, "@img/sharp-win32-x64": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.0.tgz", - "integrity": "sha512-i3DtP/2ce1yKFj4OzOnOYltOEL/+dp4dc4dJXJBv6god1AFTcmkaA99H/7SwOmkCOBQkbVvA3lCGm3/5nDtf9Q==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz", + "integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==", "optional": true }, "@isaacs/cliui": { @@ -14411,12 +14411,12 @@ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "sharp": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.0.tgz", - "integrity": "sha512-99DZKudjm/Rmz+M0/26t4DKpXyywAOJaayGS9boEn7FvgtG0RYBi46uPE2c+obcJRtA3AZa0QwJot63gJQ1F0Q==", + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz", + "integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==", "requires": { - "@img/sharp-darwin-arm64": "0.33.0", - "@img/sharp-darwin-x64": "0.33.0", + "@img/sharp-darwin-arm64": "0.33.1", + "@img/sharp-darwin-x64": "0.33.1", "@img/sharp-libvips-darwin-arm64": "1.0.0", "@img/sharp-libvips-darwin-x64": "1.0.0", "@img/sharp-libvips-linux-arm": "1.0.0", @@ -14425,15 +14425,15 @@ "@img/sharp-libvips-linux-x64": "1.0.0", "@img/sharp-libvips-linuxmusl-arm64": "1.0.0", "@img/sharp-libvips-linuxmusl-x64": "1.0.0", - "@img/sharp-linux-arm": "0.33.0", - "@img/sharp-linux-arm64": "0.33.0", - "@img/sharp-linux-s390x": "0.33.0", - "@img/sharp-linux-x64": "0.33.0", - "@img/sharp-linuxmusl-arm64": "0.33.0", - "@img/sharp-linuxmusl-x64": "0.33.0", - "@img/sharp-wasm32": "0.33.0", - "@img/sharp-win32-ia32": "0.33.0", - "@img/sharp-win32-x64": "0.33.0", + "@img/sharp-linux-arm": "0.33.1", + "@img/sharp-linux-arm64": "0.33.1", + "@img/sharp-linux-s390x": "0.33.1", + "@img/sharp-linux-x64": "0.33.1", + "@img/sharp-linuxmusl-arm64": "0.33.1", + "@img/sharp-linuxmusl-x64": "0.33.1", + "@img/sharp-wasm32": "0.33.1", + "@img/sharp-win32-ia32": "0.33.1", + "@img/sharp-win32-x64": "0.33.1", "color": "^4.2.3", "detect-libc": "^2.0.2", "semver": "^7.5.4" diff --git a/server/package.json b/server/package.json index 5301c1f..6a11147 100644 --- a/server/package.json +++ b/server/package.json @@ -42,7 +42,7 @@ "sails-hook-orm": "^4.0.2", "sails-hook-sockets": "^2.0.4", "sails-postgresql-redacted": "^1.0.2-9", - "sharp": "^0.33.0", + "sharp": "^0.33.1", "stream-to-array": "^2.3.0", "uuid": "^9.0.1", "validator": "^13.11.0", From ce52fc5af7cfa68bb13f74c70bd2370b10bea87a Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Thu, 18 Jan 2024 10:58:26 +0100 Subject: [PATCH 41/83] fix: Fix images becoming black and white when resizing Closes #574, closes #585 --- server/package-lock.json | 370 +++++++++++++++++++-------------------- server/package.json | 2 +- temp | 1 - 3 files changed, 186 insertions(+), 187 deletions(-) delete mode 100644 temp diff --git a/server/package-lock.json b/server/package-lock.json index 1b07cb3..05527a7 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -21,7 +21,7 @@ "sails-hook-orm": "^4.0.2", "sails-hook-sockets": "^2.0.4", "sails-postgresql-redacted": "^1.0.2-9", - "sharp": "^0.33.1", + "sharp": "^0.33.2", "stream-to-array": "^2.3.0", "uuid": "^9.0.1", "validator": "^13.11.0", @@ -69,9 +69,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz", - "integrity": "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", + "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -167,9 +167,9 @@ "dev": true }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz", - "integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz", + "integrity": "sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w==", "cpu": [ "arm64" ], @@ -188,13 +188,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.0" + "@img/sharp-libvips-darwin-arm64": "1.0.1" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz", - "integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz", + "integrity": "sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg==", "cpu": [ "x64" ], @@ -213,13 +213,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.0" + "@img/sharp-libvips-darwin-x64": "1.0.1" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.0.tgz", - "integrity": "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw==", "cpu": [ "arm64" ], @@ -238,9 +238,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.0.tgz", - "integrity": "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz", + "integrity": "sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog==", "cpu": [ "x64" ], @@ -259,9 +259,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.0.tgz", - "integrity": "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz", + "integrity": "sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ==", "cpu": [ "arm" ], @@ -280,9 +280,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.0.tgz", - "integrity": "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz", + "integrity": "sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA==", "cpu": [ "arm64" ], @@ -301,9 +301,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.0.tgz", - "integrity": "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz", + "integrity": "sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ==", "cpu": [ "s390x" ], @@ -322,9 +322,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.0.tgz", - "integrity": "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz", + "integrity": "sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw==", "cpu": [ "x64" ], @@ -343,9 +343,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.0.tgz", - "integrity": "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz", + "integrity": "sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg==", "cpu": [ "arm64" ], @@ -364,9 +364,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.0.tgz", - "integrity": "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz", + "integrity": "sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw==", "cpu": [ "x64" ], @@ -385,9 +385,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz", - "integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz", + "integrity": "sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA==", "cpu": [ "arm" ], @@ -406,13 +406,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.0" + "@img/sharp-libvips-linux-arm": "1.0.1" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz", - "integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz", + "integrity": "sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew==", "cpu": [ "arm64" ], @@ -431,13 +431,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.0" + "@img/sharp-libvips-linux-arm64": "1.0.1" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz", - "integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz", + "integrity": "sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA==", "cpu": [ "s390x" ], @@ -456,13 +456,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.0" + "@img/sharp-libvips-linux-s390x": "1.0.1" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz", - "integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz", + "integrity": "sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A==", "cpu": [ "x64" ], @@ -481,13 +481,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.0" + "@img/sharp-libvips-linux-x64": "1.0.1" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz", - "integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz", + "integrity": "sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA==", "cpu": [ "arm64" ], @@ -506,13 +506,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.0" + "@img/sharp-libvips-linuxmusl-arm64": "1.0.1" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz", - "integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz", + "integrity": "sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A==", "cpu": [ "x64" ], @@ -531,19 +531,19 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.0" + "@img/sharp-libvips-linuxmusl-x64": "1.0.1" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz", - "integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz", + "integrity": "sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ==", "cpu": [ "wasm32" ], "optional": true, "dependencies": { - "@emnapi/runtime": "^0.44.0" + "@emnapi/runtime": "^0.45.0" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0", @@ -556,9 +556,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz", - "integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz", + "integrity": "sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g==", "cpu": [ "ia32" ], @@ -577,9 +577,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz", - "integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz", + "integrity": "sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg==", "cpu": [ "x64" ], @@ -7269,9 +7269,9 @@ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "node_modules/sharp": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz", - "integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.2.tgz", + "integrity": "sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ==", "hasInstallScript": true, "dependencies": { "color": "^4.2.3", @@ -7279,32 +7279,32 @@ "semver": "^7.5.4" }, "engines": { - "libvips": ">=8.15.0", + "libvips": ">=8.15.1", "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.1", - "@img/sharp-darwin-x64": "0.33.1", - "@img/sharp-libvips-darwin-arm64": "1.0.0", - "@img/sharp-libvips-darwin-x64": "1.0.0", - "@img/sharp-libvips-linux-arm": "1.0.0", - "@img/sharp-libvips-linux-arm64": "1.0.0", - "@img/sharp-libvips-linux-s390x": "1.0.0", - "@img/sharp-libvips-linux-x64": "1.0.0", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.0", - "@img/sharp-libvips-linuxmusl-x64": "1.0.0", - "@img/sharp-linux-arm": "0.33.1", - "@img/sharp-linux-arm64": "0.33.1", - "@img/sharp-linux-s390x": "0.33.1", - "@img/sharp-linux-x64": "0.33.1", - "@img/sharp-linuxmusl-arm64": "0.33.1", - "@img/sharp-linuxmusl-x64": "0.33.1", - "@img/sharp-wasm32": "0.33.1", - "@img/sharp-win32-ia32": "0.33.1", - "@img/sharp-win32-x64": "0.33.1" + "@img/sharp-darwin-arm64": "0.33.2", + "@img/sharp-darwin-x64": "0.33.2", + "@img/sharp-libvips-darwin-arm64": "1.0.1", + "@img/sharp-libvips-darwin-x64": "1.0.1", + "@img/sharp-libvips-linux-arm": "1.0.1", + "@img/sharp-libvips-linux-arm64": "1.0.1", + "@img/sharp-libvips-linux-s390x": "1.0.1", + "@img/sharp-libvips-linux-x64": "1.0.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.1", + "@img/sharp-libvips-linuxmusl-x64": "1.0.1", + "@img/sharp-linux-arm": "0.33.2", + "@img/sharp-linux-arm64": "0.33.2", + "@img/sharp-linux-s390x": "0.33.2", + "@img/sharp-linux-x64": "0.33.2", + "@img/sharp-linuxmusl-arm64": "0.33.2", + "@img/sharp-linuxmusl-x64": "0.33.2", + "@img/sharp-wasm32": "0.33.2", + "@img/sharp-win32-ia32": "0.33.2", + "@img/sharp-win32-x64": "0.33.2" } }, "node_modules/sharp/node_modules/semver": { @@ -9024,9 +9024,9 @@ } }, "@emnapi/runtime": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz", - "integrity": "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", + "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", "optional": true, "requires": { "tslib": "^2.4.0" @@ -9094,144 +9094,144 @@ "dev": true }, "@img/sharp-darwin-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz", - "integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz", + "integrity": "sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w==", "optional": true, "requires": { - "@img/sharp-libvips-darwin-arm64": "1.0.0" + "@img/sharp-libvips-darwin-arm64": "1.0.1" } }, "@img/sharp-darwin-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz", - "integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz", + "integrity": "sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg==", "optional": true, "requires": { - "@img/sharp-libvips-darwin-x64": "1.0.0" + "@img/sharp-libvips-darwin-x64": "1.0.1" } }, "@img/sharp-libvips-darwin-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.0.tgz", - "integrity": "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw==", "optional": true }, "@img/sharp-libvips-darwin-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.0.tgz", - "integrity": "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz", + "integrity": "sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog==", "optional": true }, "@img/sharp-libvips-linux-arm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.0.tgz", - "integrity": "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz", + "integrity": "sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ==", "optional": true }, "@img/sharp-libvips-linux-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.0.tgz", - "integrity": "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz", + "integrity": "sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA==", "optional": true }, "@img/sharp-libvips-linux-s390x": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.0.tgz", - "integrity": "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz", + "integrity": "sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ==", "optional": true }, "@img/sharp-libvips-linux-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.0.tgz", - "integrity": "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz", + "integrity": "sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw==", "optional": true }, "@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.0.tgz", - "integrity": "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz", + "integrity": "sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg==", "optional": true }, "@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.0.tgz", - "integrity": "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz", + "integrity": "sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw==", "optional": true }, "@img/sharp-linux-arm": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz", - "integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz", + "integrity": "sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA==", "optional": true, "requires": { - "@img/sharp-libvips-linux-arm": "1.0.0" + "@img/sharp-libvips-linux-arm": "1.0.1" } }, "@img/sharp-linux-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz", - "integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz", + "integrity": "sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew==", "optional": true, "requires": { - "@img/sharp-libvips-linux-arm64": "1.0.0" + "@img/sharp-libvips-linux-arm64": "1.0.1" } }, "@img/sharp-linux-s390x": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz", - "integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz", + "integrity": "sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA==", "optional": true, "requires": { - "@img/sharp-libvips-linux-s390x": "1.0.0" + "@img/sharp-libvips-linux-s390x": "1.0.1" } }, "@img/sharp-linux-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz", - "integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz", + "integrity": "sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A==", "optional": true, "requires": { - "@img/sharp-libvips-linux-x64": "1.0.0" + "@img/sharp-libvips-linux-x64": "1.0.1" } }, "@img/sharp-linuxmusl-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz", - "integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz", + "integrity": "sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA==", "optional": true, "requires": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.0" + "@img/sharp-libvips-linuxmusl-arm64": "1.0.1" } }, "@img/sharp-linuxmusl-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz", - "integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz", + "integrity": "sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A==", "optional": true, "requires": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.0" + "@img/sharp-libvips-linuxmusl-x64": "1.0.1" } }, "@img/sharp-wasm32": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz", - "integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz", + "integrity": "sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ==", "optional": true, "requires": { - "@emnapi/runtime": "^0.44.0" + "@emnapi/runtime": "^0.45.0" } }, "@img/sharp-win32-ia32": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz", - "integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz", + "integrity": "sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g==", "optional": true }, "@img/sharp-win32-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz", - "integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz", + "integrity": "sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg==", "optional": true }, "@isaacs/cliui": { @@ -14411,29 +14411,29 @@ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "sharp": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz", - "integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==", - "requires": { - "@img/sharp-darwin-arm64": "0.33.1", - "@img/sharp-darwin-x64": "0.33.1", - "@img/sharp-libvips-darwin-arm64": "1.0.0", - "@img/sharp-libvips-darwin-x64": "1.0.0", - "@img/sharp-libvips-linux-arm": "1.0.0", - "@img/sharp-libvips-linux-arm64": "1.0.0", - "@img/sharp-libvips-linux-s390x": "1.0.0", - "@img/sharp-libvips-linux-x64": "1.0.0", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.0", - "@img/sharp-libvips-linuxmusl-x64": "1.0.0", - "@img/sharp-linux-arm": "0.33.1", - "@img/sharp-linux-arm64": "0.33.1", - "@img/sharp-linux-s390x": "0.33.1", - "@img/sharp-linux-x64": "0.33.1", - "@img/sharp-linuxmusl-arm64": "0.33.1", - "@img/sharp-linuxmusl-x64": "0.33.1", - "@img/sharp-wasm32": "0.33.1", - "@img/sharp-win32-ia32": "0.33.1", - "@img/sharp-win32-x64": "0.33.1", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.2.tgz", + "integrity": "sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ==", + "requires": { + "@img/sharp-darwin-arm64": "0.33.2", + "@img/sharp-darwin-x64": "0.33.2", + "@img/sharp-libvips-darwin-arm64": "1.0.1", + "@img/sharp-libvips-darwin-x64": "1.0.1", + "@img/sharp-libvips-linux-arm": "1.0.1", + "@img/sharp-libvips-linux-arm64": "1.0.1", + "@img/sharp-libvips-linux-s390x": "1.0.1", + "@img/sharp-libvips-linux-x64": "1.0.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.1", + "@img/sharp-libvips-linuxmusl-x64": "1.0.1", + "@img/sharp-linux-arm": "0.33.2", + "@img/sharp-linux-arm64": "0.33.2", + "@img/sharp-linux-s390x": "0.33.2", + "@img/sharp-linux-x64": "0.33.2", + "@img/sharp-linuxmusl-arm64": "0.33.2", + "@img/sharp-linuxmusl-x64": "0.33.2", + "@img/sharp-wasm32": "0.33.2", + "@img/sharp-win32-ia32": "0.33.2", + "@img/sharp-win32-x64": "0.33.2", "color": "^4.2.3", "detect-libc": "^2.0.2", "semver": "^7.5.4" diff --git a/server/package.json b/server/package.json index 6a11147..59bb2f7 100644 --- a/server/package.json +++ b/server/package.json @@ -42,7 +42,7 @@ "sails-hook-orm": "^4.0.2", "sails-hook-sockets": "^2.0.4", "sails-postgresql-redacted": "^1.0.2-9", - "sharp": "^0.33.1", + "sharp": "^0.33.2", "stream-to-array": "^2.3.0", "uuid": "^9.0.1", "validator": "^13.11.0", diff --git a/temp b/temp deleted file mode 100644 index a1c2b7d..0000000 --- a/temp +++ /dev/null @@ -1 +0,0 @@ -Just a file to trigger the build workflow. From 76a18d06d611f4872fbf87a858b593244c3c6c85 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Thu, 18 Jan 2024 11:00:20 +0100 Subject: [PATCH 42/83] chore: Update version --- charts/planka/Chart.yaml | 4 ++-- client/.env | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/planka/Chart.yaml b/charts/planka/Chart.yaml index a24cdae..1c32bf7 100644 --- a/charts/planka/Chart.yaml +++ b/charts/planka/Chart.yaml @@ -15,13 +15,13 @@ 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.14 +version: 0.1.15 # 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 # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.15.3" +appVersion: "1.15.4" dependencies: - alias: postgresql diff --git a/client/.env b/client/.env index b798cdc..4d4ab73 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.15.3 +REACT_APP_VERSION=1.15.4 diff --git a/package-lock.json b/package-lock.json index 1078561..51e7931 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "planka", - "version": "1.15.3", + "version": "1.15.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "planka", - "version": "1.15.3", + "version": "1.15.4", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index a767321..e76450c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "planka", - "version": "1.15.3", + "version": "1.15.4", "private": true, "homepage": "https://plankanban.github.io/planka", "repository": { From 0d39a7567f01583e1a2ff6e0fbaf7bf6d9e2473c Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Thu, 25 Jan 2024 23:01:59 +0100 Subject: [PATCH 43/83] feat: Add ability to map OIDC attributes and ignore username Closes #554 --- .../AccountPane/AccountPane.jsx | 110 ++++++++++-------- .../UserSettingsModal/UserSettingsModal.jsx | 3 + .../UsersModal/Item/ActionsStep.jsx | 12 +- .../src/components/UsersModal/Item/Item.jsx | 3 + .../src/components/UsersModal/UsersModal.jsx | 1 + .../containers/UserSettingsModalContainer.js | 2 + client/src/models/User.js | 1 + docker-compose.yml | 4 + server/.env.sample | 4 + .../api/controllers/users/update-username.js | 21 ++-- .../users/get-or-create-one-using-oidc.js | 18 ++- server/api/models/User.js | 1 + server/config/custom.js | 4 + 13 files changed, 112 insertions(+), 72 deletions(-) diff --git a/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx b/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx index 7333cf5..eed1326 100644 --- a/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx +++ b/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx @@ -24,6 +24,7 @@ const AccountPane = React.memo( organization, language, isLocked, + isUsernameLocked, isAvatarUpdating, usernameUpdateForm, emailUpdateForm, @@ -104,7 +105,7 @@ const AccountPane = React.memo( value={language || 'auto'} onChange={handleLanguageChange} /> - {!isLocked && ( + {(!isLocked || !isUsernameLocked) && ( <>
@@ -113,56 +114,62 @@ const AccountPane = React.memo( })}
-
- - - -
-
- - - -
-
- - - -
+ {!isUsernameLocked && ( +
+ + + +
+ )} + {!isLocked && ( + <> +
+ + + +
+
+ + + +
+ + )} )} @@ -179,6 +186,7 @@ AccountPane.propTypes = { organization: PropTypes.string, language: PropTypes.string, isLocked: PropTypes.bool.isRequired, + isUsernameLocked: PropTypes.bool.isRequired, isAvatarUpdating: PropTypes.bool.isRequired, /* eslint-disable react/forbid-prop-types */ usernameUpdateForm: PropTypes.object.isRequired, diff --git a/client/src/components/UserSettingsModal/UserSettingsModal.jsx b/client/src/components/UserSettingsModal/UserSettingsModal.jsx index fc8ac9c..8767ac4 100644 --- a/client/src/components/UserSettingsModal/UserSettingsModal.jsx +++ b/client/src/components/UserSettingsModal/UserSettingsModal.jsx @@ -18,6 +18,7 @@ const UserSettingsModal = React.memo( language, subscribeToOwnCards, isLocked, + isUsernameLocked, isAvatarUpdating, usernameUpdateForm, emailUpdateForm, @@ -50,6 +51,7 @@ const UserSettingsModal = React.memo( organization={organization} language={language} isLocked={isLocked} + isUsernameLocked={isUsernameLocked} isAvatarUpdating={isAvatarUpdating} usernameUpdateForm={usernameUpdateForm} emailUpdateForm={emailUpdateForm} @@ -108,6 +110,7 @@ UserSettingsModal.propTypes = { language: PropTypes.string, subscribeToOwnCards: PropTypes.bool.isRequired, isLocked: PropTypes.bool.isRequired, + isUsernameLocked: PropTypes.bool.isRequired, isAvatarUpdating: PropTypes.bool.isRequired, /* eslint-disable react/forbid-prop-types */ usernameUpdateForm: PropTypes.object.isRequired, diff --git a/client/src/components/UsersModal/Item/ActionsStep.jsx b/client/src/components/UsersModal/Item/ActionsStep.jsx index f8a2959..7b0eaed 100644 --- a/client/src/components/UsersModal/Item/ActionsStep.jsx +++ b/client/src/components/UsersModal/Item/ActionsStep.jsx @@ -136,13 +136,15 @@ const ActionsStep = React.memo( context: 'title', })}
+ {!user.isUsernameLocked && ( + + {t('action.editUsername', { + context: 'title', + })} + + )} {!user.isLocked && ( <> - - {t('action.editUsername', { - context: 'title', - })} - {t('action.editEmail', { context: 'title', diff --git a/client/src/components/UsersModal/Item/Item.jsx b/client/src/components/UsersModal/Item/Item.jsx index 80db356..ed68da3 100755 --- a/client/src/components/UsersModal/Item/Item.jsx +++ b/client/src/components/UsersModal/Item/Item.jsx @@ -19,6 +19,7 @@ const Item = React.memo( isAdmin, isLocked, isRoleLocked, + isUsernameLocked, isDeletionLocked, emailUpdateForm, passwordUpdateForm, @@ -61,6 +62,7 @@ const Item = React.memo( phone, isAdmin, isLocked, + isUsernameLocked, isDeletionLocked, emailUpdateForm, passwordUpdateForm, @@ -95,6 +97,7 @@ Item.propTypes = { isAdmin: PropTypes.bool.isRequired, isLocked: PropTypes.bool.isRequired, isRoleLocked: PropTypes.bool.isRequired, + isUsernameLocked: PropTypes.bool.isRequired, isDeletionLocked: PropTypes.bool.isRequired, /* eslint-disable react/forbid-prop-types */ emailUpdateForm: PropTypes.object.isRequired, diff --git a/client/src/components/UsersModal/UsersModal.jsx b/client/src/components/UsersModal/UsersModal.jsx index 0b55778..280cd08 100755 --- a/client/src/components/UsersModal/UsersModal.jsx +++ b/client/src/components/UsersModal/UsersModal.jsx @@ -112,6 +112,7 @@ const UsersModal = React.memo( isAdmin={item.isAdmin} isLocked={item.isLocked} isRoleLocked={item.isRoleLocked} + isUsernameLocked={item.isUsernameLocked} isDeletionLocked={item.isDeletionLocked} emailUpdateForm={item.emailUpdateForm} passwordUpdateForm={item.passwordUpdateForm} diff --git a/client/src/containers/UserSettingsModalContainer.js b/client/src/containers/UserSettingsModalContainer.js index 620632e..8c3b1c3 100644 --- a/client/src/containers/UserSettingsModalContainer.js +++ b/client/src/containers/UserSettingsModalContainer.js @@ -16,6 +16,7 @@ const mapStateToProps = (state) => { language, subscribeToOwnCards, isLocked, + isUsernameLocked, isAvatarUpdating, emailUpdateForm, passwordUpdateForm, @@ -32,6 +33,7 @@ const mapStateToProps = (state) => { language, subscribeToOwnCards, isLocked, + isUsernameLocked, isAvatarUpdating, emailUpdateForm, passwordUpdateForm, diff --git a/client/src/models/User.js b/client/src/models/User.js index 8455530..9bf8208 100755 --- a/client/src/models/User.js +++ b/client/src/models/User.js @@ -46,6 +46,7 @@ export default class extends BaseModel { isAdmin: attr(), isLocked: attr(), isRoleLocked: attr(), + isUsernameLocked: attr(), isDeletionLocked: attr(), deletedAt: attr(), createdAt: attr({ diff --git a/docker-compose.yml b/docker-compose.yml index 6fac280..3e9d023 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,7 +44,11 @@ services: # - OIDC_CLIENT_SECRET= # - OIDC_SCOPES=openid email profile # - OIDC_ADMIN_ROLES=admin + # - OIDC_EMAIL_ATTRIBUTE=email + # - OIDC_NAME_ATTRIBUTE=name + # - OIDC_USERNAME_ATTRIBUTE=preferred_username # - OIDC_ROLES_ATTRIBUTE=groups + # - OIDC_IGNORE_USERNAME=true # - OIDC_IGNORE_ROLES=true # - SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx diff --git a/server/.env.sample b/server/.env.sample index f06b647..31f2342 100644 --- a/server/.env.sample +++ b/server/.env.sample @@ -27,7 +27,11 @@ SECRET_KEY=notsecretkey # OIDC_CLIENT_SECRET= # OIDC_SCOPES=openid email profile # OIDC_ADMIN_ROLES=admin +# OIDC_EMAIL_ATTRIBUTE=email +# OIDC_NAME_ATTRIBUTE=name +# OIDC_USERNAME_ATTRIBUTE=preferred_username # OIDC_ROLES_ATTRIBUTE=groups +# OIDC_IGNORE_USERNAME=true # OIDC_IGNORE_ROLES=true ## Do not edit this diff --git a/server/api/controllers/users/update-username.js b/server/api/controllers/users/update-username.js index 5805946..b55529b 100644 --- a/server/api/controllers/users/update-username.js +++ b/server/api/controllers/users/update-username.js @@ -53,11 +53,7 @@ module.exports = { async fn(inputs) { const { currentUser } = this.req; - if (inputs.id === currentUser.id) { - if (!inputs.currentPassword) { - throw Errors.INVALID_CURRENT_PASSWORD; - } - } else if (!currentUser.isAdmin) { + if (inputs.id !== currentUser.id && !currentUser.isAdmin) { throw Errors.USER_NOT_FOUND; // Forbidden } @@ -67,15 +63,18 @@ module.exports = { throw Errors.USER_NOT_FOUND; } - if (user.email === sails.config.custom.defaultAdminEmail || user.isSso) { + if (user.email === sails.config.custom.defaultAdminEmail) { throw Errors.NOT_ENOUGH_RIGHTS; } - if ( - inputs.id === currentUser.id && - !bcrypt.compareSync(inputs.currentPassword, user.password) - ) { - throw Errors.INVALID_CURRENT_PASSWORD; + if (user.isSso) { + if (!sails.config.custom.oidcIgnoreUsername) { + throw Errors.NOT_ENOUGH_RIGHTS; + } + } else if (inputs.id === currentUser.id) { + if (!inputs.currentPassword || !bcrypt.compareSync(inputs.currentPassword, user.password)) { + throw Errors.INVALID_CURRENT_PASSWORD; + } } const values = _.pick(inputs, ['username']); diff --git a/server/api/helpers/users/get-or-create-one-using-oidc.js b/server/api/helpers/users/get-or-create-one-using-oidc.js index 6d1c49f..2186c09 100644 --- a/server/api/helpers/users/get-or-create-one-using-oidc.js +++ b/server/api/helpers/users/get-or-create-one-using-oidc.js @@ -38,7 +38,10 @@ module.exports = { throw 'invalidCodeOrNonce'; } - if (!userInfo.email || !userInfo.name) { + if ( + !userInfo[sails.config.custom.oidcEmailAttribute] || + !userInfo[sails.config.custom.oidcNameAttribute] + ) { throw 'missingValues'; } @@ -56,12 +59,14 @@ module.exports = { const values = { isAdmin, - email: userInfo.email, + email: userInfo[sails.config.custom.oidcEmailAttribute], isSso: true, - name: userInfo.name, - username: userInfo.preferred_username, + name: userInfo[sails.config.custom.oidcNameAttribute], subscribeToOwnCards: false, }; + if (!sails.config.custom.oidcIgnoreUsername) { + values.username = userInfo[sails.config.custom.oidcUsernameAttribute]; + } let user; // This whole block technically needs to be executed in a transaction @@ -95,7 +100,10 @@ module.exports = { }); } - const updateFieldKeys = ['email', 'isSso', 'name', 'username']; + const updateFieldKeys = ['email', 'isSso', 'name']; + if (!sails.config.custom.oidcIgnoreUsername) { + updateFieldKeys.push('username'); + } if (!sails.config.custom.oidcIgnoreRoles) { updateFieldKeys.push('isAdmin'); } diff --git a/server/api/models/User.js b/server/api/models/User.js index 9bf8a29..1d87531 100755 --- a/server/api/models/User.js +++ b/server/api/models/User.js @@ -116,6 +116,7 @@ module.exports = { ..._.omit(this, ['password', 'isSso', 'avatar', 'passwordChangedAt']), isLocked: this.isSso || isDefaultAdmin, isRoleLocked: (this.isSso && !sails.config.custom.oidcIgnoreRoles) || isDefaultAdmin, + isUsernameLocked: (this.isSso && !sails.config.custom.oidcIgnoreUsername) || isDefaultAdmin, isDeletionLocked: isDefaultAdmin, avatarUrl: this.avatar && diff --git a/server/config/custom.js b/server/config/custom.js index cbbc89b..afd60ec 100644 --- a/server/config/custom.js +++ b/server/config/custom.js @@ -38,7 +38,11 @@ module.exports.custom = { oidcClientSecret: process.env.OIDC_CLIENT_SECRET, oidcScopes: process.env.OIDC_SCOPES || 'openid email profile', oidcAdminRoles: process.env.OIDC_ADMIN_ROLES ? process.env.OIDC_ADMIN_ROLES.split(',') : [], + oidcEmailAttribute: process.env.OIDC_EMAIL_ATTRIBUTE || 'email', + oidcNameAttribute: process.env.OIDC_NAME_ATTRIBUTE || 'name', + oidcUsernameAttribute: process.env.OIDC_USERNAME_ATTRIBUTE || 'preferred_username', oidcRolesAttribute: process.env.OIDC_ROLES_ATTRIBUTE || 'groups', + oidcIgnoreUsername: process.env.OIDC_IGNORE_USERNAME === 'true', oidcIgnoreRoles: process.env.OIDC_IGNORE_ROLES === 'true', // TODO: move client base url to environment variable? From a1a1e9a86a20172146c65590f49c3b854b57051c Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Wed, 31 Jan 2024 15:58:57 +0100 Subject: [PATCH 44/83] chore: Update version --- charts/planka/Chart.yaml | 4 ++-- client/.env | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/planka/Chart.yaml b/charts/planka/Chart.yaml index 1c32bf7..cf4fe0c 100644 --- a/charts/planka/Chart.yaml +++ b/charts/planka/Chart.yaml @@ -15,13 +15,13 @@ 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.15 +version: 0.1.16 # 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 # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.15.4" +appVersion: "1.15.5" dependencies: - alias: postgresql diff --git a/client/.env b/client/.env index 4d4ab73..b54a394 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.15.4 +REACT_APP_VERSION=1.15.5 diff --git a/package-lock.json b/package-lock.json index 51e7931..ed45ee0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "planka", - "version": "1.15.4", + "version": "1.15.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "planka", - "version": "1.15.4", + "version": "1.15.5", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index e76450c..fb2621c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "planka", - "version": "1.15.4", + "version": "1.15.5", "private": true, "homepage": "https://plankanban.github.io/planka", "repository": { From 6c65d135f765fda339b5aa0f043975c7b60eabcb Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Thu, 1 Feb 2024 00:31:15 +0100 Subject: [PATCH 45/83] feat: Add ability to enforce SSO Closes #543, closes #545 --- client/src/components/Login/Login.jsx | 90 ++++++++++--------- .../src/components/UsersModal/UsersModal.jsx | 14 +-- client/src/containers/LoginContainer.js | 1 + client/src/containers/UsersModalContainer.js | 2 + docker-compose.yml | 1 + server/.env.sample | 1 + .../api/controllers/access-tokens/create.js | 5 +- server/api/controllers/show-config.js | 1 + server/api/controllers/users/create.js | 10 +++ server/config/custom.js | 1 + 10 files changed, 80 insertions(+), 46 deletions(-) diff --git a/client/src/components/Login/Login.jsx b/client/src/components/Login/Login.jsx index 84e6a9d..6f547ee 100755 --- a/client/src/components/Login/Login.jsx +++ b/client/src/components/Login/Login.jsx @@ -68,6 +68,7 @@ const Login = React.memo( isSubmittingUsingOidc, error, withOidc, + isOidcEnforced, onAuthenticate, onAuthenticateUsingOidc, onMessageDismiss, @@ -107,8 +108,10 @@ const Login = React.memo( }, [onAuthenticate, data]); useEffect(() => { - emailOrUsernameField.current.focus(); - }, []); + if (!isOidcEnforced) { + emailOrUsernameField.current.focus(); + } + }, [isOidcEnforced]); useEffect(() => { if (wasSubmitting && !isSubmitting && error) { @@ -159,51 +162,57 @@ const Login = React.memo( onDismiss={onMessageDismiss} /> )} -
-
-
{t('common.emailOrUsername')}
- -
-
-
{t('common.password')}
- +
+
{t('common.emailOrUsername')}
+ +
+
+
{t('common.password')}
+ +
+ -
- - + + )} {withOidc && ( + /> )} @@ -242,6 +251,7 @@ Login.propTypes = { isSubmittingUsingOidc: PropTypes.bool.isRequired, error: PropTypes.object, // eslint-disable-line react/forbid-prop-types withOidc: PropTypes.bool.isRequired, + isOidcEnforced: PropTypes.bool.isRequired, onAuthenticate: PropTypes.func.isRequired, onAuthenticateUsingOidc: PropTypes.func.isRequired, onMessageDismiss: PropTypes.func.isRequired, diff --git a/client/src/components/UsersModal/UsersModal.jsx b/client/src/components/UsersModal/UsersModal.jsx index 280cd08..68f0f82 100755 --- a/client/src/components/UsersModal/UsersModal.jsx +++ b/client/src/components/UsersModal/UsersModal.jsx @@ -10,6 +10,7 @@ import Item from './Item'; const UsersModal = React.memo( ({ items, + canAdd, onUpdate, onUsernameUpdate, onUsernameUpdateMessageDismiss, @@ -130,11 +131,13 @@ const UsersModal = React.memo( - - - + onUpdate: (data) => entryActions.updateCard(id, data), onMove: (listId, index) => entryActions.moveCard(id, listId, index), onTransfer: (boardId, listId) => entryActions.transferCard(id, boardId, listId), + onDuplicate: () => entryActions.duplicateCard(id), onDelete: () => entryActions.deleteCard(id), onUserAdd: (userId) => entryActions.addUserToCard(userId, id), onUserRemove: (userId) => entryActions.removeUserFromCard(userId, id), diff --git a/client/src/containers/CardModalContainer.js b/client/src/containers/CardModalContainer.js index ceae984..a8df6ea 100755 --- a/client/src/containers/CardModalContainer.js +++ b/client/src/containers/CardModalContainer.js @@ -78,6 +78,7 @@ const mapDispatchToProps = (dispatch) => onUpdate: entryActions.updateCurrentCard, onMove: entryActions.moveCurrentCard, onTransfer: entryActions.transferCurrentCard, + onDuplicate: entryActions.duplicateCurrentCard, onDelete: entryActions.deleteCurrentCard, onUserAdd: entryActions.addUserToCurrentCard, onUserRemove: entryActions.removeUserFromCurrentCard, diff --git a/client/src/entry-actions/cards.js b/client/src/entry-actions/cards.js index d73b247..346fc8d 100755 --- a/client/src/entry-actions/cards.js +++ b/client/src/entry-actions/cards.js @@ -74,6 +74,18 @@ const transferCurrentCard = (boardId, listId, index = 0) => ({ }, }); +const duplicateCard = (id) => ({ + type: EntryActionTypes.CARD_DUPLICATE, + payload: { + id, + }, +}); + +const duplicateCurrentCard = () => ({ + type: EntryActionTypes.CURRENT_CARD_DUPLICATE, + payload: {}, +}); + const deleteCard = (id) => ({ type: EntryActionTypes.CARD_DELETE, payload: { @@ -103,6 +115,8 @@ export default { moveCurrentCard, transferCard, transferCurrentCard, + duplicateCard, + duplicateCurrentCard, deleteCard, deleteCurrentCard, handleCardDelete, diff --git a/client/src/locales/en/core.js b/client/src/locales/en/core.js index 83b0116..41e4494 100644 --- a/client/src/locales/en/core.js +++ b/client/src/locales/en/core.js @@ -51,6 +51,7 @@ export default { cardNotFound_title: 'Card Not Found', cardOrActionAreDeleted: 'Card or action are deleted.', color: 'Color', + copy_inline: 'copy', createBoard_title: 'Create Board', createLabel_title: 'Create Label', createNewOneOrSelectExistingOne: 'Create a new one or select
an existing one.', @@ -196,6 +197,8 @@ export default { deleteTask: 'Delete task', deleteTask_title: 'Delete Task', deleteUser: 'Delete user', + duplicate: 'Duplicate', + duplicateCard_title: 'Duplicate Card', edit: 'Edit', editDueDate_title: 'Edit Due Date', editDescription_title: 'Edit Description', diff --git a/client/src/locales/fr/core.js b/client/src/locales/fr/core.js index e20e291..1b9296f 100644 --- a/client/src/locales/fr/core.js +++ b/client/src/locales/fr/core.js @@ -169,6 +169,7 @@ export default { deleteTask: 'Supprimer la tâche', deleteTask_title: 'Supprimer la tâche', deleteUser: "Supprimer l'utilisateur", + duplicate: 'Dupliquer', edit: 'Modifier', editDueDate_title: "Modifier la date d'échéance", editDescription_title: 'Éditer la description', diff --git a/client/src/models/Card.js b/client/src/models/Card.js index 78c682c..3c45256 100755 --- a/client/src/models/Card.js +++ b/client/src/models/Card.js @@ -1,3 +1,4 @@ +import pick from 'lodash/pick'; import { attr, fk, many, oneToOne } from 'redux-orm'; import BaseModel from './BaseModel'; @@ -165,7 +166,6 @@ export default class extends BaseModel { break; case ActionTypes.CARD_CREATE: - case ActionTypes.CARD_CREATE_HANDLE: case ActionTypes.CARD_UPDATE__SUCCESS: case ActionTypes.CARD_UPDATE_HANDLE: Card.upsert(payload.card); @@ -176,10 +176,63 @@ export default class extends BaseModel { Card.upsert(payload.card); break; + case ActionTypes.CARD_CREATE_HANDLE: { + const cardModel = Card.upsert(payload.card); + + payload.cardMemberships.forEach(({ userId }) => { + cardModel.users.add(userId); + }); + + payload.cardLabels.forEach(({ labelId }) => { + cardModel.labels.add(labelId); + }); + + break; + } case ActionTypes.CARD_UPDATE: Card.withId(payload.id).update(payload.data); break; + case ActionTypes.CARD_DUPLICATE: { + const cardModel = Card.withId(payload.id); + + const nextCardModel = Card.upsert({ + ...pick(cardModel.ref, [ + 'boardId', + 'listId', + 'position', + 'name', + 'description', + 'dueDate', + 'stopwatch', + ]), + ...payload.card, + }); + + cardModel.users.toRefArray().forEach(({ id }) => { + nextCardModel.users.add(id); + }); + + cardModel.labels.toRefArray().forEach(({ id }) => { + nextCardModel.labels.add(id); + }); + + break; + } + case ActionTypes.CARD_DUPLICATE__SUCCESS: { + Card.withId(payload.localId).deleteWithRelated(); + const cardModel = Card.upsert(payload.card); + + payload.cardMemberships.forEach(({ userId }) => { + cardModel.users.add(userId); + }); + + payload.cardLabels.forEach(({ labelId }) => { + cardModel.labels.add(labelId); + }); + + break; + } case ActionTypes.CARD_DELETE: Card.withId(payload.id).deleteWithRelated(); diff --git a/client/src/models/Task.js b/client/src/models/Task.js index 1a47271..b990fe2 100755 --- a/client/src/models/Task.js +++ b/client/src/models/Task.js @@ -1,5 +1,6 @@ import { attr, fk } from 'redux-orm'; +import { createLocalId } from '../utils/local-id'; import BaseModel from './BaseModel'; import ActionTypes from '../constants/ActionTypes'; @@ -44,10 +45,24 @@ export default class extends BaseModel { break; case ActionTypes.BOARD_FETCH__SUCCESS: + case ActionTypes.CARD_CREATE_HANDLE: + case ActionTypes.CARD_DUPLICATE__SUCCESS: payload.tasks.forEach((task) => { Task.upsert(task); }); + break; + case ActionTypes.CARD_DUPLICATE: + payload.taskIds.forEach((taskId, index) => { + const taskModel = Task.withId(taskId); + + Task.upsert({ + ...taskModel.ref, + id: `${createLocalId()}-${index}`, // TODO: hack? + cardId: payload.card.id, + }); + }); + break; case ActionTypes.TASK_CREATE: case ActionTypes.TASK_CREATE_HANDLE: diff --git a/client/src/sagas/core/services/cards.js b/client/src/sagas/core/services/cards.js index 40c3db8..eb2d1da 100644 --- a/client/src/sagas/core/services/cards.js +++ b/client/src/sagas/core/services/cards.js @@ -5,6 +5,7 @@ import request from '../request'; import selectors from '../../../selectors'; import actions from '../../../actions'; import api from '../../../api'; +import i18n from '../../../i18n'; import { createLocalId } from '../../../utils/local-id'; export function* createCard(listId, data, autoOpen) { @@ -41,8 +42,23 @@ export function* createCard(listId, data, autoOpen) { } } -export function* handleCardCreate(card) { - yield put(actions.handleCardCreate(card)); +export function* handleCardCreate({ id }) { + let card; + let cardMemberships; + let cardLabels; + let tasks; + let attachments; + + try { + ({ + item: card, + included: { cardMemberships, cardLabels, tasks, attachments }, + } = yield call(request, api.getCard, id)); + } catch (error) { + return; + } + + yield put(actions.handleCardCreate(card, cardMemberships, cardLabels, tasks, attachments)); } export function* updateCard(id, data) { @@ -106,6 +122,55 @@ export function* transferCurrentCard(boardId, listId, index) { yield call(transferCard, cardId, boardId, listId, index); } +export function* duplicateCard(id) { + const { listId, name } = yield select(selectors.selectCardById, id); + const index = yield select(selectors.selectCardIndexById, id); + + const nextData = { + position: yield select(selectors.selectNextCardPosition, listId, index + 1), + name: `${name} (${i18n.t('common.copy', { + context: 'inline', + })})`, + }; + + const localId = yield call(createLocalId); + const taskIds = yield select(selectors.selectTaskIdsByCardId, id); + + yield put( + actions.duplicateCard( + id, + { + ...nextData, + id: localId, + }, + taskIds, + ), + ); + + let card; + let cardMemberships; + let cardLabels; + let tasks; + + try { + ({ + item: card, + included: { cardMemberships, cardLabels, tasks }, + } = yield call(request, api.duplicateCard, id, nextData)); + } catch (error) { + yield put(actions.duplicateCard.failure(localId, error)); + return; + } + + yield put(actions.duplicateCard.success(localId, card, cardMemberships, cardLabels, tasks)); +} + +export function* duplicateCurrentCard() { + const { cardId } = yield select(selectors.selectPath); + + yield call(duplicateCard, cardId); +} + export function* deleteCard(id) { const { cardId, boardId } = yield select(selectors.selectPath); @@ -147,11 +212,13 @@ export default { handleCardCreate, updateCard, updateCurrentCard, + handleCardUpdate, moveCard, moveCurrentCard, transferCard, transferCurrentCard, - handleCardUpdate, + duplicateCard, + duplicateCurrentCard, deleteCard, deleteCurrentCard, handleCardDelete, diff --git a/client/src/sagas/core/watchers/cards.js b/client/src/sagas/core/watchers/cards.js index 0290515..3fcb599 100644 --- a/client/src/sagas/core/watchers/cards.js +++ b/client/src/sagas/core/watchers/cards.js @@ -32,6 +32,8 @@ export default function* cardsWatchers() { takeEvery(EntryActionTypes.CURRENT_CARD_TRANSFER, ({ payload: { boardId, listId, index } }) => services.transferCurrentCard(boardId, listId, index), ), + takeEvery(EntryActionTypes.CARD_DUPLICATE, ({ payload: { id } }) => services.duplicateCard(id)), + takeEvery(EntryActionTypes.CURRENT_CARD_DUPLICATE, () => services.duplicateCurrentCard()), takeEvery(EntryActionTypes.CARD_DELETE, ({ payload: { id } }) => services.deleteCard(id)), takeEvery(EntryActionTypes.CURRENT_CARD_DELETE, () => services.deleteCurrentCard()), takeEvery(EntryActionTypes.CARD_DELETE_HANDLE, ({ payload: { card } }) => diff --git a/client/src/selectors/cards.js b/client/src/selectors/cards.js index 8f100d5..d1137dc 100644 --- a/client/src/selectors/cards.js +++ b/client/src/selectors/cards.js @@ -26,6 +26,24 @@ export const makeSelectCardById = () => export const selectCardById = makeSelectCardById(); +export const makeSelectCardIndexById = () => + createSelector( + orm, + (_, id) => id, + ({ Card }, id) => { + const cardModel = Card.withId(id); + + if (!cardModel) { + return cardModel; + } + + const cardModels = cardModel.list.getFilteredOrderedCardsModelArray(); + return cardModels.findIndex((cardModelItem) => cardModelItem.id === cardModel.id); + }, + ); + +export const selectCardIndexById = makeSelectCardIndexById(); + export const makeSelectUsersByCardId = () => createSelector( orm, @@ -60,6 +78,26 @@ export const makeSelectLabelsByCardId = () => export const selectLabelsByCardId = makeSelectLabelsByCardId(); +export const makeSelectTaskIdsByCardId = () => + createSelector( + orm, + (_, id) => id, + ({ Card }, id) => { + const cardModel = Card.withId(id); + + if (!cardModel) { + return cardModel; + } + + return cardModel + .getOrderedTasksQuerySet() + .toRefArray() + .map((task) => task.id); + }, + ); + +export const selectTaskIdsByCardId = makeSelectTaskIdsByCardId(); + export const makeSelectTasksByCardId = () => createSelector( orm, @@ -286,10 +324,14 @@ export const selectNotificationIdsForCurrentCard = createSelector( export default { makeSelectCardById, selectCardById, + makeSelectCardIndexById, + selectCardIndexById, makeSelectUsersByCardId, selectUsersByCardId, makeSelectLabelsByCardId, selectLabelsByCardId, + makeSelectTaskIdsByCardId, + selectTaskIdsByCardId, makeSelectTasksByCardId, selectTasksByCardId, makeSelectLastActivityIdByCardId, diff --git a/server/api/controllers/cards/duplicate.js b/server/api/controllers/cards/duplicate.js new file mode 100755 index 0000000..918a8ec --- /dev/null +++ b/server/api/controllers/cards/duplicate.js @@ -0,0 +1,82 @@ +const Errors = { + NOT_ENOUGH_RIGHTS: { + notEnoughRights: 'Not enough rights', + }, + CARD_NOT_FOUND: { + cardNotFound: 'Card not found', + }, +}; + +module.exports = { + inputs: { + id: { + type: 'string', + regex: /^[0-9]+$/, + required: true, + }, + position: { + type: 'number', + required: true, + }, + name: { + type: 'string', + }, + }, + + exits: { + notEnoughRights: { + responseType: 'forbidden', + }, + cardNotFound: { + responseType: 'notFound', + }, + }, + + async fn(inputs) { + const { currentUser } = this.req; + + const { card, list, board } = await sails.helpers.cards + .getProjectPath(inputs.id) + .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); + + const boardMembership = await BoardMembership.findOne({ + boardId: card.boardId, + userId: currentUser.id, + }); + + if (!boardMembership) { + throw Errors.CARD_NOT_FOUND; // Forbidden + } + + if (boardMembership.role !== BoardMembership.Roles.EDITOR) { + throw Errors.NOT_ENOUGH_RIGHTS; + } + + const values = _.pick(inputs, ['position', 'name']); + + const { + card: nextCard, + cardMemberships, + cardLabels, + tasks, + } = await sails.helpers.cards.duplicateOne.with({ + board, + list, + record: card, + values: { + ...values, + creatorUser: currentUser, + }, + request: this.req, + }); + + return { + item: nextCard, + included: { + cardMemberships, + cardLabels, + tasks, + }, + }; + }, +}; diff --git a/server/api/helpers/boards/import-from-trello.js b/server/api/helpers/boards/import-from-trello.js index efc74dc..2ab6e0a 100644 --- a/server/api/helpers/boards/import-from-trello.js +++ b/server/api/helpers/boards/import-from-trello.js @@ -1,3 +1,5 @@ +const POSITION_GAP = 65535; // TODO: move to config + module.exports = { inputs: { user: { @@ -124,7 +126,7 @@ module.exports = { getUsedTrelloLabels().map(async (trelloLabel, index) => { const plankaLabel = await Label.create({ boardId: inputs.board.id, - position: 65535 * (index + 1), // TODO: move to config + position: POSITION_GAP * (index + 1), name: trelloLabel.name || null, color: getPlankaLabelColor(trelloLabel.color), }).fetch(); diff --git a/server/api/helpers/cards/duplicate-one.js b/server/api/helpers/cards/duplicate-one.js new file mode 100644 index 0000000..a63feaa --- /dev/null +++ b/server/api/helpers/cards/duplicate-one.js @@ -0,0 +1,144 @@ +const valuesValidator = (value) => { + if (!_.isPlainObject(value)) { + return false; + } + + if (!_.isUndefined(value.position) && !_.isFinite(value.position)) { + return false; + } + + if (!_.isPlainObject(value.creatorUser)) { + return false; + } + + return true; +}; + +module.exports = { + inputs: { + record: { + type: 'ref', + required: true, + }, + values: { + type: 'ref', + custom: valuesValidator, + required: true, + }, + board: { + type: 'ref', + required: true, + }, + list: { + type: 'ref', + required: true, + }, + request: { + type: 'ref', + }, + }, + + async fn(inputs) { + const { values } = inputs; + + const cards = await sails.helpers.lists.getCards(inputs.record.listId); + + const { position, repositions } = sails.helpers.utils.insertToPositionables( + values.position, + cards, + ); + + repositions.forEach(async ({ id, position: nextPosition }) => { + await Card.update({ + id, + listId: inputs.record.listId, + }).set({ + position: nextPosition, + }); + + sails.sockets.broadcast(`board:${inputs.record.boardId}`, 'cardUpdate', { + item: { + id, + position: nextPosition, + }, + }); + }); + + const card = await Card.create({ + ..._.pick(inputs.record, [ + 'boardId', + 'listId', + 'name', + 'description', + 'dueDate', + 'stopwatch', + ]), + ...values, + position, + creatorUserId: values.creatorUser.id, + }).fetch(); + + const cardMemberships = await sails.helpers.cards.getCardMemberships(inputs.record.id); + const cardMembershipsValues = cardMemberships.map((cardMembership) => ({ + ..._.pick(cardMembership, ['userId']), + cardId: card.id, + })); + const nextCardMemberships = await CardMembership.createEach(cardMembershipsValues).fetch(); + + const cardLabels = await sails.helpers.cards.getCardLabels(inputs.record.id); + const cardLabelsValues = cardLabels.map((cardLabel) => ({ + ..._.pick(cardLabel, ['labelId']), + cardId: card.id, + })); + const nextCardLabels = await CardLabel.createEach(cardLabelsValues).fetch(); + + const tasks = await sails.helpers.cards.getTasks(inputs.record.id); + const tasksValues = tasks.map((task) => ({ + ..._.pick(task, ['position', 'name', 'isCompleted']), + cardId: card.id, + })); + const nextTasks = await Task.createEach(tasksValues).fetch(); + + sails.sockets.broadcast( + `board:${card.boardId}`, + 'cardCreate', + { + item: card, + }, + inputs.request, + ); + + if (values.creatorUser.subscribeToOwnCards) { + await CardSubscription.create({ + cardId: card.id, + userId: card.creatorUserId, + }).tolerate('E_UNIQUE'); + + sails.sockets.broadcast(`user:${card.creatorUserId}`, 'cardUpdate', { + item: { + id: card.id, + isSubscribed: true, + }, + }); + } + + await sails.helpers.actions.createOne.with({ + values: { + card, + type: Action.Types.CREATE_CARD, // TODO: introduce separate type? + data: { + list: _.pick(inputs.list, ['id', 'name']), + }, + user: values.creatorUser, + }, + board: inputs.board, + }); + + return { + card, + cardMemberships: nextCardMemberships, + cardLabels: nextCardLabels, + tasks: nextTasks, + }; + }, +}; diff --git a/server/config/routes.js b/server/config/routes.js index 77f5c43..8b88df5 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -55,6 +55,7 @@ module.exports.routes = { 'POST /api/lists/:listId/cards': 'cards/create', 'GET /api/cards/:id': 'cards/show', 'PATCH /api/cards/:id': 'cards/update', + 'POST /api/cards/:id/duplicate': 'cards/duplicate', 'DELETE /api/cards/:id': 'cards/delete', 'POST /api/cards/:cardId/memberships': 'card-memberships/create', 'DELETE /api/cards/:cardId/memberships': 'card-memberships/delete', From 6712625c3f8a2f022bc65f1049d86809689b87cf Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Fri, 5 Apr 2024 22:49:02 +0200 Subject: [PATCH 70/83] chore: Update version --- charts/planka/Chart.yaml | 4 ++-- client/.env | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/planka/Chart.yaml b/charts/planka/Chart.yaml index 3f291a9..5a6debe 100644 --- a/charts/planka/Chart.yaml +++ b/charts/planka/Chart.yaml @@ -15,13 +15,13 @@ 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.22 +version: 0.1.23 # 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 # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.16.1" +appVersion: "1.16.2" dependencies: - alias: postgresql diff --git a/client/.env b/client/.env index 0718c00..e17aeb7 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.16.1 +REACT_APP_VERSION=1.16.2 diff --git a/package-lock.json b/package-lock.json index 1bd8b53..eee91c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "planka", - "version": "1.16.1", + "version": "1.16.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "planka", - "version": "1.16.1", + "version": "1.16.2", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 8e047d5..ab2bc3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "planka", - "version": "1.16.1", + "version": "1.16.2", "private": true, "homepage": "https://plankanban.github.io/planka", "repository": { From 16e034efcbf6887716e302f4d086b1db45b0c5b1 Mon Sep 17 00:00:00 2001 From: leroyloren <57643470+leroyloren@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:58:27 +0200 Subject: [PATCH 71/83] fix: Update Czech translation (#679) --- client/src/locales/cs/core.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/client/src/locales/cs/core.js b/client/src/locales/cs/core.js index 04dfc8d..4001513 100644 --- a/client/src/locales/cs/core.js +++ b/client/src/locales/cs/core.js @@ -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
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', }, }, From b97667382679bbd02166f1531965d11c0875de58 Mon Sep 17 00:00:00 2001 From: Blyamur <409434+blyamur@users.noreply.github.com> Date: Sun, 7 Apr 2024 23:05:22 +0300 Subject: [PATCH 72/83] fix: Update Russian translation (#686) --- client/src/locales/ru/core.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/locales/ru/core.js b/client/src/locales/ru/core.js index 138b893..06520c6 100644 --- a/client/src/locales/ru/core.js +++ b/client/src/locales/ru/core.js @@ -199,6 +199,8 @@ export default { deleteProject: 'Удалить проект', deleteTask: 'Удалить задачу', deleteUser: 'Удалить пользователя', + duplicate: 'Дублировать', + duplicateCard_title: 'Дублировать карточку', edit: 'Изменить', editBackground: 'Изменить фон', editDueDate: 'Изменить срок', From 2990ea593aae12643ecd35205c69eef8f41e16a9 Mon Sep 17 00:00:00 2001 From: Matthieu Bollot Date: Mon, 8 Apr 2024 00:33:29 +0200 Subject: [PATCH 73/83] feat: Slack bot notifications (#676) --- docker-compose.yml | 20 +++---- 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, 128 insertions(+), 26 deletions(-) create mode 100644 server/api/helpers/utils/send-slack-message.js diff --git a/docker-compose.yml b/docker-compose.yml index 0a294fb..f7935aa 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= @@ -52,8 +44,16 @@ services: # - OIDC_IGNORE_ROLES=true # - OIDC_ENFORCED=true - # - SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx - # - SLACK_CHANNEL_ID=xxxxxxxxxx + # 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 fb5a828..15d66ed 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 84d188d..51d14a4 100755 --- a/server/api/controllers/cards/delete.js +++ b/server/api/controllers/cards/delete.js @@ -49,6 +49,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 78d5e0c..b03adfd 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 fb72787..a947f73 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 271a917..f24e4bf 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 5d9be72..7b8d08b 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 0000000..573fcf8 --- /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 7647324..ac344d0 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, }; From 4df2becd6f7608981253a1328a2ab9194a21a74f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Pedrini?= Date: Mon, 8 Apr 2024 00:50:52 +0200 Subject: [PATCH 74/83] fix: Update Italian translation (#688) --- client/src/locales/it/core.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/client/src/locales/it/core.js b/client/src/locales/it/core.js index f08a946..1f605b1 100644 --- a/client/src/locales/it/core.js +++ b/client/src/locales/it/core.js @@ -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
after connection restored.', + 'Tutte le modifiche verranno salvate
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
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', From b46fb43e6fe69c860849eb5688de238d88171358 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Mon, 8 Apr 2024 01:47:24 +0200 Subject: [PATCH 75/83] chore: Cleanup --- package.json | 1 - server/api/controllers/cards/create.js | 7 --- server/api/controllers/cards/delete.js | 7 --- server/api/controllers/cards/update.js | 14 ----- .../api/controllers/comment-actions/create.js | 8 --- server/api/services/slack.js | 58 ------------------- 6 files changed, 95 deletions(-) delete mode 100644 server/api/services/slack.js diff --git a/package.json b/package.json index ab2bc3d..7e06e77 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ } }, "dependencies": { - "axios": "^1.6.2", "concurrently": "^8.2.2", "husky": "^8.0.3", "lint-staged": "^15.1.0" diff --git a/server/api/controllers/cards/create.js b/server/api/controllers/cards/create.js index 23ee0d9..b847c3e 100755 --- a/server/api/controllers/cards/create.js +++ b/server/api/controllers/cards/create.js @@ -1,5 +1,4 @@ const moment = require('moment'); -const services = require('../../services/slack'); const Errors = { NOT_ENOUGH_RIGHTS: { @@ -110,12 +109,6 @@ module.exports = { }) .intercept('positionMustBeInValues', () => Errors.POSITION_MUST_BE_PRESENT); - const cardUrl = services.buildCardUrl(card); - const messageText = cardUrl + ' was created by ' + currentUser.name + ' in *' + list.name + '*'; - services.sendSlackMessage(messageText) - .then(() => { console.log('Slack message sent successfully.'); }) - .catch((error) => { console.error('Failed to send Slack message:', error.message); }); - return { item: card, }; diff --git a/server/api/controllers/cards/delete.js b/server/api/controllers/cards/delete.js index 51d14a4..dad82de 100755 --- a/server/api/controllers/cards/delete.js +++ b/server/api/controllers/cards/delete.js @@ -1,5 +1,3 @@ -const services = require('../../services/slack'); - const Errors = { NOT_ENOUGH_RIGHTS: { notEnoughRights: 'Not enough rights', @@ -57,11 +55,6 @@ module.exports = { throw Errors.CARD_NOT_FOUND; } - const messageText = '*' + card.name + '* was deleted by ' + currentUser.name; - services.sendSlackMessage(messageText) - .then(() => { console.log('Slack message sent successfully.'); }) - .catch((error) => { console.error('Failed to send Slack message:', error.message); }); - return { item: card, }; diff --git a/server/api/controllers/cards/update.js b/server/api/controllers/cards/update.js index 2c0a2c2..8ee3752 100755 --- a/server/api/controllers/cards/update.js +++ b/server/api/controllers/cards/update.js @@ -1,5 +1,4 @@ const moment = require('moment'); -const services = require('../../services/slack'); const Errors = { NOT_ENOUGH_RIGHTS: { @@ -176,8 +175,6 @@ module.exports = { 'isSubscribed', ]); - const cardPositionBefore = card.position; - card = await sails.helpers.cards.updateOne .with({ board, @@ -198,17 +195,6 @@ module.exports = { throw Errors.CARD_NOT_FOUND; } - const cardPositionAfter = card.position; - const cardMoved = cardPositionBefore !== cardPositionAfter; - - if (cardMoved) { - const cardUrl = services.buildCardUrl(card); - const messageText = cardUrl + ' was moved by ' + currentUser.name + ' to *' + nextList.name + '*'; - services.sendSlackMessage(messageText) - .then(() => { console.log('Slack message sent successfully.'); }) - .catch((error) => { console.error('Failed to send Slack message:', error.message); }); - } - return { item: card, }; diff --git a/server/api/controllers/comment-actions/create.js b/server/api/controllers/comment-actions/create.js index 30463fd..7712a3a 100755 --- a/server/api/controllers/comment-actions/create.js +++ b/server/api/controllers/comment-actions/create.js @@ -1,5 +1,3 @@ -const services = require('../../services/slack'); - const Errors = { NOT_ENOUGH_RIGHTS: { notEnoughRights: 'Not enough rights', @@ -66,12 +64,6 @@ module.exports = { request: this.req, }); - const cardUrl = services.buildCardUrl(card); - const messageText = '*' + currentUser.name + '* commented on ' + cardUrl + ':\n>' + inputs.text; - services.sendSlackMessage(messageText) - .then(() => { console.log('Slack message sent successfully.'); }) - .catch((error) => { console.error('Failed to send Slack message:', error.message); }); - return { item: action, }; diff --git a/server/api/services/slack.js b/server/api/services/slack.js deleted file mode 100644 index c06aa5f..0000000 --- a/server/api/services/slack.js +++ /dev/null @@ -1,58 +0,0 @@ -const axios = require('axios'); -const slackPostUrl = 'https://slack.com/api/chat.postMessage'; -const channelId = process.env.SLACK_CHANNEL_ID; -const slackAPIToken = process.env.SLACK_BOT_TOKEN; -const plankaProdUrl = process.env.BASE_URL; - -async function sendSlackMessage(messageText) { - if (!slackAPIToken) { - throw new Error('No Slack BOT token found'); - } - - console.log('Sending to Slack'); - - const postData = { - blocks: [ { - type: 'section', - text: { - type: 'mrkdwn', - text: messageText, - }, - }] - }; - - try { - const config = { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${slackAPIToken}`, - }, - }; - - axios.post(slackPostUrl, { ...postData, channel: channelId }, config) - .then(response => { - console.log('Slack response:', response.data); - }) - .catch(error => { - console.error('Error sending to Slack:', error.message); - }); - - console.log('Slack response:', response.data); - return response.data; - } catch (error) { - console.error('Error sending to Slack:', error.message); - throw error; - } - } - - function buildCardUrl(card) { - const url = plankaProdUrl + '/cards/' + card.id; - const cardUrl = '<' + url + '|' + card.name + '>'; - console.log(cardUrl); - return cardUrl; - } - - module.exports = { - sendSlackMessage, - buildCardUrl - }; From 4e2863faa7e4fe50155b0a01d668b5e2141de1e9 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Tue, 9 Apr 2024 15:12:46 +0200 Subject: [PATCH 76/83] feat: Automatic logout when session expires Closes #693 --- client/src/actions/core.js | 6 ++- client/src/entry-actions/cards.js | 8 ++-- client/src/entry-actions/core.js | 6 ++- client/src/sagas/core/request.js | 6 +-- client/src/sagas/core/services/cards.js | 4 +- client/src/sagas/core/services/core.js | 5 +-- client/src/sagas/core/services/router.js | 9 +++++ client/src/sagas/core/services/users.js | 1 + client/src/sagas/core/watchers/core.js | 6 ++- client/src/sagas/core/watchers/socket.js | 8 ++++ .../api/controllers/access-tokens/delete.js | 4 ++ server/api/hooks/current-user/index.js | 1 + server/api/hooks/oidc/index.js | 33 ++++++++++------ server/api/hooks/smtp/index.js | 37 +++++++++++------- server/api/hooks/watcher/index.js | 38 +++++++++++++++++++ 15 files changed, 130 insertions(+), 42 deletions(-) create mode 100644 server/api/hooks/watcher/index.js diff --git a/client/src/actions/core.js b/client/src/actions/core.js index 2d58587..92f081a 100644 --- a/client/src/actions/core.js +++ b/client/src/actions/core.js @@ -47,9 +47,11 @@ initializeCore.fetchConfig = (config) => ({ }, }); -const logout = () => ({ +const logout = (invalidateAccessToken) => ({ type: ActionTypes.LOGOUT, - payload: {}, + payload: { + invalidateAccessToken, + }, }); logout.invalidateAccessToken = () => ({ diff --git a/client/src/entry-actions/cards.js b/client/src/entry-actions/cards.js index 346fc8d..d3dd849 100755 --- a/client/src/entry-actions/cards.js +++ b/client/src/entry-actions/cards.js @@ -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, diff --git a/client/src/entry-actions/core.js b/client/src/entry-actions/core.js index c1e03ff..b5a3002 100644 --- a/client/src/entry-actions/core.js +++ b/client/src/entry-actions/core.js @@ -1,8 +1,10 @@ import EntryActionTypes from '../constants/EntryActionTypes'; -const logout = () => ({ +const logout = (invalidateAccessToken) => ({ type: EntryActionTypes.LOGOUT, - payload: {}, + payload: { + invalidateAccessToken, + }, }); export default { diff --git a/client/src/sagas/core/request.js b/client/src/sagas/core/request.js index 2cfd994..f905c7a 100755 --- a/client/src/sagas/core/request.js +++ b/client/src/sagas/core/request.js @@ -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(); } diff --git a/client/src/sagas/core/services/cards.js b/client/src/sagas/core/services/cards.js index eb2d1da..7232c3f 100644 --- a/client/src/sagas/core/services/cards.js +++ b/client/src/sagas/core/services/cards.js @@ -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); diff --git a/client/src/sagas/core/services/core.js b/client/src/sagas/core/services/core.js index cd69413..9296c1c 100644 --- a/client/src/sagas/core/services/core.js +++ b/client/src/sagas/core/services/core.js @@ -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 { diff --git a/client/src/sagas/core/services/router.js b/client/src/sagas/core/services/router.js index 7b981e4..3f6ab57 100644 --- a/client/src/sagas/core/services/router.js +++ b/client/src/sagas/core/services/router.js @@ -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) { diff --git a/client/src/sagas/core/services/users.js b/client/src/sagas/core/services/users.js index 8b6b865..2f6008c 100644 --- a/client/src/sagas/core/services/users.js +++ b/client/src/sagas/core/services/users.js @@ -218,6 +218,7 @@ export function* handleUserDelete(user) { if (user.id === currentUserId) { yield call(logout, false); + return; } yield put(actions.handleUserDelete(user)); diff --git a/client/src/sagas/core/watchers/core.js b/client/src/sagas/core/watchers/core.js index cab4704..dd58668 100644 --- a/client/src/sagas/core/watchers/core.js +++ b/client/src/sagas/core/watchers/core.js @@ -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), + ), + ]); } diff --git a/client/src/sagas/core/watchers/socket.js b/client/src/sagas/core/watchers/socket.js index 169de93..ddb65cf 100644 --- a/client/src/sagas/core/watchers/socket.js +++ b/client/src/sagas/core/watchers/socket.js @@ -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); diff --git a/server/api/controllers/access-tokens/delete.js b/server/api/controllers/access-tokens/delete.js index e3889e2..b252a0c 100644 --- a/server/api/controllers/access-tokens/delete.js +++ b/server/api/controllers/access-tokens/delete.js @@ -9,6 +9,10 @@ module.exports = { deletedAt: new Date().toISOString(), }); + if (this.req.isSocket) { + sails.sockets.leaveAll(`@accessToken:${accessToken}`); + } + return { item: accessToken, }; diff --git a/server/api/hooks/current-user/index.js b/server/api/hooks/current-user/index.js index dd29595..1d3214f 100644 --- a/server/api/hooks/current-user/index.js +++ b/server/api/hooks/current-user/index.js @@ -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}`); } } diff --git a/server/api/hooks/oidc/index.js b/server/api/hooks/oidc/index.js index 05d9d0b..6dbeddd 100644 --- a/server/api/hooks/oidc/index.js +++ b/server/api/hooks/oidc/index.js @@ -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() { diff --git a/server/api/hooks/smtp/index.js b/server/api/hooks/smtp/index.js index 4cd0503..d6ce84f 100644 --- a/server/api/hooks/smtp/index.js +++ b/server/api/hooks/smtp/index.js @@ -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() { diff --git a/server/api/hooks/watcher/index.js b/server/api/hooks/watcher/index.js new file mode 100644 index 0000000..835b1e8 --- /dev/null +++ b/server/api/hooks/watcher/index.js @@ -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); + }, + }; +}; From ea1b3b7f923484f225636346ad1039465daf45ff Mon Sep 17 00:00:00 2001 From: Stan M <5880433+stxm@users.noreply.github.com> Date: Wed, 10 Apr 2024 12:56:21 +0200 Subject: [PATCH 77/83] feat: Ability to run with read-only root filesystem in Helm (#695) --- charts/planka/Chart.yaml | 2 +- charts/planka/templates/deployment.yaml | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/charts/planka/Chart.yaml b/charts/planka/Chart.yaml index 5a6debe..470f805 100644 --- a/charts/planka/Chart.yaml +++ b/charts/planka/Chart.yaml @@ -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 diff --git a/charts/planka/templates/deployment.yaml b/charts/planka/templates/deployment.yaml index 2d0d35b..2ffe596 100644 --- a/charts/planka/templates/deployment.yaml +++ b/charts/planka/templates/deployment.yaml @@ -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 }} From cc32daa009c6bbd33db8c238cbf1f0f7e4a735cc Mon Sep 17 00:00:00 2001 From: HannesOberreiter Date: Wed, 10 Apr 2024 15:53:05 +0200 Subject: [PATCH 78/83] feat: Display clickable links in tasks (#694) Closes #330 --- client/package-lock.json | 16 +++++ client/package.json | 2 + client/src/components/Card/Tasks.jsx | 4 +- client/src/components/Card/Tasks.module.scss | 2 + .../src/components/CardModal/Tasks/Item.jsx | 3 +- client/src/components/Linkify.jsx | 68 +++++++++++++++++++ 6 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 client/src/components/Linkify.jsx diff --git a/client/package-lock.json b/client/package-lock.json index 62cebb9..b1e6907 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,8 @@ "initials": "^3.1.2", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", + "linkify-react": "^4.1.3", + "linkifyjs": "^4.1.3", "lodash": "^4.17.21", "nanoid": "^5.0.3", "node-sass": "^9.0.0", @@ -11911,6 +11913,20 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-react": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.1.3.tgz", + "integrity": "sha512-rhI3zM/fxn5BfRPHfi4r9N7zgac4vOIxub1wHIWXLA5ENTMs+BGaIaFO1D1PhmxgwhIKmJz3H7uCP0Dg5JwSlA==", + "peerDependencies": { + "linkifyjs": "^4.0.0", + "react": ">= 15.0.0" + } + }, + "node_modules/linkifyjs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.3.tgz", + "integrity": "sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==" + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", diff --git a/client/package.json b/client/package.json index 25cdce0..3f3c4e1 100755 --- a/client/package.json +++ b/client/package.json @@ -70,6 +70,8 @@ "initials": "^3.1.2", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", + "linkify-react": "^4.1.3", + "linkifyjs": "^4.1.3", "lodash": "^4.17.21", "nanoid": "^5.0.3", "node-sass": "^9.0.0", diff --git a/client/src/components/Card/Tasks.jsx b/client/src/components/Card/Tasks.jsx index d76340f..c530474 100644 --- a/client/src/components/Card/Tasks.jsx +++ b/client/src/components/Card/Tasks.jsx @@ -4,6 +4,8 @@ import classNames from 'classnames'; import { Progress } from 'semantic-ui-react'; import { useToggle } from '../../lib/hooks'; +import Linkify from '../Linkify'; + import styles from './Tasks.module.scss'; const Tasks = React.memo(({ items }) => { @@ -48,7 +50,7 @@ const Tasks = React.memo(({ items }) => { key={item.id} className={classNames(styles.task, item.isCompleted && styles.taskCompleted)} > - {item.name} + {item.name} ))} diff --git a/client/src/components/Card/Tasks.module.scss b/client/src/components/Card/Tasks.module.scss index 508ac89..8f8a7ec 100644 --- a/client/src/components/Card/Tasks.module.scss +++ b/client/src/components/Card/Tasks.module.scss @@ -55,8 +55,10 @@ display: block; font-size: 12px; line-height: 14px; + overflow: hidden; padding-bottom: 6px; padding-left: 14px; + text-overflow: ellipsis; &:before { content: "–"; diff --git a/client/src/components/CardModal/Tasks/Item.jsx b/client/src/components/CardModal/Tasks/Item.jsx index a34beb0..8bb3046 100755 --- a/client/src/components/CardModal/Tasks/Item.jsx +++ b/client/src/components/CardModal/Tasks/Item.jsx @@ -8,6 +8,7 @@ import { usePopup } from '../../../lib/popup'; import NameEdit from './NameEdit'; import ActionsStep from './ActionsStep'; +import Linkify from '../../Linkify'; import styles from './Item.module.scss'; @@ -65,7 +66,7 @@ const Item = React.memo( onClick={handleClick} > - {name} + {name} {isPersisted && canEdit && ( diff --git a/client/src/components/Linkify.jsx b/client/src/components/Linkify.jsx new file mode 100644 index 0000000..71b01fd --- /dev/null +++ b/client/src/components/Linkify.jsx @@ -0,0 +1,68 @@ +import React, { useCallback } from 'react'; +import PropTypes from 'prop-types'; +import LinkifyReact from 'linkify-react'; + +import history from '../history'; + +const Linkify = React.memo(({ children, linkStopPropagation, ...props }) => { + const handleLinkClick = useCallback( + (event) => { + if (linkStopPropagation) { + event.stopPropagation(); + } + + if (!event.target.getAttribute('target')) { + event.preventDefault(); + history.push(event.target.href); + } + }, + [linkStopPropagation], + ); + + const linkRenderer = useCallback( + ({ attributes: { href, ...linkProps }, content }) => { + let url; + try { + url = new URL(href, window.location); + } catch (error) {} // eslint-disable-line no-empty + + const isSameSite = !!url && url.origin === window.location.origin; + + return ( + + {isSameSite ? url.pathname : content} + + ); + }, + [handleLinkClick], + ); + + return ( + + {children} + + ); +}); + +Linkify.propTypes = { + children: PropTypes.string.isRequired, + linkStopPropagation: PropTypes.bool, +}; + +Linkify.defaultProps = { + linkStopPropagation: false, +}; + +export default Linkify; From 16499052f74b2aa4b79a68ad9504f00ff5991498 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Wed, 10 Apr 2024 15:57:15 +0200 Subject: [PATCH 79/83] chore: Update version --- charts/planka/Chart.yaml | 4 ++-- client/.env | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/planka/Chart.yaml b/charts/planka/Chart.yaml index 470f805..4a43c8f 100644 --- a/charts/planka/Chart.yaml +++ b/charts/planka/Chart.yaml @@ -15,13 +15,13 @@ 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.24 +version: 0.1.25 # 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 # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.16.2" +appVersion: "1.16.3" dependencies: - alias: postgresql diff --git a/client/.env b/client/.env index e17aeb7..31c7289 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.16.2 +REACT_APP_VERSION=1.16.3 diff --git a/package-lock.json b/package-lock.json index eee91c9..3f255b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "planka", - "version": "1.16.2", + "version": "1.16.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "planka", - "version": "1.16.2", + "version": "1.16.3", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 7e06e77..2b71a75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "planka", - "version": "1.16.2", + "version": "1.16.3", "private": true, "homepage": "https://plankanban.github.io/planka", "repository": { From 8a8c1fee0c650cfe410f2abc4756da1b4bc6cc3d Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Fri, 12 Apr 2024 12:07:19 +0200 Subject: [PATCH 80/83] fix: Fix error output when sending email or message to Slack --- client/src/containers/LoginContainer.js | 2 +- server/api/helpers/utils/send-email.js | 4 ++-- server/api/helpers/utils/send-slack-message.js | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/containers/LoginContainer.js b/client/src/containers/LoginContainer.js index 881b65b..1a16788 100755 --- a/client/src/containers/LoginContainer.js +++ b/client/src/containers/LoginContainer.js @@ -20,7 +20,7 @@ const mapStateToProps = (state) => { isSubmittingUsingOidc, error, withOidc: !!oidcConfig, - isOidcEnforced: oidcConfig && oidcConfig.isEnforced, + isOidcEnforced: !!oidcConfig && oidcConfig.isEnforced, }; }; diff --git a/server/api/helpers/utils/send-email.js b/server/api/helpers/utils/send-email.js index 7b8d08b..02593f8 100644 --- a/server/api/helpers/utils/send-email.js +++ b/server/api/helpers/utils/send-email.js @@ -23,9 +23,9 @@ module.exports = { from: sails.config.custom.smtpFrom, }); - sails.log.info('Email sent: %s', info.messageId); + sails.log.info(`Email sent: ${info.messageId}`); } catch (error) { - sails.log.error(error); // TODO: provide description text? + sails.log.error(`Error sending email: ${error}`); } }, }; diff --git a/server/api/helpers/utils/send-slack-message.js b/server/api/helpers/utils/send-slack-message.js index 573fcf8..510ae1b 100644 --- a/server/api/helpers/utils/send-slack-message.js +++ b/server/api/helpers/utils/send-slack-message.js @@ -35,19 +35,19 @@ module.exports = { body: JSON.stringify(body), }); } catch (error) { - sails.log.error(error); // TODO: provide description text? + sails.log.error(`Error sending to Slack: ${error}`); return; } if (!response.ok) { - sails.log.error('Error sending to Slack: %s', response.error); + sails.log.error(`Error sending to Slack: ${response.error}`); return; } const responseJson = await response.json(); if (!responseJson.ok) { - sails.log.error('Error sending to Slack: %s', responseJson.error); + sails.log.error(`Error sending to Slack: ${responseJson.error}`); } }, }; From 92f75789fa3c3d58655501373c3d990cde7c8d15 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Fri, 12 Apr 2024 12:09:22 +0200 Subject: [PATCH 81/83] chore: Update version --- charts/planka/Chart.yaml | 4 ++-- client/.env | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/planka/Chart.yaml b/charts/planka/Chart.yaml index 4a43c8f..02158c7 100644 --- a/charts/planka/Chart.yaml +++ b/charts/planka/Chart.yaml @@ -15,13 +15,13 @@ 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.25 +version: 0.1.26 # 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 # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.16.3" +appVersion: "1.16.4" dependencies: - alias: postgresql diff --git a/client/.env b/client/.env index 31c7289..1b48387 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.16.3 +REACT_APP_VERSION=1.16.4 diff --git a/package-lock.json b/package-lock.json index 3f255b0..9c2bff2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "planka", - "version": "1.16.3", + "version": "1.16.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "planka", - "version": "1.16.3", + "version": "1.16.4", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 2b71a75..dab2865 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "planka", - "version": "1.16.3", + "version": "1.16.4", "private": true, "homepage": "https://plankanban.github.io/planka", "repository": { From a1323cd5160124c04e5ef143b8f497a825dbf0a9 Mon Sep 17 00:00:00 2001 From: Gavin Mogan Date: Mon, 15 Apr 2024 07:19:53 -0700 Subject: [PATCH 82/83] feat: Set labels when building docker image --- .../build-and-push-docker-base-image.yml | 4 ++-- .../build-and-push-docker-image-dev.yml | 2 +- .../workflows/build-and-push-docker-image.yml | 17 ++++++++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-push-docker-base-image.yml b/.github/workflows/build-and-push-docker-base-image.yml index 427435d..2cb2dac 100644 --- a/.github/workflows/build-and-push-docker-base-image.yml +++ b/.github/workflows/build-and-push-docker-base-image.yml @@ -40,5 +40,5 @@ jobs: build-args: ALPINE_VERSION=${{ env.ALPINE_VERSION }} push: true tags: | - ghcr.io/plankanban/planka:base-latest - ghcr.io/plankanban/planka:base-${{ env.ALPINE_VERSION }} + ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:base-latest + ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:base-${{ env.ALPINE_VERSION }} diff --git a/.github/workflows/build-and-push-docker-image-dev.yml b/.github/workflows/build-and-push-docker-image-dev.yml index 7b591f2..fa5b98d 100644 --- a/.github/workflows/build-and-push-docker-image-dev.yml +++ b/.github/workflows/build-and-push-docker-image-dev.yml @@ -11,7 +11,7 @@ on: branches: [master] env: - REGISTRY_IMAGE: ghcr.io/plankanban/planka + REGISTRY_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.repository }} jobs: build: diff --git a/.github/workflows/build-and-push-docker-image.yml b/.github/workflows/build-and-push-docker-image.yml index 47bb737..51d2bde 100644 --- a/.github/workflows/build-and-push-docker-image.yml +++ b/.github/workflows/build-and-push-docker-image.yml @@ -31,12 +31,23 @@ jobs: result-encoding: string script: return context.payload.release.tag_name.replace('v', '') + - name: Generate docker image tags + id: metadata + uses: docker/metadata-action@v5 + with: + images: | + name=ghcr.io/${{ github.repository_owner }}/${{ github.repository }} + tags: | + type=raw,value=${{ steps.set-version.outputs.result }} + type=raw,value=latest + - name: Build and push uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64,linux/arm64,linux/arm/v7 push: true - tags: | - ghcr.io/plankanban/planka:latest - ghcr.io/plankanban/planka:${{ steps.set-version.outputs.result }} + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max From 5b4b0cd635cc95f423dfe9d629485f9bf2c7264c Mon Sep 17 00:00:00 2001 From: Robson Ventura <134727751+robsonvnt@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:22:51 -0300 Subject: [PATCH 83/83] feat: Development environment with docker compose (#709) --- config/development/Dockerfile.client | 18 +++++ config/development/Dockerfile.server | 14 ++++ config/development/nginx.conf | 47 ++++++++++++ docker-compose-dev.yml | 111 +++++++++++++++++++-------- 4 files changed, 157 insertions(+), 33 deletions(-) create mode 100644 config/development/Dockerfile.client create mode 100644 config/development/Dockerfile.server create mode 100644 config/development/nginx.conf diff --git a/config/development/Dockerfile.client b/config/development/Dockerfile.client new file mode 100644 index 0000000..8a5ed92 --- /dev/null +++ b/config/development/Dockerfile.client @@ -0,0 +1,18 @@ +FROM node:18-alpine as server-dependencies + +RUN apk -U upgrade \ + && apk add build-base python3 \ + --no-cache + +WORKDIR /app/client +COPY package.json package-lock.json /app/client/ +RUN npm install npm@latest --global \ + && npm install pnpm --global \ + && pnpm import \ + && pnpm install + + +WORKDIR /app/ +COPY ../../package.json ../../package-lock.json /app/ +RUN pnpm import \ + && pnpm install diff --git a/config/development/Dockerfile.server b/config/development/Dockerfile.server new file mode 100644 index 0000000..0f51abe --- /dev/null +++ b/config/development/Dockerfile.server @@ -0,0 +1,14 @@ +FROM node:18-alpine as server-dependencies + +RUN apk -U upgrade \ + && apk add build-base python3 \ + --no-cache + +WORKDIR /app + +COPY package.json package-lock.json ./ + +RUN npm install npm@latest --global \ + && npm install pnpm --global \ + && pnpm import \ + && pnpm install diff --git a/config/development/nginx.conf b/config/development/nginx.conf new file mode 100644 index 0000000..94fc7d7 --- /dev/null +++ b/config/development/nginx.conf @@ -0,0 +1,47 @@ +user nginx; +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + sendfile on; + keepalive_timeout 65; + + server { + listen 80; + + location /api/ { + proxy_pass http://server:1337; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + + location /socket.io { + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://server:1337/socket.io; + } + + location / { + proxy_pass http://client:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + } +} diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 9fe7085..b3eccfd 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -1,20 +1,18 @@ -version: '3' +version: '3.8' services: - planka: - image: ghcr.io/plankanban/planka:master - restart: on-failure + + server: + build: + context: ./server + dockerfile: ../config/development/Dockerfile.server volumes: - - user-avatars:/app/public/user-avatars - - project-background-images:/app/public/project-background-images - - attachments:/app/private/attachments - ports: - - 3000:1337 + - ./server:/app + - /app/node_modules environment: - - BASE_URL=http://localhost:3000 - - DATABASE_URL=postgresql://postgres@postgres/planka + - NODE_ENV=development + - DATABASE_URL=postgresql://user:password@postgres:5432/planka_db - SECRET_KEY=notsecretkey - # - TRUST_PROXY=0 # - TOKEN_EXPIRES_IN=365 # In days @@ -25,20 +23,6 @@ services: # Configure knex to accept SSL certificates # - KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false - - # - DEFAULT_ADMIN_EMAIL=demo@demo.demo # Do not remove if you want to prevent this user from being edited/deleted - # - DEFAULT_ADMIN_PASSWORD=demo - # - 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,26 +35,87 @@ 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= + + working_dir: /app + command: ["sh", "-c", "npm run start"] + depends_on: + postgres: + condition: service_healthy + + client: + build: + context: ./client + dockerfile: ../config/development/Dockerfile.client + volumes: + - ./client:/app/client + - /app/client/node_modules + - /app/node_modules + environment: + - NODE_ENV=development + - CHOKIDAR_USEPOLLING=true + - BASE_URL=http://localhost:3000 + - REACT_APP_SERVER_BASE_URL=http://localhost:3000 + working_dir: /app/client + command: npm start + + init-db: + build: + context: ./server + dockerfile: ../config/development/Dockerfile.server + environment: + - DATABASE_URL=postgresql://user:password@postgres:5432/planka_db + # - DEFAULT_ADMIN_EMAIL=demo@demo.demo # Do not remove if you want to prevent this user from being edited/deleted + # - DEFAULT_ADMIN_PASSWORD=demo + # - DEFAULT_ADMIN_NAME=Demo Demo + # - DEFAULT_ADMIN_USERNAME=demo + + working_dir: /app + command: ["sh", "-c", "npm run db:init"] + volumes: + - ./server:/app + - /app/node_modules depends_on: postgres: condition: service_healthy postgres: - image: postgres:14-alpine - restart: on-failure + image: postgres:latest volumes: - db-data:/var/lib/postgresql/data environment: - - POSTGRES_DB=planka - - POSTGRES_HOST_AUTH_METHOD=trust + POSTGRES_DB: planka_db + POSTGRES_USER: user + POSTGRES_PASSWORD: password + ports: + - "5432:5432" + restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d planka"] interval: 10s timeout: 5s retries: 5 + proxy: + image: nginx:alpine + ports: + - "3000:80" + volumes: + - ./config/development/nginx.conf:/etc/nginx/nginx.conf + depends_on: + - server + - client + + volumes: - user-avatars: - project-background-images: - attachments: db-data: