mirror of
https://github.com/plankanban/planka.git
synced 2025-07-23 15:19:44 +02:00
Initial commit
This commit is contained in:
commit
5ffef61fe7
613 changed files with 91659 additions and 0 deletions
0
server/api/controllers/.gitkeep
Normal file
0
server/api/controllers/.gitkeep
Normal file
48
server/api/controllers/access-tokens/create.js
Executable file
48
server/api/controllers/access-tokens/create.js
Executable file
|
@ -0,0 +1,48 @@
|
|||
const bcrypt = require('bcrypt');
|
||||
|
||||
const Errors = {
|
||||
EMAIL_NOT_EXIST: {
|
||||
unauthorized: 'Email does not exist'
|
||||
},
|
||||
PASSWORD_NOT_VALID: {
|
||||
unauthorized: 'Password is not valid'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
isEmail: true
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
unauthorized: {
|
||||
responseType: 'unauthorized'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const user = await sails.helpers.getUser({
|
||||
email: inputs.email.toLowerCase()
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw Errors.EMAIL_NOT_EXIST;
|
||||
}
|
||||
|
||||
if (!bcrypt.compareSync(inputs.password, user.password)) {
|
||||
throw Errors.PASSWORD_NOT_VALID;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: sails.helpers.signToken(user.id)
|
||||
});
|
||||
}
|
||||
};
|
55
server/api/controllers/actions/index.js
Executable file
55
server/api/controllers/actions/index.js
Executable file
|
@ -0,0 +1,55 @@
|
|||
const Errors = {
|
||||
CARD_NOT_FOUND: {
|
||||
notFound: 'Card is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
cardId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
beforeId: {
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { project } = await sails.helpers
|
||||
.getCardToProjectPath(inputs.cardId)
|
||||
.intercept('notFound', () => Errors.CARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const actions = await sails.helpers.getActionsForCard(
|
||||
inputs.cardId,
|
||||
inputs.beforeId
|
||||
);
|
||||
|
||||
const userIds = sails.helpers.mapRecords(actions, 'userId', true);
|
||||
const users = await sails.helpers.getUsers(userIds);
|
||||
|
||||
return exits.success({
|
||||
items: actions,
|
||||
included: {
|
||||
users
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
48
server/api/controllers/boards/create.js
Executable file
48
server/api/controllers/boards/create.js
Executable file
|
@ -0,0 +1,48 @@
|
|||
const Errors = {
|
||||
PROJECT_NOT_FOUND: {
|
||||
notFound: 'Project is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
projectId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
position: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const project = await Project.findOne(inputs.projectId);
|
||||
|
||||
if (!project) {
|
||||
throw Errors.PROJECT_NOT_FOUND;
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['position', 'name']);
|
||||
|
||||
const board = await sails.helpers.createBoard(project, values, this.req);
|
||||
|
||||
return exits.success({
|
||||
item: board,
|
||||
included: {
|
||||
lists: [],
|
||||
labels: []
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
38
server/api/controllers/boards/delete.js
Executable file
38
server/api/controllers/boards/delete.js
Executable file
|
@ -0,0 +1,38 @@
|
|||
const Errors = {
|
||||
BOARD_NOT_FOUND: {
|
||||
notFound: 'Board is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
let board = await Board.findOne(inputs.id);
|
||||
|
||||
if (!board) {
|
||||
throw Errors.BOARD_NOT_FOUND;
|
||||
}
|
||||
|
||||
board = await sails.helpers.deleteBoard(board, this.req);
|
||||
|
||||
if (!board) {
|
||||
throw Errors.BOARD_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: board
|
||||
});
|
||||
}
|
||||
};
|
84
server/api/controllers/boards/show.js
Executable file
84
server/api/controllers/boards/show.js
Executable file
|
@ -0,0 +1,84 @@
|
|||
const Errors = {
|
||||
BOARD_NOT_FOUND: {
|
||||
notFound: 'Board is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
// TODO: allow over HTTP without subscription
|
||||
if (!this.req.isSocket) {
|
||||
return this.res.badRequest();
|
||||
}
|
||||
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { board, project } = await sails.helpers
|
||||
.getBoardToProjectPath(inputs.id)
|
||||
.intercept('notFound', () => Errors.BOARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const lists = await sails.helpers.getListsForBoard(board.id);
|
||||
const labels = await sails.helpers.getLabelsForBoard(board.id);
|
||||
|
||||
const cards = await sails.helpers.getCardsForBoard(board.id);
|
||||
const cardIds = sails.helpers.mapRecords(cards);
|
||||
|
||||
const cardSubscriptions = await sails.helpers.getSubscriptionsByUserForCard(
|
||||
cardIds,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
const cardMemberships = await sails.helpers.getMembershipsForCard(cardIds);
|
||||
const cardLabels = await sails.helpers.getCardLabelsForCard(cardIds);
|
||||
|
||||
const tasks = await sails.helpers.getTasksForCard(cardIds);
|
||||
|
||||
const isSubscribedByCardId = cardSubscriptions.reduce(
|
||||
(result, cardSubscription) => ({
|
||||
...result,
|
||||
[cardSubscription.cardId]: true
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
cards.forEach(card => {
|
||||
card.isSubscribed = isSubscribedByCardId[card.id] || false;
|
||||
});
|
||||
|
||||
sails.sockets.join(this.req, `board:${board.id}`); // TODO: only when subscription needed
|
||||
|
||||
return exits.success({
|
||||
item: board,
|
||||
included: {
|
||||
lists,
|
||||
labels,
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
tasks
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
47
server/api/controllers/boards/update.js
Executable file
47
server/api/controllers/boards/update.js
Executable file
|
@ -0,0 +1,47 @@
|
|||
const Errors = {
|
||||
BOARD_NOT_FOUND: {
|
||||
notFound: 'Board is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
position: {
|
||||
type: 'number'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
let board = await Board.findOne(inputs.id);
|
||||
|
||||
if (!board) {
|
||||
throw Errors.BOARD_NOT_FOUND;
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['position', 'name']);
|
||||
|
||||
board = await sails.helpers.updateBoard(board, values, this.req);
|
||||
|
||||
if (!board) {
|
||||
throw Errors.BOARD_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: board
|
||||
});
|
||||
}
|
||||
};
|
67
server/api/controllers/card-labels/create.js
Executable file
67
server/api/controllers/card-labels/create.js
Executable file
|
@ -0,0 +1,67 @@
|
|||
const Errors = {
|
||||
CARD_NOT_FOUND: {
|
||||
notFound: 'Card is not found'
|
||||
},
|
||||
LABEL_NOT_FOUND: {
|
||||
notFound: 'Label is not found'
|
||||
},
|
||||
CARD_LABEL_EXIST: {
|
||||
conflict: 'Card label is already exist'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
cardId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
labelId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
},
|
||||
conflict: {
|
||||
responseType: 'conflict'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { card, project } = await sails.helpers
|
||||
.getCardToProjectPath(inputs.cardId)
|
||||
.intercept('notFound', () => Errors.CARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const label = await Label.findOne({
|
||||
id: inputs.labelId,
|
||||
boardId: card.boardId
|
||||
});
|
||||
|
||||
if (!label) {
|
||||
throw Errors.LABEL_NOT_FOUND;
|
||||
}
|
||||
|
||||
const cardLabel = await sails.helpers
|
||||
.createCardLabel(card, label, this.req)
|
||||
.intercept('conflict', () => Errors.CARD_LABEL_EXIST);
|
||||
|
||||
return exits.success({
|
||||
item: cardLabel
|
||||
});
|
||||
}
|
||||
};
|
63
server/api/controllers/card-labels/delete.js
Executable file
63
server/api/controllers/card-labels/delete.js
Executable file
|
@ -0,0 +1,63 @@
|
|||
const Errors = {
|
||||
CARD_NOT_FOUND: {
|
||||
notFound: 'Card is not found'
|
||||
},
|
||||
CARD_LABEL_NOT_FOUND: {
|
||||
notFound: 'Card label is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
cardId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
labelId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { board, project } = await sails.helpers
|
||||
.getCardToProjectPath(inputs.cardId)
|
||||
.intercept('notFound', () => Errors.CARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
let cardLabel = await CardLabel.findOne({
|
||||
cardId: inputs.cardId,
|
||||
labelId: inputs.labelId
|
||||
});
|
||||
|
||||
if (!cardLabel) {
|
||||
throw Errors.CARD_LABEL_NOT_FOUND;
|
||||
}
|
||||
|
||||
cardLabel = await sails.helpers.deleteCardLabel(cardLabel, board, this.req);
|
||||
|
||||
if (!cardLabel) {
|
||||
throw Errors.CARD_LABEL_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: cardLabel
|
||||
});
|
||||
}
|
||||
};
|
67
server/api/controllers/card-memberships/create.js
Executable file
67
server/api/controllers/card-memberships/create.js
Executable file
|
@ -0,0 +1,67 @@
|
|||
const Errors = {
|
||||
CARD_NOT_FOUND: {
|
||||
notFound: 'Card is not found'
|
||||
},
|
||||
USER_NOT_FOUND: {
|
||||
notFound: 'User is not found'
|
||||
},
|
||||
CARD_MEMBERSHIP_EXIST: {
|
||||
conflict: 'Card membership is already exist'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
cardId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
userId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
},
|
||||
conflict: {
|
||||
responseType: 'conflict'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { card, project } = await sails.helpers
|
||||
.getCardToProjectPath(inputs.cardId)
|
||||
.intercept('notFound', () => Errors.CARD_NOT_FOUND);
|
||||
|
||||
let isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
inputs.userId
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
const cardMembership = await sails.helpers
|
||||
.createCardMembership(card, inputs.userId, this.req)
|
||||
.intercept('conflict', () => Errors.CARD_MEMBERSHIP_EXIST);
|
||||
|
||||
return exits.success({
|
||||
item: cardMembership
|
||||
});
|
||||
}
|
||||
};
|
67
server/api/controllers/card-memberships/delete.js
Executable file
67
server/api/controllers/card-memberships/delete.js
Executable file
|
@ -0,0 +1,67 @@
|
|||
const Errors = {
|
||||
CARD_NOT_FOUND: {
|
||||
notFound: 'Card is not found'
|
||||
},
|
||||
CARD_MEMBERSHIP_NOT_FOUND: {
|
||||
notFound: 'Card membership is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
cardId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
userId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { board, project } = await sails.helpers
|
||||
.getCardToProjectPath(inputs.cardId)
|
||||
.intercept('notFound', () => Errors.CARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
let cardMembership = await CardMembership.findOne({
|
||||
cardId: inputs.cardId,
|
||||
userId: inputs.userId
|
||||
});
|
||||
|
||||
if (!cardMembership) {
|
||||
throw Errors.CARD_MEMBERSHIP_NOT_FOUND;
|
||||
}
|
||||
|
||||
cardMembership = await sails.helpers.deleteCardMembership(
|
||||
cardMembership,
|
||||
board,
|
||||
this.req
|
||||
);
|
||||
|
||||
if (!cardMembership) {
|
||||
throw Errors.CARD_MEMBERSHIP_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: cardMembership
|
||||
});
|
||||
}
|
||||
};
|
84
server/api/controllers/cards/create.js
Executable file
84
server/api/controllers/cards/create.js
Executable file
|
@ -0,0 +1,84 @@
|
|||
const moment = require('moment');
|
||||
|
||||
const Errors = {
|
||||
LIST_NOT_FOUND: {
|
||||
notFound: 'List is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
listId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
position: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true,
|
||||
allowNull: true
|
||||
},
|
||||
deadline: {
|
||||
type: 'string',
|
||||
custom: value => moment(value, moment.ISO_8601, true).isValid()
|
||||
},
|
||||
timer: {
|
||||
type: 'json',
|
||||
custom: value =>
|
||||
_.isPlainObject(value) &&
|
||||
_.size(value) === 2 &&
|
||||
(_.isNull(value.startedAt) ||
|
||||
moment(value.startedAt, moment.ISO_8601, true).isValid()) &&
|
||||
_.isFinite(value.total)
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { list, project } = await sails.helpers
|
||||
.getListToProjectPath(inputs.listId)
|
||||
.intercept('notFound', () => Errors.LIST_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.LIST_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, [
|
||||
'position',
|
||||
'name',
|
||||
'description',
|
||||
'deadline',
|
||||
'timer'
|
||||
]);
|
||||
|
||||
const card = await sails.helpers.createCard(
|
||||
list,
|
||||
values,
|
||||
currentUser,
|
||||
this.req
|
||||
);
|
||||
|
||||
return exits.success({
|
||||
item: card
|
||||
});
|
||||
}
|
||||
};
|
47
server/api/controllers/cards/delete.js
Executable file
47
server/api/controllers/cards/delete.js
Executable file
|
@ -0,0 +1,47 @@
|
|||
const Errors = {
|
||||
CARD_NOT_FOUND: {
|
||||
notFound: 'Card is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
let { card, project } = await sails.helpers
|
||||
.getCardToProjectPath(inputs.id)
|
||||
.intercept('notFound', () => Errors.CARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
card = await sails.helpers.deleteCard(card, this.req);
|
||||
|
||||
if (!card) {
|
||||
throw Errors.CARD_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: card
|
||||
});
|
||||
}
|
||||
};
|
41
server/api/controllers/cards/show.js
Executable file
41
server/api/controllers/cards/show.js
Executable file
|
@ -0,0 +1,41 @@
|
|||
const Errors = {
|
||||
CARD_NOT_FOUND: {
|
||||
notFound: 'Card is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { card, project } = await sails.helpers
|
||||
.getCardToProjectPath(inputs.id)
|
||||
.intercept('notFound', () => Errors.CARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: card
|
||||
});
|
||||
}
|
||||
};
|
112
server/api/controllers/cards/update.js
Executable file
112
server/api/controllers/cards/update.js
Executable file
|
@ -0,0 +1,112 @@
|
|||
const moment = require('moment');
|
||||
|
||||
const Errors = {
|
||||
CARD_NOT_FOUND: {
|
||||
notFound: 'Card is not found'
|
||||
},
|
||||
LIST_NOT_FOUND: {
|
||||
notFound: 'List is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
listId: {
|
||||
type: 'number'
|
||||
},
|
||||
position: {
|
||||
type: 'number'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true,
|
||||
allowNull: true
|
||||
},
|
||||
deadline: {
|
||||
type: 'string',
|
||||
custom: value => moment(value, moment.ISO_8601, true).isValid(),
|
||||
allowNull: true
|
||||
},
|
||||
timer: {
|
||||
type: 'json',
|
||||
custom: value =>
|
||||
_.isPlainObject(value) &&
|
||||
_.size(value) === 2 &&
|
||||
(_.isNull(value.startedAt) ||
|
||||
moment(value.startedAt, moment.ISO_8601, true).isValid()) &&
|
||||
_.isFinite(value.total)
|
||||
},
|
||||
isSubscribed: {
|
||||
type: 'boolean'
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
let { card, list, project } = await sails.helpers
|
||||
.getCardToProjectPath(inputs.id)
|
||||
.intercept('notFound', () => Errors.CARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
let toList;
|
||||
if (!_.isUndefined(inputs.listId) && inputs.listId !== list.id) {
|
||||
toList = await List.findOne({
|
||||
id: inputs.listId,
|
||||
boardId: card.boardId
|
||||
});
|
||||
|
||||
if (!toList) {
|
||||
throw Errors.LIST_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, [
|
||||
'position',
|
||||
'name',
|
||||
'description',
|
||||
'deadline',
|
||||
'timer',
|
||||
'isSubscribed'
|
||||
]);
|
||||
|
||||
card = await sails.helpers.updateCard(
|
||||
card,
|
||||
values,
|
||||
toList,
|
||||
list,
|
||||
currentUser,
|
||||
this.req
|
||||
);
|
||||
|
||||
if (!card) {
|
||||
throw Errors.CARD_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: card
|
||||
});
|
||||
}
|
||||
};
|
52
server/api/controllers/comment-actions/create.js
Executable file
52
server/api/controllers/comment-actions/create.js
Executable file
|
@ -0,0 +1,52 @@
|
|||
const Errors = {
|
||||
CARD_NOT_FOUND: {
|
||||
notFound: 'Card is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
cardId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { card, project } = await sails.helpers
|
||||
.getCardToProjectPath(inputs.cardId)
|
||||
.intercept('notFound', () => Errors.CARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const values = {
|
||||
type: 'commentCard',
|
||||
data: _.pick(inputs, ['text'])
|
||||
};
|
||||
|
||||
const action = await sails.helpers.createAction(card, currentUser, values);
|
||||
|
||||
return exits.success({
|
||||
item: action
|
||||
});
|
||||
}
|
||||
};
|
56
server/api/controllers/comment-actions/delete.js
Executable file
56
server/api/controllers/comment-actions/delete.js
Executable file
|
@ -0,0 +1,56 @@
|
|||
const Errors = {
|
||||
COMMENT_ACTION_NOT_FOUND: {
|
||||
notFound: 'Comment action is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const criteria = {
|
||||
id: inputs.id,
|
||||
type: 'commentCard'
|
||||
};
|
||||
|
||||
if (!currentUser.isAdmin) {
|
||||
criteria.userId = currentUser.id;
|
||||
}
|
||||
|
||||
let { action, board, project } = await sails.helpers
|
||||
.getActionToProjectPath(criteria)
|
||||
.intercept('notFound', () => Errors.COMMENT_ACTION_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.COMMENT_ACTION_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
action = await sails.helpers.deleteAction(action, board, this.req);
|
||||
|
||||
if (!action) {
|
||||
throw Errors.COMMENT_ACTION_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: action
|
||||
});
|
||||
}
|
||||
};
|
59
server/api/controllers/comment-actions/update.js
Executable file
59
server/api/controllers/comment-actions/update.js
Executable file
|
@ -0,0 +1,59 @@
|
|||
const Errors = {
|
||||
COMMENT_ACTION_NOT_FOUND: {
|
||||
notFound: 'Comment action is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
let { action, board, project } = await sails.helpers
|
||||
.getActionToProjectPath({
|
||||
id: inputs.id,
|
||||
type: 'commentCard',
|
||||
userId: currentUser.id
|
||||
})
|
||||
.intercept('notFound', () => Errors.COMMENT_ACTION_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.COMMENT_ACTION_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const values = {
|
||||
data: _.pick(inputs, ['text'])
|
||||
};
|
||||
|
||||
action = await sails.helpers.updateAction(action, values, board, this.req);
|
||||
|
||||
if (!action) {
|
||||
throw Errors.COMMENT_ACTION_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: action
|
||||
});
|
||||
}
|
||||
};
|
54
server/api/controllers/labels/create.js
Executable file
54
server/api/controllers/labels/create.js
Executable file
|
@ -0,0 +1,54 @@
|
|||
const Errors = {
|
||||
BOARD_NOT_FOUND: {
|
||||
notFound: 'Board is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
boardId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
color: {
|
||||
type: 'string',
|
||||
isIn: Label.COLORS,
|
||||
allowNull: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { board, project } = await sails.helpers
|
||||
.getBoardToProjectPath(inputs.boardId)
|
||||
.intercept('notFound', () => Errors.BOARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['name', 'color']);
|
||||
|
||||
const label = await sails.helpers.createLabel(board, values, this.req);
|
||||
|
||||
return exits.success({
|
||||
item: label
|
||||
});
|
||||
}
|
||||
};
|
47
server/api/controllers/labels/delete.js
Executable file
47
server/api/controllers/labels/delete.js
Executable file
|
@ -0,0 +1,47 @@
|
|||
const Errors = {
|
||||
LABEL_NOT_FOUND: {
|
||||
notFound: 'Label is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
let { label, project } = await sails.helpers
|
||||
.getLabelToProjectPath(inputs.id)
|
||||
.intercept('notFound', () => Errors.LABEL_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.LABEL_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
label = await sails.helpers.deleteLabel(label, this.req);
|
||||
|
||||
if (!label) {
|
||||
throw Errors.LABEL_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: label
|
||||
});
|
||||
}
|
||||
};
|
54
server/api/controllers/labels/update.js
Executable file
54
server/api/controllers/labels/update.js
Executable file
|
@ -0,0 +1,54 @@
|
|||
const Errors = {
|
||||
LABEL_NOT_FOUND: {
|
||||
notFound: 'Label is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true
|
||||
},
|
||||
color: {
|
||||
type: 'string',
|
||||
isIn: Label.COLORS,
|
||||
allowNull: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
let { label, project } = await sails.helpers
|
||||
.getLabelToProjectPath(inputs.id)
|
||||
.intercept('notFound', () => Errors.LABEL_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.LABEL_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['name', 'color']);
|
||||
|
||||
label = await sails.helpers.updateLabel(label, values, this.req);
|
||||
|
||||
return exits.success({
|
||||
item: label
|
||||
});
|
||||
}
|
||||
};
|
53
server/api/controllers/lists/create.js
Executable file
53
server/api/controllers/lists/create.js
Executable file
|
@ -0,0 +1,53 @@
|
|||
const Errors = {
|
||||
BOARD_NOT_FOUND: {
|
||||
notFound: 'Board is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
boardId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
position: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { board, project } = await sails.helpers
|
||||
.getBoardToProjectPath(inputs.boardId)
|
||||
.intercept('notFound', () => Errors.BOARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.BOARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['position', 'name']);
|
||||
|
||||
const list = await sails.helpers.createList(board, values, this.req);
|
||||
|
||||
return exits.success({
|
||||
item: list
|
||||
});
|
||||
}
|
||||
};
|
47
server/api/controllers/lists/delete.js
Executable file
47
server/api/controllers/lists/delete.js
Executable file
|
@ -0,0 +1,47 @@
|
|||
const Errors = {
|
||||
LIST_NOT_FOUND: {
|
||||
notFound: 'List is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
let { list, project } = await sails.helpers
|
||||
.getListToProjectPath(inputs.id)
|
||||
.intercept('notFound', () => Errors.LIST_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.LIST_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
list = await sails.helpers.deleteList(list, this.req);
|
||||
|
||||
if (!list) {
|
||||
throw Errors.LIST_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: list
|
||||
});
|
||||
}
|
||||
};
|
56
server/api/controllers/lists/update.js
Executable file
56
server/api/controllers/lists/update.js
Executable file
|
@ -0,0 +1,56 @@
|
|||
const Errors = {
|
||||
LIST_NOT_FOUND: {
|
||||
notFound: 'List is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
position: {
|
||||
type: 'number'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
let { list, project } = await sails.helpers
|
||||
.getListToProjectPath(inputs.id)
|
||||
.intercept('notFound', () => Errors.LIST_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.LIST_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['position', 'name']);
|
||||
|
||||
list = await sails.helpers.updateList(list, values, this.req);
|
||||
|
||||
if (!list) {
|
||||
throw Errors.LIST_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: list
|
||||
});
|
||||
}
|
||||
};
|
27
server/api/controllers/notifications/index.js
Executable file
27
server/api/controllers/notifications/index.js
Executable file
|
@ -0,0 +1,27 @@
|
|||
module.exports = {
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const notifications = await sails.helpers.getNotificationsForUser(
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
const actionIds = sails.helpers.mapRecords(notifications, 'actionId');
|
||||
const actions = await sails.helpers.getActions(actionIds);
|
||||
|
||||
const cardIds = sails.helpers.mapRecords(notifications, 'cardId');
|
||||
const cards = await sails.helpers.getCards(cardIds);
|
||||
|
||||
const userIds = sails.helpers.mapRecords(actions, 'userId', true);
|
||||
const users = await sails.helpers.getUsers(userIds);
|
||||
|
||||
return exits.success({
|
||||
items: notifications,
|
||||
included: {
|
||||
users,
|
||||
cards,
|
||||
actions
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
35
server/api/controllers/notifications/update.js
Executable file
35
server/api/controllers/notifications/update.js
Executable file
|
@ -0,0 +1,35 @@
|
|||
module.exports = {
|
||||
inputs: {
|
||||
ids: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
regex: /^[0-9]+(,[0-9]+)*$/
|
||||
},
|
||||
isRead: {
|
||||
type: 'boolean'
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const values = _.pick(inputs, ['isRead']);
|
||||
|
||||
const notifications = await sails.helpers.updateNotificationsForUser(
|
||||
inputs.ids.split(','),
|
||||
currentUser,
|
||||
values,
|
||||
this.req
|
||||
);
|
||||
|
||||
return exits.success({
|
||||
items: notifications
|
||||
});
|
||||
}
|
||||
};
|
58
server/api/controllers/project-memberships/create.js
Executable file
58
server/api/controllers/project-memberships/create.js
Executable file
|
@ -0,0 +1,58 @@
|
|||
const Errors = {
|
||||
PROJECT_NOT_FOUND: {
|
||||
notFound: 'Project is not found'
|
||||
},
|
||||
USER_NOT_FOUND: {
|
||||
notFound: 'User is not found'
|
||||
},
|
||||
PROJECT_MEMBERSHIP_EXIST: {
|
||||
conflict: 'Project membership is already exist'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
projectId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
userId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
},
|
||||
conflict: {
|
||||
responseType: 'conflict'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const project = await Project.findOne(inputs.projectId);
|
||||
|
||||
if (!project) {
|
||||
throw Errors.PROJECT_NOT_FOUND;
|
||||
}
|
||||
|
||||
const user = await sails.helpers.getUser(inputs.userId);
|
||||
|
||||
if (!user) {
|
||||
throw Error.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
const projectMembership = await sails.helpers
|
||||
.createProjectMembership(project, user, this.req)
|
||||
.intercept('conflict', () => Errors.PROJECT_MEMBERSHIP_EXIST);
|
||||
|
||||
return exits.success({
|
||||
item: projectMembership,
|
||||
included: {
|
||||
users: [user]
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
41
server/api/controllers/project-memberships/delete.js
Executable file
41
server/api/controllers/project-memberships/delete.js
Executable file
|
@ -0,0 +1,41 @@
|
|||
const Errors = {
|
||||
PROJECT_MEMBERSHIP_NOT_FOUND: {
|
||||
notFound: 'Project membership is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
let projectMembership = await ProjectMembership.findOne(inputs.id);
|
||||
|
||||
if (!projectMembership) {
|
||||
throw Errors.PROJECT_MEMBERSHIP_NOT_FOUND;
|
||||
}
|
||||
|
||||
projectMembership = await sails.helpers.deleteProjectMembership(
|
||||
projectMembership,
|
||||
this.req
|
||||
);
|
||||
|
||||
if (!projectMembership) {
|
||||
throw Errors.PROJECT_MEMBERSHIP_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: projectMembership
|
||||
});
|
||||
}
|
||||
};
|
30
server/api/controllers/projects/create.js
Executable file
30
server/api/controllers/projects/create.js
Executable file
|
@ -0,0 +1,30 @@
|
|||
module.exports = {
|
||||
inputs: {
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const values = _.pick(inputs, ['name']);
|
||||
|
||||
const { project, projectMembership } = await sails.helpers.createProject(
|
||||
values,
|
||||
currentUser,
|
||||
this.req,
|
||||
true
|
||||
);
|
||||
|
||||
return exits.success({
|
||||
item: project,
|
||||
included: {
|
||||
users: [currentUser],
|
||||
projectMemberships: [projectMembership],
|
||||
boards: []
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
38
server/api/controllers/projects/delete.js
Executable file
38
server/api/controllers/projects/delete.js
Executable file
|
@ -0,0 +1,38 @@
|
|||
const Errors = {
|
||||
PROJECT_NOT_FOUND: {
|
||||
notFound: 'Project is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
let project = await Project.findOne(inputs.id);
|
||||
|
||||
if (!project) {
|
||||
throw Errors.PROJECT_NOT_FOUND;
|
||||
}
|
||||
|
||||
project = await sails.helpers.deleteProject(project, this.req);
|
||||
|
||||
if (!project) {
|
||||
throw Errors.PROJECT_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: project
|
||||
});
|
||||
}
|
||||
};
|
29
server/api/controllers/projects/index.js
Executable file
29
server/api/controllers/projects/index.js
Executable file
|
@ -0,0 +1,29 @@
|
|||
module.exports = {
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const projectIds = await sails.helpers.getMembershipProjectIdsForUser(
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
const projects = await sails.helpers.getProjects(projectIds);
|
||||
|
||||
const {
|
||||
userIds,
|
||||
projectMemberships
|
||||
} = await sails.helpers.getMembershipUserIdsForProject(projectIds, true);
|
||||
|
||||
const users = await sails.helpers.getUsers(userIds);
|
||||
|
||||
const boards = await sails.helpers.getBoardsForProject(projectIds);
|
||||
|
||||
return exits.success({
|
||||
items: projects,
|
||||
included: {
|
||||
users,
|
||||
projectMemberships,
|
||||
boards
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
44
server/api/controllers/projects/update.js
Executable file
44
server/api/controllers/projects/update.js
Executable file
|
@ -0,0 +1,44 @@
|
|||
const Errors = {
|
||||
PROJECT_NOT_FOUND: {
|
||||
notFound: 'Project is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
let project = await Project.findOne(inputs.id);
|
||||
|
||||
if (!project) {
|
||||
throw Errors.PROJECT_NOT_FOUND;
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['name']);
|
||||
|
||||
project = await sails.helpers.updateProject(project, values, this.req);
|
||||
|
||||
if (!project) {
|
||||
throw Errors.PROJECT_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: project
|
||||
});
|
||||
}
|
||||
};
|
52
server/api/controllers/tasks/create.js
Executable file
52
server/api/controllers/tasks/create.js
Executable file
|
@ -0,0 +1,52 @@
|
|||
const Errors = {
|
||||
CARD_NOT_FOUND: {
|
||||
notFound: 'Card is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
cardId: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
isCompleted: {
|
||||
type: 'boolean'
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { card, project } = await sails.helpers
|
||||
.getCardToProjectPath(inputs.cardId)
|
||||
.intercept('notFound', () => Errors.CARD_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.CARD_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['name', 'isCompleted']);
|
||||
|
||||
const task = await sails.helpers.createTask(card, values, this.req);
|
||||
|
||||
return exits.success({
|
||||
item: task
|
||||
});
|
||||
}
|
||||
};
|
47
server/api/controllers/tasks/delete.js
Executable file
47
server/api/controllers/tasks/delete.js
Executable file
|
@ -0,0 +1,47 @@
|
|||
const Errors = {
|
||||
TASK_NOT_FOUND: {
|
||||
notFound: 'Task is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
let { task, board, project } = await sails.helpers
|
||||
.getTaskToProjectPath(inputs.id)
|
||||
.intercept('notFound', () => Errors.TASK_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.TASK_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
task = await sails.helpers.deleteTask(task, board, this.req);
|
||||
|
||||
if (!task) {
|
||||
throw Errors.TASK_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: task
|
||||
});
|
||||
}
|
||||
};
|
56
server/api/controllers/tasks/update.js
Executable file
56
server/api/controllers/tasks/update.js
Executable file
|
@ -0,0 +1,56 @@
|
|||
const Errors = {
|
||||
TASK_NOT_FOUND: {
|
||||
notFound: 'Task is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true
|
||||
},
|
||||
isCompleted: {
|
||||
type: 'boolean'
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
let { task, board, project } = await sails.helpers
|
||||
.getTaskToProjectPath(inputs.id)
|
||||
.intercept('notFound', () => Errors.TASK_NOT_FOUND);
|
||||
|
||||
const isUserMemberForProject = await sails.helpers.isUserMemberForProject(
|
||||
project.id,
|
||||
currentUser.id
|
||||
);
|
||||
|
||||
if (!isUserMemberForProject) {
|
||||
throw Errors.TASK_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['name', 'isCompleted']);
|
||||
|
||||
task = await sails.helpers.updateTask(task, values, board, this.req);
|
||||
|
||||
if (!task) {
|
||||
throw Errors.TASK_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: task
|
||||
});
|
||||
}
|
||||
};
|
41
server/api/controllers/users/create.js
Executable file
41
server/api/controllers/users/create.js
Executable file
|
@ -0,0 +1,41 @@
|
|||
const Errors = {
|
||||
USER_EXIST: {
|
||||
conflict: 'User is already exist'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
email: {
|
||||
type: 'string',
|
||||
isEmail: true,
|
||||
required: true
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
conflict: {
|
||||
responseType: 'conflict'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const values = _.pick(inputs, ['email', 'password', 'name']);
|
||||
|
||||
const user = await sails.helpers
|
||||
.createUser(values, this.req)
|
||||
.intercept('conflict', () => Errors.USER_EXIST);
|
||||
|
||||
return exits.success({
|
||||
item: user
|
||||
});
|
||||
}
|
||||
};
|
38
server/api/controllers/users/delete.js
Executable file
38
server/api/controllers/users/delete.js
Executable file
|
@ -0,0 +1,38 @@
|
|||
const Errors = {
|
||||
USER_NOT_FOUND: {
|
||||
notFound: 'User is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
let user = await sails.helpers.getUser(inputs.id);
|
||||
|
||||
if (!user) {
|
||||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
user = await sails.helpers.deleteUser(user, this.req);
|
||||
|
||||
if (!user) {
|
||||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: user
|
||||
});
|
||||
}
|
||||
};
|
9
server/api/controllers/users/index.js
Executable file
9
server/api/controllers/users/index.js
Executable file
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
fn: async function(inputs, exits) {
|
||||
const users = await sails.helpers.getUsers();
|
||||
|
||||
return exits.success({
|
||||
items: users
|
||||
});
|
||||
}
|
||||
};
|
16
server/api/controllers/users/show.js
Executable file
16
server/api/controllers/users/show.js
Executable file
|
@ -0,0 +1,16 @@
|
|||
module.exports = {
|
||||
fn: async function(inputs, exits) {
|
||||
// TODO: allow over HTTP without subscription
|
||||
if (!this.req.isSocket) {
|
||||
return this.res.badRequest();
|
||||
}
|
||||
|
||||
const { currentUser } = this.req;
|
||||
|
||||
sails.sockets.join(this.req, `user:${currentUser.id}`); // TODO: only when subscription needed
|
||||
|
||||
return exits.success({
|
||||
item: currentUser
|
||||
});
|
||||
}
|
||||
};
|
61
server/api/controllers/users/update.js
Executable file
61
server/api/controllers/users/update.js
Executable file
|
@ -0,0 +1,61 @@
|
|||
const Errors = {
|
||||
USER_NOT_FOUND: {
|
||||
notFound: 'User is not found'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
isAdmin: {
|
||||
type: 'boolean'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true
|
||||
},
|
||||
avatar: {
|
||||
type: 'json',
|
||||
custom: value => _.isNull(value)
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
if (!currentUser.isAdmin) {
|
||||
if (inputs.id !== currentUser.id) {
|
||||
throw Errors.USER_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
delete inputs.isAdmin;
|
||||
}
|
||||
|
||||
let user = await sails.helpers.getUser(inputs.id);
|
||||
|
||||
if (!user) {
|
||||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['isAdmin', 'name', 'avatar']);
|
||||
|
||||
user = await sails.helpers.updateUser(user, values, this.req);
|
||||
|
||||
if (!user) {
|
||||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
return exits.success({
|
||||
item: user
|
||||
});
|
||||
}
|
||||
};
|
124
server/api/controllers/users/upload-avatar.js
Executable file
124
server/api/controllers/users/upload-avatar.js
Executable file
|
@ -0,0 +1,124 @@
|
|||
const stream = require('stream');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const uuid = require('uuid/v4');
|
||||
const sharp = require('sharp');
|
||||
|
||||
const Errors = {
|
||||
USER_NOT_FOUND: {
|
||||
notFound: 'User is not found'
|
||||
}
|
||||
};
|
||||
|
||||
const createReceiver = () => {
|
||||
const receiver = require('stream').Writable({ objectMode: true });
|
||||
|
||||
let firstFileHandled = false;
|
||||
receiver._write = (file, encoding, done) => {
|
||||
if (firstFileHandled) {
|
||||
file.pipe(
|
||||
new stream.Writable({
|
||||
write(chunk, encoding, callback) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return done();
|
||||
}
|
||||
firstFileHandled = true;
|
||||
|
||||
const resize = sharp()
|
||||
.resize(36, 36)
|
||||
.jpeg();
|
||||
|
||||
const transform = new stream.Transform({
|
||||
transform(chunk, encoding, callback) {
|
||||
callback(null, chunk);
|
||||
}
|
||||
});
|
||||
|
||||
stream.pipeline(file, resize, transform, error => {
|
||||
if (error) {
|
||||
return done(error.message);
|
||||
}
|
||||
|
||||
file.fd = `${uuid()}.jpg`;
|
||||
|
||||
const output = fs.createWriteStream(
|
||||
path.join(sails.config.custom.uploadsPath, file.fd)
|
||||
);
|
||||
|
||||
stream.pipeline(transform, output, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return receiver;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
exits: {
|
||||
notFound: {
|
||||
responseType: 'notFound'
|
||||
},
|
||||
unprocessableEntity: {
|
||||
responseType: 'unprocessableEntity'
|
||||
}
|
||||
},
|
||||
|
||||
fn: async function(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
let user;
|
||||
if (currentUser.isAdmin) {
|
||||
user = await sails.helpers.getUser(inputs.id);
|
||||
|
||||
if (!user) {
|
||||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
} else if (inputs.id !== currentUser.id) {
|
||||
throw Errors.USER_NOT_FOUND; // Forbidden
|
||||
} else {
|
||||
user = currentUser;
|
||||
}
|
||||
|
||||
this.req.file('file').upload(createReceiver(), async (error, files) => {
|
||||
if (error) {
|
||||
return exits.unprocessableEntity(error);
|
||||
}
|
||||
|
||||
if (files.length === 0) {
|
||||
return exits.unprocessableEntity('No file was uploaded');
|
||||
}
|
||||
|
||||
user = await sails.helpers.updateUser(
|
||||
user,
|
||||
{
|
||||
avatar: files[0].fd
|
||||
},
|
||||
this.req
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
return this.res.json({
|
||||
item: user
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue