mirror of
https://github.com/plankanban/planka.git
synced 2025-07-18 20:59:44 +02:00
parent
281cb4a71b
commit
f9e0147f33
61 changed files with 1063 additions and 191 deletions
|
@ -69,6 +69,36 @@ handleBoardMembershipCreate.fetchProject = (id, currentUserId, currentBoardId) =
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updateBoardMembership = (id, data) => ({
|
||||||
|
type: ActionTypes.BOARD_MEMBERSHIP_UPDATE,
|
||||||
|
payload: {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
updateBoardMembership.success = (boardMembership) => ({
|
||||||
|
type: ActionTypes.BOARD_MEMBERSHIP_UPDATE__SUCCESS,
|
||||||
|
payload: {
|
||||||
|
boardMembership,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
updateBoardMembership.failure = (id, error) => ({
|
||||||
|
type: ActionTypes.BOARD_MEMBERSHIP_UPDATE__FAILURE,
|
||||||
|
payload: {
|
||||||
|
id,
|
||||||
|
error,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBoardMembershipUpdate = (boardMembership) => ({
|
||||||
|
type: ActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE,
|
||||||
|
payload: {
|
||||||
|
boardMembership,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const deleteBoardMembership = (id) => ({
|
const deleteBoardMembership = (id) => ({
|
||||||
type: ActionTypes.BOARD_MEMBERSHIP_DELETE,
|
type: ActionTypes.BOARD_MEMBERSHIP_DELETE,
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -101,6 +131,8 @@ const handleBoardMembershipDelete = (boardMembership) => ({
|
||||||
export default {
|
export default {
|
||||||
createBoardMembership,
|
createBoardMembership,
|
||||||
handleBoardMembershipCreate,
|
handleBoardMembershipCreate,
|
||||||
|
updateBoardMembership,
|
||||||
|
handleBoardMembershipUpdate,
|
||||||
deleteBoardMembership,
|
deleteBoardMembership,
|
||||||
handleBoardMembershipDelete,
|
handleBoardMembershipDelete,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,10 +5,14 @@ import socket from './socket';
|
||||||
const createBoardMembership = (boardId, data, headers) =>
|
const createBoardMembership = (boardId, data, headers) =>
|
||||||
socket.post(`/boards/${boardId}/memberships`, data, headers);
|
socket.post(`/boards/${boardId}/memberships`, data, headers);
|
||||||
|
|
||||||
|
const updateBoardMembership = (id, data, headers) =>
|
||||||
|
socket.patch(`/board-memberships/${id}`, data, headers);
|
||||||
|
|
||||||
const deleteBoardMembership = (id, headers) =>
|
const deleteBoardMembership = (id, headers) =>
|
||||||
socket.delete(`/board-memberships/${id}`, undefined, headers);
|
socket.delete(`/board-memberships/${id}`, undefined, headers);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createBoardMembership,
|
createBoardMembership,
|
||||||
|
updateBoardMembership,
|
||||||
deleteBoardMembership,
|
deleteBoardMembership,
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Filters from './Filters';
|
import Filters from './Filters';
|
||||||
import Memberships from '../Memberships';
|
import Memberships from '../Memberships';
|
||||||
|
import BoardMembershipPermissionsSelectStep from '../BoardMembershipPermissionsSelectStep';
|
||||||
|
|
||||||
import styles from './BoardActions.module.scss';
|
import styles from './BoardActions.module.scss';
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ const BoardActions = React.memo(
|
||||||
allUsers,
|
allUsers,
|
||||||
canEditMemberships,
|
canEditMemberships,
|
||||||
onMembershipCreate,
|
onMembershipCreate,
|
||||||
|
onMembershipUpdate,
|
||||||
onMembershipDelete,
|
onMembershipDelete,
|
||||||
onUserToFilterAdd,
|
onUserToFilterAdd,
|
||||||
onUserFromFilterRemove,
|
onUserFromFilterRemove,
|
||||||
|
@ -30,8 +32,10 @@ const BoardActions = React.memo(
|
||||||
<Memberships
|
<Memberships
|
||||||
items={memberships}
|
items={memberships}
|
||||||
allUsers={allUsers}
|
allUsers={allUsers}
|
||||||
|
permissionsSelectStep={BoardMembershipPermissionsSelectStep}
|
||||||
canEdit={canEditMemberships}
|
canEdit={canEditMemberships}
|
||||||
onCreate={onMembershipCreate}
|
onCreate={onMembershipCreate}
|
||||||
|
onUpdate={onMembershipUpdate}
|
||||||
onDelete={onMembershipDelete}
|
onDelete={onMembershipDelete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,6 +69,7 @@ BoardActions.propTypes = {
|
||||||
/* eslint-enable react/forbid-prop-types */
|
/* eslint-enable react/forbid-prop-types */
|
||||||
canEditMemberships: PropTypes.bool.isRequired,
|
canEditMemberships: PropTypes.bool.isRequired,
|
||||||
onMembershipCreate: PropTypes.func.isRequired,
|
onMembershipCreate: PropTypes.func.isRequired,
|
||||||
|
onMembershipUpdate: PropTypes.func.isRequired,
|
||||||
onMembershipDelete: PropTypes.func.isRequired,
|
onMembershipDelete: PropTypes.func.isRequired,
|
||||||
onUserToFilterAdd: PropTypes.func.isRequired,
|
onUserToFilterAdd: PropTypes.func.isRequired,
|
||||||
onUserFromFilterRemove: PropTypes.func.isRequired,
|
onUserFromFilterRemove: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
import omit from 'lodash/omit';
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Button, Form, Menu, Radio, Segment } from 'semantic-ui-react';
|
||||||
|
import { Popup } from '../../lib/custom-ui';
|
||||||
|
|
||||||
|
import { BoardMembershipRoles } from '../../constants/Enums';
|
||||||
|
|
||||||
|
import styles from './BoardMembershipPermissionsSelectStep.module.scss';
|
||||||
|
|
||||||
|
const BoardMembershipPermissionsSelectStep = React.memo(
|
||||||
|
({ defaultData, title, buttonContent, onSelect, onBack }) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
const [data, setData] = useState(() => ({
|
||||||
|
role: BoardMembershipRoles.EDITOR,
|
||||||
|
canComment: null,
|
||||||
|
...defaultData,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handleRoleSelectClick = useCallback((role) => {
|
||||||
|
setData((prevData) => ({
|
||||||
|
...prevData,
|
||||||
|
role,
|
||||||
|
canComment: role === BoardMembershipRoles.EDITOR ? null : !!prevData.canComment,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSettingChange = useCallback((_, { name: fieldName, checked: value }) => {
|
||||||
|
setData((prevData) => ({
|
||||||
|
...prevData,
|
||||||
|
[fieldName]: value,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
onSelect(data.role === BoardMembershipRoles.EDITOR ? omit(data, 'canComment') : data);
|
||||||
|
}, [onSelect, data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Popup.Header onBack={onBack}>
|
||||||
|
{t(title, {
|
||||||
|
context: 'title',
|
||||||
|
})}
|
||||||
|
</Popup.Header>
|
||||||
|
<Popup.Content>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<Menu secondary vertical className={styles.menu}>
|
||||||
|
<Menu.Item
|
||||||
|
active={data.role === BoardMembershipRoles.EDITOR}
|
||||||
|
onClick={() => handleRoleSelectClick(BoardMembershipRoles.EDITOR)}
|
||||||
|
>
|
||||||
|
<div className={styles.menuItemTitle}>{t('common.editor')}</div>
|
||||||
|
<div className={styles.menuItemDescription}>
|
||||||
|
{t('common.canEditContentOfBoard')}
|
||||||
|
</div>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
active={data.role === BoardMembershipRoles.VIEWER}
|
||||||
|
onClick={() => handleRoleSelectClick(BoardMembershipRoles.VIEWER)}
|
||||||
|
>
|
||||||
|
<div className={styles.menuItemTitle}>{t('common.viewer')}</div>
|
||||||
|
<div className={styles.menuItemDescription}>{t('common.canOnlyViewBoard')}</div>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
{data.role !== BoardMembershipRoles.EDITOR && (
|
||||||
|
<Segment basic className={styles.settings}>
|
||||||
|
<Radio
|
||||||
|
toggle
|
||||||
|
name="canComment"
|
||||||
|
checked={data.canComment}
|
||||||
|
label={t('common.canComment')}
|
||||||
|
onChange={handleSettingChange}
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
)}
|
||||||
|
<Button positive content={t(buttonContent)} />
|
||||||
|
</Form>
|
||||||
|
</Popup.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
BoardMembershipPermissionsSelectStep.propTypes = {
|
||||||
|
defaultData: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
|
title: PropTypes.string,
|
||||||
|
buttonContent: PropTypes.string,
|
||||||
|
onSelect: PropTypes.func.isRequired,
|
||||||
|
onBack: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
BoardMembershipPermissionsSelectStep.defaultProps = {
|
||||||
|
defaultData: undefined,
|
||||||
|
title: 'common.selectPermissions',
|
||||||
|
buttonContent: 'action.selectPermissions',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoardMembershipPermissionsSelectStep;
|
|
@ -0,0 +1,18 @@
|
||||||
|
:global(#app) {
|
||||||
|
.menu {
|
||||||
|
margin: 0 auto 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItemDescription {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItemTitle {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import BoardMembershipPermissionsSelectStep from './BoardMembershipPermissionsSelectStep';
|
||||||
|
|
||||||
|
export default BoardMembershipPermissionsSelectStep;
|
|
@ -13,7 +13,7 @@ import styles from './Attachments.module.scss';
|
||||||
const INITIALLY_VISIBLE = 4;
|
const INITIALLY_VISIBLE = 4;
|
||||||
|
|
||||||
const Attachments = React.memo(
|
const Attachments = React.memo(
|
||||||
({ items, onUpdate, onDelete, onCoverUpdate, onGalleryOpen, onGalleryClose }) => {
|
({ items, canEdit, onUpdate, onDelete, onCoverUpdate, onGalleryOpen, onGalleryClose }) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const [isAllVisible, toggleAllVisible] = useToggle();
|
const [isAllVisible, toggleAllVisible] = useToggle();
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@ const Attachments = React.memo(
|
||||||
createdAt={item.createdAt}
|
createdAt={item.createdAt}
|
||||||
isCover={item.isCover}
|
isCover={item.isCover}
|
||||||
isPersisted={item.isPersisted}
|
isPersisted={item.isPersisted}
|
||||||
|
canEdit={canEdit}
|
||||||
onClick={item.image || isPdf ? open : undefined}
|
onClick={item.image || isPdf ? open : undefined}
|
||||||
onCoverSelect={() => handleCoverSelect(item.id)}
|
onCoverSelect={() => handleCoverSelect(item.id)}
|
||||||
onCoverDeselect={handleCoverDeselect}
|
onCoverDeselect={handleCoverDeselect}
|
||||||
|
@ -151,6 +152,7 @@ const Attachments = React.memo(
|
||||||
|
|
||||||
Attachments.propTypes = {
|
Attachments.propTypes = {
|
||||||
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
canEdit: PropTypes.bool.isRequired,
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func.isRequired,
|
||||||
onCoverUpdate: PropTypes.func.isRequired,
|
onCoverUpdate: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -17,6 +17,7 @@ const Item = React.forwardRef(
|
||||||
createdAt,
|
createdAt,
|
||||||
isCover,
|
isCover,
|
||||||
isPersisted,
|
isPersisted,
|
||||||
|
canEdit,
|
||||||
onCoverSelect,
|
onCoverSelect,
|
||||||
onCoverDeselect,
|
onCoverDeselect,
|
||||||
onClick,
|
onClick,
|
||||||
|
@ -96,7 +97,7 @@ const Item = React.forwardRef(
|
||||||
value: createdAt,
|
value: createdAt,
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
{coverUrl && (
|
{coverUrl && canEdit && (
|
||||||
<span className={styles.options}>
|
<span className={styles.options}>
|
||||||
<button type="button" className={styles.option} onClick={handleToggleCoverClick}>
|
<button type="button" className={styles.option} onClick={handleToggleCoverClick}>
|
||||||
<Icon
|
<Icon
|
||||||
|
@ -118,17 +119,19 @@ const Item = React.forwardRef(
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<EditPopup
|
{canEdit && (
|
||||||
defaultData={{
|
<EditPopup
|
||||||
name,
|
defaultData={{
|
||||||
}}
|
name,
|
||||||
onUpdate={onUpdate}
|
}}
|
||||||
onDelete={onDelete}
|
onUpdate={onUpdate}
|
||||||
>
|
onDelete={onDelete}
|
||||||
<Button className={classNames(styles.button, styles.target)}>
|
>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Button className={classNames(styles.button, styles.target)}>
|
||||||
</Button>
|
<Icon fitted name="pencil" size="small" />
|
||||||
</EditPopup>
|
</Button>
|
||||||
|
</EditPopup>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -141,6 +144,7 @@ Item.propTypes = {
|
||||||
createdAt: PropTypes.instanceOf(Date),
|
createdAt: PropTypes.instanceOf(Date),
|
||||||
isCover: PropTypes.bool.isRequired,
|
isCover: PropTypes.bool.isRequired,
|
||||||
isPersisted: PropTypes.bool.isRequired,
|
isPersisted: PropTypes.bool.isRequired,
|
||||||
|
canEdit: PropTypes.bool.isRequired,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
onCoverSelect: PropTypes.func.isRequired,
|
onCoverSelect: PropTypes.func.isRequired,
|
||||||
onCoverDeselect: PropTypes.func.isRequired,
|
onCoverDeselect: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -48,6 +48,7 @@ const CardModal = React.memo(
|
||||||
allBoardMemberships,
|
allBoardMemberships,
|
||||||
allLabels,
|
allLabels,
|
||||||
canEdit,
|
canEdit,
|
||||||
|
canEditCommentActivities,
|
||||||
canEditAllCommentActivities,
|
canEditAllCommentActivities,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
onMove,
|
onMove,
|
||||||
|
@ -302,7 +303,10 @@ const CardModal = React.memo(
|
||||||
{canEdit ? (
|
{canEdit ? (
|
||||||
<DescriptionEdit defaultValue={description} onUpdate={handleDescriptionUpdate}>
|
<DescriptionEdit defaultValue={description} onUpdate={handleDescriptionUpdate}>
|
||||||
{description ? (
|
{description ? (
|
||||||
<button type="button" className={styles.descriptionText}>
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames(styles.descriptionText, styles.cursorPointer)}
|
||||||
|
>
|
||||||
<Markdown linkStopPropagation linkTarget="_blank">
|
<Markdown linkStopPropagation linkTarget="_blank">
|
||||||
{description}
|
{description}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
|
@ -348,6 +352,7 @@ const CardModal = React.memo(
|
||||||
<div className={styles.moduleHeader}>{t('common.attachments')}</div>
|
<div className={styles.moduleHeader}>{t('common.attachments')}</div>
|
||||||
<Attachments
|
<Attachments
|
||||||
items={attachments}
|
items={attachments}
|
||||||
|
canEdit={canEdit}
|
||||||
onUpdate={onAttachmentUpdate}
|
onUpdate={onAttachmentUpdate}
|
||||||
onDelete={onAttachmentDelete}
|
onDelete={onAttachmentDelete}
|
||||||
onCoverUpdate={handleCoverUpdate}
|
onCoverUpdate={handleCoverUpdate}
|
||||||
|
@ -363,7 +368,7 @@ const CardModal = React.memo(
|
||||||
isAllFetched={isAllActivitiesFetched}
|
isAllFetched={isAllActivitiesFetched}
|
||||||
isDetailsVisible={isActivitiesDetailsVisible}
|
isDetailsVisible={isActivitiesDetailsVisible}
|
||||||
isDetailsFetching={isActivitiesDetailsFetching}
|
isDetailsFetching={isActivitiesDetailsFetching}
|
||||||
canEdit={canEdit}
|
canEdit={canEditCommentActivities}
|
||||||
canEditAllComments={canEditAllCommentActivities}
|
canEditAllComments={canEditAllCommentActivities}
|
||||||
onFetch={onActivitiesFetch}
|
onFetch={onActivitiesFetch}
|
||||||
onDetailsToggle={onActivitiesDetailsToggle}
|
onDetailsToggle={onActivitiesDetailsToggle}
|
||||||
|
@ -508,6 +513,7 @@ CardModal.propTypes = {
|
||||||
allLabels: PropTypes.array.isRequired,
|
allLabels: PropTypes.array.isRequired,
|
||||||
/* eslint-enable react/forbid-prop-types */
|
/* eslint-enable react/forbid-prop-types */
|
||||||
canEdit: PropTypes.bool.isRequired,
|
canEdit: PropTypes.bool.isRequired,
|
||||||
|
canEditCommentActivities: PropTypes.bool.isRequired,
|
||||||
canEditAllCommentActivities: PropTypes.bool.isRequired,
|
canEditAllCommentActivities: PropTypes.bool.isRequired,
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
onMove: PropTypes.func.isRequired,
|
onMove: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -64,6 +64,10 @@
|
||||||
padding: 8px 8px 0 16px;
|
padding: 8px 8px 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cursorPointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.dueDate {
|
.dueDate {
|
||||||
background: rgba(9, 30, 66, 0.04);
|
background: rgba(9, 30, 66, 0.04);
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -114,7 +118,6 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
color: #17394d;
|
color: #17394d;
|
||||||
cursor: pointer;
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import pick from 'lodash/pick';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -11,12 +12,14 @@ import DeleteStep from '../DeleteStep';
|
||||||
import styles from './ActionsPopup.module.scss';
|
import styles from './ActionsPopup.module.scss';
|
||||||
|
|
||||||
const StepTypes = {
|
const StepTypes = {
|
||||||
|
EDIT_PERMISSIONS: 'EDIT_PERMISSIONS',
|
||||||
DELETE: 'DELETE',
|
DELETE: 'DELETE',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActionsStep = React.memo(
|
const ActionsStep = React.memo(
|
||||||
({
|
({
|
||||||
user,
|
membership,
|
||||||
|
permissionsSelectStep,
|
||||||
leaveButtonContent,
|
leaveButtonContent,
|
||||||
leaveConfirmationTitle,
|
leaveConfirmationTitle,
|
||||||
leaveConfirmationContent,
|
leaveConfirmationContent,
|
||||||
|
@ -26,63 +29,115 @@ const ActionsStep = React.memo(
|
||||||
deleteConfirmationContent,
|
deleteConfirmationContent,
|
||||||
deleteConfirmationButtonContent,
|
deleteConfirmationButtonContent,
|
||||||
canLeave,
|
canLeave,
|
||||||
canDelete,
|
canEdit,
|
||||||
|
onUpdate,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const [step, openStep, handleBack] = useSteps();
|
const [step, openStep, handleBack] = useSteps();
|
||||||
|
|
||||||
|
const handleEditPermissionsClick = useCallback(() => {
|
||||||
|
openStep(StepTypes.EDIT_PERMISSIONS);
|
||||||
|
}, [openStep]);
|
||||||
|
|
||||||
const handleDeleteClick = useCallback(() => {
|
const handleDeleteClick = useCallback(() => {
|
||||||
openStep(StepTypes.DELETE);
|
openStep(StepTypes.DELETE);
|
||||||
}, [openStep]);
|
}, [openStep]);
|
||||||
|
|
||||||
if (step && step.type === StepTypes.DELETE) {
|
const handleRoleSelect = useCallback(
|
||||||
return (
|
(data) => {
|
||||||
<DeleteStep
|
if (onUpdate) {
|
||||||
title={t(user.isCurrent ? leaveConfirmationTitle : deleteConfirmationTitle, {
|
onUpdate(data);
|
||||||
context: 'title',
|
}
|
||||||
})}
|
|
||||||
content={t(user.isCurrent ? leaveConfirmationContent : deleteConfirmationContent)}
|
onClose();
|
||||||
buttonContent={t(
|
},
|
||||||
user.isCurrent ? leaveConfirmationButtonContent : deleteConfirmationButtonContent,
|
[onUpdate, onClose],
|
||||||
)}
|
);
|
||||||
onConfirm={onDelete}
|
|
||||||
onBack={handleBack}
|
if (step) {
|
||||||
/>
|
switch (step.type) {
|
||||||
);
|
case StepTypes.EDIT_PERMISSIONS: {
|
||||||
|
const PermissionsSelectStep = permissionsSelectStep;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PermissionsSelectStep
|
||||||
|
defaultData={pick(membership, ['role', 'canComment'])}
|
||||||
|
title="common.editPermissions"
|
||||||
|
buttonContent="action.save"
|
||||||
|
onSelect={handleRoleSelect}
|
||||||
|
onBack={handleBack}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case StepTypes.DELETE:
|
||||||
|
return (
|
||||||
|
<DeleteStep
|
||||||
|
title={t(
|
||||||
|
membership.user.isCurrent ? leaveConfirmationTitle : deleteConfirmationTitle,
|
||||||
|
{
|
||||||
|
context: 'title',
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
content={t(
|
||||||
|
membership.user.isCurrent ? leaveConfirmationContent : deleteConfirmationContent,
|
||||||
|
)}
|
||||||
|
buttonContent={t(
|
||||||
|
membership.user.isCurrent
|
||||||
|
? leaveConfirmationButtonContent
|
||||||
|
: deleteConfirmationButtonContent,
|
||||||
|
)}
|
||||||
|
onConfirm={onDelete}
|
||||||
|
onBack={handleBack}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className={styles.user}>
|
<span className={styles.user}>
|
||||||
<User name={user.name} avatarUrl={user.avatarUrl} size="large" />
|
<User name={membership.user.name} avatarUrl={membership.user.avatarUrl} size="large" />
|
||||||
</span>
|
</span>
|
||||||
<span className={styles.content}>
|
<span className={styles.content}>
|
||||||
<div className={styles.name}>{user.name}</div>
|
<div className={styles.name}>{membership.user.name}</div>
|
||||||
<div className={styles.email}>{user.email}</div>
|
<div className={styles.email}>{membership.user.email}</div>
|
||||||
{user.isCurrent
|
|
||||||
? canLeave && (
|
|
||||||
<Button
|
|
||||||
content={t(leaveButtonContent)}
|
|
||||||
className={styles.deleteButton}
|
|
||||||
onClick={handleDeleteClick}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
: canDelete && (
|
|
||||||
<Button
|
|
||||||
content={t(deleteButtonContent)}
|
|
||||||
className={styles.deleteButton}
|
|
||||||
onClick={handleDeleteClick}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
|
{permissionsSelectStep && canEdit && (
|
||||||
|
<Button
|
||||||
|
fluid
|
||||||
|
content={t('action.editPermissions')}
|
||||||
|
className={styles.button}
|
||||||
|
onClick={handleEditPermissionsClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{membership.user.isCurrent
|
||||||
|
? canLeave && (
|
||||||
|
<Button
|
||||||
|
fluid
|
||||||
|
content={t(leaveButtonContent)}
|
||||||
|
className={styles.button}
|
||||||
|
onClick={handleDeleteClick}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: canEdit && (
|
||||||
|
<Button
|
||||||
|
fluid
|
||||||
|
content={t(deleteButtonContent)}
|
||||||
|
className={styles.button}
|
||||||
|
onClick={handleDeleteClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
ActionsStep.propTypes = {
|
ActionsStep.propTypes = {
|
||||||
user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
membership: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
permissionsSelectStep: PropTypes.elementType,
|
||||||
leaveButtonContent: PropTypes.string,
|
leaveButtonContent: PropTypes.string,
|
||||||
leaveConfirmationTitle: PropTypes.string,
|
leaveConfirmationTitle: PropTypes.string,
|
||||||
leaveConfirmationContent: PropTypes.string,
|
leaveConfirmationContent: PropTypes.string,
|
||||||
|
@ -92,11 +147,14 @@ ActionsStep.propTypes = {
|
||||||
deleteConfirmationContent: PropTypes.string,
|
deleteConfirmationContent: PropTypes.string,
|
||||||
deleteConfirmationButtonContent: PropTypes.string,
|
deleteConfirmationButtonContent: PropTypes.string,
|
||||||
canLeave: PropTypes.bool.isRequired,
|
canLeave: PropTypes.bool.isRequired,
|
||||||
canDelete: PropTypes.bool.isRequired,
|
canEdit: PropTypes.bool.isRequired,
|
||||||
|
onUpdate: PropTypes.func,
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
ActionsStep.defaultProps = {
|
ActionsStep.defaultProps = {
|
||||||
|
permissionsSelectStep: undefined,
|
||||||
leaveButtonContent: 'action.leaveBoard',
|
leaveButtonContent: 'action.leaveBoard',
|
||||||
leaveConfirmationTitle: 'common.leaveBoard',
|
leaveConfirmationTitle: 'common.leaveBoard',
|
||||||
leaveConfirmationContent: 'common.areYouSureYouWantToLeaveBoard',
|
leaveConfirmationContent: 'common.areYouSureYouWantToLeaveBoard',
|
||||||
|
@ -105,6 +163,7 @@ ActionsStep.defaultProps = {
|
||||||
deleteConfirmationTitle: 'common.removeMember',
|
deleteConfirmationTitle: 'common.removeMember',
|
||||||
deleteConfirmationContent: 'common.areYouSureYouWantToRemoveThisMemberFromBoard',
|
deleteConfirmationContent: 'common.areYouSureYouWantToRemoveThisMemberFromBoard',
|
||||||
deleteConfirmationButtonContent: 'action.removeMember',
|
deleteConfirmationButtonContent: 'action.removeMember',
|
||||||
|
onUpdate: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withPopup(ActionsStep);
|
export default withPopup(ActionsStep);
|
||||||
|
|
|
@ -1,28 +1,31 @@
|
||||||
:global(#app) {
|
:global(#app) {
|
||||||
|
.button {
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
color: #6b808c;
|
||||||
|
font-weight: normal;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 6px 11px;
|
||||||
|
text-align: left;
|
||||||
|
text-decoration: underline;
|
||||||
|
transition: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(9, 30, 66, 0.08);
|
||||||
|
color: #092d42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: calc(100% - 44px);
|
width: calc(100% - 44px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.deleteButton {
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0 0 0 -10px;
|
|
||||||
padding: 11px 10px;
|
|
||||||
text-decoration: underline;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #e9e9e9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.email {
|
.email {
|
||||||
color: #888888;
|
color: #888888;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
padding: 4px 0 4px 2px;
|
padding: 2px 0 2px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
|
@ -36,7 +39,7 @@
|
||||||
.user {
|
.user {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
padding-top: 8px;
|
padding-top: 10px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,89 +4,141 @@ import { useTranslation } from 'react-i18next';
|
||||||
import { withPopup } from '../../../lib/popup';
|
import { withPopup } from '../../../lib/popup';
|
||||||
import { Input, Popup } from '../../../lib/custom-ui';
|
import { Input, Popup } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import { useField } from '../../../hooks';
|
import { useField, useSteps } from '../../../hooks';
|
||||||
import UserItem from './UserItem';
|
import UserItem from './UserItem';
|
||||||
|
|
||||||
import styles from './AddPopup.module.scss';
|
import styles from './AddPopup.module.scss';
|
||||||
|
|
||||||
const AddStep = React.memo(({ users, currentUserIds, title, onCreate, onClose }) => {
|
const StepTypes = {
|
||||||
const [t] = useTranslation();
|
SELECT_PERMISSIONS: 'SELECT_PERMISSIONS',
|
||||||
const [searchValue, handleSearchFieldChange] = useField('');
|
};
|
||||||
const search = useMemo(() => searchValue.trim().toLowerCase(), [searchValue]);
|
|
||||||
|
|
||||||
const filteredUsers = useMemo(
|
const AddStep = React.memo(
|
||||||
() =>
|
({ users, currentUserIds, permissionsSelectStep, title, onCreate, onClose }) => {
|
||||||
users.filter(
|
const [t] = useTranslation();
|
||||||
(user) =>
|
const [step, openStep, handleBack] = useSteps();
|
||||||
user.email.includes(search) ||
|
const [searchValue, handleSearchFieldChange] = useField('');
|
||||||
user.name.toLowerCase().includes(search) ||
|
const search = useMemo(() => searchValue.trim().toLowerCase(), [searchValue]);
|
||||||
(user.username && user.username.includes(search)),
|
|
||||||
),
|
|
||||||
[users, search],
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchField = useRef(null);
|
const filteredUsers = useMemo(
|
||||||
|
() =>
|
||||||
|
users.filter(
|
||||||
|
(user) =>
|
||||||
|
user.email.includes(search) ||
|
||||||
|
user.name.toLowerCase().includes(search) ||
|
||||||
|
(user.username && user.username.includes(search)),
|
||||||
|
),
|
||||||
|
[users, search],
|
||||||
|
);
|
||||||
|
|
||||||
const handleUserSelect = useCallback(
|
const searchField = useRef(null);
|
||||||
(id) => {
|
|
||||||
onCreate({
|
|
||||||
userId: id,
|
|
||||||
});
|
|
||||||
|
|
||||||
onClose();
|
const handleUserSelect = useCallback(
|
||||||
},
|
(id) => {
|
||||||
[onCreate, onClose],
|
if (permissionsSelectStep) {
|
||||||
);
|
openStep(StepTypes.SELECT_PERMISSIONS, {
|
||||||
|
userId: id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onCreate({
|
||||||
|
userId: id,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
onClose();
|
||||||
searchField.current.focus();
|
}
|
||||||
}, []);
|
},
|
||||||
|
[permissionsSelectStep, onCreate, onClose, openStep],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
const handleRoleSelect = useCallback(
|
||||||
<>
|
(data) => {
|
||||||
<Popup.Header>
|
onCreate({
|
||||||
{t(title, {
|
userId: step.params.userId,
|
||||||
context: 'title',
|
...data,
|
||||||
})}
|
});
|
||||||
</Popup.Header>
|
|
||||||
<Popup.Content>
|
onClose();
|
||||||
<Input
|
},
|
||||||
fluid
|
[onCreate, onClose, step],
|
||||||
ref={searchField}
|
);
|
||||||
value={searchValue}
|
|
||||||
placeholder={t('common.searchUsers')}
|
useEffect(() => {
|
||||||
icon="search"
|
searchField.current.focus();
|
||||||
onChange={handleSearchFieldChange}
|
}, []);
|
||||||
/>
|
|
||||||
{filteredUsers.length > 0 && (
|
if (step) {
|
||||||
<div className={styles.users}>
|
switch (step.type) {
|
||||||
{filteredUsers.map((user) => (
|
case StepTypes.SELECT_PERMISSIONS: {
|
||||||
<UserItem
|
const currentUser = users.find((user) => user.id === step.params.userId);
|
||||||
key={user.id}
|
|
||||||
name={user.name}
|
if (currentUser) {
|
||||||
avatarUrl={user.avatarUrl}
|
const PermissionsSelectStep = permissionsSelectStep;
|
||||||
isActive={currentUserIds.includes(user.id)}
|
|
||||||
onSelect={() => handleUserSelect(user.id)}
|
return (
|
||||||
|
<PermissionsSelectStep
|
||||||
|
buttonContent="action.addMember"
|
||||||
|
onSelect={handleRoleSelect}
|
||||||
|
onBack={handleBack}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
</div>
|
}
|
||||||
)}
|
|
||||||
</Popup.Content>
|
openStep(null);
|
||||||
</>
|
|
||||||
);
|
break;
|
||||||
});
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Popup.Header>
|
||||||
|
{t(title, {
|
||||||
|
context: 'title',
|
||||||
|
})}
|
||||||
|
</Popup.Header>
|
||||||
|
<Popup.Content>
|
||||||
|
<Input
|
||||||
|
fluid
|
||||||
|
ref={searchField}
|
||||||
|
value={searchValue}
|
||||||
|
placeholder={t('common.searchUsers')}
|
||||||
|
icon="search"
|
||||||
|
onChange={handleSearchFieldChange}
|
||||||
|
/>
|
||||||
|
{filteredUsers.length > 0 && (
|
||||||
|
<div className={styles.users}>
|
||||||
|
{filteredUsers.map((user) => (
|
||||||
|
<UserItem
|
||||||
|
key={user.id}
|
||||||
|
name={user.name}
|
||||||
|
avatarUrl={user.avatarUrl}
|
||||||
|
isActive={currentUserIds.includes(user.id)}
|
||||||
|
onSelect={() => handleUserSelect(user.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Popup.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
AddStep.propTypes = {
|
AddStep.propTypes = {
|
||||||
/* eslint-disable react/forbid-prop-types */
|
/* eslint-disable react/forbid-prop-types */
|
||||||
users: PropTypes.array.isRequired,
|
users: PropTypes.array.isRequired,
|
||||||
currentUserIds: PropTypes.array.isRequired,
|
currentUserIds: PropTypes.array.isRequired,
|
||||||
/* eslint-disable react/forbid-prop-types */
|
/* eslint-disable react/forbid-prop-types */
|
||||||
|
permissionsSelectStep: PropTypes.elementType,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
onCreate: PropTypes.func.isRequired,
|
onCreate: PropTypes.func.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
AddStep.defaultProps = {
|
AddStep.defaultProps = {
|
||||||
|
permissionsSelectStep: undefined,
|
||||||
title: 'common.addMember',
|
title: 'common.addMember',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ const Memberships = React.memo(
|
||||||
({
|
({
|
||||||
items,
|
items,
|
||||||
allUsers,
|
allUsers,
|
||||||
|
permissionsSelectStep,
|
||||||
addTitle,
|
addTitle,
|
||||||
leaveButtonContent,
|
leaveButtonContent,
|
||||||
leaveConfirmationTitle,
|
leaveConfirmationTitle,
|
||||||
|
@ -24,6 +25,7 @@ const Memberships = React.memo(
|
||||||
canEdit,
|
canEdit,
|
||||||
canLeaveIfLast,
|
canLeaveIfLast,
|
||||||
onCreate,
|
onCreate,
|
||||||
|
onUpdate,
|
||||||
onDelete,
|
onDelete,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
|
@ -32,7 +34,8 @@ const Memberships = React.memo(
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<span key={item.id} className={styles.user}>
|
<span key={item.id} className={styles.user}>
|
||||||
<ActionsPopup
|
<ActionsPopup
|
||||||
user={item.user}
|
membership={item}
|
||||||
|
permissionsSelectStep={permissionsSelectStep}
|
||||||
leaveButtonContent={leaveButtonContent}
|
leaveButtonContent={leaveButtonContent}
|
||||||
leaveConfirmationTitle={leaveConfirmationTitle}
|
leaveConfirmationTitle={leaveConfirmationTitle}
|
||||||
leaveConfirmationContent={leaveConfirmationContent}
|
leaveConfirmationContent={leaveConfirmationContent}
|
||||||
|
@ -42,7 +45,8 @@ const Memberships = React.memo(
|
||||||
deleteConfirmationContent={deleteConfirmationContent}
|
deleteConfirmationContent={deleteConfirmationContent}
|
||||||
deleteConfirmationButtonContent={deleteConfirmationButtonContent}
|
deleteConfirmationButtonContent={deleteConfirmationButtonContent}
|
||||||
canLeave={items.length > 1 || canLeaveIfLast}
|
canLeave={items.length > 1 || canLeaveIfLast}
|
||||||
canDelete={canEdit}
|
canEdit={canEdit}
|
||||||
|
onUpdate={(data) => onUpdate(item.id, data)}
|
||||||
onDelete={() => onDelete(item.id)}
|
onDelete={() => onDelete(item.id)}
|
||||||
>
|
>
|
||||||
<User
|
<User
|
||||||
|
@ -59,6 +63,7 @@ const Memberships = React.memo(
|
||||||
<AddPopup
|
<AddPopup
|
||||||
users={allUsers}
|
users={allUsers}
|
||||||
currentUserIds={items.map((item) => item.user.id)}
|
currentUserIds={items.map((item) => item.user.id)}
|
||||||
|
permissionsSelectStep={permissionsSelectStep}
|
||||||
title={addTitle}
|
title={addTitle}
|
||||||
onCreate={onCreate}
|
onCreate={onCreate}
|
||||||
>
|
>
|
||||||
|
@ -75,6 +80,7 @@ Memberships.propTypes = {
|
||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
allUsers: PropTypes.array.isRequired,
|
allUsers: PropTypes.array.isRequired,
|
||||||
/* eslint-enable react/forbid-prop-types */
|
/* eslint-enable react/forbid-prop-types */
|
||||||
|
permissionsSelectStep: PropTypes.elementType,
|
||||||
addTitle: PropTypes.string,
|
addTitle: PropTypes.string,
|
||||||
leaveButtonContent: PropTypes.string,
|
leaveButtonContent: PropTypes.string,
|
||||||
leaveConfirmationTitle: PropTypes.string,
|
leaveConfirmationTitle: PropTypes.string,
|
||||||
|
@ -87,10 +93,12 @@ Memberships.propTypes = {
|
||||||
canEdit: PropTypes.bool,
|
canEdit: PropTypes.bool,
|
||||||
canLeaveIfLast: PropTypes.bool,
|
canLeaveIfLast: PropTypes.bool,
|
||||||
onCreate: PropTypes.func.isRequired,
|
onCreate: PropTypes.func.isRequired,
|
||||||
|
onUpdate: PropTypes.func,
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
Memberships.defaultProps = {
|
Memberships.defaultProps = {
|
||||||
|
permissionsSelectStep: undefined,
|
||||||
addTitle: undefined,
|
addTitle: undefined,
|
||||||
leaveButtonContent: undefined,
|
leaveButtonContent: undefined,
|
||||||
leaveConfirmationTitle: undefined,
|
leaveConfirmationTitle: undefined,
|
||||||
|
@ -102,6 +110,7 @@ Memberships.defaultProps = {
|
||||||
deleteConfirmationButtonContent: undefined,
|
deleteConfirmationButtonContent: undefined,
|
||||||
canEdit: true,
|
canEdit: true,
|
||||||
canLeaveIfLast: true,
|
canLeaveIfLast: true,
|
||||||
|
onUpdate: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Memberships;
|
export default Memberships;
|
||||||
|
|
|
@ -123,6 +123,10 @@ export default {
|
||||||
BOARD_MEMBERSHIP_CREATE__FAILURE: 'BOARD_MEMBERSHIP_CREATE__FAILURE',
|
BOARD_MEMBERSHIP_CREATE__FAILURE: 'BOARD_MEMBERSHIP_CREATE__FAILURE',
|
||||||
BOARD_MEMBERSHIP_CREATE_HANDLE: 'BOARD_MEMBERSHIP_CREATE_HANDLE',
|
BOARD_MEMBERSHIP_CREATE_HANDLE: 'BOARD_MEMBERSHIP_CREATE_HANDLE',
|
||||||
BOARD_MEMBERSHIP_CREATE_HANDLE__PROJECT_FETCH: 'BOARD_MEMBERSHIP_CREATE_HANDLE__PROJECT_FETCH',
|
BOARD_MEMBERSHIP_CREATE_HANDLE__PROJECT_FETCH: 'BOARD_MEMBERSHIP_CREATE_HANDLE__PROJECT_FETCH',
|
||||||
|
BOARD_MEMBERSHIP_UPDATE: 'BOARD_MEMBERSHIP_UPDATE',
|
||||||
|
BOARD_MEMBERSHIP_UPDATE__SUCCESS: 'BOARD_MEMBERSHIP_UPDATE__SUCCESS',
|
||||||
|
BOARD_MEMBERSHIP_UPDATE__FAILURE: 'BOARD_MEMBERSHIP_UPDATE__FAILURE',
|
||||||
|
BOARD_MEMBERSHIP_UPDATE_HANDLE: 'BOARD_MEMBERSHIP_UPDATE_HANDLE',
|
||||||
BOARD_MEMBERSHIP_DELETE: 'BOARD_MEMBERSHIP_DELETE',
|
BOARD_MEMBERSHIP_DELETE: 'BOARD_MEMBERSHIP_DELETE',
|
||||||
BOARD_MEMBERSHIP_DELETE__SUCCESS: 'BOARD_MEMBERSHIP_DELETE__SUCCESS',
|
BOARD_MEMBERSHIP_DELETE__SUCCESS: 'BOARD_MEMBERSHIP_DELETE__SUCCESS',
|
||||||
BOARD_MEMBERSHIP_DELETE__FAILURE: 'BOARD_MEMBERSHIP_DELETE__FAILURE',
|
BOARD_MEMBERSHIP_DELETE__FAILURE: 'BOARD_MEMBERSHIP_DELETE__FAILURE',
|
||||||
|
|
|
@ -88,6 +88,8 @@ export default {
|
||||||
|
|
||||||
MEMBERSHIP_IN_CURRENT_BOARD_CREATE: `${PREFIX}/MEMBERSHIP_IN_CURRENT_BOARD_CREATE`,
|
MEMBERSHIP_IN_CURRENT_BOARD_CREATE: `${PREFIX}/MEMBERSHIP_IN_CURRENT_BOARD_CREATE`,
|
||||||
BOARD_MEMBERSHIP_CREATE_HANDLE: `${PREFIX}/BOARD_MEMBERSHIP_CREATE_HANDLE`,
|
BOARD_MEMBERSHIP_CREATE_HANDLE: `${PREFIX}/BOARD_MEMBERSHIP_CREATE_HANDLE`,
|
||||||
|
BOARD_MEMBERSHIP_UPDATE: `${PREFIX}/BOARD_MEMBERSHIP_UPDATE`,
|
||||||
|
BOARD_MEMBERSHIP_UPDATE_HANDLE: `${PREFIX}/BOARD_MEMBERSHIP_UPDATE_HANDLE`,
|
||||||
BOARD_MEMBERSHIP_DELETE: `${PREFIX}/BOARD_MEMBERSHIP_DELETE`,
|
BOARD_MEMBERSHIP_DELETE: `${PREFIX}/BOARD_MEMBERSHIP_DELETE`,
|
||||||
BOARD_MEMBERSHIP_DELETE_HANDLE: `${PREFIX}/BOARD_MEMBERSHIP_DELETE_HANDLE`,
|
BOARD_MEMBERSHIP_DELETE_HANDLE: `${PREFIX}/BOARD_MEMBERSHIP_DELETE_HANDLE`,
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,11 @@ export const BoardTypes = {
|
||||||
KANBAN: 'kanban',
|
KANBAN: 'kanban',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BoardMembershipRoles = {
|
||||||
|
EDITOR: 'editor',
|
||||||
|
VIEWER: 'viewer',
|
||||||
|
};
|
||||||
|
|
||||||
export const ActivityTypes = {
|
export const ActivityTypes = {
|
||||||
CREATE_CARD: 'createCard',
|
CREATE_CARD: 'createCard',
|
||||||
MOVE_CARD: 'moveCard',
|
MOVE_CARD: 'moveCard',
|
||||||
|
|
|
@ -27,6 +27,7 @@ const mapDispatchToProps = (dispatch) =>
|
||||||
bindActionCreators(
|
bindActionCreators(
|
||||||
{
|
{
|
||||||
onMembershipCreate: entryActions.createMembershipInCurrentBoard,
|
onMembershipCreate: entryActions.createMembershipInCurrentBoard,
|
||||||
|
onMembershipUpdate: entryActions.updateBoardMembership,
|
||||||
onMembershipDelete: entryActions.deleteBoardMembership,
|
onMembershipDelete: entryActions.deleteBoardMembership,
|
||||||
onUserToFilterAdd: entryActions.addUserToFilterInCurrentBoard,
|
onUserToFilterAdd: entryActions.addUserToFilterInCurrentBoard,
|
||||||
onUserFromFilterRemove: entryActions.removeUserFromFilterInCurrentBoard,
|
onUserFromFilterRemove: entryActions.removeUserFromFilterInCurrentBoard,
|
||||||
|
|
|
@ -3,17 +3,18 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import selectors from '../selectors';
|
import selectors from '../selectors';
|
||||||
import entryActions from '../entry-actions';
|
import entryActions from '../entry-actions';
|
||||||
|
import { BoardMembershipRoles } from '../constants/Enums';
|
||||||
import BoardKanban from '../components/BoardKanban';
|
import BoardKanban from '../components/BoardKanban';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
const { cardId } = selectors.selectPath(state);
|
const { cardId } = selectors.selectPath(state);
|
||||||
const isCurrentUserMember = selectors.selectIsCurrentUserMemberForCurrentBoard(state);
|
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
||||||
const listIds = selectors.selectListIdsForCurrentBoard(state);
|
const listIds = selectors.selectListIdsForCurrentBoard(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listIds,
|
listIds,
|
||||||
isCardModalOpened: !!cardId,
|
isCardModalOpened: !!cardId,
|
||||||
canEdit: isCurrentUserMember,
|
canEdit: !!currentUserMembership && currentUserMembership.role === BoardMembershipRoles.EDITOR,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import selectors from '../selectors';
|
import selectors from '../selectors';
|
||||||
import entryActions from '../entry-actions';
|
import entryActions from '../entry-actions';
|
||||||
|
import { BoardMembershipRoles } from '../constants/Enums';
|
||||||
import Card from '../components/Card';
|
import Card from '../components/Card';
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
|
@ -17,7 +18,7 @@ const makeMapStateToProps = () => {
|
||||||
const allProjectsToLists = selectors.selectProjectsToListsForCurrentUser(state);
|
const allProjectsToLists = selectors.selectProjectsToListsForCurrentUser(state);
|
||||||
const allBoardMemberships = selectors.selectMembershipsForCurrentBoard(state);
|
const allBoardMemberships = selectors.selectMembershipsForCurrentBoard(state);
|
||||||
const allLabels = selectors.selectLabelsForCurrentBoard(state);
|
const allLabels = selectors.selectLabelsForCurrentBoard(state);
|
||||||
const isCurrentUserMember = selectors.selectIsCurrentUserMemberForCurrentBoard(state);
|
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
||||||
|
|
||||||
const { name, dueDate, timer, coverUrl, boardId, listId, isPersisted } = selectCardById(
|
const { name, dueDate, timer, coverUrl, boardId, listId, isPersisted } = selectCardById(
|
||||||
state,
|
state,
|
||||||
|
@ -47,7 +48,8 @@ const makeMapStateToProps = () => {
|
||||||
allProjectsToLists,
|
allProjectsToLists,
|
||||||
allBoardMemberships,
|
allBoardMemberships,
|
||||||
allLabels,
|
allLabels,
|
||||||
canEdit: isCurrentUserMember,
|
canEdit:
|
||||||
|
!!currentUserMembership && currentUserMembership.role === BoardMembershipRoles.EDITOR,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,7 @@ import omit from 'lodash/omit';
|
||||||
import selectors from '../selectors';
|
import selectors from '../selectors';
|
||||||
import entryActions from '../entry-actions';
|
import entryActions from '../entry-actions';
|
||||||
import Paths from '../constants/Paths';
|
import Paths from '../constants/Paths';
|
||||||
|
import { BoardMembershipRoles } from '../constants/Enums';
|
||||||
import CardModal from '../components/CardModal';
|
import CardModal from '../components/CardModal';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
|
@ -14,7 +15,7 @@ const mapStateToProps = (state) => {
|
||||||
const isCurrentUserManager = selectors.selectIsCurrentUserManagerForCurrentProject(state);
|
const isCurrentUserManager = selectors.selectIsCurrentUserManagerForCurrentProject(state);
|
||||||
const allBoardMemberships = selectors.selectMembershipsForCurrentBoard(state);
|
const allBoardMemberships = selectors.selectMembershipsForCurrentBoard(state);
|
||||||
const allLabels = selectors.selectLabelsForCurrentBoard(state);
|
const allLabels = selectors.selectLabelsForCurrentBoard(state);
|
||||||
const isCurrentUserMember = selectors.selectIsCurrentUserMemberForCurrentBoard(state);
|
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
|
@ -57,7 +58,11 @@ const mapStateToProps = (state) => {
|
||||||
allProjectsToLists,
|
allProjectsToLists,
|
||||||
allBoardMemberships,
|
allBoardMemberships,
|
||||||
allLabels,
|
allLabels,
|
||||||
canEdit: isCurrentUserMember,
|
canEdit: !!currentUserMembership && currentUserMembership.role === BoardMembershipRoles.EDITOR,
|
||||||
|
canEditCommentActivities:
|
||||||
|
!!currentUserMembership &&
|
||||||
|
(currentUserMembership.role === BoardMembershipRoles.EDITOR ||
|
||||||
|
currentUserMembership.canComment),
|
||||||
canEditAllCommentActivities: isCurrentUserManager,
|
canEditAllCommentActivities: isCurrentUserManager,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import selectors from '../selectors';
|
import selectors from '../selectors';
|
||||||
import entryActions from '../entry-actions';
|
import entryActions from '../entry-actions';
|
||||||
|
import { BoardMembershipRoles } from '../constants/Enums';
|
||||||
import List from '../components/List';
|
import List from '../components/List';
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
|
@ -12,7 +13,7 @@ const makeMapStateToProps = () => {
|
||||||
return (state, { id, index }) => {
|
return (state, { id, index }) => {
|
||||||
const { name, isPersisted } = selectListById(state, id);
|
const { name, isPersisted } = selectListById(state, id);
|
||||||
const cardIds = selectCardIdsByListId(state, id);
|
const cardIds = selectCardIdsByListId(state, id);
|
||||||
const isCurrentUserMember = selectors.selectIsCurrentUserMemberForCurrentBoard(state);
|
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
|
@ -20,7 +21,8 @@ const makeMapStateToProps = () => {
|
||||||
name,
|
name,
|
||||||
isPersisted,
|
isPersisted,
|
||||||
cardIds,
|
cardIds,
|
||||||
canEdit: isCurrentUserMember,
|
canEdit:
|
||||||
|
!!currentUserMembership && currentUserMembership.role === BoardMembershipRoles.EDITOR,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,21 @@ const handleBoardMembershipCreate = (boardMembership) => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updateBoardMembership = (id, data) => ({
|
||||||
|
type: EntryActionTypes.BOARD_MEMBERSHIP_UPDATE,
|
||||||
|
payload: {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBoardMembershipUpdate = (boardMembership) => ({
|
||||||
|
type: EntryActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE,
|
||||||
|
payload: {
|
||||||
|
boardMembership,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const deleteBoardMembership = (id) => ({
|
const deleteBoardMembership = (id) => ({
|
||||||
type: EntryActionTypes.BOARD_MEMBERSHIP_DELETE,
|
type: EntryActionTypes.BOARD_MEMBERSHIP_DELETE,
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -31,6 +46,8 @@ const handleBoardMembershipDelete = (boardMembership) => ({
|
||||||
export default {
|
export default {
|
||||||
createMembershipInCurrentBoard,
|
createMembershipInCurrentBoard,
|
||||||
handleBoardMembershipCreate,
|
handleBoardMembershipCreate,
|
||||||
|
updateBoardMembership,
|
||||||
|
handleBoardMembershipUpdate,
|
||||||
deleteBoardMembership,
|
deleteBoardMembership,
|
||||||
handleBoardMembershipDelete,
|
handleBoardMembershipDelete,
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,6 +41,9 @@ export default {
|
||||||
background: 'Background',
|
background: 'Background',
|
||||||
board: 'Board',
|
board: 'Board',
|
||||||
boardNotFound_title: 'Board Not Found',
|
boardNotFound_title: 'Board Not Found',
|
||||||
|
canComment: 'Can comment',
|
||||||
|
canEditContentOfBoard: 'Can edit the content of the board',
|
||||||
|
canOnlyViewBoard: 'Can only view the board',
|
||||||
cardActions_title: 'Card Actions',
|
cardActions_title: 'Card Actions',
|
||||||
cardNotFound_title: 'Card Not Found',
|
cardNotFound_title: 'Card Not Found',
|
||||||
cardOrActionAreDeleted: 'Card or action are deleted',
|
cardOrActionAreDeleted: 'Card or action are deleted',
|
||||||
|
@ -66,6 +69,7 @@ export default {
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
detectAutomatically: 'Detect automatically',
|
detectAutomatically: 'Detect automatically',
|
||||||
dropFileToUpload: 'Drop file to upload',
|
dropFileToUpload: 'Drop file to upload',
|
||||||
|
editor: 'Editor',
|
||||||
editAttachment_title: 'Edit Attachment',
|
editAttachment_title: 'Edit Attachment',
|
||||||
editAvatar_title: 'Edit Avatar',
|
editAvatar_title: 'Edit Avatar',
|
||||||
editBoard_title: 'Edit Board',
|
editBoard_title: 'Edit Board',
|
||||||
|
@ -74,6 +78,7 @@ export default {
|
||||||
editInformation_title: 'Edit Information',
|
editInformation_title: 'Edit Information',
|
||||||
editLabel_title: 'Edit Label',
|
editLabel_title: 'Edit Label',
|
||||||
editPassword_title: 'Edit Password',
|
editPassword_title: 'Edit Password',
|
||||||
|
editPermissions_title: 'Edit Permissions',
|
||||||
editTimer_title: 'Edit Timer',
|
editTimer_title: 'Edit Timer',
|
||||||
editUsername_title: 'Edit Username',
|
editUsername_title: 'Edit Username',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
|
@ -127,6 +132,7 @@ export default {
|
||||||
seconds: 'Seconds',
|
seconds: 'Seconds',
|
||||||
selectBoard: 'Select board',
|
selectBoard: 'Select board',
|
||||||
selectList: 'Select list',
|
selectList: 'Select list',
|
||||||
|
selectPermissions_title: 'Select Permissions',
|
||||||
selectProject: 'Select project',
|
selectProject: 'Select project',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default',
|
subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default',
|
||||||
|
@ -146,6 +152,7 @@ export default {
|
||||||
username: 'Username',
|
username: 'Username',
|
||||||
usernameAlreadyInUse: 'Username already in use',
|
usernameAlreadyInUse: 'Username already in use',
|
||||||
users: 'Users',
|
users: 'Users',
|
||||||
|
viewer: 'Viewer',
|
||||||
writeComment: 'Write a comment...',
|
writeComment: 'Write a comment...',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -157,6 +164,7 @@ export default {
|
||||||
addCard_title: 'Add Card',
|
addCard_title: 'Add Card',
|
||||||
addComment: 'Add comment',
|
addComment: 'Add comment',
|
||||||
addList: 'Add list',
|
addList: 'Add list',
|
||||||
|
addMember: 'Add member',
|
||||||
addMoreDetailedDescription: 'Add more detailed description',
|
addMoreDetailedDescription: 'Add more detailed description',
|
||||||
addTask: 'Add task',
|
addTask: 'Add task',
|
||||||
addToCard: 'Add to card',
|
addToCard: 'Add to card',
|
||||||
|
@ -188,6 +196,7 @@ export default {
|
||||||
editEmail_title: 'Edit E-mail',
|
editEmail_title: 'Edit E-mail',
|
||||||
editInformation_title: 'Edit Information',
|
editInformation_title: 'Edit Information',
|
||||||
editPassword_title: 'Edit Password',
|
editPassword_title: 'Edit Password',
|
||||||
|
editPermissions: 'Edit permissions',
|
||||||
editTimer_title: 'Edit Timer',
|
editTimer_title: 'Edit Timer',
|
||||||
editTitle_title: 'Edit Title',
|
editTitle_title: 'Edit Title',
|
||||||
editUsername_title: 'Edit Username',
|
editUsername_title: 'Edit Username',
|
||||||
|
|
|
@ -39,6 +39,9 @@ export default {
|
||||||
authentication: 'Аутентификация',
|
authentication: 'Аутентификация',
|
||||||
board: 'Доска',
|
board: 'Доска',
|
||||||
boardNotFound: 'Доска не найдена',
|
boardNotFound: 'Доска не найдена',
|
||||||
|
canComment: 'Может комментировать',
|
||||||
|
canEditContentOfBoard: 'Может редактировать содержимое доски',
|
||||||
|
canOnlyViewBoard: 'Может только просматривать доску',
|
||||||
cardActions: 'Действия с карточкой',
|
cardActions: 'Действия с карточкой',
|
||||||
cardNotFound: 'Карточка не найдена',
|
cardNotFound: 'Карточка не найдена',
|
||||||
cardOrActionAreDeleted: 'Карточка или действие удалены',
|
cardOrActionAreDeleted: 'Карточка или действие удалены',
|
||||||
|
@ -64,6 +67,7 @@ export default {
|
||||||
description: 'Описание',
|
description: 'Описание',
|
||||||
detectAutomatically: 'Определить автоматически',
|
detectAutomatically: 'Определить автоматически',
|
||||||
dropFileToUpload: 'Перетяните файл, чтобы загрузить',
|
dropFileToUpload: 'Перетяните файл, чтобы загрузить',
|
||||||
|
editor: 'Редактор',
|
||||||
editAttachment: 'Изменение вложения',
|
editAttachment: 'Изменение вложения',
|
||||||
editAvatar: 'Изменение аватара',
|
editAvatar: 'Изменение аватара',
|
||||||
editBoard: 'Изменение доски',
|
editBoard: 'Изменение доски',
|
||||||
|
@ -72,6 +76,7 @@ export default {
|
||||||
editEmail: 'Изменение e-mail',
|
editEmail: 'Изменение e-mail',
|
||||||
editLabel: 'Изменения метки',
|
editLabel: 'Изменения метки',
|
||||||
editPassword: 'Изменение пароля',
|
editPassword: 'Изменение пароля',
|
||||||
|
editPermissions: 'Редактирование разрешений',
|
||||||
editTimer: 'Изменение таймера',
|
editTimer: 'Изменение таймера',
|
||||||
editTitle: 'Изменение названия',
|
editTitle: 'Изменение названия',
|
||||||
editUsername: 'Изменение имени пользователя',
|
editUsername: 'Изменение имени пользователя',
|
||||||
|
@ -122,6 +127,7 @@ export default {
|
||||||
seconds: 'Секунды',
|
seconds: 'Секунды',
|
||||||
selectBoard: 'Выберите доску',
|
selectBoard: 'Выберите доску',
|
||||||
selectList: 'Выберите список',
|
selectList: 'Выберите список',
|
||||||
|
selectPermissions: 'Выбор разрешений',
|
||||||
selectProject: 'Выберите проект',
|
selectProject: 'Выберите проект',
|
||||||
settings: 'Настройки',
|
settings: 'Настройки',
|
||||||
subscribeToMyOwnCardsByDefault: 'По умолчанию подписаться на мои собственные карточки',
|
subscribeToMyOwnCardsByDefault: 'По умолчанию подписаться на мои собственные карточки',
|
||||||
|
@ -141,6 +147,7 @@ export default {
|
||||||
username: 'Имя пользователя',
|
username: 'Имя пользователя',
|
||||||
usernameAlreadyInUse: 'Имя пользователя уже занято',
|
usernameAlreadyInUse: 'Имя пользователя уже занято',
|
||||||
users: 'Пользователи',
|
users: 'Пользователи',
|
||||||
|
viewer: 'Читатель',
|
||||||
writeComment: 'Напишите комментарий...',
|
writeComment: 'Напишите комментарий...',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -151,6 +158,7 @@ export default {
|
||||||
addCard: 'Добавить карточку',
|
addCard: 'Добавить карточку',
|
||||||
addComment: 'Добавить комментарий',
|
addComment: 'Добавить комментарий',
|
||||||
addList: 'Добавить список',
|
addList: 'Добавить список',
|
||||||
|
addMember: 'Добавить участника',
|
||||||
addMoreDetailedDescription: 'Добавить более подробное описание',
|
addMoreDetailedDescription: 'Добавить более подробное описание',
|
||||||
addTask: 'Добавить задачу',
|
addTask: 'Добавить задачу',
|
||||||
addToCard: 'Добавить на карточку',
|
addToCard: 'Добавить на карточку',
|
||||||
|
@ -178,6 +186,7 @@ export default {
|
||||||
editDescription: 'Изменить описание',
|
editDescription: 'Изменить описание',
|
||||||
editEmail: 'Изменить e-mail',
|
editEmail: 'Изменить e-mail',
|
||||||
editPassword: 'Изменить пароль',
|
editPassword: 'Изменить пароль',
|
||||||
|
editPermissions: 'Изменить разрешения',
|
||||||
editTask: 'Изменить задачу',
|
editTask: 'Изменить задачу',
|
||||||
editTimer: 'Изменить таймер',
|
editTimer: 'Изменить таймер',
|
||||||
editTitle: 'Изменить название',
|
editTitle: 'Изменить название',
|
||||||
|
|
|
@ -175,6 +175,14 @@ export default class extends Model {
|
||||||
return this.lists.orderBy('position');
|
return this.lists.orderBy('position');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMembershipModel(userId) {
|
||||||
|
return this.memberships
|
||||||
|
.filter({
|
||||||
|
userId,
|
||||||
|
})
|
||||||
|
.first();
|
||||||
|
}
|
||||||
|
|
||||||
hasMemberUser(userId) {
|
hasMemberUser(userId) {
|
||||||
return this.memberships
|
return this.memberships
|
||||||
.filter({
|
.filter({
|
||||||
|
|
|
@ -7,6 +7,8 @@ export default class extends Model {
|
||||||
|
|
||||||
static fields = {
|
static fields = {
|
||||||
id: attr(),
|
id: attr(),
|
||||||
|
role: attr(),
|
||||||
|
canComment: attr(),
|
||||||
boardId: fk({
|
boardId: fk({
|
||||||
to: 'Board',
|
to: 'Board',
|
||||||
as: 'board',
|
as: 'board',
|
||||||
|
@ -65,6 +67,15 @@ export default class extends Model {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ActionTypes.BOARD_MEMBERSHIP_UPDATE:
|
||||||
|
BoardMembership.withId(payload.id).update(payload.data);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ActionTypes.BOARD_MEMBERSHIP_UPDATE__SUCCESS:
|
||||||
|
case ActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE:
|
||||||
|
BoardMembership.upsert(payload.boardMembership);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ActionTypes.BOARD_MEMBERSHIP_DELETE:
|
case ActionTypes.BOARD_MEMBERSHIP_DELETE:
|
||||||
BoardMembership.withId(payload.id).deleteWithRelated();
|
BoardMembership.withId(payload.id).deleteWithRelated();
|
||||||
|
|
|
@ -135,6 +135,24 @@ export function* handleBoardMembershipCreate(boardMembership) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* updateBoardMembership(id, data) {
|
||||||
|
yield put(actions.updateBoardMembership(id, data));
|
||||||
|
|
||||||
|
let boardMembership;
|
||||||
|
try {
|
||||||
|
({ item: boardMembership } = yield call(request, api.updateBoardMembership, id, data));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(actions.updateBoardMembership.failure(id, error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield put(actions.updateBoardMembership.success(boardMembership));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* handleBoardMembershipUpdate(boardMembership) {
|
||||||
|
yield put(actions.handleBoardMembershipUpdate(boardMembership));
|
||||||
|
}
|
||||||
|
|
||||||
export function* deleteBoardMembership(id) {
|
export function* deleteBoardMembership(id) {
|
||||||
let boardMembership = yield select(selectors.selectBoardMembershipById, id);
|
let boardMembership = yield select(selectors.selectBoardMembershipById, id);
|
||||||
|
|
||||||
|
@ -184,6 +202,8 @@ export default {
|
||||||
createBoardMembership,
|
createBoardMembership,
|
||||||
createMembershipInCurrentBoard,
|
createMembershipInCurrentBoard,
|
||||||
handleBoardMembershipCreate,
|
handleBoardMembershipCreate,
|
||||||
|
updateBoardMembership,
|
||||||
|
handleBoardMembershipUpdate,
|
||||||
deleteBoardMembership,
|
deleteBoardMembership,
|
||||||
handleBoardMembershipDelete,
|
handleBoardMembershipDelete,
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,12 @@ export default function* boardMembershipsWatchers() {
|
||||||
takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE, ({ payload: { boardMembership } }) =>
|
takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE, ({ payload: { boardMembership } }) =>
|
||||||
services.handleBoardMembershipCreate(boardMembership),
|
services.handleBoardMembershipCreate(boardMembership),
|
||||||
),
|
),
|
||||||
|
takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_UPDATE, ({ payload: { id, data } }) =>
|
||||||
|
services.updateBoardMembership(id, data),
|
||||||
|
),
|
||||||
|
takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE, ({ payload: { boardMembership } }) =>
|
||||||
|
services.handleBoardMembershipUpdate(boardMembership),
|
||||||
|
),
|
||||||
takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_DELETE, ({ payload: { id } }) =>
|
takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_DELETE, ({ payload: { id } }) =>
|
||||||
services.deleteBoardMembership(id),
|
services.deleteBoardMembership(id),
|
||||||
),
|
),
|
||||||
|
|
|
@ -64,6 +64,10 @@ const createSocketEventsChannel = () =>
|
||||||
emit(entryActions.handleBoardMembershipCreate(item));
|
emit(entryActions.handleBoardMembershipCreate(item));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBoardMembershipUpdate = ({ item }) => {
|
||||||
|
emit(entryActions.handleBoardMembershipUpdate(item));
|
||||||
|
};
|
||||||
|
|
||||||
const handleBoardMembershipDelete = ({ item }) => {
|
const handleBoardMembershipDelete = ({ item }) => {
|
||||||
emit(entryActions.handleBoardMembershipDelete(item));
|
emit(entryActions.handleBoardMembershipDelete(item));
|
||||||
};
|
};
|
||||||
|
@ -183,6 +187,7 @@ const createSocketEventsChannel = () =>
|
||||||
socket.on('boardDelete', handleBoardDelete);
|
socket.on('boardDelete', handleBoardDelete);
|
||||||
|
|
||||||
socket.on('boardMembershipCreate', handleBoardMembershipCreate);
|
socket.on('boardMembershipCreate', handleBoardMembershipCreate);
|
||||||
|
socket.on('boardMembershipUpdate', handleBoardMembershipUpdate);
|
||||||
socket.on('boardMembershipDelete', handleBoardMembershipDelete);
|
socket.on('boardMembershipDelete', handleBoardMembershipDelete);
|
||||||
|
|
||||||
socket.on('listCreate', handleListCreate);
|
socket.on('listCreate', handleListCreate);
|
||||||
|
@ -238,6 +243,7 @@ const createSocketEventsChannel = () =>
|
||||||
socket.off('boardDelete', handleBoardDelete);
|
socket.off('boardDelete', handleBoardDelete);
|
||||||
|
|
||||||
socket.off('boardMembershipCreate', handleBoardMembershipCreate);
|
socket.off('boardMembershipCreate', handleBoardMembershipCreate);
|
||||||
|
socket.off('boardMembershipUpdate', handleBoardMembershipUpdate);
|
||||||
socket.off('boardMembershipDelete', handleBoardMembershipDelete);
|
socket.off('boardMembershipDelete', handleBoardMembershipDelete);
|
||||||
|
|
||||||
socket.off('listCreate', handleListCreate);
|
socket.off('listCreate', handleListCreate);
|
||||||
|
|
|
@ -147,22 +147,28 @@ export const selectFilterLabelsForCurrentBoard = createSelector(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectIsCurrentUserMemberForCurrentBoard = createSelector(
|
export const selectCurrentUserMembershipForCurrentBoard = createSelector(
|
||||||
orm,
|
orm,
|
||||||
(state) => selectPath(state).boardId,
|
(state) => selectPath(state).boardId,
|
||||||
(state) => selectCurrentUserId(state),
|
(state) => selectCurrentUserId(state),
|
||||||
({ Board }, id, currentUserId) => {
|
({ Board }, id, currentUserId) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return false;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const boardModel = Board.withId(id);
|
const boardModel = Board.withId(id);
|
||||||
|
|
||||||
if (!boardModel) {
|
if (!boardModel) {
|
||||||
return false;
|
return boardModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
return boardModel.hasMemberUser(currentUserId);
|
const boardMembershipModel = boardModel.getMembershipModel(currentUserId);
|
||||||
|
|
||||||
|
if (!boardMembershipModel) {
|
||||||
|
return boardMembershipModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return boardMembershipModel.ref;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -175,5 +181,5 @@ export default {
|
||||||
selectListIdsForCurrentBoard,
|
selectListIdsForCurrentBoard,
|
||||||
selectFilterUsersForCurrentBoard,
|
selectFilterUsersForCurrentBoard,
|
||||||
selectFilterLabelsForCurrentBoard,
|
selectFilterLabelsForCurrentBoard,
|
||||||
selectIsCurrentUserMemberForCurrentBoard,
|
selectCurrentUserMembershipForCurrentBoard,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
CARD_NOT_FOUND: {
|
CARD_NOT_FOUND: {
|
||||||
cardNotFound: 'Card not found',
|
cardNotFound: 'Card not found',
|
||||||
},
|
},
|
||||||
|
@ -18,6 +21,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
cardNotFound: {
|
cardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -33,12 +39,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.cardId)
|
.getProjectPath(inputs.cardId)
|
||||||
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, card.boardId);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: card.boardId,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
this.req
|
this.req
|
||||||
.file('file')
|
.file('file')
|
||||||
.upload(sails.helpers.utils.createAttachmentReceiver(), async (error, files) => {
|
.upload(sails.helpers.utils.createAttachmentReceiver(), async (error, files) => {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
ATTACHMENT_NOT_FOUND: {
|
ATTACHMENT_NOT_FOUND: {
|
||||||
attachmentNotFound: 'Attachment not found',
|
attachmentNotFound: 'Attachment not found',
|
||||||
},
|
},
|
||||||
|
@ -14,6 +17,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
attachmentNotFound: {
|
attachmentNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -29,12 +35,19 @@ module.exports = {
|
||||||
let { attachment } = path;
|
let { attachment } = path;
|
||||||
const { card, board } = path;
|
const { card, board } = path;
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.ATTACHMENT_NOT_FOUND; // Forbidden
|
throw Errors.ATTACHMENT_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
attachment = await sails.helpers.attachments.deleteOne(attachment, board, card, this.req);
|
attachment = await sails.helpers.attachments.deleteOne(attachment, board, card, this.req);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
ATTACHMENT_NOT_FOUND: {
|
ATTACHMENT_NOT_FOUND: {
|
||||||
attachmentNotFound: 'Attachment not found',
|
attachmentNotFound: 'Attachment not found',
|
||||||
},
|
},
|
||||||
|
@ -18,6 +21,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
attachmentNotFound: {
|
attachmentNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -33,12 +39,19 @@ module.exports = {
|
||||||
let { attachment } = path;
|
let { attachment } = path;
|
||||||
const { board } = path;
|
const { board } = path;
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.ATTACHMENT_NOT_FOUND; // Forbidden
|
throw Errors.ATTACHMENT_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['name']);
|
const values = _.pick(inputs, ['name']);
|
||||||
attachment = await sails.helpers.attachments.updateOne(attachment, values, board, this.req);
|
attachment = await sails.helpers.attachments.updateOne(attachment, values, board, this.req);
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,14 @@ module.exports = {
|
||||||
regex: /^[0-9]+$/,
|
regex: /^[0-9]+$/,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
role: {
|
||||||
|
type: 'string',
|
||||||
|
isIn: Object.values(BoardMembership.Roles),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
canComment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
@ -58,8 +66,10 @@ module.exports = {
|
||||||
throw Error.USER_NOT_FOUND;
|
throw Error.USER_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const values = _.pick(inputs, ['role', 'canComment']);
|
||||||
|
|
||||||
const boardMembership = await sails.helpers.boardMemberships
|
const boardMembership = await sails.helpers.boardMemberships
|
||||||
.createOne(user, board, this.req)
|
.createOne(values, user, board, this.req)
|
||||||
.intercept('userAlreadyBoardMember', () => Errors.USER_ALREADY_BOARD_MEMBER);
|
.intercept('userAlreadyBoardMember', () => Errors.USER_ALREADY_BOARD_MEMBER);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
57
server/api/controllers/board-memberships/update.js
Normal file
57
server/api/controllers/board-memberships/update.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
const Errors = {
|
||||||
|
BOARD_MEMBERSHIP_NOT_FOUND: {
|
||||||
|
boardMembershipNotFound: 'Board membership not found',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
inputs: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^[0-9]+$/,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: 'string',
|
||||||
|
isIn: Object.values(BoardMembership.Roles),
|
||||||
|
},
|
||||||
|
canComment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
exits: {
|
||||||
|
boardMembershipNotFound: {
|
||||||
|
responseType: 'notFound',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
async fn(inputs) {
|
||||||
|
const { currentUser } = this.req;
|
||||||
|
|
||||||
|
const path = await sails.helpers.boardMemberships
|
||||||
|
.getProjectPath(inputs.id)
|
||||||
|
.intercept('pathNotFound', () => Errors.BOARD_MEMBERSHIP_NOT_FOUND);
|
||||||
|
|
||||||
|
let { boardMembership } = path;
|
||||||
|
const { project } = path;
|
||||||
|
|
||||||
|
const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
|
||||||
|
|
||||||
|
if (!isProjectManager) {
|
||||||
|
throw Errors.BOARD_MEMBERSHIP_NOT_FOUND; // Forbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = _.pick(inputs, ['role', 'canComment']);
|
||||||
|
|
||||||
|
boardMembership = await sails.helpers.boardMemberships.updateOne(
|
||||||
|
boardMembership,
|
||||||
|
values,
|
||||||
|
this.req,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
item: boardMembership,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
CARD_NOT_FOUND: {
|
CARD_NOT_FOUND: {
|
||||||
cardNotFound: 'Card not found',
|
cardNotFound: 'Card not found',
|
||||||
},
|
},
|
||||||
|
@ -25,6 +28,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
cardNotFound: {
|
cardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -43,12 +49,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.cardId)
|
.getProjectPath(inputs.cardId)
|
||||||
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, card.boardId);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: card.boardId,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
const label = await Label.findOne({
|
const label = await Label.findOne({
|
||||||
id: inputs.labelId,
|
id: inputs.labelId,
|
||||||
boardId: card.boardId,
|
boardId: card.boardId,
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
CARD_NOT_FOUND: {
|
CARD_NOT_FOUND: {
|
||||||
cardNotFound: 'Card not found',
|
cardNotFound: 'Card not found',
|
||||||
},
|
},
|
||||||
|
@ -22,6 +25,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
cardNotFound: {
|
cardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -37,12 +43,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.cardId)
|
.getProjectPath(inputs.cardId)
|
||||||
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
let cardLabel = await CardLabel.findOne({
|
let cardLabel = await CardLabel.findOne({
|
||||||
cardId: inputs.cardId,
|
cardId: inputs.cardId,
|
||||||
labelId: inputs.labelId,
|
labelId: inputs.labelId,
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
CARD_NOT_FOUND: {
|
CARD_NOT_FOUND: {
|
||||||
cardNotFound: 'Card not found',
|
cardNotFound: 'Card not found',
|
||||||
},
|
},
|
||||||
|
@ -25,6 +28,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
cardNotFound: {
|
cardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -43,13 +49,20 @@ module.exports = {
|
||||||
.getProjectPath(inputs.cardId)
|
.getProjectPath(inputs.cardId)
|
||||||
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
||||||
|
|
||||||
let isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, card.boardId);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: card.boardId,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
isBoardMember = await sails.helpers.users.isBoardMember(inputs.userId, card.boardId);
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBoardMember = await sails.helpers.users.isBoardMember(inputs.userId, card.boardId);
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!isBoardMember) {
|
||||||
throw Errors.USER_NOT_FOUND;
|
throw Errors.USER_NOT_FOUND;
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
CARD_NOT_FOUND: {
|
CARD_NOT_FOUND: {
|
||||||
cardNotFound: 'Card not found',
|
cardNotFound: 'Card not found',
|
||||||
},
|
},
|
||||||
|
@ -22,6 +25,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
cardNotFound: {
|
cardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -37,12 +43,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.cardId)
|
.getProjectPath(inputs.cardId)
|
||||||
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
let cardMembership = await CardMembership.findOne({
|
let cardMembership = await CardMembership.findOne({
|
||||||
cardId: inputs.cardId,
|
cardId: inputs.cardId,
|
||||||
userId: inputs.userId,
|
userId: inputs.userId,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
BOARD_NOT_FOUND: {
|
BOARD_NOT_FOUND: {
|
||||||
boardNotFound: 'Board not found',
|
boardNotFound: 'Board not found',
|
||||||
},
|
},
|
||||||
|
@ -67,6 +70,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
boardNotFound: {
|
boardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -88,12 +94,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.boardId)
|
.getProjectPath(inputs.boardId)
|
||||||
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
let list;
|
let list;
|
||||||
if (!_.isUndefined(inputs.listId)) {
|
if (!_.isUndefined(inputs.listId)) {
|
||||||
list = await List.findOne({
|
list = await List.findOne({
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
CARD_NOT_FOUND: {
|
CARD_NOT_FOUND: {
|
||||||
cardNotFound: 'Card not found',
|
cardNotFound: 'Card not found',
|
||||||
},
|
},
|
||||||
|
@ -14,6 +17,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
cardNotFound: {
|
cardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -26,12 +32,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.id)
|
.getProjectPath(inputs.id)
|
||||||
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, card.boardId);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: card.boardId,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
card = await sails.helpers.cards.deleteOne(card, this.req);
|
card = await sails.helpers.cards.deleteOne(card, this.req);
|
||||||
|
|
||||||
if (!card) {
|
if (!card) {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
CARD_NOT_FOUND: {
|
CARD_NOT_FOUND: {
|
||||||
cardNotFound: 'Card not found',
|
cardNotFound: 'Card not found',
|
||||||
},
|
},
|
||||||
|
@ -83,6 +86,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
cardNotFound: {
|
cardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -110,23 +116,37 @@ module.exports = {
|
||||||
let { card } = path;
|
let { card } = path;
|
||||||
const { list, board } = path;
|
const { list, board } = path;
|
||||||
|
|
||||||
let isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
let boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
let nextBoard;
|
let nextBoard;
|
||||||
if (!_.isUndefined(inputs.boardId)) {
|
if (!_.isUndefined(inputs.boardId)) {
|
||||||
({ board: nextBoard } = await sails.helpers.boards
|
({ board: nextBoard } = await sails.helpers.boards
|
||||||
.getProjectPath(inputs.boardId)
|
.getProjectPath(inputs.boardId)
|
||||||
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND));
|
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND));
|
||||||
|
|
||||||
isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, nextBoard.id);
|
boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: nextBoard.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextList;
|
let nextList;
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
CARD_NOT_FOUND: {
|
CARD_NOT_FOUND: {
|
||||||
cardNotFound: 'Card not found',
|
cardNotFound: 'Card not found',
|
||||||
},
|
},
|
||||||
|
@ -18,6 +21,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
cardNotFound: {
|
cardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -30,12 +36,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.cardId)
|
.getProjectPath(inputs.cardId)
|
||||||
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, card.boardId);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: card.boardId,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR && !boardMembership.canComment) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
type: Action.Types.COMMENT_CARD,
|
type: Action.Types.COMMENT_CARD,
|
||||||
data: _.pick(inputs, ['text']),
|
data: _.pick(inputs, ['text']),
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
COMMENT_ACTION_NOT_FOUND: {
|
COMMENT_ACTION_NOT_FOUND: {
|
||||||
commentActionNotFound: 'Comment action not found',
|
commentActionNotFound: 'Comment action not found',
|
||||||
},
|
},
|
||||||
|
@ -14,6 +17,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
commentActionNotFound: {
|
commentActionNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -39,11 +45,18 @@ module.exports = {
|
||||||
throw Errors.COMMENT_ACTION_NOT_FOUND; // Forbidden
|
throw Errors.COMMENT_ACTION_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.COMMENT_ACTION_NOT_FOUND; // Forbidden
|
throw Errors.COMMENT_ACTION_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR && !boardMembership.canComment) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
action = await sails.helpers.actions.deleteOne(action, board, this.req);
|
action = await sails.helpers.actions.deleteOne(action, board, this.req);
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
COMMENT_ACTION_NOT_FOUND: {
|
COMMENT_ACTION_NOT_FOUND: {
|
||||||
commentActionNotFound: 'Comment action not found',
|
commentActionNotFound: 'Comment action not found',
|
||||||
},
|
},
|
||||||
|
@ -18,6 +21,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
commentActionNotFound: {
|
commentActionNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -43,11 +49,18 @@ module.exports = {
|
||||||
throw Errors.COMMENT_ACTION_NOT_FOUND; // Forbidden
|
throw Errors.COMMENT_ACTION_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.COMMENT_ACTION_NOT_FOUND; // Forbidden
|
throw Errors.COMMENT_ACTION_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR && !boardMembership.canComment) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
BOARD_NOT_FOUND: {
|
BOARD_NOT_FOUND: {
|
||||||
boardNotFound: 'Board not found',
|
boardNotFound: 'Board not found',
|
||||||
},
|
},
|
||||||
|
@ -24,6 +27,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
boardNotFound: {
|
boardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -36,12 +42,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.boardId)
|
.getProjectPath(inputs.boardId)
|
||||||
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['name', 'color']);
|
const values = _.pick(inputs, ['name', 'color']);
|
||||||
const label = await sails.helpers.labels.createOne(values, board, this.req);
|
const label = await sails.helpers.labels.createOne(values, board, this.req);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
LABEL_NOT_FOUND: {
|
LABEL_NOT_FOUND: {
|
||||||
labelNotFound: 'Label not found',
|
labelNotFound: 'Label not found',
|
||||||
},
|
},
|
||||||
|
@ -14,6 +17,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
labelNotFound: {
|
labelNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -26,12 +32,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.id)
|
.getProjectPath(inputs.id)
|
||||||
.intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, label.boardId);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: label.boardId,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.LABEL_NOT_FOUND; // Forbidden
|
throw Errors.LABEL_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
label = await sails.helpers.labels.deleteOne(label, this.req);
|
label = await sails.helpers.labels.deleteOne(label, this.req);
|
||||||
|
|
||||||
if (!label) {
|
if (!label) {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
LABEL_NOT_FOUND: {
|
LABEL_NOT_FOUND: {
|
||||||
labelNotFound: 'Label not found',
|
labelNotFound: 'Label not found',
|
||||||
},
|
},
|
||||||
|
@ -24,6 +27,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
labelNotFound: {
|
labelNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -36,12 +42,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.id)
|
.getProjectPath(inputs.id)
|
||||||
.intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, label.boardId);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: label.boardId,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.LABEL_NOT_FOUND; // Forbidden
|
throw Errors.LABEL_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['name', 'color']);
|
const values = _.pick(inputs, ['name', 'color']);
|
||||||
label = await sails.helpers.labels.updateOne(label, values, this.req);
|
label = await sails.helpers.labels.updateOne(label, values, this.req);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
BOARD_NOT_FOUND: {
|
BOARD_NOT_FOUND: {
|
||||||
boardNotFound: 'Board not found',
|
boardNotFound: 'Board not found',
|
||||||
},
|
},
|
||||||
|
@ -22,6 +25,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
boardNotFound: {
|
boardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -34,12 +40,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.boardId)
|
.getProjectPath(inputs.boardId)
|
||||||
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['position', 'name']);
|
const values = _.pick(inputs, ['position', 'name']);
|
||||||
const list = await sails.helpers.lists.createOne(values, board, this.req);
|
const list = await sails.helpers.lists.createOne(values, board, this.req);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
LIST_NOT_FOUND: {
|
LIST_NOT_FOUND: {
|
||||||
listNotFound: 'List not found',
|
listNotFound: 'List not found',
|
||||||
},
|
},
|
||||||
|
@ -14,6 +17,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
listNotFound: {
|
listNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -26,12 +32,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.id)
|
.getProjectPath(inputs.id)
|
||||||
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, list.boardId);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: list.boardId,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.LIST_NOT_FOUND; // Forbidden
|
throw Errors.LIST_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
list = await sails.helpers.lists.deleteOne(list, this.req);
|
list = await sails.helpers.lists.deleteOne(list, this.req);
|
||||||
|
|
||||||
if (!list) {
|
if (!list) {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
LIST_NOT_FOUND: {
|
LIST_NOT_FOUND: {
|
||||||
listNotFound: 'List not found',
|
listNotFound: 'List not found',
|
||||||
},
|
},
|
||||||
|
@ -21,6 +24,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
listNotFound: {
|
listNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -33,12 +39,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.id)
|
.getProjectPath(inputs.id)
|
||||||
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, list.boardId);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: list.boardId,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.LIST_NOT_FOUND; // Forbidden
|
throw Errors.LIST_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['position', 'name']);
|
const values = _.pick(inputs, ['position', 'name']);
|
||||||
list = await sails.helpers.lists.updateOne(list, values, this.req);
|
list = await sails.helpers.lists.updateOne(list, values, this.req);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
CARD_NOT_FOUND: {
|
CARD_NOT_FOUND: {
|
||||||
cardNotFound: 'Card not found',
|
cardNotFound: 'Card not found',
|
||||||
},
|
},
|
||||||
|
@ -25,6 +28,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
cardNotFound: {
|
cardNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -37,12 +43,19 @@ module.exports = {
|
||||||
.getProjectPath(inputs.cardId)
|
.getProjectPath(inputs.cardId)
|
||||||
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, card.boardId);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: card.boardId,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['position', 'name', 'isCompleted']);
|
const values = _.pick(inputs, ['position', 'name', 'isCompleted']);
|
||||||
const task = await sails.helpers.tasks.createOne(values, card, this.req);
|
const task = await sails.helpers.tasks.createOne(values, card, this.req);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
TASK_NOT_FOUND: {
|
TASK_NOT_FOUND: {
|
||||||
taskNotFound: 'Task not found',
|
taskNotFound: 'Task not found',
|
||||||
},
|
},
|
||||||
|
@ -14,6 +17,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
taskNotFound: {
|
taskNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -29,12 +35,19 @@ module.exports = {
|
||||||
let { task } = path;
|
let { task } = path;
|
||||||
const { board } = path;
|
const { board } = path;
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.TASK_NOT_FOUND; // Forbidden
|
throw Errors.TASK_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
task = await sails.helpers.tasks.deleteOne(task, board, this.req);
|
task = await sails.helpers.tasks.deleteOne(task, board, this.req);
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
TASK_NOT_FOUND: {
|
TASK_NOT_FOUND: {
|
||||||
taskNotFound: 'Task not found',
|
taskNotFound: 'Task not found',
|
||||||
},
|
},
|
||||||
|
@ -24,6 +27,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
taskNotFound: {
|
taskNotFound: {
|
||||||
responseType: 'notFound',
|
responseType: 'notFound',
|
||||||
},
|
},
|
||||||
|
@ -39,12 +45,19 @@ module.exports = {
|
||||||
let { task } = path;
|
let { task } = path;
|
||||||
const { board } = path;
|
const { board } = path;
|
||||||
|
|
||||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);
|
const boardMembership = await BoardMembership.findOne({
|
||||||
|
boardId: board.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isBoardMember) {
|
if (!boardMembership) {
|
||||||
throw Errors.TASK_NOT_FOUND; // Forbidden
|
throw Errors.TASK_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['position', 'name', 'isCompleted']);
|
const values = _.pick(inputs, ['position', 'name', 'isCompleted']);
|
||||||
task = await sails.helpers.tasks.updateOne(task, values, board, this.req);
|
task = await sails.helpers.tasks.updateOne(task, values, board, this.req);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
inputs: {
|
inputs: {
|
||||||
|
values: {
|
||||||
|
type: 'json',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
user: {
|
user: {
|
||||||
type: 'ref',
|
type: 'ref',
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -18,7 +22,16 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
|
if (inputs.values.role === BoardMembership.Roles.EDITOR) {
|
||||||
|
delete inputs.values.canComment; // eslint-disable-line no-param-reassign
|
||||||
|
} else if (inputs.values.role === BoardMembership.Roles.VIEWER) {
|
||||||
|
if (_.isNil(inputs.values.canComment)) {
|
||||||
|
inputs.values.canComment = false; // eslint-disable-line no-param-reassign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const boardMembership = await BoardMembership.create({
|
const boardMembership = await BoardMembership.create({
|
||||||
|
...inputs.values,
|
||||||
boardId: inputs.board.id,
|
boardId: inputs.board.id,
|
||||||
userId: inputs.user.id,
|
userId: inputs.user.id,
|
||||||
})
|
})
|
||||||
|
|
46
server/api/helpers/board-memberships/update-one.js
Normal file
46
server/api/helpers/board-memberships/update-one.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
module.exports = {
|
||||||
|
inputs: {
|
||||||
|
record: {
|
||||||
|
type: 'ref',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
type: 'json',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
type: 'ref',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
async fn(inputs) {
|
||||||
|
const role = inputs.values.role || inputs.record.role;
|
||||||
|
|
||||||
|
if (role === BoardMembership.Roles.EDITOR) {
|
||||||
|
inputs.values.canComment = null; // eslint-disable-line no-param-reassign
|
||||||
|
} else if (role === BoardMembership.Roles.VIEWER) {
|
||||||
|
const canComment = _.isUndefined(inputs.values.canComment)
|
||||||
|
? inputs.record.canComment
|
||||||
|
: inputs.values.canComment;
|
||||||
|
|
||||||
|
if (_.isNull(canComment)) {
|
||||||
|
inputs.values.canComment = false; // eslint-disable-line no-param-reassign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const boardMembership = await BoardMembership.updateOne(inputs.record.id).set(inputs.values);
|
||||||
|
|
||||||
|
if (boardMembership) {
|
||||||
|
sails.sockets.broadcast(
|
||||||
|
`board:${boardMembership.boardId}`,
|
||||||
|
'boardMembershipUpdate',
|
||||||
|
{
|
||||||
|
item: boardMembership,
|
||||||
|
},
|
||||||
|
inputs.request,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return boardMembership;
|
||||||
|
},
|
||||||
|
};
|
|
@ -57,6 +57,7 @@ module.exports = {
|
||||||
const boardMembership = await BoardMembership.create({
|
const boardMembership = await BoardMembership.create({
|
||||||
boardId: board.id,
|
boardId: board.id,
|
||||||
userId: inputs.user.id,
|
userId: inputs.user.id,
|
||||||
|
role: BoardMembership.Roles.EDITOR,
|
||||||
}).fetch();
|
}).fetch();
|
||||||
|
|
||||||
managerUserIds.forEach((userId) => {
|
managerUserIds.forEach((userId) => {
|
||||||
|
|
|
@ -5,12 +5,30 @@
|
||||||
* @docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
|
* @docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const Roles = {
|
||||||
|
EDITOR: 'editor',
|
||||||
|
VIEWER: 'viewer',
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
Roles,
|
||||||
|
|
||||||
attributes: {
|
attributes: {
|
||||||
// ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗
|
// ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗
|
||||||
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
|
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
|
||||||
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
|
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
|
||||||
|
|
||||||
|
role: {
|
||||||
|
type: 'string',
|
||||||
|
isIn: Object.values(Roles),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
canComment: {
|
||||||
|
type: 'boolean',
|
||||||
|
allowNull: true,
|
||||||
|
columnName: 'can_comment',
|
||||||
|
},
|
||||||
|
|
||||||
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
|
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
|
||||||
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
|
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
|
||||||
// ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝
|
// ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝
|
||||||
|
|
|
@ -37,6 +37,7 @@ module.exports.routes = {
|
||||||
'DELETE /api/boards/:id': 'boards/delete',
|
'DELETE /api/boards/:id': 'boards/delete',
|
||||||
|
|
||||||
'POST /api/boards/:boardId/memberships': 'board-memberships/create',
|
'POST /api/boards/:boardId/memberships': 'board-memberships/create',
|
||||||
|
'PATCH /api/board-memberships/:id': 'board-memberships/update',
|
||||||
'DELETE /api/board-memberships/:id': 'board-memberships/delete',
|
'DELETE /api/board-memberships/:id': 'board-memberships/delete',
|
||||||
|
|
||||||
'POST /api/boards/:boardId/labels': 'labels/create',
|
'POST /api/boards/:boardId/labels': 'labels/create',
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
module.exports.up = async (knex) => {
|
||||||
|
await knex.schema.table('board_membership', (table) => {
|
||||||
|
/* Columns */
|
||||||
|
|
||||||
|
table.text('role').notNullable().defaultTo('editor');
|
||||||
|
table.boolean('can_comment');
|
||||||
|
});
|
||||||
|
|
||||||
|
return knex.schema.alterTable('board_membership', (table) => {
|
||||||
|
table.text('role').notNullable().alter();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.down = (knex) =>
|
||||||
|
knex.schema.table('board_membership', (table) => {
|
||||||
|
table.dropColumn('role');
|
||||||
|
table.dropColumn('can_comment');
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue