1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-18 20:59:44 +02:00

feat: Move webhooks configuration from environment variable to UI

This commit is contained in:
Maksim Eltyshev 2025-07-04 22:04:11 +02:00
parent f0680831c2
commit b22dba0d11
128 changed files with 2077 additions and 206 deletions

View file

@ -8,6 +8,7 @@ import ActionTypes from '../constants/ActionTypes';
const initializeCore = ( const initializeCore = (
user, user,
board, board,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,
@ -33,6 +34,7 @@ const initializeCore = (
payload: { payload: {
user, user,
board, board,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,

View file

@ -8,6 +8,7 @@ import socket from './socket';
import login from './login'; import login from './login';
import core from './core'; import core from './core';
import modals from './modals'; import modals from './modals';
import webhooks from './webhooks';
import users from './users'; import users from './users';
import projects from './projects'; import projects from './projects';
import projectManagers from './project-managers'; import projectManagers from './project-managers';
@ -35,6 +36,7 @@ export default {
...login, ...login,
...core, ...core,
...modals, ...modals,
...webhooks,
...users, ...users,
...projects, ...projects,
...projectManagers, ...projectManagers,

View file

@ -14,6 +14,7 @@ const handleSocketReconnect = (
config, config,
user, user,
board, board,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,
@ -40,6 +41,7 @@ const handleSocketReconnect = (
config, config,
user, user,
board, board,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,

View file

@ -67,6 +67,7 @@ const handleUserUpdate = (
boardIds, boardIds,
config, config,
board, board,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,
@ -95,6 +96,7 @@ const handleUserUpdate = (
boardIds, boardIds,
config, config,
board, board,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,

View file

@ -0,0 +1,104 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createWebhook = (webhook) => ({
type: ActionTypes.WEBHOOK_CREATE,
payload: {
webhook,
},
});
createWebhook.success = (localId, webhook) => ({
type: ActionTypes.WEBHOOK_CREATE__SUCCESS,
payload: {
localId,
webhook,
},
});
createWebhook.failure = (localId, error) => ({
type: ActionTypes.WEBHOOK_CREATE__FAILURE,
payload: {
localId,
error,
},
});
const handleWebhookCreate = (webhook) => ({
type: ActionTypes.WEBHOOK_CREATE_HANDLE,
payload: {
webhook,
},
});
const updateWebhook = (id, data) => ({
type: ActionTypes.WEBHOOK_UPDATE,
payload: {
id,
data,
},
});
updateWebhook.success = (webhook) => ({
type: ActionTypes.WEBHOOK_UPDATE__SUCCESS,
payload: {
webhook,
},
});
updateWebhook.failure = (id, error) => ({
type: ActionTypes.WEBHOOK_UPDATE__FAILURE,
payload: {
id,
error,
},
});
const handleWebhookUpdate = (webhook) => ({
type: ActionTypes.WEBHOOK_UPDATE_HANDLE,
payload: {
webhook,
},
});
const deleteWebhook = (id) => ({
type: ActionTypes.WEBHOOK_DELETE,
payload: {
id,
},
});
deleteWebhook.success = (webhook) => ({
type: ActionTypes.WEBHOOK_DELETE__SUCCESS,
payload: {
webhook,
},
});
deleteWebhook.failure = (id, error) => ({
type: ActionTypes.WEBHOOK_DELETE__FAILURE,
payload: {
id,
error,
},
});
const handleWebhookDelete = (webhook) => ({
type: ActionTypes.WEBHOOK_DELETE_HANDLE,
payload: {
webhook,
},
});
export default {
createWebhook,
handleWebhookCreate,
updateWebhook,
handleWebhookUpdate,
deleteWebhook,
handleWebhookDelete,
};

View file

@ -7,6 +7,7 @@ import http from './http';
import socket from './socket'; import socket from './socket';
import config from './config'; import config from './config';
import accessTokens from './access-tokens'; import accessTokens from './access-tokens';
import webhooks from './webhooks';
import users from './users'; import users from './users';
import projects from './projects'; import projects from './projects';
import projectManagers from './project-managers'; import projectManagers from './project-managers';
@ -35,6 +36,7 @@ export { http, socket };
export default { export default {
...config, ...config,
...accessTokens, ...accessTokens,
...webhooks,
...users, ...users,
...projects, ...projects,
...projectManagers, ...projectManagers,

23
client/src/api/webhooks.js Executable file
View file

@ -0,0 +1,23 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Actions */
const getWebhooks = (headers) => socket.get('/webhooks', undefined, headers);
const createWebhook = (data, headers) => socket.post('/webhooks', data, headers);
const updateWebhook = (id, data, headers) => socket.patch(`/webhooks/${id}`, data, headers);
const deleteWebhook = (id, headers) => socket.delete(`/webhooks/${id}`, undefined, headers);
export default {
getWebhooks,
createWebhook,
updateWebhook,
deleteWebhook,
};

View file

@ -12,6 +12,7 @@ import { Modal, Tab } from 'semantic-ui-react';
import entryActions from '../../../entry-actions'; import entryActions from '../../../entry-actions';
import { useClosableModal } from '../../../hooks'; import { useClosableModal } from '../../../hooks';
import UsersPane from './UsersPane'; import UsersPane from './UsersPane';
import WebhooksPane from './WebhooksPane';
import styles from './AdministrationModal.module.scss'; import styles from './AdministrationModal.module.scss';
@ -37,6 +38,12 @@ const AdministrationModal = React.memo(() => {
}), }),
render: () => <UsersPane />, render: () => <UsersPane />,
}, },
{
menuItem: t('common.webhooks', {
context: 'title',
}),
render: () => <WebhooksPane />,
},
]; ];
const isUsersPaneActive = activeTabIndex === 0; const isUsersPaneActive = activeTabIndex === 0;

View 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
*/
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Tab } from 'semantic-ui-react';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import Webhooks from '../../webhooks/Webhooks';
import styles from './WebhooksPane.module.scss';
const WebhooksPane = React.memo(() => {
const webhookIds = useSelector(selectors.selectWebhookIds);
const dispatch = useDispatch();
const handleCreate = useCallback(
(data) => {
dispatch(entryActions.createWebhook(data));
},
[dispatch],
);
return (
<Tab.Pane attached={false} className={styles.wrapper}>
<Webhooks ids={webhookIds} onCreate={handleCreate} />
</Tab.Pane>
);
});
export default WebhooksPane;

View file

@ -0,0 +1,11 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
:global(#app) {
.wrapper {
border: none;
box-shadow: none;
}
}

View file

@ -28,21 +28,26 @@ const ConfirmationStep = React.memo(
const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef'); const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');
const handleSubmit = useCallback(() => { const handleSubmit = useCallback(
if (typeValue) { (event) => {
const cleanData = { event.stopPropagation();
...data,
typeValue: data.typeValue.trim(),
};
if (cleanData.typeValue.toLowerCase() !== typeValue.toLowerCase()) { if (typeValue) {
nameFieldRef.current.select(); const cleanData = {
return; ...data,
typeValue: data.typeValue.trim(),
};
if (cleanData.typeValue.toLowerCase() !== typeValue.toLowerCase()) {
nameFieldRef.current.select();
return;
}
} }
}
onConfirm(); onConfirm();
}, [typeValue, onConfirm, data, nameFieldRef]); },
[typeValue, onConfirm, data, nameFieldRef],
);
useEffect(() => { useEffect(() => {
if (typeValue) { if (typeValue) {

View file

@ -125,7 +125,7 @@ const Item = React.memo(({ id }) => {
dispatch(entryActions.deleteNotificationService(id)); dispatch(entryActions.deleteNotificationService(id));
}, [id, dispatch]); }, [id, dispatch]);
const handleUpdateSubmit = useCallback(() => { const handleSubmit = useCallback(() => {
urlFieldRef.current.blur(); urlFieldRef.current.blur();
}, [urlFieldRef]); }, [urlFieldRef]);
@ -153,7 +153,7 @@ const Item = React.memo(({ id }) => {
const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep); const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);
return ( return (
<Form className={styles.wrapper} onSubmit={handleUpdateSubmit}> <Form className={styles.wrapper} onSubmit={handleSubmit}>
<Input <Input
ref={handleUrlFieldRef} ref={handleUrlFieldRef}
name="url" name="url"

View file

@ -0,0 +1,152 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import React, { useCallback, useImperativeHandle } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Dropdown, Input } from 'semantic-ui-react';
import { useNestedRef } from '../../../hooks';
import WEBHOOK_EVENTS from '../../../constants/WebhookEvents';
import styles from './Editor.module.scss';
const Editor = React.forwardRef(({ data, isReadOnly, onFieldChange }, ref) => {
const [t] = useTranslation();
const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');
const [urlFieldRef, handleUrlFieldRef] = useNestedRef('inputRef');
const focusNameField = useCallback(() => {
nameFieldRef.current.focus({
preventScroll: true,
});
}, [nameFieldRef]);
const selectNameField = useCallback(() => {
nameFieldRef.current.select();
}, [nameFieldRef]);
const selectUrlField = useCallback(() => {
urlFieldRef.current.select();
}, [urlFieldRef]);
useImperativeHandle(
ref,
() => ({
focusNameField,
selectNameField,
selectUrlField,
}),
[focusNameField, selectNameField, selectUrlField],
);
return (
<>
<div className={styles.text}>{t('common.title')}</div>
<Input
fluid
ref={handleNameFieldRef}
name="name"
value={data.name}
maxLength={128}
readOnly={isReadOnly}
className={styles.field}
onChange={onFieldChange}
/>
<div className={styles.text}>{t('common.url')}</div>
<Input
fluid
ref={handleUrlFieldRef}
name="url"
value={data.url}
maxLength={2048}
readOnly={isReadOnly}
className={styles.field}
onChange={onFieldChange}
/>
<div className={styles.text}>
{t('common.accessToken')} (
{t('common.optional', {
context: 'inline',
})}
)
</div>
<Input
fluid
name="accessToken"
value={data.accessToken}
maxLength={512}
readOnly={isReadOnly}
className={styles.field}
onChange={onFieldChange}
/>
{data.excludedEvents.length === 0 && (
<>
<div className={styles.text}>
{t('common.events')} (
{t('common.optional', {
context: 'inline',
})}
)
</div>
<Dropdown
selection
multiple
fluid
name="events"
options={WEBHOOK_EVENTS.map((event) => ({
text: event,
value: event,
}))}
value={data.events}
placeholder="All"
readOnly={isReadOnly}
className={styles.field}
onChange={onFieldChange}
/>
</>
)}
{data.events.length === 0 && (
<>
<div className={styles.text}>
{t('common.excludedEvents')} (
{t('common.optional', {
context: 'inline',
})}
)
</div>
<Dropdown
selection
multiple
fluid
name="excludedEvents"
options={WEBHOOK_EVENTS.map((event) => ({
text: event,
value: event,
}))}
value={data.excludedEvents}
placeholder="None"
readOnly={isReadOnly}
className={styles.field}
onChange={onFieldChange}
/>
</>
)}
</>
);
});
Editor.propTypes = {
data: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
isReadOnly: PropTypes.bool,
onFieldChange: PropTypes.func.isRequired,
};
Editor.defaultProps = {
isReadOnly: false,
};
export default React.memo(Editor);

View file

@ -0,0 +1,17 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
:global(#app) {
.field {
margin-bottom: 8px;
}
.text {
color: #444444;
font-size: 12px;
font-weight: bold;
padding-bottom: 6px;
}
}

View file

@ -0,0 +1,137 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { dequal } from 'dequal';
import React, { useCallback, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Accordion, Button, Form, Icon } from 'semantic-ui-react';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import { useForm, usePopupInClosableContext } from '../../../hooks';
import { isUrl } from '../../../utils/validator';
import Editor from './Editor';
import ConfirmationStep from '../../common/ConfirmationStep';
import styles from './Item.module.scss';
import { useToggle } from '../../../lib/hooks';
const Item = React.memo(({ id }) => {
const selectWebhookById = useMemo(() => selectors.makeSelectWebhookById(), []);
const webhook = useSelector((state) => selectWebhookById(state, id));
const dispatch = useDispatch();
const [t] = useTranslation();
const [isOpened, toggleOpened] = useToggle();
const defaultData = useMemo(
() => ({
name: webhook.name,
url: webhook.url,
accessToken: webhook.accessToken,
events: webhook.events,
excludedEvents: webhook.excludedEvents,
}),
[webhook],
);
const [data, handleFieldChange] = useForm(() => ({
name: '',
url: '',
...defaultData,
accessToken: defaultData.accessToken || '',
events: defaultData.events || [],
excludedEvents: defaultData.excludedEvents || [],
}));
const cleanData = useMemo(
() => ({
...data,
name: data.name.trim(),
url: data.url.trim(),
accessToken: data.accessToken.trim() || null,
events: data.events.length === 0 ? null : data.events,
excludedEvents: data.excludedEvents.length === 0 ? null : data.excludedEvents,
}),
[data],
);
const editorRef = useRef(null);
const handleDeleteConfirm = useCallback(() => {
dispatch(entryActions.deleteWebhook(id));
}, [id, dispatch]);
const handleSubmit = useCallback(() => {
if (!cleanData.name) {
editorRef.current.selectNameField();
return;
}
if (!cleanData.url || !isUrl(cleanData.url)) {
editorRef.current.selectUrlField();
return;
}
dispatch(entryActions.updateWebhook(id, cleanData));
}, [id, dispatch, cleanData]);
const handleOpenClick = useCallback(() => {
toggleOpened();
}, [toggleOpened]);
const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);
return (
<>
<Accordion.Title active={isOpened} className={styles.title} onClick={handleOpenClick}>
<Icon name="dropdown" />
{defaultData.name}
</Accordion.Title>
<Accordion.Content active={isOpened}>
<div>
<Form onSubmit={handleSubmit}>
<Editor
ref={editorRef}
data={data}
isReadOnly={!webhook.isPersisted}
onFieldChange={handleFieldChange}
/>
<div className={styles.controls}>
<Button
positive
disabled={dequal(cleanData, defaultData)}
content={t('action.save')}
/>
<ConfirmationPopup
title="common.deleteWebhook"
content="common.areYouSureYouWantToDeleteThisWebhook"
buttonContent="action.deleteWebhook"
onConfirm={handleDeleteConfirm}
>
<Button
type="button"
disabled={!webhook.isPersisted}
className={styles.deleteButton}
>
{t('action.delete')}
</Button>
</ConfirmationPopup>
</div>
</Form>
</div>
</Accordion.Content>
</>
);
});
Item.propTypes = {
id: PropTypes.string.isRequired,
};
export default Item;

View 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
*/
:global(#app) {
.controls {
display: flex;
justify-content: space-between;
}
.deleteButton {
box-shadow: 0 1px 0 #cbcccc;
margin-right: 0;
}
.title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

View file

@ -0,0 +1,96 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import React, { useCallback, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Accordion, Button, Form, Segment } from 'semantic-ui-react';
import { useDidUpdate, useToggle } from '../../../lib/hooks';
import { useForm } from '../../../hooks';
import { isUrl } from '../../../utils/validator';
import Item from './Item';
import Editor from './Editor';
const DEFAULT_DATA = {
name: '',
url: '',
accessToken: '',
events: [],
excludedEvents: [],
};
const Webhooks = React.memo(({ ids, onCreate }) => {
const [t] = useTranslation();
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
const [focusNameFieldState, focusNameField] = useToggle();
const editorRef = useRef(null);
const handleCreateSubmit = useCallback(() => {
const cleanData = {
...data,
name: data.name.trim(),
url: data.url.trim(),
accessToken: data.accessToken.trim() || null,
events: data.events.length === 0 ? null : data.events,
excludedEvents: data.excludedEvents.length === 0 ? null : data.excludedEvents,
};
if (!cleanData.name) {
editorRef.current.selectNameField();
return;
}
if (!cleanData.url || !isUrl(cleanData.url)) {
editorRef.current.selectUrlField();
return;
}
onCreate(cleanData);
setData(DEFAULT_DATA);
focusNameField();
}, [onCreate, data, setData, focusNameField]);
useEffect(() => {
if (editorRef.current) {
editorRef.current.focusNameField();
}
}, []);
useDidUpdate(() => {
if (editorRef.current) {
editorRef.current.focusNameField();
}
}, [focusNameFieldState]);
return (
<>
{ids.length > 0 && (
<Accordion styled fluid>
{ids.map((id) => (
<Item key={id} id={id} />
))}
</Accordion>
)}
{ids.length < 10 && (
<Segment>
<Form onSubmit={handleCreateSubmit}>
<Editor ref={editorRef} data={data} onFieldChange={handleFieldChange} />
<Button positive>{t('action.addWebhook')}</Button>
</Form>
</Segment>
)}
</>
);
});
Webhooks.propTypes = {
ids: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
onCreate: PropTypes.func.isRequired,
};
export default Webhooks;

View file

@ -0,0 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import Webhooks from './Webhooks';
export default Webhooks;

View file

@ -42,6 +42,21 @@ export default {
MODAL_OPEN: 'MODAL_OPEN', MODAL_OPEN: 'MODAL_OPEN',
MODAL_CLOSE: 'MODAL_CLOSE', MODAL_CLOSE: 'MODAL_CLOSE',
/* Webhooks */
WEBHOOK_CREATE: 'WEBHOOK_CREATE',
WEBHOOK_CREATE__SUCCESS: 'WEBHOOK_CREATE__SUCCESS',
WEBHOOK_CREATE__FAILURE: 'WEBHOOK_CREATE__FAILURE',
WEBHOOK_CREATE_HANDLE: 'WEBHOOK_CREATE_HANDLE',
WEBHOOK_UPDATE: 'WEBHOOK_UPDATE',
WEBHOOK_UPDATE__SUCCESS: 'WEBHOOK_UPDATE__SUCCESS',
WEBHOOK_UPDATE__FAILURE: 'WEBHOOK_UPDATE__FAILURE',
WEBHOOK_UPDATE_HANDLE: 'WEBHOOK_UPDATE_HANDLE',
WEBHOOK_DELETE: 'WEBHOOK_DELETE',
WEBHOOK_DELETE__SUCCESS: 'WEBHOOK_DELETE__SUCCESS',
WEBHOOK_DELETE__FAILURE: 'WEBHOOK_DELETE__FAILURE',
WEBHOOK_DELETE_HANDLE: 'WEBHOOK_DELETE_HANDLE',
/* Users */ /* Users */
USER_CREATE: 'USER_CREATE', USER_CREATE: 'USER_CREATE',

View file

@ -31,6 +31,15 @@ export default {
MODAL_OPEN: `${PREFIX}/MODAL_OPEN`, MODAL_OPEN: `${PREFIX}/MODAL_OPEN`,
MODAL_CLOSE: `${PREFIX}/MODAL_CLOSE`, MODAL_CLOSE: `${PREFIX}/MODAL_CLOSE`,
/* Webhooks */
WEBHOOK_CREATE: `${PREFIX}/WEBHOOK_CREATE`,
WEBHOOK_CREATE_HANDLE: `${PREFIX}/WEBHOOK_CREATE_HANDLE`,
WEBHOOK_UPDATE: `${PREFIX}/WEBHOOK_UPDATE`,
WEBHOOK_UPDATE_HANDLE: `${PREFIX}/WEBHOOK_UPDATE_HANDLE`,
WEBHOOK_DELETE: `${PREFIX}/WEBHOOK_DELETE`,
WEBHOOK_DELETE_HANDLE: `${PREFIX}/WEBHOOK_DELETE_HANDLE`,
/* Users */ /* Users */
USER_CREATE: `${PREFIX}/USER_CREATE`, USER_CREATE: `${PREFIX}/USER_CREATE`,

View file

@ -0,0 +1,91 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
export default [
'actionCreate',
'attachmentCreate',
'attachmentUpdate',
'attachmentDelete',
'backgroundImageCreate',
'backgroundImageDelete',
'baseCustomFieldGroupCreate',
'baseCustomFieldGroupUpdate',
'baseCustomFieldGroupDelete',
'boardCreate',
'boardUpdate',
'boardDelete',
'boardMembershipCreate',
'boardMembershipUpdate',
'boardMembershipDelete',
'cardCreate',
'cardUpdate',
'cardDelete',
'cardLabelCreate',
'cardLabelDelete',
'cardMembershipCreate',
'cardMembershipDelete',
'commentCreate',
'commentUpdate',
'commentDelete',
'customFieldCreate',
'customFieldUpdate',
'customFieldDelete',
'customFieldGroupCreate',
'customFieldGroupUpdate',
'customFieldGroupDelete',
'customFieldValueUpdate',
'customFieldValueDelete',
'labelCreate',
'labelUpdate',
'labelDelete',
'listCreate',
'listUpdate',
'listClear',
'listDelete',
'notificationCreate',
'notificationUpdate',
'notificationServiceCreate',
'notificationServiceUpdate',
'notificationServiceDelete',
'projectCreate',
'projectUpdate',
'projectDelete',
'projectManagerCreate',
'projectManagerDelete',
'taskCreate',
'taskUpdate',
'taskDelete',
'taskListCreate',
'taskListUpdate',
'taskListDelete',
'userCreate',
'userUpdate',
'userDelete',
'webhookCreate',
'webhookUpdate',
'webhookDelete',
];

View file

@ -7,6 +7,7 @@ import socket from './socket';
import login from './login'; import login from './login';
import core from './core'; import core from './core';
import modals from './modals'; import modals from './modals';
import webhooks from './webhooks';
import users from './users'; import users from './users';
import projects from './projects'; import projects from './projects';
import projectManagers from './project-managers'; import projectManagers from './project-managers';
@ -33,6 +34,7 @@ export default {
...login, ...login,
...core, ...core,
...modals, ...modals,
...webhooks,
...users, ...users,
...projects, ...projects,
...projectManagers, ...projectManagers,

View file

@ -0,0 +1,58 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import EntryActionTypes from '../constants/EntryActionTypes';
const createWebhook = (data) => ({
type: EntryActionTypes.WEBHOOK_CREATE,
payload: {
data,
},
});
const handleWebhookCreate = (webhook) => ({
type: EntryActionTypes.WEBHOOK_CREATE_HANDLE,
payload: {
webhook,
},
});
const updateWebhook = (id, data) => ({
type: EntryActionTypes.WEBHOOK_UPDATE,
payload: {
id,
data,
},
});
const handleWebhookUpdate = (webhook) => ({
type: EntryActionTypes.WEBHOOK_UPDATE_HANDLE,
payload: {
webhook,
},
});
const deleteWebhook = (id) => ({
type: EntryActionTypes.WEBHOOK_DELETE,
payload: {
id,
},
});
const handleWebhookDelete = (webhook) => ({
type: EntryActionTypes.WEBHOOK_DELETE_HANDLE,
payload: {
webhook,
},
});
export default {
createWebhook,
handleWebhookCreate,
updateWebhook,
handleWebhookUpdate,
deleteWebhook,
handleWebhookDelete,
};

View file

@ -21,6 +21,7 @@ export default {
translation: { translation: {
common: { common: {
aboutPlanka: 'About PLANKA', aboutPlanka: 'About PLANKA',
accessToken: 'Access token',
account: 'Account', account: 'Account',
actions: 'Actions', actions: 'Actions',
activateUser_title: 'Activate User', activateUser_title: 'Activate User',
@ -69,6 +70,7 @@ export default {
areYouSureYouWantToDeleteThisTask: 'Are you sure you want to delete this task?', areYouSureYouWantToDeleteThisTask: 'Are you sure you want to delete this task?',
areYouSureYouWantToDeleteThisTaskList: 'Are you sure you want to delete this task list?', areYouSureYouWantToDeleteThisTaskList: 'Are you sure you want to delete this task list?',
areYouSureYouWantToDeleteThisUser: 'Are you sure you want to delete this user?', areYouSureYouWantToDeleteThisUser: 'Are you sure you want to delete this user?',
areYouSureYouWantToDeleteThisWebhook: 'Are you sure you want to delete this webhook?',
areYouSureYouWantToEmptyTrash: 'Are you sure you want to empty the trash?', areYouSureYouWantToEmptyTrash: 'Are you sure you want to empty the trash?',
areYouSureYouWantToLeaveBoard: 'Are you sure you want to leave the board?', areYouSureYouWantToLeaveBoard: 'Are you sure you want to leave the board?',
areYouSureYouWantToLeaveProject: 'Are you sure you want to leave the project?', areYouSureYouWantToLeaveProject: 'Are you sure you want to leave the project?',
@ -152,6 +154,7 @@ export default {
deleteTask_title: 'Delete Task', deleteTask_title: 'Delete Task',
deleteUser_title: 'Delete User', deleteUser_title: 'Delete User',
deletedUser_title: 'Deleted User', deletedUser_title: 'Deleted User',
deleteWebhook_title: 'Delete Webhook',
description: 'Description', description: 'Description',
detectAutomatically: 'Detect automatically', detectAutomatically: 'Detect automatically',
display: 'Display', display: 'Display',
@ -182,6 +185,8 @@ export default {
enterFilename: 'Enter filename', enterFilename: 'Enter filename',
enterListTitle: 'Enter list title...', enterListTitle: 'Enter list title...',
enterTaskDescription: 'Enter task description...', enterTaskDescription: 'Enter task description...',
events: 'Events',
excludedEvents: 'Excluded events',
filterByLabels_title: 'Filter By Labels', filterByLabels_title: 'Filter By Labels',
filterByMembers_title: 'Filter By Members', filterByMembers_title: 'Filter By Members',
forPersonalProjects: 'For personal projects.', forPersonalProjects: 'For personal projects.',
@ -286,6 +291,7 @@ export default {
typeTitleToConfirm: 'Type the title to confirm.', typeTitleToConfirm: 'Type the title to confirm.',
unsavedChanges: 'Unsaved changes', unsavedChanges: 'Unsaved changes',
uploadedImages: 'Uploaded images', uploadedImages: 'Uploaded images',
url: 'URL',
userActions_title: 'User Actions', userActions_title: 'User Actions',
userAddedCardToList: '<0>{{user}}</0> added <2>{{card}}</2> to {{list}}', userAddedCardToList: '<0>{{user}}</0> added <2>{{card}}</2> to {{list}}',
userAddedThisCardToList: '<0>{{user}}</0> added this card to {{list}}', userAddedThisCardToList: '<0>{{user}}</0> added this card to {{list}}',
@ -313,6 +319,7 @@ export default {
userRemovedUserFromThisCard: '<0>{{actorUser}}</0> removed {{removedUser}} from this card', userRemovedUserFromThisCard: '<0>{{actorUser}}</0> removed {{removedUser}} from this card',
username: 'Username', username: 'Username',
users: 'Users', users: 'Users',
webhooks: 'Webhooks',
viewer: 'Viewer', viewer: 'Viewer',
viewers: 'Viewers', viewers: 'Viewers',
visualTaskManagementWithLists: 'Visual task management with lists.', visualTaskManagementWithLists: 'Visual task management with lists.',
@ -338,6 +345,7 @@ export default {
addTaskList: 'Add task list', addTaskList: 'Add task list',
addToCard: 'Add to card', addToCard: 'Add to card',
addUser: 'Add user', addUser: 'Add user',
addWebhook: 'Add webhook',
archive: 'Archive', archive: 'Archive',
archiveCard: 'Archive card', archiveCard: 'Archive card',
archiveCard_title: 'Archive Card', archiveCard_title: 'Archive Card',
@ -378,6 +386,7 @@ export default {
deleteTask_title: 'Delete Task', deleteTask_title: 'Delete Task',
deleteUser: 'Delete user', deleteUser: 'Delete user',
deleteUser_title: 'Delete User', deleteUser_title: 'Delete User',
deleteWebhook: 'Delete webhook',
dismissAll: 'Dismiss all', dismissAll: 'Dismiss all',
duplicate: 'Duplicate', duplicate: 'Duplicate',
duplicateCard_title: 'Duplicate Card', duplicateCard_title: 'Duplicate Card',

View file

@ -16,6 +16,7 @@ export default {
translation: { translation: {
common: { common: {
aboutPlanka: 'About PLANKA', aboutPlanka: 'About PLANKA',
accessToken: 'Access token',
account: 'Account', account: 'Account',
actions: 'Actions', actions: 'Actions',
activateUser_title: 'Activate User', activateUser_title: 'Activate User',
@ -64,6 +65,7 @@ export default {
areYouSureYouWantToDeleteThisTask: 'Are you sure you want to delete this task?', areYouSureYouWantToDeleteThisTask: 'Are you sure you want to delete this task?',
areYouSureYouWantToDeleteThisTaskList: 'Are you sure you want to delete this task list?', areYouSureYouWantToDeleteThisTaskList: 'Are you sure you want to delete this task list?',
areYouSureYouWantToDeleteThisUser: 'Are you sure you want to delete this user?', areYouSureYouWantToDeleteThisUser: 'Are you sure you want to delete this user?',
areYouSureYouWantToDeleteThisWebhook: 'Are you sure you want to delete this webhook?',
areYouSureYouWantToEmptyTrash: 'Are you sure you want to empty the trash?', areYouSureYouWantToEmptyTrash: 'Are you sure you want to empty the trash?',
areYouSureYouWantToLeaveBoard: 'Are you sure you want to leave the board?', areYouSureYouWantToLeaveBoard: 'Are you sure you want to leave the board?',
areYouSureYouWantToLeaveProject: 'Are you sure you want to leave the project?', areYouSureYouWantToLeaveProject: 'Are you sure you want to leave the project?',
@ -147,6 +149,7 @@ export default {
deleteTask_title: 'Delete Task', deleteTask_title: 'Delete Task',
deleteUser_title: 'Delete User', deleteUser_title: 'Delete User',
deletedUser_title: 'Deleted User', deletedUser_title: 'Deleted User',
deleteWebhook_title: 'Delete Webhook',
description: 'Description', description: 'Description',
detectAutomatically: 'Detect automatically', detectAutomatically: 'Detect automatically',
display: 'Display', display: 'Display',
@ -177,6 +180,8 @@ export default {
enterFilename: 'Enter filename', enterFilename: 'Enter filename',
enterListTitle: 'Enter list title...', enterListTitle: 'Enter list title...',
enterTaskDescription: 'Enter task description...', enterTaskDescription: 'Enter task description...',
events: 'Events',
excludedEvents: 'Excluded events',
filterByLabels_title: 'Filter By Labels', filterByLabels_title: 'Filter By Labels',
filterByMembers_title: 'Filter By Members', filterByMembers_title: 'Filter By Members',
forPersonalProjects: 'For personal projects.', forPersonalProjects: 'For personal projects.',
@ -281,6 +286,7 @@ export default {
typeTitleToConfirm: 'Type the title to confirm.', typeTitleToConfirm: 'Type the title to confirm.',
unsavedChanges: 'Unsaved changes', unsavedChanges: 'Unsaved changes',
uploadedImages: 'Uploaded images', uploadedImages: 'Uploaded images',
url: 'URL',
userActions_title: 'User Actions', userActions_title: 'User Actions',
userAddedCardToList: '<0>{{user}}</0> added <2>{{card}}</2> to {{list}}', userAddedCardToList: '<0>{{user}}</0> added <2>{{card}}</2> to {{list}}',
userAddedThisCardToList: '<0>{{user}}</0> added this card to {{list}}', userAddedThisCardToList: '<0>{{user}}</0> added this card to {{list}}',
@ -308,6 +314,7 @@ export default {
userRemovedUserFromThisCard: '<0>{{actorUser}}</0> removed {{removedUser}} from this card', userRemovedUserFromThisCard: '<0>{{actorUser}}</0> removed {{removedUser}} from this card',
username: 'Username', username: 'Username',
users: 'Users', users: 'Users',
webhooks: 'Webhooks',
viewer: 'Viewer', viewer: 'Viewer',
viewers: 'Viewers', viewers: 'Viewers',
visualTaskManagementWithLists: 'Visual task management with lists.', visualTaskManagementWithLists: 'Visual task management with lists.',
@ -333,6 +340,7 @@ export default {
addTaskList: 'Add task list', addTaskList: 'Add task list',
addToCard: 'Add to card', addToCard: 'Add to card',
addUser: 'Add user', addUser: 'Add user',
addWebhook: 'Add webhook',
archive: 'Archive', archive: 'Archive',
archiveCard: 'Archive card', archiveCard: 'Archive card',
archiveCard_title: 'Archive Card', archiveCard_title: 'Archive Card',
@ -373,6 +381,7 @@ export default {
deleteTask_title: 'Delete Task', deleteTask_title: 'Delete Task',
deleteUser: 'Delete user', deleteUser: 'Delete user',
deleteUser_title: 'Delete User', deleteUser_title: 'Delete User',
deleteWebhook: 'Delete webhook',
dismissAll: 'Dismiss all', dismissAll: 'Dismiss all',
duplicate: 'Duplicate', duplicate: 'Duplicate',
duplicateCard_title: 'Duplicate Card', duplicateCard_title: 'Duplicate Card',

View file

@ -0,0 +1,88 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { attr, fk } from 'redux-orm';
import BaseModel from './BaseModel';
import ActionTypes from '../constants/ActionTypes';
export default class extends BaseModel {
static modelName = 'Webhook';
static fields = {
id: attr(),
name: attr(),
url: attr(),
accessToken: attr(),
events: attr(),
excludedEvents: attr(),
boardId: fk({
to: 'Board',
as: 'board',
relatedName: 'webhooks',
}),
};
static reducer({ type, payload }, Webhook) {
switch (type) {
case ActionTypes.SOCKET_RECONNECT_HANDLE:
Webhook.all().delete();
payload.webhooks.forEach((webhook) => {
Webhook.upsert(webhook);
});
break;
case ActionTypes.CORE_INITIALIZE:
case ActionTypes.USER_UPDATE_HANDLE:
if (payload.webhooks) {
payload.webhooks.forEach((webhook) => {
Webhook.upsert(webhook);
});
}
break;
case ActionTypes.WEBHOOK_CREATE:
case ActionTypes.WEBHOOK_CREATE_HANDLE:
case ActionTypes.WEBHOOK_UPDATE__SUCCESS:
case ActionTypes.WEBHOOK_UPDATE_HANDLE:
Webhook.upsert(payload.webhook);
break;
case ActionTypes.WEBHOOK_CREATE__SUCCESS:
Webhook.withId(payload.localId).delete();
Webhook.upsert(payload.webhook);
break;
case ActionTypes.WEBHOOK_CREATE__FAILURE:
Webhook.withId(payload.localId).delete();
break;
case ActionTypes.WEBHOOK_UPDATE:
Webhook.withId(payload.id).update(payload.data);
break;
case ActionTypes.WEBHOOK_DELETE:
Webhook.withId(payload.id).delete();
break;
case ActionTypes.WEBHOOK_DELETE__SUCCESS:
case ActionTypes.WEBHOOK_DELETE_HANDLE: {
const webhookModel = Webhook.withId(payload.webhook.id);
if (webhookModel) {
webhookModel.delete();
}
break;
}
default:
}
}
static getAllQuerySet() {
return this.orderBy(['id.length', 'id']);
}
}

View file

@ -3,6 +3,7 @@
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/ */
import Webhook from './Webhook';
import User from './User'; import User from './User';
import Project from './Project'; import Project from './Project';
import ProjectManager from './ProjectManager'; import ProjectManager from './ProjectManager';
@ -25,6 +26,7 @@ import Notification from './Notification';
import NotificationService from './NotificationService'; import NotificationService from './NotificationService';
export { export {
Webhook,
User, User,
Project, Project,
ProjectManager, ProjectManager,

View file

@ -26,6 +26,7 @@ import {
Task, Task,
TaskList, TaskList,
User, User,
Webhook,
} from './models'; } from './models';
const orm = new ORM({ const orm = new ORM({
@ -33,6 +34,7 @@ const orm = new ORM({
}); });
orm.register( orm.register(
Webhook,
User, User,
Project, Project,
ProjectManager, ProjectManager,

View file

@ -10,6 +10,7 @@ import request from '../request';
import api from '../../../api'; import api from '../../../api';
import mergeRecords from '../../../utils/merge-records'; import mergeRecords from '../../../utils/merge-records';
import { isUserAdminOrProjectOwner } from '../../../utils/record-helpers'; import { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';
import { UserRoles } from '../../../constants/Enums';
export function* fetchCore() { export function* fetchCore() {
const { const {
@ -17,6 +18,11 @@ export function* fetchCore() {
included: { notificationServices: notificationServices1 }, included: { notificationServices: notificationServices1 },
} = yield call(request, api.getCurrentUser, true); } = yield call(request, api.getCurrentUser, true);
let webhooks;
if (user.role === UserRoles.ADMIN) {
({ items: webhooks } = yield call(request, api.getWebhooks));
}
let users1; let users1;
if (isUserAdminOrProjectOwner(user)) { if (isUserAdminOrProjectOwner(user)) {
({ items: users1 } = yield call(request, api.getUsers)); ({ items: users1 } = yield call(request, api.getUsers));
@ -101,6 +107,7 @@ export function* fetchCore() {
return { return {
user, user,
board, board,
webhooks,
projectManagers, projectManagers,
backgroundImages, backgroundImages,
baseCustomFieldGroups, baseCustomFieldGroups,

View file

@ -21,6 +21,7 @@ export function* initializeCore() {
const { const {
user, user,
board, board,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,
@ -50,6 +51,7 @@ export function* initializeCore() {
actions.initializeCore( actions.initializeCore(
user, user,
board, board,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,

View file

@ -7,6 +7,7 @@ import router from './router';
import socket from './socket'; import socket from './socket';
import core from './core'; import core from './core';
import modals from './modals'; import modals from './modals';
import webhooks from './webhooks';
import users from './users'; import users from './users';
import projects from './projects'; import projects from './projects';
import projectManagers from './project-managers'; import projectManagers from './project-managers';
@ -33,6 +34,7 @@ export default {
...socket, ...socket,
...core, ...core,
...modals, ...modals,
...webhooks,
...users, ...users,
...projects, ...projects,
...projectManagers, ...projectManagers,

View file

@ -24,6 +24,7 @@ export function* handleSocketReconnect() {
let config; let config;
let user; let user;
let board; let board;
let webhooks;
let users; let users;
let projects; let projects;
let projectManagers; let projectManagers;
@ -51,6 +52,7 @@ export function* handleSocketReconnect() {
({ ({
user, user,
board, board,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,
@ -81,6 +83,7 @@ export function* handleSocketReconnect() {
config, config,
user, user,
board, board,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,

View file

@ -70,6 +70,7 @@ export function* handleUserUpdate(user) {
let config; let config;
let board; let board;
let webhooks;
let users1; let users1;
let users2; let users2;
let users3; let users3;
@ -102,6 +103,7 @@ export function* handleUserUpdate(user) {
if (user.role === UserRoles.ADMIN) { if (user.role === UserRoles.ADMIN) {
({ item: config } = yield call(request, api.getConfig)); ({ item: config } = yield call(request, api.getConfig));
({ items: webhooks } = yield call(request, api.getWebhooks));
({ ({
items: projects, items: projects,
@ -164,6 +166,7 @@ export function* handleUserUpdate(user) {
boardIds, boardIds,
config, config,
board, board,
webhooks,
mergeRecords(users1, users2, users3), mergeRecords(users1, users2, users3),
projects, projects,
projectManagers, projectManagers,

View file

@ -0,0 +1,89 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put } from 'redux-saga/effects';
import request from '../request';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
export function* createWebhook(data) {
const localId = yield call(createLocalId);
yield put(
actions.createWebhook({
...data,
id: localId,
}),
);
let webhook;
try {
({ item: webhook } = yield call(request, api.createWebhook, {
...data,
events: data.events && data.events.join(','),
excludedEvents: data.excludedEvents && data.excludedEvents.join(','),
}));
} catch (error) {
yield put(actions.createWebhook.failure(localId, error));
return;
}
yield put(actions.createWebhook.success(localId, webhook));
}
export function* handleWebhookCreate(webhook) {
yield put(actions.handleWebhookCreate(webhook));
}
export function* updateWebhook(id, data) {
yield put(actions.updateWebhook(id, data));
let webhook;
try {
({ item: webhook } = yield call(request, api.updateWebhook, id, {
...data,
events: data.events && data.events.join(','),
excludedEvents: data.excludedEvents && data.excludedEvents.join(','),
}));
} catch (error) {
yield put(actions.updateWebhook.failure(id, error));
return;
}
yield put(actions.updateWebhook.success(webhook));
}
export function* handleWebhookUpdate(webhook) {
yield put(actions.handleWebhookUpdate(webhook));
}
export function* deleteWebhook(id) {
yield put(actions.deleteWebhook(id));
let webhook;
try {
({ item: webhook } = yield call(request, api.deleteWebhook, id));
} catch (error) {
yield put(actions.deleteWebhook.failure(id, error));
return;
}
yield put(actions.deleteWebhook.success(webhook));
}
export function* handleWebhookDelete(webhook) {
yield put(actions.handleWebhookDelete(webhook));
}
export default {
createWebhook,
handleWebhookCreate,
updateWebhook,
handleWebhookUpdate,
deleteWebhook,
handleWebhookDelete,
};

View file

@ -7,6 +7,7 @@ import router from './router';
import socket from './socket'; import socket from './socket';
import core from './core'; import core from './core';
import modals from './modals'; import modals from './modals';
import webhooks from './webhooks';
import users from './users'; import users from './users';
import projects from './projects'; import projects from './projects';
import projectManagers from './project-managers'; import projectManagers from './project-managers';
@ -33,6 +34,7 @@ export default [
socket, socket,
core, core,
modals, modals,
webhooks,
users, users,
projects, projects,
projectManagers, projectManagers,

View file

@ -0,0 +1,30 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* webhooksWatchers() {
yield all([
takeEvery(EntryActionTypes.WEBHOOK_CREATE, ({ payload: { data } }) =>
services.createWebhook(data),
),
takeEvery(EntryActionTypes.WEBHOOK_CREATE_HANDLE, ({ payload: { webhook } }) =>
services.handleWebhookCreate(webhook),
),
takeEvery(EntryActionTypes.WEBHOOK_UPDATE, ({ payload: { id, data } }) =>
services.updateWebhook(id, data),
),
takeEvery(EntryActionTypes.WEBHOOK_UPDATE_HANDLE, ({ payload: { webhook } }) =>
services.handleWebhookUpdate(webhook),
),
takeEvery(EntryActionTypes.WEBHOOK_DELETE, ({ payload: { id } }) => services.deleteWebhook(id)),
takeEvery(EntryActionTypes.WEBHOOK_DELETE_HANDLE, ({ payload: { webhook } }) =>
services.handleWebhookDelete(webhook),
),
]);
}

View file

@ -8,6 +8,7 @@ import common from './common';
import core from './core'; import core from './core';
import modals from './modals'; import modals from './modals';
import positioning from './positioning'; import positioning from './positioning';
import webhooks from './webhooks';
import users from './users'; import users from './users';
import projects from './projects'; import projects from './projects';
import projectManagers from './project-managers'; import projectManagers from './project-managers';
@ -35,6 +36,7 @@ export default {
...core, ...core,
...modals, ...modals,
...positioning, ...positioning,
...webhooks,
...users, ...users,
...projects, ...projects,
...projectManagers, ...projectManagers,

View file

@ -0,0 +1,41 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { createSelector } from 'redux-orm';
import orm from '../orm';
import { isLocalId } from '../utils/local-id';
export const makeSelectWebhookById = () =>
createSelector(
orm,
(_, id) => id,
({ Webhook }, id) => {
const webhookModel = Webhook.withId(id);
if (!webhookModel) {
return webhookModel;
}
return {
...webhookModel.ref,
isPersisted: !isLocalId(webhookModel.id),
};
},
);
export const selectWebhookById = makeSelectWebhookById();
export const selectWebhookIds = createSelector(orm, ({ Webhook }) =>
Webhook.getAllQuerySet()
.toRefArray()
.map((webhook) => webhook.id),
);
export default {
makeSelectWebhookById,
selectWebhookById,
selectWebhookIds,
};

View file

@ -80,15 +80,6 @@ services:
# - SMTP_PASSWORD= # - SMTP_PASSWORD=
# - SMTP_FROM="Demo Demo" <demo@demo.demo> # - SMTP_FROM="Demo Demo" <demo@demo.demo>
# - SMTP_TLS_REJECT_UNAUTHORIZED=false # - SMTP_TLS_REJECT_UNAUTHORIZED=false
# Optional fields: accessToken, events, excludedEvents
# - |
# WEBHOOKS=[{
# "url": "http://localhost:3001",
# "accessToken": "notaccesstoken",
# "events": ["cardCreate", "cardUpdate", "cardDelete"],
# "excludedEvents": ["notificationCreate", "notificationUpdate"]
# }]
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy

View file

@ -100,15 +100,6 @@ services:
# - SMTP_PASSWORD__FILE=/run/secrets/smtp_password # - SMTP_PASSWORD__FILE=/run/secrets/smtp_password
# - SMTP_FROM="Demo Demo" <demo@demo.demo> # - SMTP_FROM="Demo Demo" <demo@demo.demo>
# - SMTP_TLS_REJECT_UNAUTHORIZED=false # - SMTP_TLS_REJECT_UNAUTHORIZED=false
# Optional fields: accessToken, events, excludedEvents
# - |
# WEBHOOKS=[{
# "url": "http://localhost:3001",
# "accessToken": "notaccesstoken",
# "events": ["cardCreate", "cardUpdate", "cardDelete"],
# "excludedEvents": ["notificationCreate", "notificationUpdate"]
# }]
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy

View file

@ -72,14 +72,6 @@ SECRET_KEY=notsecretkey
# SMTP_FROM="Demo Demo" <demo@demo.demo> # SMTP_FROM="Demo Demo" <demo@demo.demo>
# SMTP_TLS_REJECT_UNAUTHORIZED=false # SMTP_TLS_REJECT_UNAUTHORIZED=false
# Optional fields: accessToken, events, excludedEvents
# WEBHOOKS='[{
# "url": "http://localhost:3001",
# "accessToken": "notaccesstoken",
# "events": ["cardCreate", "cardUpdate", "cardDelete"],
# "excludedEvents": ["notificationCreate", "notificationUpdate"]
# }]'
## Do not edit this ## Do not edit this
TZ=UTC TZ=UTC

View file

@ -0,0 +1,76 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const { isUrl } = require('../../../utils/validators');
const Errors = {
LIMIT_REACHED: {
limitReached: 'Limit reached',
},
};
module.exports = {
inputs: {
name: {
type: 'string',
maxLength: 128,
required: true,
},
url: {
type: 'string',
maxLength: 2048,
custom: isUrl,
required: true,
},
accessToken: {
type: 'string',
isNotEmptyString: true,
maxLength: 512,
allowNull: true,
},
events: {
type: 'string',
isNotEmptyString: true,
maxLength: 2048,
allowNull: true,
},
excludedEvents: {
type: 'string',
isNotEmptyString: true,
maxLength: 2048,
allowNull: true,
},
},
exits: {
limitReached: {
responseType: 'conflict',
},
},
async fn(inputs) {
const { currentUser } = this.req;
const values = _.pick(inputs, ['name', 'url', 'accessToken']);
const events = inputs.events && inputs.events.split(',');
const excludedEvents = inputs.excludedEvents && inputs.excludedEvents.split(',');
const webhook = await sails.helpers.webhooks.createOne
.with({
values: {
...values,
events,
excludedEvents,
},
actorUser: currentUser,
request: this.req,
})
.intercept('limitReached', () => Errors.LIMIT_REACHED);
return {
item: webhook,
};
},
};

View file

@ -0,0 +1,51 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const { idInput } = require('../../../utils/inputs');
const Errors = {
WEBHOOK_NOT_FOUND: {
webhookNotFound: 'Webhook not found',
},
};
module.exports = {
inputs: {
id: {
...idInput,
required: true,
},
},
exits: {
webhookNotFound: {
responseType: 'notFound',
},
},
async fn(inputs) {
const { currentUser } = this.req;
let webhook = await Webhook.qm.getOneById(inputs.id);
if (!webhook) {
throw Errors.WEBHOOK_NOT_FOUND;
}
webhook = await sails.helpers.webhooks.deleteOne.with({
record: webhook,
actorUser: currentUser,
request: this.req,
});
if (!webhook) {
throw Errors.WEBHOOK_NOT_FOUND;
}
return {
item: webhook,
};
},
};

View file

@ -0,0 +1,14 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
module.exports = {
async fn() {
const webhooks = await Webhook.qm.getAll();
return {
items: webhooks,
};
},
};

View file

@ -0,0 +1,89 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const { isUrl } = require('../../../utils/validators');
const { idInput } = require('../../../utils/inputs');
const Errors = {
WEBHOOK_NOT_FOUND: {
webhookNotFound: 'Webhook not found',
},
};
module.exports = {
inputs: {
id: {
...idInput,
required: true,
},
name: {
type: 'string',
isNotEmptyString: true,
maxLength: 128,
},
url: {
type: 'string',
maxLength: 2048,
custom: isUrl,
},
accessToken: {
type: 'string',
isNotEmptyString: true,
maxLength: 512,
allowNull: true,
},
events: {
type: 'string',
isNotEmptyString: true,
maxLength: 2048,
allowNull: true,
},
excludedEvents: {
type: 'string',
isNotEmptyString: true,
maxLength: 2048,
allowNull: true,
},
},
exits: {
webhookNotFound: {
responseType: 'notFound',
},
},
async fn(inputs) {
const { currentUser } = this.req;
let webhook = await Webhook.qm.getOneById(inputs.id);
if (!webhook) {
throw Errors.WEBHOOK_NOT_FOUND;
}
const values = _.pick(inputs, ['name', 'url', 'accessToken']);
const events = inputs.events && inputs.events.split(',');
const excludedEvents = inputs.excludedEvents && inputs.excludedEvents.split(',');
webhook = await sails.helpers.webhooks.updateOne.with({
record: webhook,
values: {
...values,
events,
excludedEvents,
},
actorUser: currentUser,
request: this.req,
});
if (!webhook) {
throw Errors.WEBHOOK_NOT_FOUND;
}
return {
item: webhook,
};
},
};

View file

@ -105,6 +105,10 @@ module.exports = {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
webhooks: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@ -130,7 +134,8 @@ module.exports = {
); );
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'actionCreate', webhooks: inputs.webhooks,
event: Webhook.Events.ACTION_CREATE,
buildData: () => ({ buildData: () => ({
item: action, item: action,
included: { included: {
@ -158,6 +163,7 @@ module.exports = {
project: inputs.project, project: inputs.project,
board: inputs.board, board: inputs.board,
list: inputs.list, list: inputs.list,
webhooks: inputs.webhooks,
}); });
} }
} else { } else {
@ -187,6 +193,7 @@ module.exports = {
project: inputs.project, project: inputs.project,
board: inputs.board, board: inputs.board,
list: inputs.list, list: inputs.list,
webhooks: inputs.webhooks,
}), }),
), ),
); );

View file

@ -48,8 +48,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'attachmentCreate', webhooks,
event: Webhook.Events.ATTACHMENT_CREATE,
buildData: () => ({ buildData: () => ({
item: sails.helpers.attachments.presentOne(attachment), item: sails.helpers.attachments.presentOne(attachment),
included: { included: {
@ -65,6 +68,7 @@ module.exports = {
if (!values.card.coverAttachmentId) { if (!values.card.coverAttachmentId) {
if (attachment.type === Attachment.Types.FILE && attachment.data.image) { if (attachment.type === Attachment.Types.FILE && attachment.data.image) {
await sails.helpers.cards.updateOne.with({ await sails.helpers.cards.updateOne.with({
webhooks,
record: values.card, record: values.card,
values: { values: {
coverAttachmentId: attachment.id, coverAttachmentId: attachment.id,

View file

@ -37,6 +37,7 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
if (inputs.record.id === inputs.card.coverAttachmentId) { if (inputs.record.id === inputs.card.coverAttachmentId) {
await sails.helpers.cards.updateOne.with({ await sails.helpers.cards.updateOne.with({
webhooks,
record: inputs.card, record: inputs.card,
values: { values: {
coverAttachmentId: null, coverAttachmentId: null,
@ -66,8 +67,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'attachmentDelete', webhooks,
event: Webhook.Events.ATTACHMENT_DELETE,
buildData: () => ({ buildData: () => ({
item: sails.helpers.attachments.presentOne(attachment), item: sails.helpers.attachments.presentOne(attachment),
included: { included: {

View file

@ -53,8 +53,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'attachmentUpdate', webhooks,
event: Webhook.Events.ATTACHMENT_UPDATE,
buildData: () => ({ buildData: () => ({
item: sails.helpers.attachments.presentOne(attachment), item: sails.helpers.attachments.presentOne(attachment),
included: { included: {

View file

@ -47,8 +47,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'backgroundImageCreate', webhooks,
event: Webhook.Events.BACKGROUND_IMAGE_CREATE,
buildData: () => ({ buildData: () => ({
item: sails.helpers.backgroundImages.presentOne(backgroundImage), item: sails.helpers.backgroundImages.presentOne(backgroundImage),
included: { included: {
@ -60,6 +63,7 @@ module.exports = {
await sails.helpers.projects.updateOne.with({ await sails.helpers.projects.updateOne.with({
scoper, scoper,
webhooks,
record: values.project, record: values.project,
values: { values: {
backgroundImage, backgroundImage,

View file

@ -31,6 +31,7 @@ module.exports = {
if (inputs.record.id === inputs.project.backgroundImageId) { if (inputs.record.id === inputs.project.backgroundImageId) {
await sails.helpers.projects.updateOne.with({ await sails.helpers.projects.updateOne.with({
scoper, scoper,
webhooks,
record: inputs.project, record: inputs.project,
values: { values: {
backgroundType: null, backgroundType: null,
@ -58,8 +59,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'backgroundImageDelete', webhooks,
event: Webhook.Events.BACKGROUND_IMAGE_DELETE,
buildData: () => ({ buildData: () => ({
item: sails.helpers.backgroundImages.presentOne(backgroundImage), item: sails.helpers.backgroundImages.presentOne(backgroundImage),
included: { included: {

View file

@ -43,8 +43,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'baseCustomFieldGroupCreate', webhooks,
event: Webhook.Events.BASE_CUSTOM_FIELD_GROUP_CREATE,
buildData: () => ({ buildData: () => ({
item: baseCustomFieldGroup, item: baseCustomFieldGroup,
included: { included: {

View file

@ -45,8 +45,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'baseCustomFieldGroupDelete', webhooks,
event: Webhook.Events.BASE_CUSTOM_FIELD_GROUP_DELETE,
buildData: () => ({ buildData: () => ({
item: baseCustomFieldGroup, item: baseCustomFieldGroup,
included: { included: {

View file

@ -49,8 +49,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'baseCustomFieldGroupUpdate', webhooks,
event: Webhook.Events.BASE_CUSTOM_FIELD_GROUP_UPDATE,
buildData: () => ({ buildData: () => ({
item: baseCustomFieldGroup, item: baseCustomFieldGroup,
included: { included: {

View file

@ -86,8 +86,11 @@ module.exports = {
}); });
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'boardMembershipCreate', webhooks,
event: Webhook.Events.BOARD_MEMBERSHIP_CREATE,
buildData: () => ({ buildData: () => ({
item: boardMembership, item: boardMembership,
included: { included: {

View file

@ -106,8 +106,11 @@ module.exports = {
}); });
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'boardMembershipDelete', webhooks,
event: Webhook.Events.BOARD_MEMBERSHIP_DELETE,
buildData: () => ({ buildData: () => ({
item: boardMembership, item: boardMembership,
included: { included: {

View file

@ -75,8 +75,11 @@ module.exports = {
}); });
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'boardMembershipUpdate', webhooks,
event: Webhook.Events.BOARD_MEMBERSHIP_UPDATE,
buildData: () => ({ buildData: () => ({
item: boardMembership, item: boardMembership,
included: { included: {

View file

@ -108,8 +108,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'boardCreate', webhooks,
event: Webhook.Events.BOARD_CREATE,
buildData: () => ({ buildData: () => ({
item: board, item: board,
included: { included: {

View file

@ -49,8 +49,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'boardDelete', webhooks,
event: Webhook.Events.BOARD_DELETE,
buildData: () => ({ buildData: () => ({
item: board, item: board,
included: { included: {

View file

@ -104,8 +104,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'boardUpdate', webhooks,
event: Webhook.Events.BOARD_UPDATE,
buildData: () => ({ buildData: () => ({
item: board, item: board,
included: { included: {

View file

@ -61,8 +61,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'cardLabelCreate', webhooks,
event: Webhook.Events.CARD_LABEL_CREATE,
buildData: () => ({ buildData: () => ({
item: cardLabel, item: cardLabel,
included: { included: {

View file

@ -47,8 +47,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'cardLabelDelete', webhooks,
event: Webhook.Events.CARD_LABEL_DELETE,
buildData: () => ({ buildData: () => ({
item: cardLabel, item: cardLabel,
included: { included: {

View file

@ -61,8 +61,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'cardMembershipCreate', webhooks,
event: Webhook.Events.CARD_MEMBERSHIP_CREATE,
buildData: () => ({ buildData: () => ({
item: cardMembership, item: cardMembership,
included: { included: {
@ -106,6 +109,7 @@ module.exports = {
} }
await sails.helpers.actions.createOne.with({ await sails.helpers.actions.createOne.with({
webhooks,
values: { values: {
type: Action.Types.ADD_MEMBER_TO_CARD, type: Action.Types.ADD_MEMBER_TO_CARD,
data: { data: {

View file

@ -51,8 +51,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'cardMembershipDelete', webhooks,
event: Webhook.Events.CARD_MEMBERSHIP_DELETE,
buildData: () => ({ buildData: () => ({
item: cardMembership, item: cardMembership,
included: { included: {
@ -82,6 +85,7 @@ module.exports = {
} }
await sails.helpers.actions.createOne.with({ await sails.helpers.actions.createOne.with({
webhooks,
values: { values: {
type: Action.Types.REMOVE_MEMBER_FROM_CARD, type: Action.Types.REMOVE_MEMBER_FROM_CARD,
data: { data: {

View file

@ -84,8 +84,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'cardCreate', webhooks,
event: Webhook.Events.CARD_CREATE,
buildData: () => ({ buildData: () => ({
item: card, item: card,
included: { included: {
@ -120,6 +123,7 @@ module.exports = {
} }
await sails.helpers.actions.createOne.with({ await sails.helpers.actions.createOne.with({
webhooks,
values: { values: {
card, card,
type: Action.Types.CREATE_CARD, type: Action.Types.CREATE_CARD,

View file

@ -45,8 +45,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'cardDelete', webhooks,
event: Webhook.Events.CARD_DELETE,
buildData: () => ({ buildData: () => ({
item: card, item: card,
included: { included: {

View file

@ -228,8 +228,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'cardCreate', webhooks,
event: Webhook.Events.CARD_CREATE,
buildData: () => ({ buildData: () => ({
item: card, item: card,
included: { included: {
@ -272,6 +275,7 @@ module.exports = {
} }
await sails.helpers.actions.createOne.with({ await sails.helpers.actions.createOne.with({
webhooks,
values: { values: {
card, card,
type: Action.Types.CREATE_CARD, // TODO: introduce separate type? type: Action.Types.CREATE_CARD, // TODO: introduce separate type?

View file

@ -31,6 +31,8 @@ module.exports = {
}, },
); );
const webhooks = await Webhook.qm.getAll();
notifications.forEach((notification) => { notifications.forEach((notification) => {
sails.sockets.broadcast( sails.sockets.broadcast(
`user:${notification.userId}`, `user:${notification.userId}`,
@ -43,7 +45,8 @@ module.exports = {
// TODO: with prevData? // TODO: with prevData?
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'notificationUpdate', webhooks,
event: Webhook.Events.NOTIFICATION_UPDATE,
buildData: () => ({ buildData: () => ({
item: notification, item: notification,
}), }),

View file

@ -31,6 +31,9 @@ module.exports = {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
webhooks: {
type: 'ref',
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@ -104,6 +107,8 @@ module.exports = {
if (_.isEmpty(values)) { if (_.isEmpty(values)) {
card = inputs.record; card = inputs.record;
} else { } else {
const { webhooks = await Webhook.qm.getAll() } = inputs;
if (!_.isNil(values.position)) { if (!_.isNil(values.position)) {
const cards = await Card.qm.getByListId(list.id, { const cards = await Card.qm.getByListId(list.id, {
exceptIdOrIds: inputs.record.id, exceptIdOrIds: inputs.record.id,
@ -402,6 +407,7 @@ module.exports = {
const { id } = await sails.helpers.labels.createOne.with({ const { id } = await sails.helpers.labels.createOne.with({
project, project,
webhooks,
values: { values: {
..._.omit(label, ['id', 'boardId', 'createdAt', 'updatedAt']), ..._.omit(label, ['id', 'boardId', 'createdAt', 'updatedAt']),
board, board,
@ -459,6 +465,7 @@ module.exports = {
if (values.list) { if (values.list) {
await sails.helpers.actions.createOne.with({ await sails.helpers.actions.createOne.with({
webhooks,
values: { values: {
card, card,
type: Action.Types.MOVE_CARD, type: Action.Types.MOVE_CARD,
@ -477,7 +484,8 @@ module.exports = {
} }
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'cardUpdate', webhooks,
event: Webhook.Events.CARD_UPDATE,
buildData: () => ({ buildData: () => ({
item: card, item: card,
included: { included: {

View file

@ -79,8 +79,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'commentCreate', webhooks,
event: Webhook.Events.COMMENT_CREATE,
buildData: () => ({ buildData: () => ({
item: comment, item: comment,
included: { included: {
@ -125,6 +128,7 @@ module.exports = {
await Promise.all( await Promise.all(
notifiableUserIds.map((userId) => notifiableUserIds.map((userId) =>
sails.helpers.notifications.createOne.with({ sails.helpers.notifications.createOne.with({
webhooks,
values: { values: {
userId, userId,
comment, comment,

View file

@ -47,8 +47,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'commentDelete', webhooks,
event: Webhook.Events.COMMENT_DELETE,
buildData: () => ({ buildData: () => ({
item: comment, item: comment,
included: { included: {

View file

@ -53,8 +53,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'commentUpdate', webhooks,
event: Webhook.Events.COMMENT_UPDATE,
buildData: () => ({ buildData: () => ({
item: comment, item: comment,
included: { included: {

View file

@ -87,8 +87,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldGroupCreate', webhooks,
event: Webhook.Events.CUSTOM_FIELD_GROUP_CREATE,
buildData: () => ({ buildData: () => ({
item: customFieldGroup, item: customFieldGroup,
included: { included: {

View file

@ -95,8 +95,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldGroupCreate', webhooks,
event: Webhook.Events.CUSTOM_FIELD_GROUP_CREATE,
buildData: () => ({ buildData: () => ({
item: customFieldGroup, item: customFieldGroup,
included: { included: {

View file

@ -41,8 +41,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldGroupDelete', webhooks,
event: Webhook.Events.CUSTOM_FIELD_GROUP_DELETE,
buildData: () => ({ buildData: () => ({
item: customFieldGroup, item: customFieldGroup,
included: { included: {

View file

@ -49,8 +49,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldGroupDelete', webhooks,
event: Webhook.Events.CUSTOM_FIELD_GROUP_DELETE,
buildData: () => ({ buildData: () => ({
item: customFieldGroup, item: customFieldGroup,
included: { included: {

View file

@ -91,8 +91,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldGroupUpdate', webhooks,
event: Webhook.Events.CUSTOM_FIELD_GROUP_UPDATE,
buildData: () => ({ buildData: () => ({
item: customFieldGroup, item: customFieldGroup,
included: { included: {

View file

@ -99,8 +99,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldGroupUpdate', webhooks,
event: Webhook.Events.CUSTOM_FIELD_GROUP_UPDATE,
buildData: () => ({ buildData: () => ({
item: customFieldGroup, item: customFieldGroup,
included: { included: {

View file

@ -49,9 +49,12 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
// TODO: with prevData? // TODO: with prevData?
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldValueUpdate', webhooks,
event: Webhook.Events.CUSTOM_FIELD_VALUE_UPDATE,
buildData: () => ({ buildData: () => ({
item: customFieldValue, item: customFieldValue,
included: { included: {

View file

@ -51,8 +51,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldValueDelete', webhooks,
event: Webhook.Events.CUSTOM_FIELD_VALUE_DELETE,
buildData: () => ({ buildData: () => ({
item: customFieldValue, item: customFieldValue,
included: { included: {

View file

@ -93,8 +93,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldCreate', webhooks,
event: Webhook.Events.CUSTOM_FIELD_CREATE,
buildData: () => ({ buildData: () => ({
item: customField, item: customField,
included: { included: {

View file

@ -103,8 +103,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldCreate', webhooks,
event: Webhook.Events.CUSTOM_FIELD_CREATE,
buildData: () => ({ buildData: () => ({
item: customField, item: customField,
included: { included: {

View file

@ -49,8 +49,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldDelete', webhooks,
event: Webhook.Events.CUSTOM_FIELD_DELETE,
buildData: () => ({ buildData: () => ({
item: customField, item: customField,
included: { included: {

View file

@ -66,8 +66,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldDelete', webhooks,
event: Webhook.Events.CUSTOM_FIELD_DELETE,
buildData: () => ({ buildData: () => ({
item: customField, item: customField,
included: { included: {

View file

@ -94,8 +94,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldUpdate', webhooks,
event: Webhook.Events.CUSTOM_FIELD_UPDATE,
buildData: () => ({ buildData: () => ({
item: customField, item: customField,
included: { included: {

View file

@ -109,8 +109,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'customFieldUpdate', webhooks,
event: Webhook.Events.CUSTOM_FIELD_UPDATE,
buildData: () => ({ buildData: () => ({
item: customField, item: customField,
included: { included: {

View file

@ -17,6 +17,9 @@ module.exports = {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
webhooks: {
type: 'ref',
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@ -70,8 +73,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const { webhooks = await Webhook.qm.getAll() } = inputs;
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'labelCreate', webhooks,
event: Webhook.Events.LABEL_CREATE,
buildData: () => ({ buildData: () => ({
item: label, item: label,
included: { included: {

View file

@ -41,8 +41,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'labelDelete', webhooks,
event: Webhook.Events.LABEL_DELETE,
buildData: () => ({ buildData: () => ({
item: label, item: label,
included: { included: {

View file

@ -81,8 +81,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'labelUpdate', webhooks,
event: Webhook.Events.LABEL_UPDATE,
buildData: () => ({ buildData: () => ({
item: label, item: label,
included: { included: {

View file

@ -38,8 +38,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'listClear', webhooks,
event: Webhook.Events.LIST_CLEAR,
buildData: () => ({ buildData: () => ({
item: inputs.record, item: inputs.record,
included: { included: {

View file

@ -72,8 +72,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'listCreate', webhooks,
event: Webhook.Events.LIST_CREATE,
buildData: () => ({ buildData: () => ({
item: list, item: list,
included: { included: {

View file

@ -57,8 +57,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'listDelete', webhooks,
event: Webhook.Events.LIST_DELETE,
buildData: () => ({ buildData: () => ({
item: list, item: list,
included: { included: {

View file

@ -91,10 +91,13 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
cards.forEach((card) => { cards.forEach((card) => {
// TODO: with prevData? // TODO: with prevData?
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'cardUpdate', webhooks,
event: Webhook.Events.CARD_UPDATE,
buildData: () => ({ buildData: () => ({
item: card, item: card,
included: { included: {

View file

@ -100,10 +100,13 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
cards.forEach((card) => { cards.forEach((card) => {
// TODO: with prevData? // TODO: with prevData?
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'cardUpdate', webhooks,
event: Webhook.Events.CARD_UPDATE,
buildData: () => ({ buildData: () => ({
item: card, item: card,
included: { included: {

View file

@ -84,8 +84,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'listUpdate', webhooks,
event: Webhook.Events.LIST_UPDATE,
buildData: () => ({ buildData: () => ({
item: list, item: list,
included: { included: {

View file

@ -62,8 +62,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'notificationServiceCreate', webhooks,
event: Webhook.Events.NOTIFICATION_SERVICE_CREATE,
buildData: () => ({ buildData: () => ({
item: notificationService, item: notificationService,
included: { included: {

View file

@ -48,8 +48,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'notificationServiceCreate', webhooks,
event: Webhook.Events.NOTIFICATION_SERVICE_CREATE,
buildData: () => ({ buildData: () => ({
item: notificationService, item: notificationService,
included: { included: {

View file

@ -49,8 +49,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'notificationServiceDelete', webhooks,
event: Webhook.Events.NOTIFICATION_SERVICE_DELETE,
buildData: () => ({ buildData: () => ({
item: notificationService, item: notificationService,
included: { included: {

View file

@ -35,8 +35,11 @@ module.exports = {
inputs.request, inputs.request,
); );
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'notificationServiceDelete', webhooks,
event: Webhook.Events.NOTIFICATION_SERVICE_DELETE,
buildData: () => ({ buildData: () => ({
item: notificationService, item: notificationService,
included: { included: {

View file

@ -55,8 +55,11 @@ module.exports = {
); );
}); });
const webhooks = await Webhook.qm.getAll();
sails.helpers.utils.sendWebhooks.with({ sails.helpers.utils.sendWebhooks.with({
event: 'notificationServiceUpdate', webhooks,
event: Webhook.Events.NOTIFICATION_SERVICE_UPDATE,
buildData: () => ({ buildData: () => ({
item: notificationService, item: notificationService,
included: { included: {

Some files were not shown because too many files have changed in this diff Show more