REST API - Meteor 1.4 - first step issue

This commit is contained in:
Lauri Ojansivu 2017-04-27 20:49:24 +03:00
parent a99218e2c7
commit 0319bcf7bb
9 changed files with 361 additions and 137 deletions

View file

@ -52,8 +52,8 @@
"prefer-const": 2, "prefer-const": 2,
"prefer-spread": 2, "prefer-spread": 2,
"prefer-template": 2, "prefer-template": 2,
"no-console":"off", "no-console": 0,
"no-unused-vars":"warn" "no-unused-vars" : "warn"
}, },
"globals": { "globals": {
"Meteor": false, "Meteor": false,
@ -125,6 +125,10 @@
"Checklists": true, "Checklists": true,
"Settings": true, "Settings": true,
"InvitationCodes": true, "InvitationCodes": true,
<<<<<<< HEAD
"Winston":true "Winston":true
=======
"JsonRoutes" : true
>>>>>>> 3a5150f6eef86816471f7b0134d3d93cf6686413
} }
} }

View file

@ -1,6 +1,6 @@
3stack:presence@1.0.5 3stack:presence@1.0.5
accounts-base@1.2.15 accounts-base@1.2.16
accounts-password@1.3.4 accounts-password@1.3.5
aldeed:collection2@2.10.0 aldeed:collection2@2.10.0
aldeed:collection2-core@1.2.0 aldeed:collection2-core@1.2.0
aldeed:schema-deny@1.1.0 aldeed:schema-deny@1.1.0
@ -11,7 +11,7 @@ allow-deny@1.0.5
arillo:flow-router-helpers@0.5.2 arillo:flow-router-helpers@0.5.2
audit-argument-checks@1.0.7 audit-argument-checks@1.0.7
autoupdate@1.3.12 autoupdate@1.3.12
babel-compiler@6.14.1 babel-compiler@6.18.1
babel-runtime@1.0.1 babel-runtime@1.0.1
base64@1.0.10 base64@1.0.10
binary-heap@1.0.10 binary-heap@1.0.10
@ -44,16 +44,16 @@ coffeescript@1.12.3_1
cottz:publish-relations@2.0.7 cottz:publish-relations@2.0.7
dburles:collection-helpers@1.1.0 dburles:collection-helpers@1.1.0
ddp@1.2.5 ddp@1.2.5
ddp-client@1.3.3 ddp-client@1.3.4
ddp-common@1.2.8 ddp-common@1.2.8
ddp-rate-limiter@1.0.7 ddp-rate-limiter@1.0.7
ddp-server@1.3.13 ddp-server@1.3.14
deps@1.0.12 deps@1.0.12
diff-sequence@1.0.7 diff-sequence@1.0.7
ecmascript@0.6.3 ecmascript@0.7.2
ecmascript-runtime@0.3.15 ecmascript-runtime@0.3.15
ejson@1.0.13 ejson@1.0.13
email@1.1.18 email@1.2.0
es5-shim@4.6.15 es5-shim@4.6.15
fastclick@1.0.13 fastclick@1.0.13
fortawesome:fontawesome@4.7.0 fortawesome:fontawesome@4.7.0
@ -92,8 +92,8 @@ minifier-js@1.2.18
minifiers@1.1.8-faster-rebuild.0 minifiers@1.1.8-faster-rebuild.0
minimongo@1.0.21 minimongo@1.0.21
mobile-status-bar@1.0.14 mobile-status-bar@1.0.14
modules@0.7.9 modules@0.8.1
modules-runtime@0.7.9 modules-runtime@0.7.10
mongo@1.1.16 mongo@1.1.16
mongo-id@1.0.6 mongo-id@1.0.6
mongo-livedata@1.0.12 mongo-livedata@1.0.12
@ -123,7 +123,7 @@ raix:eventemitter@0.1.3
raix:handlebar-helpers@0.2.5 raix:handlebar-helpers@0.2.5
rajit:bootstrap3-datepicker@1.6.4 rajit:bootstrap3-datepicker@1.6.4
random@1.0.10 random@1.0.10
rate-limit@1.0.7 rate-limit@1.0.8
reactive-dict@1.1.8 reactive-dict@1.1.8
reactive-var@1.0.11 reactive-var@1.0.11
reload@1.1.11 reload@1.1.11
@ -134,7 +134,7 @@ service-configuration@1.0.11
session@1.1.7 session@1.1.7
sha@1.0.9 sha@1.0.9
shell-server@0.2.3 shell-server@0.2.3
simple:json-routes@1.0.4 simple:json-routes@2.1.0
softwarerero:accounts-t9n@1.3.9 softwarerero:accounts-t9n@1.3.9
spacebars@1.0.15 spacebars@1.0.15
spacebars-compiler@1.1.2 spacebars-compiler@1.1.2
@ -156,6 +156,6 @@ useraccounts:core@1.14.2
useraccounts:flow-routing@1.14.2 useraccounts:flow-routing@1.14.2
useraccounts:unstyled@1.14.2 useraccounts:unstyled@1.14.2
verron:autosize@3.0.8 verron:autosize@3.0.8
webapp@1.3.14 webapp@1.3.15
webapp-hashing@1.0.9 webapp-hashing@1.0.9
zimme:active-route@2.3.2 zimme:active-route@2.3.2

View file

@ -15,17 +15,17 @@ Template.boardMenuPopup.events({
}), }),
}); });
Template.boardMenuPopup.helpers({ // Template.boardMenuPopup.helpers({
exportUrl() { // exportUrl() {
const boardId = Session.get('currentBoard'); // const boardId = Session.get('currentBoard');
const loginToken = Accounts._storedLoginToken(); // const loginToken = Accounts._storedLoginToken();
return FlowRouter.url(`api/boards/${boardId}?authToken=${loginToken}`); // return FlowRouter.url(`api/boards/${boardId}?authToken=${loginToken}`);
}, // },
exportFilename() { // exportFilename() {
const boardId = Session.get('currentBoard'); // const boardId = Session.get('currentBoard');
return `wekan-export-board-${boardId}.json`; // return `wekan-export-board-${boardId}.json`;
}, // },
}); // });
Template.boardChangeTitlePopup.events({ Template.boardChangeTitlePopup.events({
submit(evt, tpl) { submit(evt, tpl) {

View file

@ -156,7 +156,7 @@ Boards.helpers({
* Is supplied user authorized to view this board? * Is supplied user authorized to view this board?
*/ */
isVisibleBy(user) { isVisibleBy(user) {
if(this.isPublic()) { if (this.isPublic()) {
// public boards are visible to everyone // public boards are visible to everyone
return true; return true;
} else { } else {
@ -172,7 +172,7 @@ Boards.helpers({
* @returns {boolean} the member that matches, or undefined/false * @returns {boolean} the member that matches, or undefined/false
*/ */
isActiveMember(userId) { isActiveMember(userId) {
if(userId) { if (userId) {
return this.members.find((member) => (member.userId === userId && member.isActive)); return this.members.find((member) => (member.userId === userId && member.isActive));
} else { } else {
return false; return false;
@ -184,23 +184,23 @@ Boards.helpers({
}, },
lists() { lists() {
return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 }}); return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } });
}, },
activities() { activities() {
return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 }}); return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 } });
}, },
activeMembers() { activeMembers() {
return _.where(this.members, {isActive: true}); return _.where(this.members, { isActive: true });
}, },
activeAdmins() { activeAdmins() {
return _.where(this.members, {isActive: true, isAdmin: true}); return _.where(this.members, { isActive: true, isAdmin: true });
}, },
memberUsers() { memberUsers() {
return Users.find({ _id: {$in: _.pluck(this.members, 'userId')} }); return Users.find({ _id: { $in: _.pluck(this.members, 'userId') } });
}, },
getLabel(name, color) { getLabel(name, color) {
@ -216,15 +216,15 @@ Boards.helpers({
}, },
hasMember(memberId) { hasMember(memberId) {
return !!_.findWhere(this.members, {userId: memberId, isActive: true}); return !!_.findWhere(this.members, { userId: memberId, isActive: true });
}, },
hasAdmin(memberId) { hasAdmin(memberId) {
return !!_.findWhere(this.members, {userId: memberId, isActive: true, isAdmin: true}); return !!_.findWhere(this.members, { userId: memberId, isActive: true, isAdmin: true });
}, },
hasCommentOnly(memberId) { hasCommentOnly(memberId) {
return !!_.findWhere(this.members, {userId: memberId, isActive: true, isAdmin: false, isCommentOnly: true}); return !!_.findWhere(this.members, { userId: memberId, isActive: true, isAdmin: false, isCommentOnly: true });
}, },
absoluteUrl() { absoluteUrl() {
@ -239,34 +239,34 @@ Boards.helpers({
// XXX waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove... // XXX waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove...
pushLabel(name, color) { pushLabel(name, color) {
const _id = Random.id(6); const _id = Random.id(6);
Boards.direct.update(this._id, { $push: {labels: { _id, name, color }}}); Boards.direct.update(this._id, { $push: { labels: { _id, name, color } } });
return _id; return _id;
}, },
}); });
Boards.mutations({ Boards.mutations({
archive() { archive() {
return { $set: { archived: true }}; return { $set: { archived: true } };
}, },
restore() { restore() {
return { $set: { archived: false }}; return { $set: { archived: false } };
}, },
rename(title) { rename(title) {
return { $set: { title }}; return { $set: { title } };
}, },
setDescription(description) { setDescription(description) {
return { $set: {description} }; return { $set: { description } };
}, },
setColor(color) { setColor(color) {
return { $set: { color }}; return { $set: { color } };
}, },
setVisibility(visibility) { setVisibility(visibility) {
return { $set: { permission: visibility }}; return { $set: { permission: visibility } };
}, },
addLabel(name, color) { addLabel(name, color) {
@ -276,7 +276,7 @@ Boards.mutations({
// user). // user).
if (!this.getLabel(name, color)) { if (!this.getLabel(name, color)) {
const _id = Random.id(6); const _id = Random.id(6);
return { $push: {labels: { _id, name, color }}}; return { $push: { labels: { _id, name, color } } };
} }
return {}; return {};
}, },
@ -295,7 +295,7 @@ Boards.mutations({
}, },
removeLabel(labelId) { removeLabel(labelId) {
return { $pull: { labels: { _id: labelId }}}; return { $pull: { labels: { _id: labelId } } };
}, },
addMember(memberId) { addMember(memberId) {
@ -386,7 +386,7 @@ if (Meteor.isServer) {
return false; return false;
// If there is more than one admin, it's ok to remove anyone // If there is more than one admin, it's ok to remove anyone
const nbAdmins = _.where(doc.members, {isActive: true, isAdmin: true}).length; const nbAdmins = _.where(doc.members, { isActive: true, isAdmin: true }).length;
if (nbAdmins > 1) if (nbAdmins > 1)
return false; return false;
@ -408,7 +408,7 @@ if (Meteor.isServer) {
if (board) { if (board) {
const userId = Meteor.userId(); const userId = Meteor.userId();
const index = board.memberIndex(userId); const index = board.memberIndex(userId);
if (index>=0) { if (index >= 0) {
board.removeMember(userId); board.removeMember(userId);
return true; return true;
} else throw new Meteor.Error('error-board-notAMember'); } else throw new Meteor.Error('error-board-notAMember');
@ -424,7 +424,7 @@ if (Meteor.isServer) {
_id: 1, _id: 1,
'members.userId': 1, 'members.userId': 1,
}, { unique: true }); }, { unique: true });
Boards._collection._ensureIndex({'members.userId': 1}); Boards._collection._ensureIndex({ 'members.userId': 1 });
}); });
// Genesis: the first activity of the newly created board // Genesis: the first activity of the newly created board
@ -553,3 +553,60 @@ if (Meteor.isServer) {
} }
}); });
} }
//BOARDS REST API
if (Meteor.isServer) {
JsonRoutes.add('GET', '/api/boards', function (req, res, next) {
JsonRoutes.sendResult(res, {
code: 200,
data: Boards.find({ permission: 'public' }).map(function (doc) {
return {
_id: doc._id,
title: doc.title,
};
}),
});
});
JsonRoutes.add('GET', '/api/boards/:id', function (req, res, next) {
const id = req.params.id;
JsonRoutes.sendResult(res, {
code: 200,
data: Boards.findOne({ _id: id }),
});
});
JsonRoutes.add('POST', '/api/boards', function (req, res, next) {
const id = Boards.insert({
title: req.body.title,
members: [
{
userId: req.body.owner,
isAdmin: true,
isActive: true,
isCommentOnly: false,
},
],
permission: 'public',
color: 'belize',
});
JsonRoutes.sendResult(res, {
code: 200,
data: {
_id: id,
},
});
});
JsonRoutes.add('DELETE', '/api/boards/:id', function (req, res, next) {
const id = req.params.id;
Boards.remove({ _id: id });
JsonRoutes.sendResult(res, {
code: 200,
data:{
_id: id,
},
});
});
}

View file

@ -123,15 +123,15 @@ Cards.helpers({
}, },
activities() { activities() {
return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 }}); return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 } });
}, },
comments() { comments() {
return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }}); return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 } });
}, },
attachments() { attachments() {
return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 }}); return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 } });
}, },
cover() { cover() {
@ -142,7 +142,7 @@ Cards.helpers({
}, },
checklists() { checklists() {
return Checklists.find({ cardId: this._id }, { sort: { createdAt: 1 }}); return Checklists.find({ cardId: this._id }, { sort: { createdAt: 1 } });
}, },
checklistItemCount() { checklistItemCount() {
@ -183,19 +183,19 @@ Cards.helpers({
Cards.mutations({ Cards.mutations({
archive() { archive() {
return { $set: { archived: true }}; return { $set: { archived: true } };
}, },
restore() { restore() {
return { $set: { archived: false }}; return { $set: { archived: false } };
}, },
setTitle(title) { setTitle(title) {
return { $set: { title }}; return { $set: { title } };
}, },
setDescription(description) { setDescription(description) {
return { $set: { description }}; return { $set: { description } };
}, },
move(listId, sortIndex) { move(listId, sortIndex) {
@ -207,11 +207,11 @@ Cards.mutations({
}, },
addLabel(labelId) { addLabel(labelId) {
return { $addToSet: { labelIds: labelId }}; return { $addToSet: { labelIds: labelId } };
}, },
removeLabel(labelId) { removeLabel(labelId) {
return { $pull: { labelIds: labelId }}; return { $pull: { labelIds: labelId } };
}, },
toggleLabel(labelId) { toggleLabel(labelId) {
@ -223,11 +223,11 @@ Cards.mutations({
}, },
assignMember(memberId) { assignMember(memberId) {
return { $addToSet: { members: memberId }}; return { $addToSet: { members: memberId } };
}, },
unassignMember(memberId) { unassignMember(memberId) {
return { $pull: { members: memberId }}; return { $pull: { members: memberId } };
}, },
toggleMember(memberId) { toggleMember(memberId) {
@ -239,27 +239,27 @@ Cards.mutations({
}, },
setCover(coverId) { setCover(coverId) {
return { $set: { coverId }}; return { $set: { coverId } };
}, },
unsetCover() { unsetCover() {
return { $unset: { coverId: '' }}; return { $unset: { coverId: '' } };
}, },
setStart(startAt) { setStart(startAt) {
return { $set: { startAt }}; return { $set: { startAt } };
}, },
unsetStart() { unsetStart() {
return { $unset: { startAt: '' }}; return { $unset: { startAt: '' } };
}, },
setDue(dueAt) { setDue(dueAt) {
return { $set: { dueAt }}; return { $set: { dueAt } };
}, },
unsetDue() { unsetDue() {
return { $unset: { dueAt: '' }}; return { $unset: { dueAt: '' } };
}, },
}); });
@ -304,7 +304,7 @@ if (Meteor.isServer) {
}); });
// New activity for card moves // New activity for card moves
Cards.after.update(function(userId, doc, fieldNames) { Cards.after.update(function (userId, doc, fieldNames) {
const oldListId = this.previous.listId; const oldListId = this.previous.listId;
if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) { if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) {
Activities.insert({ Activities.insert({
@ -370,3 +370,63 @@ if (Meteor.isServer) {
}); });
}); });
} }
//LISTS REST API
if (Meteor.isServer) {
JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function (req, res, next) {
const paramBoardId = req.params.boardId;
const paramListId = req.params.listId;
JsonRoutes.sendResult(res, {
code: 200,
data: Cards.find({ boardId: paramBoardId, listId: paramListId, archived: false }).map(function (doc) {
return {
_id: doc._id,
title: doc.title,
description: doc.description,
};
}),
});
});
JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res, next) {
const paramBoardId = req.params.boardId;
const paramListId = req.params.listId;
const paramCardId = req.params.cardId;
JsonRoutes.sendResult(res, {
code: 200,
data: Cards.findOne({ _id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false }),
});
});
JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function (req, res, next) {
const paramBoardId = req.params.boardId;
const paramListId = req.params.listId;
const id = Cards.insert({
title: req.body.title,
boardId: paramBoardId,
listId: paramListId,
description: req.body.description,
userId : req.body.authorId,
sort: 0,
members:[ req.body.authorId ],
});
JsonRoutes.sendResult(res, {
code: 200,
data: {
_id: id,
},
});
});
JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res, next) {
const paramBoardId = req.params.boardId;
const paramListId = req.params.listId;
const paramCardId = req.params.cardId;
Cards.remove({ _id: paramCardId, listId: paramListId, boardId: paramBoardId });
JsonRoutes.sendResult(res, {
code: 200,
data: {
_id: paramCardId,
},
});
});
}

View file

@ -1,5 +1,5 @@
/* global JsonRoutes */ /* global JsonRoutes */
if(Meteor.isServer) { if (Meteor.isServer) {
// todo XXX once we have a real API in place, move that route there // todo XXX once we have a real API in place, move that route there
// todo XXX also share the route definition between the client and the server // todo XXX also share the route definition between the client and the server
// so that we could use something like // so that we could use something like
@ -14,28 +14,28 @@ if(Meteor.isServer) {
* See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
* for detailed explanations * for detailed explanations
*/ */
JsonRoutes.add('get', '/api/boards/:boardId', function (req, res) { // JsonRoutes.add('get', '/api/boards/:boardId', function (req, res) {
const boardId = req.params.boardId; // const boardId = req.params.boardId;
let user = null; // let user = null;
// todo XXX for real API, first look for token in Authentication: header // // todo XXX for real API, first look for token in Authentication: header
// then fallback to parameter // // then fallback to parameter
const loginToken = req.query.authToken; // const loginToken = req.query.authToken;
if (loginToken) { // if (loginToken) {
const hashToken = Accounts._hashLoginToken(loginToken); // const hashToken = Accounts._hashLoginToken(loginToken);
user = Meteor.users.findOne({ // user = Meteor.users.findOne({
'services.resume.loginTokens.hashedToken': hashToken, // 'services.resume.loginTokens.hashedToken': hashToken,
}); // });
} // }
const exporter = new Exporter(boardId); // const exporter = new Exporter(boardId);
if(exporter.canExport(user)) { // if(exporter.canExport(user)) {
JsonRoutes.sendResult(res, 200, exporter.build()); // JsonRoutes.sendResult(res, 200, exporter.build());
} else { // } else {
// we could send an explicit error message, but on the other hand the only // // we could send an explicit error message, but on the other hand the only
// way to get there is by hacking the UI so let's keep it raw. // // way to get there is by hacking the UI so let's keep it raw.
JsonRoutes.sendResult(res, 403); // JsonRoutes.sendResult(res, 403);
} // }
}); // });
} }
class Exporter { class Exporter {
@ -44,13 +44,13 @@ class Exporter {
} }
build() { build() {
const byBoard = {boardId: this._boardId}; const byBoard = { boardId: this._boardId };
// we do not want to retrieve boardId in related elements // we do not want to retrieve boardId in related elements
const noBoardId = {fields: {boardId: 0}}; const noBoardId = { fields: { boardId: 0 } };
const result = { const result = {
_format: 'wekan-board-1.0.0', _format: 'wekan-board-1.0.0',
}; };
_.extend(result, Boards.findOne(this._boardId, {fields: {stars: 0}})); _.extend(result, Boards.findOne(this._boardId, { fields: { stars: 0 } }));
result.lists = Lists.find(byBoard, noBoardId).fetch(); result.lists = Lists.find(byBoard, noBoardId).fetch();
result.cards = Cards.find(byBoard, noBoardId).fetch(); result.cards = Cards.find(byBoard, noBoardId).fetch();
result.comments = CardComments.find(byBoard, noBoardId).fetch(); result.comments = CardComments.find(byBoard, noBoardId).fetch();
@ -69,29 +69,31 @@ class Exporter {
// 1- only exports users that are linked somehow to that board // 1- only exports users that are linked somehow to that board
// 2- do not export any sensitive information // 2- do not export any sensitive information
const users = {}; const users = {};
result.members.forEach((member) => {users[member.userId] = true;}); result.members.forEach((member) => { users[member.userId] = true; });
result.lists.forEach((list) => {users[list.userId] = true;}); result.lists.forEach((list) => { users[list.userId] = true; });
result.cards.forEach((card) => { result.cards.forEach((card) => {
users[card.userId] = true; users[card.userId] = true;
if (card.members) { if (card.members) {
card.members.forEach((memberId) => {users[memberId] = true;}); card.members.forEach((memberId) => { users[memberId] = true; });
} }
}); });
result.comments.forEach((comment) => {users[comment.userId] = true;}); result.comments.forEach((comment) => { users[comment.userId] = true; });
result.activities.forEach((activity) => {users[activity.userId] = true;}); result.activities.forEach((activity) => { users[activity.userId] = true; });
const byUserIds = {_id: {$in: Object.getOwnPropertyNames(users)}}; const byUserIds = { _id: { $in: Object.getOwnPropertyNames(users) } };
// we use whitelist to be sure we do not expose inadvertently // we use whitelist to be sure we do not expose inadvertently
// some secret fields that gets added to User later. // some secret fields that gets added to User later.
const userFields = {fields: { const userFields = {
_id: 1, fields: {
username: 1, _id: 1,
'profile.fullname': 1, username: 1,
'profile.initials': 1, 'profile.fullname': 1,
'profile.avatarUrl': 1, 'profile.initials': 1,
}}; 'profile.avatarUrl': 1,
},
};
result.users = Users.find(byUserIds, userFields).fetch().map((user) => { result.users = Users.find(byUserIds, userFields).fetch().map((user) => {
// user avatar is stored as a relative url, we export absolute // user avatar is stored as a relative url, we export absolute
if(user.profile.avatarUrl) { if (user.profile.avatarUrl) {
user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl); user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl);
} }
return user; return user;

View file

@ -76,15 +76,15 @@ Lists.helpers({
Lists.mutations({ Lists.mutations({
rename(title) { rename(title) {
return { $set: { title }}; return { $set: { title } };
}, },
archive() { archive() {
return { $set: { archived: true }}; return { $set: { archived: true } };
}, },
restore() { restore() {
return { $set: { archived: false }}; return { $set: { archived: false } };
}, },
}); });
@ -128,3 +128,55 @@ if (Meteor.isServer) {
} }
}); });
} }
//LISTS REST API
if (Meteor.isServer) {
JsonRoutes.add('GET', '/api/boards/:boardId/lists', function (req, res, next) {
const paramBoardId = req.params.boardId;
JsonRoutes.sendResult(res, {
code: 200,
data: Lists.find({ boardId: paramBoardId, archived: false }).map(function (doc) {
return {
_id: doc._id,
title: doc.title,
};
}),
});
});
JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', function (req, res, next) {
const paramBoardId = req.params.boardId;
const paramListId = req.params.listId;
JsonRoutes.sendResult(res, {
code: 200,
data: Lists.findOne({ _id: paramListId, boardId: paramBoardId, archived: false }),
});
});
JsonRoutes.add('POST', '/api/boards/:boardId/lists', function (req, res, next) {
const paramBoardId = req.params.boardId;
const id = Lists.insert({
title: req.body.title,
boardId: paramBoardId,
});
JsonRoutes.sendResult(res, {
code: 200,
data: {
_id: id,
},
});
});
JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId', function (req, res, next) {
const paramBoardId = req.params.boardId;
const paramListId = req.params.listId;
Lists.remove({ _id: paramListId, boardId: paramBoardId });
JsonRoutes.sendResult(res, {
code: 200,
data: {
_id: paramListId,
},
});
});
}

View file

@ -1,7 +1,7 @@
// Sandstorm context is detected using the METEOR_SETTINGS environment variable // Sandstorm context is detected using the METEOR_SETTINGS environment variable
// in the package definition. // in the package definition.
const isSandstorm = Meteor.settings && Meteor.settings.public && const isSandstorm = Meteor.settings && Meteor.settings.public &&
Meteor.settings.public.sandstorm; Meteor.settings.public.sandstorm;
Users = Meteor.users; Users = Meteor.users;
Users.attachSchema(new SimpleSchema({ Users.attachSchema(new SimpleSchema({
@ -148,32 +148,32 @@ Users.helpers({
}, },
starredBoards() { starredBoards() {
const {starredBoards = []} = this.profile; const { starredBoards = [] } = this.profile;
return Boards.find({archived: false, _id: {$in: starredBoards}}); return Boards.find({ archived: false, _id: { $in: starredBoards } });
}, },
hasStarred(boardId) { hasStarred(boardId) {
const {starredBoards = []} = this.profile; const { starredBoards = [] } = this.profile;
return _.contains(starredBoards, boardId); return _.contains(starredBoards, boardId);
}, },
invitedBoards() { invitedBoards() {
const {invitedBoards = []} = this.profile; const { invitedBoards = [] } = this.profile;
return Boards.find({archived: false, _id: {$in: invitedBoards}}); return Boards.find({ archived: false, _id: { $in: invitedBoards } });
}, },
isInvitedTo(boardId) { isInvitedTo(boardId) {
const {invitedBoards = []} = this.profile; const { invitedBoards = [] } = this.profile;
return _.contains(invitedBoards, boardId); return _.contains(invitedBoards, boardId);
}, },
hasTag(tag) { hasTag(tag) {
const {tags = []} = this.profile; const { tags = [] } = this.profile;
return _.contains(tags, tag); return _.contains(tags, tag);
}, },
hasNotification(activityId) { hasNotification(activityId) {
const {notifications = []} = this.profile; const { notifications = [] } = this.profile;
return _.contains(notifications, activityId); return _.contains(notifications, activityId);
}, },
@ -183,7 +183,7 @@ Users.helpers({
}, },
getEmailBuffer() { getEmailBuffer() {
const {emailBuffer = []} = this.profile; const { emailBuffer = [] } = this.profile;
return emailBuffer; return emailBuffer;
}, },
@ -308,7 +308,7 @@ Users.mutations({
}, },
setAvatarUrl(avatarUrl) { setAvatarUrl(avatarUrl) {
return { $set: { 'profile.avatarUrl': avatarUrl }}; return { $set: { 'profile.avatarUrl': avatarUrl } };
}, },
setShowCardsCountAt(limit) { setShowCardsCountAt(limit) {
@ -323,7 +323,7 @@ Meteor.methods({
if (nUsersWithUsername > 0) { if (nUsersWithUsername > 0) {
throw new Meteor.Error('username-already-taken'); throw new Meteor.Error('username-already-taken');
} else { } else {
Users.update(this.userId, {$set: { username }}); Users.update(this.userId, { $set: { username } });
} }
}, },
toggleSystemMessages() { toggleSystemMessages() {
@ -346,19 +346,19 @@ if (Meteor.isServer) {
const inviter = Meteor.user(); const inviter = Meteor.user();
const board = Boards.findOne(boardId); const board = Boards.findOne(boardId);
const allowInvite = inviter && const allowInvite = inviter &&
board && board &&
board.members && board.members &&
_.contains(_.pluck(board.members, 'userId'), inviter._id) && _.contains(_.pluck(board.members, 'userId'), inviter._id) &&
_.where(board.members, {userId: inviter._id})[0].isActive && _.where(board.members, { userId: inviter._id })[0].isActive &&
_.where(board.members, {userId: inviter._id})[0].isAdmin; _.where(board.members, { userId: inviter._id })[0].isAdmin;
if (!allowInvite) throw new Meteor.Error('error-board-notAMember'); if (!allowInvite) throw new Meteor.Error('error-board-notAMember');
this.unblock(); this.unblock();
const posAt = username.indexOf('@'); const posAt = username.indexOf('@');
let user = null; let user = null;
if (posAt>=0) { if (posAt >= 0) {
user = Users.findOne({emails: {$elemMatch: {address: username}}}); user = Users.findOne({ emails: { $elemMatch: { address: username } } });
} else { } else {
user = Users.findOne(username) || Users.findOne({ username }); user = Users.findOne(username) || Users.findOne({ username });
} }
@ -409,7 +409,7 @@ if (Meteor.isServer) {
}); });
Accounts.onCreateUser((options, user) => { Accounts.onCreateUser((options, user) => {
const userCount = Users.find().count(); const userCount = Users.find().count();
if (!isSandstorm && userCount === 0 ){ if (!isSandstorm && userCount === 0) {
user.isAdmin = true; user.isAdmin = true;
return user; return user;
} }
@ -421,11 +421,11 @@ if (Meteor.isServer) {
if (!options || !options.profile) { if (!options || !options.profile) {
throw new Meteor.Error('error-invitation-code-blank', 'The invitation code is required'); throw new Meteor.Error('error-invitation-code-blank', 'The invitation code is required');
} }
const invitationCode = InvitationCodes.findOne({code: options.profile.invitationcode, email: options.email, valid: true}); const invitationCode = InvitationCodes.findOne({ code: options.profile.invitationcode, email: options.email, valid: true });
if (!invitationCode) { if (!invitationCode) {
throw new Meteor.Error('error-invitation-code-not-exist', 'The invitation code doesn\'t exist'); throw new Meteor.Error('error-invitation-code-not-exist', 'The invitation code doesn\'t exist');
}else{ } else {
user.profile = {icode: options.profile.invitationcode}; user.profile = { icode: options.profile.invitationcode };
} }
return user; return user;
@ -445,7 +445,7 @@ if (Meteor.isServer) {
// counter. // counter.
// We need to run this code on the server only, otherwise the incrementation // We need to run this code on the server only, otherwise the incrementation
// will be done twice. // will be done twice.
Users.after.update(function(userId, user, fieldNames) { Users.after.update(function (userId, user, fieldNames) {
// The `starredBoards` list is hosted on the `profile` field. If this // The `starredBoards` list is hosted on the `profile` field. If this
// field hasn't been modificated we don't need to run this hook. // field hasn't been modificated we don't need to run this hook.
if (!_.contains(fieldNames, 'profile')) if (!_.contains(fieldNames, 'profile'))
@ -464,7 +464,7 @@ if (Meteor.isServer) {
// direction and then in the other. // direction and then in the other.
function incrementBoards(boardsIds, inc) { function incrementBoards(boardsIds, inc) {
boardsIds.forEach((boardId) => { boardsIds.forEach((boardId) => {
Boards.update(boardId, {$inc: {stars: inc}}); Boards.update(boardId, { $inc: { stars: inc } });
}); });
} }
incrementBoards(_.difference(oldIds, newIds), -1); incrementBoards(_.difference(oldIds, newIds), -1);
@ -505,10 +505,10 @@ if (Meteor.isServer) {
//invite user to corresponding boards //invite user to corresponding boards
const disableRegistration = Settings.findOne().disableRegistration; const disableRegistration = Settings.findOne().disableRegistration;
if (disableRegistration) { if (disableRegistration) {
const invitationCode = InvitationCodes.findOne({code: doc.profile.icode, valid:true}); const invitationCode = InvitationCodes.findOne({ code: doc.profile.icode, valid: true });
if (!invitationCode) { if (!invitationCode) {
throw new Meteor.Error('error-invitation-code-not-exist'); throw new Meteor.Error('error-invitation-code-not-exist');
}else{ } else {
invitationCode.boardsToBeInvited.forEach((boardId) => { invitationCode.boardsToBeInvited.forEach((boardId) => {
const board = Boards.findOne(boardId); const board = Boards.findOne(boardId);
board.addMember(doc._id); board.addMember(doc._id);
@ -517,9 +517,55 @@ if (Meteor.isServer) {
doc.profile = {}; doc.profile = {};
} }
doc.profile.invitedBoards = invitationCode.boardsToBeInvited; doc.profile.invitedBoards = invitationCode.boardsToBeInvited;
Users.update(doc._id, {$set:{profile: doc.profile}}); Users.update(doc._id, { $set: { profile: doc.profile } });
InvitationCodes.update(invitationCode._id, {$set: {valid:false}}); InvitationCodes.update(invitationCode._id, { $set: { valid: false } });
} }
} }
}); });
} }
// USERS REST API
if (Meteor.isServer) {
JsonRoutes.add('GET', '/api/users', function (req, res, next) {
JsonRoutes.sendResult(res, {
code: 200,
data: Meteor.users.find({}).map(function (doc) {
return { _id: doc._id, username: doc.username };
}),
});
});
JsonRoutes.add('GET', '/api/users/:id', function (req, res, next) {
const id = req.params.id;
JsonRoutes.sendResult(res, {
code: 200,
data: Meteor.users.findOne({ _id: id }),
});
});
JsonRoutes.add('POST', '/api/users/', function (req, res, next) {
const id = Accounts.createUser({
username: req.body.username,
email: req.body.email,
password: 'default',
});
JsonRoutes.sendResult(res, {
code: 200,
data: {
_id: id,
},
});
});
JsonRoutes.add('DELETE', '/api/users/:id', function (req, res, next) {
const id = req.params.id;
Meteor.users.remove({ _id: id });
JsonRoutes.sendResult(res, {
code: 200,
data: {
_id: id,
},
});
});
}

View file

@ -7,6 +7,9 @@
"lint": "eslint --ignore-pattern 'packages/*' .", "lint": "eslint --ignore-pattern 'packages/*' .",
"test": "npm run --silent lint" "test": "npm run --silent lint"
}, },
"eslintConfig": {
"extends": "@meteorjs/eslint-config-meteor"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/wekan/wekan.git" "url": "git+https://github.com/wekan/wekan.git"