mirror of
https://github.com/plankanban/planka.git
synced 2025-07-24 07:39:44 +02:00
parent
ad7fb51cfa
commit
2ee1166747
1557 changed files with 76832 additions and 47042 deletions
|
@ -1,3 +1,8 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
sync: true,
|
||||
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const { v4: uuid } = require('uuid');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
|
|
182
server/api/helpers/utils/download-favicon.js
Normal file
182
server/api/helpers/utils/download-favicon.js
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const { URL } = require('url');
|
||||
const icoToPng = require('ico-to-png');
|
||||
const sharp = require('sharp');
|
||||
|
||||
const FETCH_TIMEOUT = 4000;
|
||||
const MAX_RESPONSE_LENGTH_IN_BYTES = 1024 * 1024;
|
||||
|
||||
const FAVICON_TAGS_REGEX = /<link [^>]*rel="([^"]* )?icon( [^"]*)?"[^>]*>/gi;
|
||||
const HREF_REGEX = /href="(.*?)"/i;
|
||||
const SIZES_REGEX = /sizes="(.*?)"/i;
|
||||
|
||||
const fetchWithTimeout = (url) => {
|
||||
const abortController = new AbortController();
|
||||
setTimeout(() => abortController.abort(), FETCH_TIMEOUT);
|
||||
|
||||
return fetch(url, {
|
||||
signal: abortController.signal,
|
||||
});
|
||||
};
|
||||
|
||||
const readResponse = async (response) => {
|
||||
const reader = response.body.getReader();
|
||||
|
||||
const chunks = [];
|
||||
let receivedLength = 0;
|
||||
|
||||
for (;;) {
|
||||
const { value, done } = await reader.read(); // eslint-disable-line no-await-in-loop
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
chunks.push(value);
|
||||
receivedLength += value.length;
|
||||
|
||||
if (receivedLength > MAX_RESPONSE_LENGTH_IN_BYTES) {
|
||||
reader.cancel();
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
buffer: Buffer.concat(chunks),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
buffer: Buffer.concat(chunks),
|
||||
};
|
||||
};
|
||||
|
||||
const isWantedFaviconTag = (faviconTag) => {
|
||||
const sizesMatch = faviconTag.match(SIZES_REGEX);
|
||||
|
||||
if (!sizesMatch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sizes = sizesMatch[1].split('x');
|
||||
return parseInt(sizes[0], 10) >= 32 && parseInt(sizes[1], 10) >= 32;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
const { hostname, origin } = new URL(inputs.url);
|
||||
|
||||
let response;
|
||||
let readedResponse;
|
||||
|
||||
try {
|
||||
response = await fetchWithTimeout(origin);
|
||||
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
readedResponse = await readResponse(response);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = readedResponse.buffer.toString();
|
||||
const faviconTagsMatch = content.match(FAVICON_TAGS_REGEX);
|
||||
|
||||
let faviconUrl;
|
||||
if (faviconTagsMatch && faviconTagsMatch.length > 0) {
|
||||
let faviconTag;
|
||||
if (faviconTagsMatch.length > 1) {
|
||||
faviconTag = faviconTagsMatch.find(isWantedFaviconTag);
|
||||
}
|
||||
|
||||
if (!faviconTag) {
|
||||
[faviconTag] = faviconTagsMatch;
|
||||
}
|
||||
|
||||
const hrefMatch = faviconTag.match(HREF_REGEX);
|
||||
|
||||
if (hrefMatch) {
|
||||
faviconUrl = new URL(hrefMatch[1], response.url).href;
|
||||
}
|
||||
}
|
||||
|
||||
if (!faviconUrl) {
|
||||
faviconUrl = new URL('/favicon.ico', response.url).href;
|
||||
}
|
||||
|
||||
try {
|
||||
response = await fetchWithTimeout(faviconUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
readedResponse = await readResponse(response);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!readedResponse.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
let image = sharp(readedResponse.buffer);
|
||||
|
||||
let metadata;
|
||||
try {
|
||||
metadata = await image.metadata();
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
if (!metadata || metadata.format === 'magick') {
|
||||
try {
|
||||
const buffer = await icoToPng(readedResponse.buffer, 32);
|
||||
|
||||
image = sharp(buffer);
|
||||
metadata = await image.metadata();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const fileManager = sails.hooks['file-manager'].getInstance();
|
||||
const { width, height } = metadata;
|
||||
|
||||
try {
|
||||
const buffer = await image
|
||||
.resize(
|
||||
32,
|
||||
32,
|
||||
width < 32 || height < 32
|
||||
? {
|
||||
kernel: sharp.kernel.nearest,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
.png()
|
||||
.toBuffer();
|
||||
|
||||
await fileManager.save(
|
||||
`${sails.config.custom.faviconsPathSegment}/${hostname}.png`,
|
||||
buffer,
|
||||
'image/png',
|
||||
);
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
}
|
||||
},
|
||||
};
|
22
server/api/helpers/utils/generate-ids.js
Normal file
22
server/api/helpers/utils/generate-ids.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
total: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
const queryResult = await sails.sendNativeQuery(
|
||||
'SELECT next_id() as id from generate_series(1, $1) ORDER BY id',
|
||||
[inputs.total],
|
||||
);
|
||||
|
||||
return sails.helpers.utils.mapRecords(queryResult.rows);
|
||||
},
|
||||
};
|
|
@ -1,3 +1,8 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const GAP = 2 ** 14;
|
||||
const MIN_GAP = 0.125;
|
||||
const MAX_POSITION = 2 ** 50;
|
||||
|
@ -106,14 +111,14 @@ module.exports = {
|
|||
|
||||
const repositions = [];
|
||||
|
||||
_.forEachRight(inputs.records, ({ id, position: currentPosition }) => {
|
||||
if (_.isEmpty(repositionsMap[currentPosition])) {
|
||||
_.forEachRight(inputs.records, (record) => {
|
||||
if (_.isEmpty(repositionsMap[record.position])) {
|
||||
return;
|
||||
}
|
||||
|
||||
repositions.unshift({
|
||||
id,
|
||||
position: repositionsMap[currentPosition].pop(),
|
||||
record,
|
||||
position: repositionsMap[record.position].pop(),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
35
server/api/helpers/utils/is-preloaded-favicon-exists.js
Normal file
35
server/api/helpers/utils/is-preloaded-favicon-exists.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const PRELOADED_FAVICON_FILENAMES = fs
|
||||
.readdirSync(
|
||||
path.join(
|
||||
sails.config.custom.uploadsBasePath,
|
||||
sails.config.custom.preloadedFaviconsPathSegment,
|
||||
),
|
||||
)
|
||||
.filter((filename) => filename.endsWith('.png'));
|
||||
|
||||
const PRELOADED_FAVICON_HOSTNAMES_SET = new Set(
|
||||
PRELOADED_FAVICON_FILENAMES.map((faviconFilename) => faviconFilename.slice(0, -4)),
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
sync: true,
|
||||
|
||||
inputs: {
|
||||
hostname: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
fn(inputs) {
|
||||
return PRELOADED_FAVICON_HOSTNAMES_SET.has(inputs.hostname);
|
||||
},
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
module.exports = {
|
||||
sync: true,
|
||||
|
||||
inputs: {
|
||||
record: {
|
||||
type: 'ref',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
fn(inputs) {
|
||||
return inputs.record.toJSON ? inputs.record.toJSON() : inputs.record;
|
||||
},
|
||||
};
|
22
server/api/helpers/utils/make-translator.js
Normal file
22
server/api/helpers/utils/make-translator.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
sync: true,
|
||||
|
||||
inputs: {
|
||||
language: {
|
||||
type: 'string',
|
||||
allowNull: true,
|
||||
},
|
||||
},
|
||||
|
||||
fn(inputs) {
|
||||
const i18n = _.cloneDeep(sails.hooks.i18n);
|
||||
i18n.setLocale(inputs.language || sails.config.i18n.defaultLocale);
|
||||
|
||||
return i18n.__.bind(i18n); // eslint-disable-line no-underscore-dangle
|
||||
},
|
||||
};
|
|
@ -1,4 +1,7 @@
|
|||
const recordsValidator = (value) => _.isArray(value);
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
sync: true,
|
||||
|
@ -6,7 +9,6 @@ module.exports = {
|
|||
inputs: {
|
||||
records: {
|
||||
type: 'ref',
|
||||
custom: recordsValidator,
|
||||
required: true,
|
||||
},
|
||||
attribute: {
|
||||
|
@ -17,14 +19,23 @@ module.exports = {
|
|||
type: 'boolean',
|
||||
defaultsTo: false,
|
||||
},
|
||||
withoutNull: {
|
||||
type: 'boolean',
|
||||
defaultsTo: false,
|
||||
},
|
||||
},
|
||||
|
||||
fn(inputs) {
|
||||
let result = _.map(inputs.records, inputs.attribute);
|
||||
|
||||
if (inputs.unique) {
|
||||
result = _.uniq(result);
|
||||
}
|
||||
|
||||
if (inputs.withoutNull) {
|
||||
result = _.without(result, null);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const util = require('util');
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
async function doUpload(paramName, req, options) {
|
||||
const uploadOptions = {
|
||||
...options,
|
||||
dirname: options.dirname || sails.config.custom.uploadsTempPath,
|
||||
};
|
||||
const upload = util.promisify((opts, callback) => {
|
||||
return req.file(paramName).upload(opts, (error, files) => callback(error, files));
|
||||
});
|
||||
return upload(uploadOptions);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
friendlyName: 'Receive uploaded file from request',
|
||||
|
||||
description:
|
||||
"Store a file uploaded from a MIME-multipart request part. The request part name must be 'file'; the resulting file will have a unique UUID-based name with the same extension.",
|
||||
'Store a file uploaded from a MIME-multipart request part. The resulting file will have a unique UUID-based name with the same extension.',
|
||||
|
||||
inputs: {
|
||||
paramName: {
|
||||
type: 'string',
|
||||
|
@ -29,11 +25,15 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
|
||||
fn: async function modFn(inputs, exits) {
|
||||
exits.success(
|
||||
await doUpload(inputs.paramName, inputs.req, {
|
||||
saveAs: uuid(),
|
||||
async fn(inputs, exits) {
|
||||
const upload = util.promisify((options, callback) =>
|
||||
inputs.req.file(inputs.paramName).upload(options, (error, files) => callback(error, files)),
|
||||
);
|
||||
|
||||
return exits.success(
|
||||
await upload({
|
||||
dirname: sails.config.custom.uploadsTempPath,
|
||||
saveAs: uuid(),
|
||||
maxBytes: null,
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
module.exports = {
|
||||
// TODO: make sync?
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
to: {
|
||||
type: 'string',
|
||||
|
@ -17,7 +20,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
async fn(inputs) {
|
||||
const transporter = sails.hooks.smtp.getTransporter(); // TODO: check if active?
|
||||
const transporter = sails.hooks.smtp.getTransporter(); // TODO: check if enabled?
|
||||
|
||||
try {
|
||||
const info = await transporter.sendMail({
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
module.exports = {
|
||||
inputs: {
|
||||
markdown: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
};
|
||||
|
||||
const body = {
|
||||
text: inputs.markdown,
|
||||
};
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(sails.config.custom.googleChatWebhookUrl, {
|
||||
headers,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
} catch (error) {
|
||||
sails.log.error(`Error sending to Google Chat: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
sails.log.error(`Error sending to Google Chat: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const responseJson = await response.json();
|
||||
|
||||
if (!responseJson.ok) {
|
||||
sails.log.error(`Error sending to Google Chat: ${responseJson.error}`);
|
||||
}
|
||||
},
|
||||
};
|
35
server/api/helpers/utils/send-notifications.js
Normal file
35
server/api/helpers/utils/send-notifications.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const { execFile } = require('child_process');
|
||||
const util = require('util');
|
||||
|
||||
const promisifyExecFile = util.promisify(execFile);
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
services: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
bodyByFormat: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
return promisifyExecFile(`${sails.config.appPath}/.venv/bin/python3`, [
|
||||
`${sails.config.appPath}/utils/send_notifications.py`,
|
||||
JSON.stringify(inputs.services),
|
||||
inputs.title,
|
||||
JSON.stringify(inputs.bodyByFormat),
|
||||
]);
|
||||
},
|
||||
};
|
|
@ -1,55 +0,0 @@
|
|||
const POST_MESSAGE_API_URL = 'https://slack.com/api/chat.postMessage';
|
||||
|
||||
module.exports = {
|
||||
// TODO: make sync?
|
||||
|
||||
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 sending to Slack: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
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: ${responseJson.error}`);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -1,44 +0,0 @@
|
|||
const buildSendMessageApiUrl = (telegramBotToken) =>
|
||||
`https://api.telegram.org/bot${telegramBotToken}/sendMessage`;
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
html: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
};
|
||||
|
||||
const body = {
|
||||
chat_id: sails.config.custom.telegramChatId,
|
||||
text: inputs.html,
|
||||
parse_mode: 'HTML',
|
||||
};
|
||||
|
||||
if (sails.config.custom.telegramThreadId) {
|
||||
body.message_thread_id = sails.config.custom.telegramThreadId;
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(buildSendMessageApiUrl(sails.config.custom.telegramBotToken), {
|
||||
headers,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
} catch (error) {
|
||||
sails.log.error(`Error sending to Telegram: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const responseJson = await response.json();
|
||||
sails.log.error(`Error sending to Telegram: ${responseJson.description}`);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -1,23 +1,33 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const EVENT_TYPES = {
|
||||
ACTION_CREATE: 'actionCreate',
|
||||
ACTION_DELETE: 'actionDelete',
|
||||
ACTION_UPDATE: 'actionUpdate',
|
||||
|
||||
ATTACHMENT_CREATE: 'attachmentCreate',
|
||||
ATTACHMENT_DELETE: 'attachmentDelete',
|
||||
ATTACHMENT_UPDATE: 'attachmentUpdate',
|
||||
ATTACHMENT_DELETE: 'attachmentDelete',
|
||||
|
||||
BACKGROUND_IMAGE_CREATE: 'backgroundImageCreate',
|
||||
BACKGROUND_IMAGE_DELETE: 'backgroundImageDelete',
|
||||
|
||||
BASE_CUSTOM_FIELD_GROUP_CREATE: 'baseCustomFieldGroupCreate',
|
||||
BASE_CUSTOM_FIELD_GROUP_UPDATE: 'baseCustomFieldGroupUpdate',
|
||||
BASE_CUSTOM_FIELD_GROUP_DELETE: 'baseCustomFieldGroupDelete',
|
||||
|
||||
BOARD_CREATE: 'boardCreate',
|
||||
BOARD_DELETE: 'boardDelete',
|
||||
BOARD_UPDATE: 'boardUpdate',
|
||||
BOARD_DELETE: 'boardDelete',
|
||||
|
||||
BOARD_MEMBERSHIP_CREATE: 'boardMembershipCreate',
|
||||
BOARD_MEMBERSHIP_DELETE: 'boardMembershipDelete',
|
||||
BOARD_MEMBERSHIP_UPDATE: 'boardMembershipUpdate',
|
||||
BOARD_MEMBERSHIP_DELETE: 'boardMembershipDelete',
|
||||
|
||||
CARD_CREATE: 'cardCreate',
|
||||
CARD_DELETE: 'cardDelete',
|
||||
CARD_UPDATE: 'cardUpdate',
|
||||
CARD_DELETE: 'cardDelete',
|
||||
|
||||
CARD_LABEL_CREATE: 'cardLabelCreate',
|
||||
CARD_LABEL_DELETE: 'cardLabelDelete',
|
||||
|
@ -25,64 +35,73 @@ const EVENT_TYPES = {
|
|||
CARD_MEMBERSHIP_CREATE: 'cardMembershipCreate',
|
||||
CARD_MEMBERSHIP_DELETE: 'cardMembershipDelete',
|
||||
|
||||
COMMENT_CREATE: 'commentCreate',
|
||||
COMMENT_UPDATE: 'commentUpdate',
|
||||
COMMENT_DELETE: 'commentDelete',
|
||||
|
||||
CUSTOM_FIELD_CREATE: 'customFieldCreate',
|
||||
CUSTOM_FIELD_UPDATE: 'customFieldUpdate',
|
||||
CUSTOM_FIELD_DELETE: 'customFieldDelete',
|
||||
|
||||
CUSTOM_FIELD_GROUP_CREATE: 'customFieldGroupCreate',
|
||||
CUSTOM_FIELD_GROUP_UPDATE: 'customFieldGroupUpdate',
|
||||
CUSTOM_FIELD_GROUP_DELETE: 'customFieldGroupDelete',
|
||||
|
||||
CUSTOM_FIELD_VALUE_UPDATE: 'customFieldValueUpdate',
|
||||
CUSTOM_FIELD_VALUE_DELETE: 'customFieldValueDelete',
|
||||
|
||||
LABEL_CREATE: 'labelCreate',
|
||||
LABEL_DELETE: 'labelDelete',
|
||||
LABEL_UPDATE: 'labelUpdate',
|
||||
LABEL_DELETE: 'labelDelete',
|
||||
|
||||
LIST_CREATE: 'listCreate',
|
||||
LIST_DELETE: 'listDelete',
|
||||
LIST_SORT: 'listSort',
|
||||
LIST_UPDATE: 'listUpdate',
|
||||
LIST_CLEAR: 'listClear',
|
||||
LIST_DELETE: 'listDelete',
|
||||
|
||||
NOTIFICATION_CREATE: 'notificationCreate',
|
||||
NOTIFICATION_UPDATE: 'notificationUpdate',
|
||||
|
||||
NOTIFICATION_SERVICE_CREATE: 'notificationServiceCreate',
|
||||
NOTIFICATION_SERVICE_UPDATE: 'notificationServiceUpdate',
|
||||
NOTIFICATION_SERVICE_DELETE: 'notificationServiceDelete',
|
||||
|
||||
PROJECT_CREATE: 'projectCreate',
|
||||
PROJECT_DELETE: 'projectDelete',
|
||||
PROJECT_UPDATE: 'projectUpdate',
|
||||
PROJECT_DELETE: 'projectDelete',
|
||||
|
||||
PROJECT_MANAGER_CREATE: 'projectManagerCreate',
|
||||
PROJECT_MANAGER_DELETE: 'projectManagerDelete',
|
||||
|
||||
TASK_CREATE: 'taskCreate',
|
||||
TASK_DELETE: 'taskDelete',
|
||||
TASK_UPDATE: 'taskUpdate',
|
||||
TASK_DELETE: 'taskDelete',
|
||||
|
||||
TASK_LIST_CREATE: 'taskListCreate',
|
||||
TASK_LIST_UPDATE: 'taskListUpdate',
|
||||
TASK_LIST_DELETE: 'taskListDelete',
|
||||
|
||||
USER_CREATE: 'userCreate',
|
||||
USER_DELETE: 'userDelete',
|
||||
USER_UPDATE: 'userUpdate',
|
||||
};
|
||||
|
||||
const jsonifyData = (data) => {
|
||||
const nextData = {};
|
||||
|
||||
if (data.item) {
|
||||
nextData.item = sails.helpers.utils.jsonifyRecord(data.item);
|
||||
}
|
||||
|
||||
if (data.items) {
|
||||
nextData.items = data.items.map((item) => sails.helpers.utils.jsonifyRecord(item));
|
||||
}
|
||||
|
||||
if (data.included) {
|
||||
nextData.included = Object.entries(data.included).reduce(
|
||||
(result, [key, items]) => ({
|
||||
...result,
|
||||
[key]: items.map((item) => sails.helpers.utils.jsonifyRecord(item)),
|
||||
}),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
return nextData;
|
||||
USER_DELETE: 'userDelete',
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} Included
|
||||
* @property {any[]} [users] - Array of users (optional).
|
||||
* @property {any[]} [projects] - Array of projects (optional).
|
||||
* @property {any[]} [baseCustomFieldGroups] - Array of base custom field groups (optional).
|
||||
* @property {any[]} [boards] - Array of boards (optional).
|
||||
* @property {any[]} [boardMemberships] - Array of board memberships (optional).
|
||||
* @property {any[]} [labels] - Array of labels (optional).
|
||||
* @property {any[]} [lists] - Array of lists (optional).
|
||||
* @property {any[]} [cards] - Array of cards (optional).
|
||||
* @property {any[]} [cardMemberships] - Array of card memberships (optional).
|
||||
* @property {any[]} [taskLists] - Array of task lists (optional).
|
||||
* @property {any[]} [customFieldGroups] - Array of custom field groups (optional).
|
||||
* @property {any[]} [customFields] - Array of custom fields (optional).
|
||||
* @property {any[]} [comments] - Array of comments (optional).
|
||||
* @property {any[]} [actions] - Array of actions (optional).
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -113,9 +132,9 @@ async function sendWebhook(webhook, event, data, prevData, user) {
|
|||
|
||||
const body = JSON.stringify({
|
||||
event,
|
||||
data: jsonifyData(data),
|
||||
prevData: prevData && jsonifyData(prevData),
|
||||
user: sails.helpers.utils.jsonifyRecord(user),
|
||||
data,
|
||||
prevData,
|
||||
user,
|
||||
});
|
||||
|
||||
try {
|
||||
|
@ -146,11 +165,11 @@ module.exports = {
|
|||
required: true,
|
||||
isIn: Object.values(EVENT_TYPES),
|
||||
},
|
||||
data: {
|
||||
buildData: {
|
||||
type: 'ref',
|
||||
required: true,
|
||||
},
|
||||
prevData: {
|
||||
buildPrevData: {
|
||||
type: 'ref',
|
||||
},
|
||||
user: {
|
||||
|
@ -164,20 +183,37 @@ module.exports = {
|
|||
return;
|
||||
}
|
||||
|
||||
sails.config.custom.webhooks.forEach((webhook) => {
|
||||
const webhooks = sails.config.custom.webhooks.filter((webhook) => {
|
||||
if (!webhook.url) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (webhook.excludedEvents && webhook.excludedEvents.includes(inputs.event)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (webhook.events && !webhook.events.includes(inputs.event)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
sendWebhook(webhook, inputs.event, inputs.data, inputs.prevData, inputs.user);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = inputs.buildData();
|
||||
const prevData = inputs.buildPrevData && inputs.buildPrevData();
|
||||
|
||||
webhooks.forEach((webhook) => {
|
||||
sendWebhook(
|
||||
webhook,
|
||||
inputs.event,
|
||||
data,
|
||||
prevData,
|
||||
sails.helpers.users.presentOne(inputs.user),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
sync: true,
|
||||
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
module.exports = {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue