1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-08-10 07:55:27 +02:00

fix: Add members and labels when duplicating, refactoring

This commit is contained in:
Maksim Eltyshev 2024-04-05 22:37:31 +02:00
parent bec23795c2
commit b86af05358
21 changed files with 427 additions and 187 deletions

View file

@ -23,10 +23,14 @@ createCard.failure = (localId, error) => ({
},
});
const handleCardCreate = (card) => ({
const handleCardCreate = (card, cardMemberships, cardLabels, tasks, attachments) => ({
type: ActionTypes.CARD_CREATE_HANDLE,
payload: {
card,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
@ -60,6 +64,34 @@ const handleCardUpdate = (card) => ({
},
});
const duplicateCard = (id, card, taskIds) => ({
type: ActionTypes.CARD_DUPLICATE,
payload: {
id,
card,
taskIds,
},
});
duplicateCard.success = (localId, card, cardMemberships, cardLabels, tasks) => ({
type: ActionTypes.CARD_DUPLICATE__SUCCESS,
payload: {
localId,
card,
cardMemberships,
cardLabels,
tasks,
},
});
duplicateCard.failure = (id, error) => ({
type: ActionTypes.CARD_DUPLICATE__FAILURE,
payload: {
id,
error,
},
});
const deleteCard = (id) => ({
type: ActionTypes.CARD_DELETE,
payload: {
@ -89,44 +121,12 @@ const handleCardDelete = (card) => ({
},
});
const duplicateCard = (id, localId) => ({
type: ActionTypes.CARD_DUPLICATE,
payload: {
id,
localId,
},
});
duplicateCard.success = (localId, card) => ({
type: ActionTypes.CARD_DUPLICATE__SUCCESS,
payload: {
localId,
card,
},
});
duplicateCard.failure = (id, error) => ({
type: ActionTypes.CARD_DUPLICATE__FAILURE,
payload: {
id,
error,
},
});
const handleCardDuplicate = (card) => ({
type: ActionTypes.CARD_DUPLICATE_HANDLE,
payload: {
card,
},
});
export default {
createCard,
handleCardCreate,
updateCard,
handleCardUpdate,
duplicateCard,
deleteCard,
handleCardDelete,
duplicateCard,
handleCardDuplicate,
};

View file

@ -7,15 +7,6 @@ const createTask = (task) => ({
},
});
const createTasks = () => ({});
createTasks.success = (tasks) => ({
type: ActionTypes.TASKS_CREATE__SUCCESS,
payload: {
tasks,
},
});
createTask.success = (localId, task) => ({
type: ActionTypes.TASK_CREATE__SUCCESS,
payload: {
@ -99,7 +90,6 @@ const handleTaskDelete = (task) => ({
});
export default {
createTasks,
createTask,
handleTaskCreate,
updateTask,

View file

@ -57,17 +57,18 @@ const updateCard = (id, data, headers) =>
item: transformCard(body.item),
}));
const duplicateCard = (id, data, headers) =>
socket.post(`/cards/${id}/duplicate`, data, headers).then((body) => ({
...body,
item: transformCard(body.item),
}));
const deleteCard = (id, headers) =>
socket.delete(`/cards/${id}`, undefined, headers).then((body) => ({
...body,
item: transformCard(body.item),
}));
const duplicateCard = (id, headers) =>
socket.post(`/cards/${id}/duplicate`, undefined, headers).then((body) => ({
...body,
item: transformCard(body.item),
}));
/* Event handlers */
const makeHandleCardCreate = (next) => (body) => {

View file

@ -21,7 +21,6 @@ const StepTypes = {
EDIT_DUE_DATE: 'EDIT_DUE_DATE',
EDIT_STOPWATCH: 'EDIT_STOPWATCH',
MOVE: 'MOVE',
DUPLICATE: 'DUPLICATE',
DELETE: 'DELETE',
};
@ -37,8 +36,8 @@ const ActionsStep = React.memo(
onUpdate,
onMove,
onTransfer,
onDelete,
onDuplicate,
onDelete,
onUserAdd,
onUserRemove,
onBoardFetch,
@ -215,7 +214,7 @@ const ActionsStep = React.memo(
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDuplicateClick}>
{t('action.duplicate', {
{t('action.duplicateCard', {
context: 'title',
})}
</Menu.Item>
@ -244,8 +243,8 @@ ActionsStep.propTypes = {
onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onDuplicate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired,
onBoardFetch: PropTypes.func.isRequired,

View file

@ -41,8 +41,8 @@ const Card = React.memo(
onUpdate,
onMove,
onTransfer,
onDelete,
onDuplicate,
onDelete,
onUserAdd,
onUserRemove,
onBoardFetch,
@ -240,8 +240,8 @@ Card.propTypes = {
onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onDuplicate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired,
onBoardFetch: PropTypes.func.isRequired,

View file

@ -55,8 +55,8 @@ const CardModal = React.memo(
onUpdate,
onMove,
onTransfer,
onDelete,
onDuplicate,
onDelete,
onUserAdd,
onUserRemove,
onBoardFetch,
@ -565,8 +565,8 @@ CardModal.propTypes = {
onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onDuplicate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired,
onBoardFetch: PropTypes.func.isRequired,

View file

@ -194,14 +194,13 @@ export default {
CARD_TRANSFER: 'CARD_TRANSFER',
CARD_TRANSFER__SUCCESS: 'CARD_TRANSFER__SUCCESS',
CARD_TRANSFER__FAILURE: 'CARD_TRANSFER__FAILURE',
CARD_DUPLICATE: 'CARD_DUPLICATE',
CARD_DUPLICATE__SUCCESS: 'CARD_DUPLICATE__SUCCESS',
CARD_DUPLICATE__FAILURE: 'CARD_DUPLICATE__FAILURE',
CARD_DELETE: 'CARD_DELETE',
CARD_DELETE__SUCCESS: 'CARD_DELETE__SUCCESS',
CARD_DELETE__FAILURE: 'CARD_DELETE__FAILURE',
CARD_DELETE_HANDLE: 'CARD_DELETE_HANDLE',
CARD_DUPLICATE: 'CARD_DUPLICATE',
CARD_DUPLICATE__SUCCESS: 'CARD_DUPLICATE__SUCCESS',
CARD_DUPLICATE__FAILURE: 'CARD_DUPLICATE__FAILURE',
CARD_DUPLICATE_HANDLE: 'CARD_DUPLICATE_HANDLE',
/* Tasks */
@ -209,7 +208,6 @@ export default {
TASK_CREATE__SUCCESS: 'TASK_CREATE__SUCCESS',
TASK_CREATE__FAILURE: 'TASK_CREATE__FAILURE',
TASK_CREATE_HANDLE: 'TASK_CREATE_HANDLE',
TASKS_CREATE__SUCCESS: 'TASKS_CREATE__SUCCESS',
TASK_UPDATE: 'TASK_UPDATE',
TASK_UPDATE__SUCCESS: 'TASK_UPDATE__SUCCESS',
TASK_UPDATE__FAILURE: 'TASK_UPDATE__FAILURE',

View file

@ -132,12 +132,11 @@ export default {
CURRENT_CARD_MOVE: `${PREFIX}/CURRENT_CARD_MOVE`,
CARD_TRANSFER: `${PREFIX}/CARD_TRANSFER`,
CURRENT_CARD_TRANSFER: `${PREFIX}/CURRENT_CARD_TRANSFER`,
CARD_DUPLICATE: `${PREFIX}/CARD_DUPLICATE`,
CURRENT_CARD_DUPLICATE: `${PREFIX}/CURRENT_CARD_DUPLICATE`,
CARD_DELETE: `${PREFIX}/CARD_DELETE`,
CURRENT_CARD_DELETE: `${PREFIX}/CURRENT_CARD_DELETE`,
CARD_DELETE_HANDLE: `${PREFIX}/CARD_DELETE_HANDLE`,
CARD_DUPLICATE: `${PREFIX}/CARD_DUPLICATE`,
CURRENT_CARD_DUPLICATE: `${PREFIX}/CURRENT_CARD_DUPLICATE`,
CARD_DUPLICATE_HANDLE: `${PREFIX}/CARD_DUPLICATE_HANDLE`,
/* Tasks */

View file

@ -62,8 +62,8 @@ const mapDispatchToProps = (dispatch, { id }) =>
onUpdate: (data) => entryActions.updateCard(id, data),
onMove: (listId, index) => entryActions.moveCard(id, listId, index),
onTransfer: (boardId, listId) => entryActions.transferCard(id, boardId, listId),
onDelete: () => entryActions.deleteCard(id),
onDuplicate: () => entryActions.duplicateCard(id),
onDelete: () => entryActions.deleteCard(id),
onUserAdd: (userId) => entryActions.addUserToCard(userId, id),
onUserRemove: (userId) => entryActions.removeUserFromCard(userId, id),
onBoardFetch: entryActions.fetchBoard,

View file

@ -78,8 +78,8 @@ const mapDispatchToProps = (dispatch) =>
onUpdate: entryActions.updateCurrentCard,
onMove: entryActions.moveCurrentCard,
onTransfer: entryActions.transferCurrentCard,
onDelete: entryActions.deleteCurrentCard,
onDuplicate: entryActions.duplicateCurrentCard,
onDelete: entryActions.deleteCurrentCard,
onUserAdd: entryActions.addUserToCurrentCard,
onUserRemove: entryActions.removeUserFromCurrentCard,
onBoardFetch: entryActions.fetchBoard,

View file

@ -74,6 +74,18 @@ const transferCurrentCard = (boardId, listId, index = 0) => ({
},
});
const duplicateCard = (id) => ({
type: EntryActionTypes.CARD_DUPLICATE,
payload: {
id,
},
});
const duplicateCurrentCard = () => ({
type: EntryActionTypes.CURRENT_CARD_DUPLICATE,
payload: {},
});
const deleteCard = (id) => ({
type: EntryActionTypes.CARD_DELETE,
payload: {
@ -93,25 +105,6 @@ const handleCardDelete = (card) => ({
},
});
const duplicateCard = (id) => ({
type: EntryActionTypes.CARD_DUPLICATE,
payload: {
id,
},
});
const duplicateCurrentCard = () => ({
type: EntryActionTypes.CURRENT_CARD_DUPLICATE,
payload: {},
});
const handleCardDuplicate = (card) => ({
type: EntryActionTypes.CARD_DUPLICATE_HANDLE,
payload: {
card,
},
});
export default {
createCard,
handleCardCreate,
@ -122,10 +115,9 @@ export default {
moveCurrentCard,
transferCard,
transferCurrentCard,
duplicateCard,
duplicateCurrentCard,
deleteCard,
deleteCurrentCard,
handleCardDelete,
duplicateCard,
duplicateCurrentCard,
handleCardDuplicate,
};

View file

@ -51,6 +51,7 @@ export default {
cardNotFound_title: 'Card Not Found',
cardOrActionAreDeleted: 'Card or action are deleted.',
color: 'Color',
copy_inline: 'copy',
createBoard_title: 'Create Board',
createLabel_title: 'Create Label',
createNewOneOrSelectExistingOne: 'Create a new one or select<br />an existing one.',
@ -197,6 +198,7 @@ export default {
deleteTask_title: 'Delete Task',
deleteUser: 'Delete user',
duplicate: 'Duplicate',
duplicateCard_title: 'Duplicate Card',
edit: 'Edit',
editDueDate_title: 'Edit Due Date',
editDescription_title: 'Edit Description',

View file

@ -1,3 +1,4 @@
import pick from 'lodash/pick';
import { attr, fk, many, oneToOne } from 'redux-orm';
import BaseModel from './BaseModel';
@ -165,7 +166,6 @@ export default class extends BaseModel {
break;
case ActionTypes.CARD_CREATE:
case ActionTypes.CARD_CREATE_HANDLE:
case ActionTypes.CARD_UPDATE__SUCCESS:
case ActionTypes.CARD_UPDATE_HANDLE:
Card.upsert(payload.card);
@ -176,10 +176,63 @@ export default class extends BaseModel {
Card.upsert(payload.card);
break;
case ActionTypes.CARD_CREATE_HANDLE: {
const cardModel = Card.upsert(payload.card);
payload.cardMemberships.forEach(({ userId }) => {
cardModel.users.add(userId);
});
payload.cardLabels.forEach(({ labelId }) => {
cardModel.labels.add(labelId);
});
break;
}
case ActionTypes.CARD_UPDATE:
Card.withId(payload.id).update(payload.data);
break;
case ActionTypes.CARD_DUPLICATE: {
const cardModel = Card.withId(payload.id);
const nextCardModel = Card.upsert({
...pick(cardModel.ref, [
'boardId',
'listId',
'position',
'name',
'description',
'dueDate',
'stopwatch',
]),
...payload.card,
});
cardModel.users.toRefArray().forEach(({ id }) => {
nextCardModel.users.add(id);
});
cardModel.labels.toRefArray().forEach(({ id }) => {
nextCardModel.labels.add(id);
});
break;
}
case ActionTypes.CARD_DUPLICATE__SUCCESS: {
Card.withId(payload.localId).deleteWithRelated();
const cardModel = Card.upsert(payload.card);
payload.cardMemberships.forEach(({ userId }) => {
cardModel.users.add(userId);
});
payload.cardLabels.forEach(({ labelId }) => {
cardModel.labels.add(labelId);
});
break;
}
case ActionTypes.CARD_DELETE:
Card.withId(payload.id).deleteWithRelated();
@ -194,21 +247,6 @@ export default class extends BaseModel {
break;
}
case ActionTypes.CARD_DUPLICATE: {
const cardModel = Card.withId(payload.id);
Card.upsert({
...cardModel.fields,
name: `${cardModel.name} (copy)`,
id: payload.localId,
});
break;
}
case ActionTypes.CARD_DUPLICATE__SUCCESS:
case ActionTypes.CARD_DUPLICATE_HANDLE: {
Card.withId(payload.localId).delete();
Card.upsert(payload.card);
break;
}
case ActionTypes.ACTIVITIES_FETCH:
Card.withId(payload.cardId).update({
isActivitiesFetching: true,

View file

@ -1,5 +1,6 @@
import { attr, fk } from 'redux-orm';
import { createLocalId } from '../utils/local-id';
import BaseModel from './BaseModel';
import ActionTypes from '../constants/ActionTypes';
@ -44,17 +45,25 @@ export default class extends BaseModel {
break;
case ActionTypes.BOARD_FETCH__SUCCESS:
case ActionTypes.CARD_CREATE_HANDLE:
case ActionTypes.CARD_DUPLICATE__SUCCESS:
payload.tasks.forEach((task) => {
Task.upsert(task);
});
break;
case ActionTypes.TASKS_CREATE__SUCCESS: {
payload.tasks.forEach((task) => {
Task.upsert(task);
case ActionTypes.CARD_DUPLICATE:
payload.taskIds.forEach((taskId, index) => {
const taskModel = Task.withId(taskId);
Task.upsert({
...taskModel.ref,
id: `${createLocalId()}-${index}`, // TODO: hack?
cardId: payload.card.id,
});
});
break;
}
case ActionTypes.TASK_CREATE:
case ActionTypes.TASK_CREATE_HANDLE:
case ActionTypes.TASK_UPDATE__SUCCESS:

View file

@ -5,6 +5,7 @@ import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import i18n from '../../../i18n';
import { createLocalId } from '../../../utils/local-id';
export function* createCard(listId, data, autoOpen) {
@ -41,8 +42,23 @@ export function* createCard(listId, data, autoOpen) {
}
}
export function* handleCardCreate(card) {
yield put(actions.handleCardCreate(card));
export function* handleCardCreate({ id }) {
let card;
let cardMemberships;
let cardLabels;
let tasks;
let attachments;
try {
({
item: card,
included: { cardMemberships, cardLabels, tasks, attachments },
} = yield call(request, api.getCard, id));
} catch (error) {
return;
}
yield put(actions.handleCardCreate(card, cardMemberships, cardLabels, tasks, attachments));
}
export function* updateCard(id, data) {
@ -106,6 +122,55 @@ export function* transferCurrentCard(boardId, listId, index) {
yield call(transferCard, cardId, boardId, listId, index);
}
export function* duplicateCard(id) {
const { listId, name } = yield select(selectors.selectCardById, id);
const index = yield select(selectors.selectCardIndexById, id);
const nextData = {
position: yield select(selectors.selectNextCardPosition, listId, index + 1),
name: `${name} (${i18n.t('common.copy', {
context: 'inline',
})})`,
};
const localId = yield call(createLocalId);
const taskIds = yield select(selectors.selectTaskIdsByCardId, id);
yield put(
actions.duplicateCard(
id,
{
...nextData,
id: localId,
},
taskIds,
),
);
let card;
let cardMemberships;
let cardLabels;
let tasks;
try {
({
item: card,
included: { cardMemberships, cardLabels, tasks },
} = yield call(request, api.duplicateCard, id, nextData));
} catch (error) {
yield put(actions.duplicateCard.failure(localId, error));
return;
}
yield put(actions.duplicateCard.success(localId, card, cardMemberships, cardLabels, tasks));
}
export function* duplicateCurrentCard() {
const { cardId } = yield select(selectors.selectPath);
yield call(duplicateCard, cardId);
}
export function* deleteCard(id) {
const { cardId, boardId } = yield select(selectors.selectPath);
@ -142,48 +207,19 @@ export function* handleCardDelete(card) {
yield put(actions.handleCardDelete(card));
}
export function* duplicateCard(id) {
const localId = yield call(createLocalId);
yield put(actions.duplicateCard(id, localId));
let card;
let included;
try {
({ item: card, included } = yield call(request, api.duplicateCard, id));
} catch (error) {
yield put(actions.duplicateCard.failure(localId, error));
return;
}
yield put(actions.duplicateCard.success(localId, card));
yield put(actions.createTasks.success(included.tasks));
}
export function* duplicateCurrentCard() {
const { cardId } = yield select(selectors.selectPath);
yield call(duplicateCard, cardId);
}
export function* handleCardDuplicate(card) {
yield put(actions.handleCardDuplicate(card));
}
export default {
createCard,
handleCardCreate,
updateCard,
updateCurrentCard,
handleCardUpdate,
moveCard,
moveCurrentCard,
transferCard,
transferCurrentCard,
handleCardUpdate,
duplicateCard,
duplicateCurrentCard,
deleteCard,
deleteCurrentCard,
handleCardDelete,
duplicateCard,
duplicateCurrentCard,
handleCardDuplicate,
};

View file

@ -32,15 +32,12 @@ export default function* cardsWatchers() {
takeEvery(EntryActionTypes.CURRENT_CARD_TRANSFER, ({ payload: { boardId, listId, index } }) =>
services.transferCurrentCard(boardId, listId, index),
),
takeEvery(EntryActionTypes.CARD_DUPLICATE, ({ payload: { id } }) => services.duplicateCard(id)),
takeEvery(EntryActionTypes.CURRENT_CARD_DUPLICATE, () => services.duplicateCurrentCard()),
takeEvery(EntryActionTypes.CARD_DELETE, ({ payload: { id } }) => services.deleteCard(id)),
takeEvery(EntryActionTypes.CURRENT_CARD_DELETE, () => services.deleteCurrentCard()),
takeEvery(EntryActionTypes.CARD_DELETE_HANDLE, ({ payload: { card } }) =>
services.handleCardDelete(card),
),
takeEvery(EntryActionTypes.CARD_DUPLICATE, ({ payload: { id } }) => services.duplicateCard(id)),
takeEvery(EntryActionTypes.CURRENT_CARD_DUPLICATE, () => services.duplicateCurrentCard()),
takeEvery(EntryActionTypes.CARD_DUPLICATE_HANDLE, ({ payload: { card } }) =>
services.handleCardDuplicate(card),
),
]);
}

View file

@ -26,6 +26,24 @@ export const makeSelectCardById = () =>
export const selectCardById = makeSelectCardById();
export const makeSelectCardIndexById = () =>
createSelector(
orm,
(_, id) => id,
({ Card }, id) => {
const cardModel = Card.withId(id);
if (!cardModel) {
return cardModel;
}
const cardModels = cardModel.list.getFilteredOrderedCardsModelArray();
return cardModels.findIndex((cardModelItem) => cardModelItem.id === cardModel.id);
},
);
export const selectCardIndexById = makeSelectCardIndexById();
export const makeSelectUsersByCardId = () =>
createSelector(
orm,
@ -60,6 +78,26 @@ export const makeSelectLabelsByCardId = () =>
export const selectLabelsByCardId = makeSelectLabelsByCardId();
export const makeSelectTaskIdsByCardId = () =>
createSelector(
orm,
(_, id) => id,
({ Card }, id) => {
const cardModel = Card.withId(id);
if (!cardModel) {
return cardModel;
}
return cardModel
.getOrderedTasksQuerySet()
.toRefArray()
.map((task) => task.id);
},
);
export const selectTaskIdsByCardId = makeSelectTaskIdsByCardId();
export const makeSelectTasksByCardId = () =>
createSelector(
orm,
@ -286,10 +324,14 @@ export const selectNotificationIdsForCurrentCard = createSelector(
export default {
makeSelectCardById,
selectCardById,
makeSelectCardIndexById,
selectCardIndexById,
makeSelectUsersByCardId,
selectUsersByCardId,
makeSelectLabelsByCardId,
selectLabelsByCardId,
makeSelectTaskIdsByCardId,
selectTaskIdsByCardId,
makeSelectTasksByCardId,
selectTasksByCardId,
makeSelectLastActivityIdByCardId,

View file

@ -14,6 +14,13 @@ module.exports = {
regex: /^[0-9]+$/,
required: true,
},
position: {
type: 'number',
required: true,
},
name: {
type: 'string',
},
},
exits: {
@ -28,7 +35,7 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { card } = await sails.helpers.cards
const { card, list, board } = await sails.helpers.cards
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
@ -45,46 +52,30 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS;
}
const { board, list } = await sails.helpers.lists
.getProjectPath(card.listId)
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
const values = _.pick(inputs, ['position', 'name']);
const values = _.pick(card, ['position', 'name', 'description', 'dueDate', 'stopwatch']);
const {
card: nextCard,
cardMemberships,
cardLabels,
tasks,
} = await sails.helpers.cards.duplicateOne.with({
board,
list,
record: card,
values: {
...values,
creatorUser: currentUser,
},
request: this.req,
});
const newCard = await sails.helpers.cards.createOne
.with({
board,
values: {
...values,
name: `${card.name} (copy)`,
list,
creatorUser: currentUser,
},
request: this.req,
})
.intercept('positionMustBeInValues', () => Errors.POSITION_MUST_BE_PRESENT);
const tasks = await sails.helpers.cards.getTasks(inputs.id);
const newTasks = await Promise.all(
tasks.map(async (task) => {
const taskValues = _.pick(task, ['position', 'name', 'isCompleted']);
const newTask = await sails.helpers.tasks.createOne.with({
values: {
...taskValues,
card: newCard,
},
request: this.req,
});
return newTask;
}),
);
// TODO: add labels
return {
item: newCard,
item: nextCard,
included: {
tasks: newTasks,
cardMemberships,
cardLabels,
tasks,
},
};
},

View file

@ -1,3 +1,5 @@
const POSITION_GAP = 65535; // TODO: move to config
module.exports = {
inputs: {
user: {
@ -124,7 +126,7 @@ module.exports = {
getUsedTrelloLabels().map(async (trelloLabel, index) => {
const plankaLabel = await Label.create({
boardId: inputs.board.id,
position: 65535 * (index + 1), // TODO: move to config
position: POSITION_GAP * (index + 1),
name: trelloLabel.name || null,
color: getPlankaLabelColor(trelloLabel.color),
}).fetch();

View file

@ -0,0 +1,144 @@
const valuesValidator = (value) => {
if (!_.isPlainObject(value)) {
return false;
}
if (!_.isUndefined(value.position) && !_.isFinite(value.position)) {
return false;
}
if (!_.isPlainObject(value.creatorUser)) {
return false;
}
return true;
};
module.exports = {
inputs: {
record: {
type: 'ref',
required: true,
},
values: {
type: 'ref',
custom: valuesValidator,
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
},
async fn(inputs) {
const { values } = inputs;
const cards = await sails.helpers.lists.getCards(inputs.record.listId);
const { position, repositions } = sails.helpers.utils.insertToPositionables(
values.position,
cards,
);
repositions.forEach(async ({ id, position: nextPosition }) => {
await Card.update({
id,
listId: inputs.record.listId,
}).set({
position: nextPosition,
});
sails.sockets.broadcast(`board:${inputs.record.boardId}`, 'cardUpdate', {
item: {
id,
position: nextPosition,
},
});
});
const card = await Card.create({
..._.pick(inputs.record, [
'boardId',
'listId',
'name',
'description',
'dueDate',
'stopwatch',
]),
...values,
position,
creatorUserId: values.creatorUser.id,
}).fetch();
const cardMemberships = await sails.helpers.cards.getCardMemberships(inputs.record.id);
const cardMembershipsValues = cardMemberships.map((cardMembership) => ({
..._.pick(cardMembership, ['userId']),
cardId: card.id,
}));
const nextCardMemberships = await CardMembership.createEach(cardMembershipsValues).fetch();
const cardLabels = await sails.helpers.cards.getCardLabels(inputs.record.id);
const cardLabelsValues = cardLabels.map((cardLabel) => ({
..._.pick(cardLabel, ['labelId']),
cardId: card.id,
}));
const nextCardLabels = await CardLabel.createEach(cardLabelsValues).fetch();
const tasks = await sails.helpers.cards.getTasks(inputs.record.id);
const tasksValues = tasks.map((task) => ({
..._.pick(task, ['position', 'name', 'isCompleted']),
cardId: card.id,
}));
const nextTasks = await Task.createEach(tasksValues).fetch();
sails.sockets.broadcast(
`board:${card.boardId}`,
'cardCreate',
{
item: card,
},
inputs.request,
);
if (values.creatorUser.subscribeToOwnCards) {
await CardSubscription.create({
cardId: card.id,
userId: card.creatorUserId,
}).tolerate('E_UNIQUE');
sails.sockets.broadcast(`user:${card.creatorUserId}`, 'cardUpdate', {
item: {
id: card.id,
isSubscribed: true,
},
});
}
await sails.helpers.actions.createOne.with({
values: {
card,
type: Action.Types.CREATE_CARD, // TODO: introduce separate type?
data: {
list: _.pick(inputs.list, ['id', 'name']),
},
user: values.creatorUser,
},
board: inputs.board,
});
return {
card,
cardMemberships: nextCardMemberships,
cardLabels: nextCardLabels,
tasks: nextTasks,
};
},
};

View file

@ -54,8 +54,8 @@ module.exports.routes = {
'POST /api/lists/:listId/cards': 'cards/create',
'GET /api/cards/:id': 'cards/show',
'POST /api/cards/:id/duplicate': 'cards/duplicate',
'PATCH /api/cards/:id': 'cards/update',
'POST /api/cards/:id/duplicate': 'cards/duplicate',
'DELETE /api/cards/:id': 'cards/delete',
'POST /api/cards/:cardId/memberships': 'card-memberships/create',
'DELETE /api/cards/:cardId/memberships': 'card-memberships/delete',