diff options
-rw-r--r-- | .eslintrc.json | 8 | ||||
-rw-r--r-- | .meteor/.finished-upgraders | 3 | ||||
-rw-r--r-- | .meteor/packages | 39 | ||||
-rw-r--r-- | .meteor/release | 2 | ||||
-rw-r--r-- | .meteor/versions | 181 | ||||
-rw-r--r-- | .travis.yml | 8 | ||||
-rw-r--r-- | Dockerfile | 30 | ||||
-rw-r--r-- | client/components/boards/boardHeader.js | 22 | ||||
-rw-r--r-- | models/boards.js | 101 | ||||
-rw-r--r-- | models/cards.js | 98 | ||||
-rw-r--r-- | models/export.js | 80 | ||||
-rw-r--r-- | models/lists.js | 58 | ||||
-rw-r--r-- | models/users.js | 108 | ||||
-rw-r--r-- | package.json | 10 | ||||
-rw-r--r-- | server/logger.js | 59 | ||||
-rw-r--r-- | server/observableChanges.js | 100 |
16 files changed, 660 insertions, 247 deletions
diff --git a/.eslintrc.json b/.eslintrc.json index 416548eb..0e2fd495 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -51,7 +51,9 @@ "object-shorthand": 2, "prefer-const": 2, "prefer-spread": 2, - "prefer-template": 2 + "prefer-template": 2, + "no-console": 0, + "no-unused-vars" : "warn" }, "globals": { "Meteor": false, @@ -122,6 +124,8 @@ "Emoji": true, "Checklists": true, "Settings": true, - "InvitationCodes": true + "InvitationCodes": true, + "Winston":true + "JsonRoutes" : true } } diff --git a/.meteor/.finished-upgraders b/.meteor/.finished-upgraders index 7bd71978..a8020370 100644 --- a/.meteor/.finished-upgraders +++ b/.meteor/.finished-upgraders @@ -12,3 +12,6 @@ notices-for-facebook-graph-api-2 1.2.0-breaking-changes 1.3.0-split-minifiers-package 1.3.5-remove-old-dev-bundle-link +1.4.0-remove-old-dev-bundle-link +1.4.1-add-shell-server-package +1.4.3-split-account-service-packages diff --git a/.meteor/packages b/.meteor/packages index 7ede4008..67678ccb 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -3,17 +3,17 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. -meteor-base +meteor-base@1.0.4 # Build system -ecmascript -stylus -standard-minifier-css -standard-minifier-js +ecmascript@0.7.2 +stylus@2.513.9 +standard-minifier-css@1.3.4 +standard-minifier-js@2.0.0 mquandalle:jade # Polyfills -es5-shim +es5-shim@4.6.15 # Collections aldeed:collection2 @@ -24,30 +24,30 @@ dburles:collection-helpers idmontie:migrations matb33:collection-hooks matteodem:easy-search -mongo +mongo@1.1.16 mquandalle:collection-mutations # Account system -accounts-password +accounts-password@1.3.5 kenton:accounts-sandstorm -service-configuration +service-configuration@1.0.11 useraccounts:core useraccounts:unstyled useraccounts:flow-routing -email +email@1.2.0 # Utilities -check -jquery -random -reactive-dict -session -tracker -underscore +check@1.2.5 +jquery@1.11.10 +random@1.0.10 +reactive-dict@1.1.8 +session@1.1.7 +tracker@1.1.2 +underscore@1.0.10 3stack:presence alethes:pages arillo:flow-router-helpers -audit-argument-checks +audit-argument-checks@1.0.7 kadira:blaze-layout kadira:dochead meteorhacks:fast-render @@ -61,7 +61,7 @@ tap:i18n # UI components blaze -reactive-var +reactive-var@1.0.11 fortawesome:fontawesome mousetrap:mousetrap mquandalle:jquery-textcomplete @@ -76,3 +76,4 @@ verron:autosize simple:json-routes rajit:bootstrap3-datepicker kadira:flow-router +shell-server@0.2.3 diff --git a/.meteor/release b/.meteor/release index fcf9d2d7..605b4e1f 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@1.3.5.1 +METEOR@1.4.4.1 diff --git a/.meteor/versions b/.meteor/versions index 4ca2f780..1a040534 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,26 +1,26 @@ 3stack:presence@1.0.5 -accounts-base@1.2.8 -accounts-password@1.1.13 -aldeed:collection2@2.9.1 -aldeed:collection2-core@1.1.1 -aldeed:schema-deny@1.0.1 -aldeed:schema-index@1.0.1 +accounts-base@1.2.16 +accounts-password@1.3.5 +aldeed:collection2@2.10.0 +aldeed:collection2-core@1.2.0 +aldeed:schema-deny@1.1.0 +aldeed:schema-index@1.1.1 aldeed:simple-schema@1.5.3 alethes:pages@1.8.6 allow-deny@1.0.5 arillo:flow-router-helpers@0.5.2 audit-argument-checks@1.0.7 -autoupdate@1.2.11 -babel-compiler@6.8.5 -babel-runtime@0.1.9_1 -base64@1.0.9 -binary-heap@1.0.9 -blaze@2.1.8 -blaze-tools@1.0.9 -boilerplate-generator@1.0.9 -caching-compiler@1.0.6 -caching-html-compiler@1.0.6 -callback-hook@1.0.9 +autoupdate@1.3.12 +babel-compiler@6.18.2 +babel-runtime@1.0.1 +base64@1.0.10 +binary-heap@1.0.10 +blaze@2.3.2 +blaze-tools@1.0.10 +boilerplate-generator@1.0.11 +caching-compiler@1.1.9 +caching-html-compiler@1.1.2 +callback-hook@1.0.10 cfs:access-point@0.1.49 cfs:base-package@0.0.30 cfs:collection@0.5.5 @@ -38,64 +38,64 @@ cfs:storage-adapter@0.2.3 cfs:tempstore@0.1.5 cfs:upload-http@0.0.20 cfs:worker@0.1.4 -check@1.2.3 +check@1.2.5 chuangbo:cookie@1.1.0 -coffeescript@1.1.4 -cottz:publish-relations@2.0.6 -dburles:collection-helpers@1.0.4 +coffeescript@1.12.3_1 +cottz:publish-relations@2.0.7 +dburles:collection-helpers@1.1.0 ddp@1.2.5 -ddp-client@1.2.9 -ddp-common@1.2.6 -ddp-rate-limiter@1.0.5 -ddp-server@1.2.10 +ddp-client@1.3.4 +ddp-common@1.2.8 +ddp-rate-limiter@1.0.7 +ddp-server@1.3.14 deps@1.0.12 -diff-sequence@1.0.6 -ecmascript@0.4.8 -ecmascript-runtime@0.2.12 -ejson@1.0.12 -email@1.0.16 -es5-shim@4.5.13 -fastclick@1.0.12 -fortawesome:fontawesome@4.5.0 -geojson-utils@1.0.9 +diff-sequence@1.0.7 +ecmascript@0.7.3 +ecmascript-runtime@0.3.15 +ejson@1.0.13 +email@1.2.1 +es5-shim@4.6.15 +fastclick@1.0.13 +fortawesome:fontawesome@4.7.0 +geojson-utils@1.0.10 hot-code-push@1.0.4 -html-tools@1.0.10 -htmljs@1.0.10 -http@1.1.8 -id-map@1.0.8 -idmontie:migrations@1.0.1 -jquery@1.11.9 +html-tools@1.0.11 +htmljs@1.0.11 +http@1.2.12 +id-map@1.0.9 +idmontie:migrations@1.0.3 +jquery@1.11.10 kadira:blaze-layout@2.3.0 kadira:dochead@1.5.0 kadira:flow-router@2.12.1 -kenton:accounts-sandstorm@0.5.1 -launch-screen@1.0.12 +kenton:accounts-sandstorm@0.6.0 +launch-screen@1.1.1 livedata@1.0.18 -localstorage@1.0.11 -logging@1.0.14 -matb33:collection-hooks@0.8.1 +localstorage@1.0.12 +logging@1.1.17 +matb33:collection-hooks@0.8.4 matteodem:easy-search@1.6.4 -mdg:validation-error@0.2.0 -meteor@1.1.16 +mdg:validation-error@0.5.1 +meteor@1.6.1 meteor-base@1.0.4 meteor-platform@1.2.6 meteorhacks:aggregate@1.3.0 meteorhacks:collection-utils@1.2.0 -meteorhacks:fast-render@2.14.0 +meteorhacks:fast-render@2.16.0 meteorhacks:inject-data@2.0.0 meteorhacks:meteorx@1.4.1 meteorhacks:picker@1.0.3 meteorhacks:subs-manager@1.6.4 meteorspark:util@0.2.0 -minifier-css@1.1.13 -minifier-js@1.1.13 +minifier-css@1.2.16 +minifier-js@2.0.0 minifiers@1.1.8-faster-rebuild.0 -minimongo@1.0.17 -mobile-status-bar@1.0.12 -modules@0.6.5 -modules-runtime@0.6.5 -mongo@1.1.9_1 -mongo-id@1.0.5 +minimongo@1.0.21 +mobile-status-bar@1.0.14 +modules@0.8.2 +modules-runtime@0.7.10 +mongo@1.1.16 +mongo-id@1.0.6 mongo-livedata@1.0.12 mousetrap:mousetrap@1.4.6_1 mquandalle:autofocus@1.0.0 @@ -107,52 +107,55 @@ mquandalle:jquery-ui-drag-drop-sort@0.2.0 mquandalle:moment@1.0.1 mquandalle:mousetrap-bindglobal@0.0.1 mquandalle:perfect-scrollbar@0.6.5_2 -npm-bcrypt@0.8.6_3 -npm-mongo@1.4.45 -observe-sequence@1.0.12 +npm-bcrypt@0.9.2 +npm-mongo@2.2.24 +observe-sequence@1.0.16 ongoworks:speakingurl@1.1.0 -ordered-dict@1.0.8 +ordered-dict@1.0.9 peerlibrary:assert@0.2.5 -peerlibrary:base-component@0.14.0 +peerlibrary:base-component@0.16.0 peerlibrary:blaze-components@0.15.1 -peerlibrary:computed-field@0.3.1 -peerlibrary:reactive-field@0.1.0 +peerlibrary:computed-field@0.6.1 +peerlibrary:reactive-field@0.3.0 perak:markdown@1.0.5 -promise@0.7.3 +promise@0.8.8 raix:eventemitter@0.1.3 raix:handlebar-helpers@0.2.5 -rajit:bootstrap3-datepicker@1.5.1 +rajit:bootstrap3-datepicker@1.6.4 random@1.0.10 -rate-limit@1.0.5 +rate-limit@1.0.8 reactive-dict@1.1.8 -reactive-var@1.0.10 -reload@1.1.10 -retry@1.0.8 -routepolicy@1.0.11 +reactive-var@1.0.11 +reload@1.1.11 +retry@1.0.9 +routepolicy@1.0.12 seriousm:emoji-continued@1.4.0 -service-configuration@1.0.10 -session@1.1.6 -sha@1.0.8 -simple:json-routes@1.0.4 -softwarerero:accounts-t9n@1.3.4 -spacebars@1.0.12 -spacebars-compiler@1.0.12 -srp@1.0.9 -standard-minifier-css@1.0.8 -standard-minifier-js@1.0.8 -stylus@2.512.5 +service-configuration@1.0.11 +session@1.1.7 +sha@1.0.9 +shell-server@0.2.3 +simple:json-routes@2.1.0 +softwarerero:accounts-t9n@1.3.9 +spacebars@1.0.15 +spacebars-compiler@1.1.2 +srp@1.0.10 +standard-minifier-css@1.3.4 +standard-minifier-js@2.0.0 +stylus@2.513.9 tap:i18n@1.8.2 -templates:tabs@2.2.2 -templating@1.1.14 -templating-tools@1.0.4 -tracker@1.0.15 -ui@1.0.11 -underscore@1.0.9 -url@1.0.10 +templates:tabs@2.3.0 +templating@1.3.2 +templating-compiler@1.3.2 +templating-runtime@1.3.2 +templating-tools@1.1.2 +tracker@1.1.2 +ui@1.0.13 +underscore@1.0.10 +url@1.1.0 useraccounts:core@1.14.2 useraccounts:flow-routing@1.14.2 useraccounts:unstyled@1.14.2 verron:autosize@3.0.8 -webapp@1.2.11 +webapp@1.3.15 webapp-hashing@1.0.9 zimme:active-route@2.3.2 diff --git a/.travis.yml b/.travis.yml index 9a760beb..01d60165 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,13 +10,15 @@ before_install: - sudo chmod +x docker-compose - sudo mv docker-compose /usr/local/bin - sudo docker-compose build --no-cache --force-rm - - sudo docker-compose up && docker ps -a - - sudo docker run wekan-app /bin/sh -c "npm test" + - sudo docker-compose up -d wekandb + - sudo docker-compose up -d wekan + # ^^ Note - need to come up with some way of checking the output from docker run + # that it was a success... perhaps the nodejs server can output a success message? language: node_js node_js: - - "0.10.48" + - "6.10.2" install: - "npm install" @@ -1,9 +1,10 @@ -FROM debian:wheezy +FROM debian:8.7 MAINTAINER wekan # Declare Arguments ARG NODE_VERSION ARG METEOR_RELEASE +ARG METEOR_EDGE ARG NPM_VERSION ARG ARCHITECTURE ARG SRC_PATH @@ -11,9 +12,10 @@ ARG SRC_PATH # Set the environment variables (defaults where required) ENV BUILD_DEPS="wget curl bzip2 build-essential python git ca-certificates" ENV GOSU_VERSION=1.10 -ENV NODE_VERSION ${NODE_VERSION:-v0.10.48} -ENV METEOR_RELEASE ${METEOR_RELEASE:-1.3.5.1} -ENV NPM_VERSION ${NPM_VERSION:-4.2.0} +ENV NODE_VERSION ${NODE_VERSION:-v6.10.2} +ENV METEOR_RELEASE ${METEOR_RELEASE:-1.4.5} +ENV METEOR_EDGE ${METEOR_EDGE:-1.4.4-rc.6} +ENV NPM_VERSION ${NPM_VERSION:-3.10.10} ENV ARCHITECTURE ${ARCHITECTURE:-linux-x64} ENV SRC_PATH ${SRC_PATH:-./} @@ -63,7 +65,7 @@ RUN \ ln -s /opt/nodejs/bin/npm /usr/bin/npm && \ \ # Install Node dependencies - npm install npm@${NPM_VERSION} -g && \ + npm install -g npm@${NPM_VERSION} && \ npm install -g node-gyp && \ npm install -g fibers && \ \ @@ -74,11 +76,23 @@ RUN \ sed -i "s|RELEASE=.*|RELEASE=${METEOR_RELEASE}\"\"|g" ./install_meteor.sh && \ echo "Starting meteor ${METEOR_RELEASE} installation... \n" && \ chown wekan:wekan ./install_meteor.sh && \ - gosu wekan:wekan sh ./install_meteor.sh && \ + ########################### + ########################### + # Block for ensuring installation of release candidates - perhaps remove later. + gosu wekan:wekan sh ./install_meteor.sh || \ + ( \ + gosu wekan:wekan git clone --recursive git://github.com/meteor/meteor.git /home/wekan/.meteor && \ + cd /home/wekan/.meteor && \ + gosu wekan:wekan git checkout release/METEOR@${METEOR_EDGE} && \ + gosu wekan /home/wekan/.meteor/meteor -- help \ + ) && \ + ########################### + ########################### \ # Build app cd /home/wekan/app && \ - gosu wekan /home/wekan/.meteor/meteor npm install --save xss && \ + gosu wekan /home/wekan/.meteor/meteor add standard-minifier-js && \ + gosu wekan /home/wekan/.meteor/meteor npm install && \ gosu wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build && \ cd /home/wekan/app_build/bundle/programs/server/ && \ gosu wekan npm install && \ @@ -96,4 +110,4 @@ RUN \ ENV PORT=80 EXPOSE $PORT -CMD ["node", "/build/main.js"] +CMD ["node", "/build/main.js"]
\ No newline at end of file diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 44532c3e..10d9925a 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -15,17 +15,17 @@ Template.boardMenuPopup.events({ }), }); -Template.boardMenuPopup.helpers({ - exportUrl() { - const boardId = Session.get('currentBoard'); - const loginToken = Accounts._storedLoginToken(); - return FlowRouter.url(`api/boards/${boardId}?authToken=${loginToken}`); - }, - exportFilename() { - const boardId = Session.get('currentBoard'); - return `wekan-export-board-${boardId}.json`; - }, -}); +// Template.boardMenuPopup.helpers({ +// exportUrl() { +// const boardId = Session.get('currentBoard'); +// const loginToken = Accounts._storedLoginToken(); +// return FlowRouter.url(`api/boards/${boardId}?authToken=${loginToken}`); +// }, +// exportFilename() { +// const boardId = Session.get('currentBoard'); +// return `wekan-export-board-${boardId}.json`; +// }, +// }); Template.boardChangeTitlePopup.events({ submit(evt, tpl) { diff --git a/models/boards.js b/models/boards.js index f4296a6c..9cbb5b63 100644 --- a/models/boards.js +++ b/models/boards.js @@ -156,7 +156,7 @@ Boards.helpers({ * Is supplied user authorized to view this board? */ isVisibleBy(user) { - if(this.isPublic()) { + if (this.isPublic()) { // public boards are visible to everyone return true; } else { @@ -172,7 +172,7 @@ Boards.helpers({ * @returns {boolean} the member that matches, or undefined/false */ isActiveMember(userId) { - if(userId) { + if (userId) { return this.members.find((member) => (member.userId === userId && member.isActive)); } else { return false; @@ -184,23 +184,23 @@ Boards.helpers({ }, lists() { - return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 }}); + return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } }); }, activities() { - return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 }}); + return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 } }); }, activeMembers() { - return _.where(this.members, {isActive: true}); + return _.where(this.members, { isActive: true }); }, activeAdmins() { - return _.where(this.members, {isActive: true, isAdmin: true}); + return _.where(this.members, { isActive: true, isAdmin: true }); }, memberUsers() { - return Users.find({ _id: {$in: _.pluck(this.members, 'userId')} }); + return Users.find({ _id: { $in: _.pluck(this.members, 'userId') } }); }, getLabel(name, color) { @@ -216,15 +216,15 @@ Boards.helpers({ }, hasMember(memberId) { - return !!_.findWhere(this.members, {userId: memberId, isActive: true}); + return !!_.findWhere(this.members, { userId: memberId, isActive: true }); }, hasAdmin(memberId) { - return !!_.findWhere(this.members, {userId: memberId, isActive: true, isAdmin: true}); + return !!_.findWhere(this.members, { userId: memberId, isActive: true, isAdmin: true }); }, 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() { @@ -239,34 +239,34 @@ Boards.helpers({ // XXX waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove... pushLabel(name, color) { 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; }, }); Boards.mutations({ archive() { - return { $set: { archived: true }}; + return { $set: { archived: true } }; }, restore() { - return { $set: { archived: false }}; + return { $set: { archived: false } }; }, rename(title) { - return { $set: { title }}; + return { $set: { title } }; }, setDescription(description) { - return { $set: {description} }; + return { $set: { description } }; }, setColor(color) { - return { $set: { color }}; + return { $set: { color } }; }, setVisibility(visibility) { - return { $set: { permission: visibility }}; + return { $set: { permission: visibility } }; }, addLabel(name, color) { @@ -276,7 +276,7 @@ Boards.mutations({ // user). if (!this.getLabel(name, color)) { const _id = Random.id(6); - return { $push: {labels: { _id, name, color }}}; + return { $push: { labels: { _id, name, color } } }; } return {}; }, @@ -295,7 +295,7 @@ Boards.mutations({ }, removeLabel(labelId) { - return { $pull: { labels: { _id: labelId }}}; + return { $pull: { labels: { _id: labelId } } }; }, addMember(memberId) { @@ -386,7 +386,7 @@ if (Meteor.isServer) { return false; // 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) return false; @@ -408,7 +408,7 @@ if (Meteor.isServer) { if (board) { const userId = Meteor.userId(); const index = board.memberIndex(userId); - if (index>=0) { + if (index >= 0) { board.removeMember(userId); return true; } else throw new Meteor.Error('error-board-notAMember'); @@ -424,7 +424,7 @@ if (Meteor.isServer) { _id: 1, 'members.userId': 1, }, { unique: true }); - Boards._collection._ensureIndex({'members.userId': 1}); + Boards._collection._ensureIndex({ 'members.userId': 1 }); }); // 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, + }, + }); + }); + +} diff --git a/models/cards.js b/models/cards.js index 922fbecd..2d585825 100644 --- a/models/cards.js +++ b/models/cards.js @@ -123,15 +123,15 @@ Cards.helpers({ }, activities() { - return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 }}); + return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 } }); }, comments() { - return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }}); + return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 } }); }, attachments() { - return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 }}); + return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 } }); }, cover() { @@ -142,7 +142,7 @@ Cards.helpers({ }, checklists() { - return Checklists.find({ cardId: this._id }, { sort: { createdAt: 1 }}); + return Checklists.find({ cardId: this._id }, { sort: { createdAt: 1 } }); }, checklistItemCount() { @@ -183,19 +183,19 @@ Cards.helpers({ Cards.mutations({ archive() { - return { $set: { archived: true }}; + return { $set: { archived: true } }; }, restore() { - return { $set: { archived: false }}; + return { $set: { archived: false } }; }, setTitle(title) { - return { $set: { title }}; + return { $set: { title } }; }, setDescription(description) { - return { $set: { description }}; + return { $set: { description } }; }, move(listId, sortIndex) { @@ -207,11 +207,11 @@ Cards.mutations({ }, addLabel(labelId) { - return { $addToSet: { labelIds: labelId }}; + return { $addToSet: { labelIds: labelId } }; }, removeLabel(labelId) { - return { $pull: { labelIds: labelId }}; + return { $pull: { labelIds: labelId } }; }, toggleLabel(labelId) { @@ -223,11 +223,11 @@ Cards.mutations({ }, assignMember(memberId) { - return { $addToSet: { members: memberId }}; + return { $addToSet: { members: memberId } }; }, unassignMember(memberId) { - return { $pull: { members: memberId }}; + return { $pull: { members: memberId } }; }, toggleMember(memberId) { @@ -239,27 +239,27 @@ Cards.mutations({ }, setCover(coverId) { - return { $set: { coverId }}; + return { $set: { coverId } }; }, unsetCover() { - return { $unset: { coverId: '' }}; + return { $unset: { coverId: '' } }; }, setStart(startAt) { - return { $set: { startAt }}; + return { $set: { startAt } }; }, unsetStart() { - return { $unset: { startAt: '' }}; + return { $unset: { startAt: '' } }; }, setDue(dueAt) { - return { $set: { dueAt }}; + return { $set: { dueAt } }; }, unsetDue() { - return { $unset: { dueAt: '' }}; + return { $unset: { dueAt: '' } }; }, }); @@ -304,7 +304,7 @@ if (Meteor.isServer) { }); // New activity for card moves - Cards.after.update(function(userId, doc, fieldNames) { + Cards.after.update(function (userId, doc, fieldNames) { const oldListId = this.previous.listId; if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) { 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, + }, + }); + }); +} diff --git a/models/export.js b/models/export.js index b774cf15..7a363dd3 100644 --- a/models/export.js +++ b/models/export.js @@ -1,5 +1,5 @@ /* global JsonRoutes */ -if(Meteor.isServer) { +if (Meteor.isServer) { // 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 // 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/ * for detailed explanations */ - JsonRoutes.add('get', '/api/boards/:boardId', function (req, res) { - const boardId = req.params.boardId; - let user = null; - // todo XXX for real API, first look for token in Authentication: header - // then fallback to parameter - const loginToken = req.query.authToken; - if (loginToken) { - const hashToken = Accounts._hashLoginToken(loginToken); - user = Meteor.users.findOne({ - 'services.resume.loginTokens.hashedToken': hashToken, - }); - } + // JsonRoutes.add('get', '/api/boards/:boardId', function (req, res) { + // const boardId = req.params.boardId; + // let user = null; + // // todo XXX for real API, first look for token in Authentication: header + // // then fallback to parameter + // const loginToken = req.query.authToken; + // if (loginToken) { + // const hashToken = Accounts._hashLoginToken(loginToken); + // user = Meteor.users.findOne({ + // 'services.resume.loginTokens.hashedToken': hashToken, + // }); + // } - const exporter = new Exporter(boardId); - if(exporter.canExport(user)) { - JsonRoutes.sendResult(res, 200, exporter.build()); - } else { - // 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. - JsonRoutes.sendResult(res, 403); - } - }); + // const exporter = new Exporter(boardId); + // if(exporter.canExport(user)) { + // JsonRoutes.sendResult(res, 200, exporter.build()); + // } else { + // // 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. + // JsonRoutes.sendResult(res, 403); + // } + // }); } class Exporter { @@ -44,13 +44,13 @@ class Exporter { } build() { - const byBoard = {boardId: this._boardId}; + const byBoard = { boardId: this._boardId }; // we do not want to retrieve boardId in related elements - const noBoardId = {fields: {boardId: 0}}; + const noBoardId = { fields: { boardId: 0 } }; const result = { _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.cards = Cards.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 // 2- do not export any sensitive information const users = {}; - result.members.forEach((member) => {users[member.userId] = true;}); - result.lists.forEach((list) => {users[list.userId] = true;}); + result.members.forEach((member) => { users[member.userId] = true; }); + result.lists.forEach((list) => { users[list.userId] = true; }); result.cards.forEach((card) => { users[card.userId] = true; 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.activities.forEach((activity) => {users[activity.userId] = true;}); - const byUserIds = {_id: {$in: Object.getOwnPropertyNames(users)}}; + result.comments.forEach((comment) => { users[comment.userId] = true; }); + result.activities.forEach((activity) => { users[activity.userId] = true; }); + const byUserIds = { _id: { $in: Object.getOwnPropertyNames(users) } }; // we use whitelist to be sure we do not expose inadvertently // some secret fields that gets added to User later. - const userFields = {fields: { - _id: 1, - username: 1, - 'profile.fullname': 1, - 'profile.initials': 1, - 'profile.avatarUrl': 1, - }}; + const userFields = { + fields: { + _id: 1, + username: 1, + 'profile.fullname': 1, + 'profile.initials': 1, + 'profile.avatarUrl': 1, + }, + }; result.users = Users.find(byUserIds, userFields).fetch().map((user) => { // 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); } return user; diff --git a/models/lists.js b/models/lists.js index 0ae3ca5f..a10e23b6 100644 --- a/models/lists.js +++ b/models/lists.js @@ -76,15 +76,15 @@ Lists.helpers({ Lists.mutations({ rename(title) { - return { $set: { title }}; + return { $set: { title } }; }, archive() { - return { $set: { archived: true }}; + return { $set: { archived: true } }; }, 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, + }, + }); + }); + +} diff --git a/models/users.js b/models/users.js index 236feb45..c1ce146a 100644 --- a/models/users.js +++ b/models/users.js @@ -1,7 +1,7 @@ // Sandstorm context is detected using the METEOR_SETTINGS environment variable // in the package definition. const isSandstorm = Meteor.settings && Meteor.settings.public && - Meteor.settings.public.sandstorm; + Meteor.settings.public.sandstorm; Users = Meteor.users; Users.attachSchema(new SimpleSchema({ @@ -106,8 +106,8 @@ Users.attachSchema(new SimpleSchema({ }, isAdmin: { type: Boolean, - optional: true - } + optional: true, + }, })); // Search a user in the complete server database by its name or username. This @@ -148,32 +148,32 @@ Users.helpers({ }, starredBoards() { - const {starredBoards = []} = this.profile; - return Boards.find({archived: false, _id: {$in: starredBoards}}); + const { starredBoards = [] } = this.profile; + return Boards.find({ archived: false, _id: { $in: starredBoards } }); }, hasStarred(boardId) { - const {starredBoards = []} = this.profile; + const { starredBoards = [] } = this.profile; return _.contains(starredBoards, boardId); }, invitedBoards() { - const {invitedBoards = []} = this.profile; - return Boards.find({archived: false, _id: {$in: invitedBoards}}); + const { invitedBoards = [] } = this.profile; + return Boards.find({ archived: false, _id: { $in: invitedBoards } }); }, isInvitedTo(boardId) { - const {invitedBoards = []} = this.profile; + const { invitedBoards = [] } = this.profile; return _.contains(invitedBoards, boardId); }, hasTag(tag) { - const {tags = []} = this.profile; + const { tags = [] } = this.profile; return _.contains(tags, tag); }, hasNotification(activityId) { - const {notifications = []} = this.profile; + const { notifications = [] } = this.profile; return _.contains(notifications, activityId); }, @@ -183,7 +183,7 @@ Users.helpers({ }, getEmailBuffer() { - const {emailBuffer = []} = this.profile; + const { emailBuffer = [] } = this.profile; return emailBuffer; }, @@ -308,7 +308,7 @@ Users.mutations({ }, setAvatarUrl(avatarUrl) { - return { $set: { 'profile.avatarUrl': avatarUrl }}; + return { $set: { 'profile.avatarUrl': avatarUrl } }; }, setShowCardsCountAt(limit) { @@ -323,7 +323,7 @@ Meteor.methods({ if (nUsersWithUsername > 0) { throw new Meteor.Error('username-already-taken'); } else { - Users.update(this.userId, {$set: { username }}); + Users.update(this.userId, { $set: { username } }); } }, toggleSystemMessages() { @@ -346,19 +346,19 @@ if (Meteor.isServer) { const inviter = Meteor.user(); const board = Boards.findOne(boardId); const allowInvite = inviter && - board && - board.members && - _.contains(_.pluck(board.members, 'userId'), inviter._id) && - _.where(board.members, {userId: inviter._id})[0].isActive && - _.where(board.members, {userId: inviter._id})[0].isAdmin; + board && + board.members && + _.contains(_.pluck(board.members, 'userId'), inviter._id) && + _.where(board.members, { userId: inviter._id })[0].isActive && + _.where(board.members, { userId: inviter._id })[0].isAdmin; if (!allowInvite) throw new Meteor.Error('error-board-notAMember'); this.unblock(); const posAt = username.indexOf('@'); let user = null; - if (posAt>=0) { - user = Users.findOne({emails: {$elemMatch: {address: username}}}); + if (posAt >= 0) { + user = Users.findOne({ emails: { $elemMatch: { address: username } } }); } else { user = Users.findOne(username) || Users.findOne({ username }); } @@ -409,7 +409,7 @@ if (Meteor.isServer) { }); Accounts.onCreateUser((options, user) => { const userCount = Users.find().count(); - if (!isSandstorm && userCount === 0 ){ + if (!isSandstorm && userCount === 0) { user.isAdmin = true; return user; } @@ -421,11 +421,11 @@ if (Meteor.isServer) { if (!options || !options.profile) { 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) { throw new Meteor.Error('error-invitation-code-not-exist', 'The invitation code doesn\'t exist'); - }else{ - user.profile = {icode: options.profile.invitationcode}; + } else { + user.profile = { icode: options.profile.invitationcode }; } return user; @@ -445,7 +445,7 @@ if (Meteor.isServer) { // counter. // We need to run this code on the server only, otherwise the incrementation // 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 // field hasn't been modificated we don't need to run this hook. if (!_.contains(fieldNames, 'profile')) @@ -464,7 +464,7 @@ if (Meteor.isServer) { // direction and then in the other. function incrementBoards(boardsIds, inc) { boardsIds.forEach((boardId) => { - Boards.update(boardId, {$inc: {stars: inc}}); + Boards.update(boardId, { $inc: { stars: inc } }); }); } incrementBoards(_.difference(oldIds, newIds), -1); @@ -505,10 +505,10 @@ if (Meteor.isServer) { //invite user to corresponding boards const disableRegistration = Settings.findOne().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) { throw new Meteor.Error('error-invitation-code-not-exist'); - }else{ + } else { invitationCode.boardsToBeInvited.forEach((boardId) => { const board = Boards.findOne(boardId); board.addMember(doc._id); @@ -517,9 +517,55 @@ if (Meteor.isServer) { doc.profile = {}; } doc.profile.invitedBoards = invitationCode.boardsToBeInvited; - Users.update(doc._id, {$set:{profile: doc.profile}}); - InvitationCodes.update(invitationCode._id, {$set: {valid:false}}); + Users.update(doc._id, { $set: { profile: doc.profile } }); + 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, + }, + }); + }); +} + diff --git a/package.json b/package.json index a3b44826..d591a213 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "lint": "eslint --ignore-pattern 'packages/*' .", "test": "npm run --silent lint" }, + "eslintConfig": { + "extends": "@meteorjs/eslint-config-meteor" + }, "repository": { "type": "git", "url": "git+https://github.com/wekan/wekan.git" @@ -20,6 +23,13 @@ "eslint": "^2.0.0" }, "dependencies": { + "babel-runtime": "^6.23.0", + "bcrypt": "^1.0.2", + "bson": "^1.0.4", + "es6-promise": "^4.1.0", + "meteor-node-stubs": "^0.2.6", + "winston": "^2.3.1", + "winston-zulip": "0.0.6", "xss": "^0.3.3" } } diff --git a/server/logger.js b/server/logger.js new file mode 100644 index 00000000..376e30aa --- /dev/null +++ b/server/logger.js @@ -0,0 +1,59 @@ +Meteor.startup(() => { + Winston = require('winston'); + require('winston-zulip'); + const fs = require('fs'); + + //remove default logger + Winston.remove(Winston.transports.Console); + + + const loggerEnable = process.env.LOGGER_ENABLE || false; + console.log('here1'); + console.log(loggerEnable); + if (loggerEnable) { + console.log('here2'); + const loggers = process.env.LOGGERS.split(',') || 'console'; + + if (loggers.includes('console')) { + Winston.add(Winston.transports.Console, { + json: true, + timestamp: true, + }); + } + + if (loggers.includes('file')) { + //create logs directory + fs.mkdir('logs', (err) => { + if (err) throw err; + }); + + const fileName = `logs/${process.env.LOGGER_FILE_NAME}` || 'logs/server.log'; + + Winston.add(Winston.transports.File, { + filename: fileName, + json: true, + options: { + flags: 'a+', + }, + }); + } + + if (loggers.includes('zulip')) { + const loggerZulipUsername = process.env.LOGGER_ZULIP_USERNAME; + const loggerZulipApikey = process.env.LOGGER_ZULIP_APIKEY; + const loggerZulipRealm = process.env.LOGGER_ZULIP_REALM; + const loggerZulipTo = process.env.LOGGER_ZULIP_TO || 'logs'; + const loggerZulipSubject = process.env.LOGGER_ZULIP_SUBJECT || 'wekan'; + + Winston.add(Winston.transports.Zulip, { + zulipUsername: loggerZulipUsername, + zulipApikey: loggerZulipApikey, + zulipRealm: loggerZulipRealm, + zulipTo: loggerZulipTo, + zulipSubject: loggerZulipSubject, + }); + } + + } +}); + diff --git a/server/observableChanges.js b/server/observableChanges.js new file mode 100644 index 00000000..390ae093 --- /dev/null +++ b/server/observableChanges.js @@ -0,0 +1,100 @@ +class Message { + constructor(userId, type, method, doc, selector, fieldNames, modifier) { + this.userId = userId; + this.type = type; + this.method = method; + this.doc = doc; + this.selector; + this.fieldNames = fieldNames; + this.modifier = modifier; + } + +} + +//------------- CARDS -------------------- +Cards.before.update(function (userId, doc, fieldNames, modifier, options) { + Winston.log('info', new Message(userId, 'card', 'update', doc, null, fieldNames, modifier)); +}); + +Cards.before.remove(function (userId, doc) { + Winston.log('info', new Message(userId, 'card', 'remove', doc)); +}); + +Cards.before.insert(function (userId, doc) { + Winston.log('info', new Message(userId, 'card', 'insert', doc)); +}); + +Cards.before.upsert(function (userId, selector, modifier, options) { + Winston.log('info', new Message(userId, 'card', 'update', null, selector, null, modifier)); +}); + + +//------------- BOARDS -------------------- +Boards.before.update(function (userId, doc, fieldNames, modifier, options) { + Winston.log('info', new Message(userId, 'board', 'update', doc, null, fieldNames, modifier)); +}); + +Boards.before.remove(function (userId, doc) { + Winston.log('info', new Message(userId, 'board', 'remove', doc)); +}); + +Boards.before.insert(function (userId, doc) { + Winston.log('info', new Message(userId, 'board', 'insert', doc)); +}); + +Boards.before.upsert(function (userId, selector, modifier, options) { + Winston.log('info', new Message(userId, 'board', 'update', null, selector, null, modifier)); +}); + +//------------- LISTS -------------------- +Lists.before.update(function (userId, doc, fieldNames, modifier, options) { + Winston.log('info', new Message(userId, 'list', 'update', doc, null, fieldNames, modifier)); +}); + +Lists.before.remove(function (userId, doc) { + Winston.log('info', new Message(userId, 'list', 'remove', doc)); +}); + +Lists.before.insert(function (userId, doc) { + Winston.log('info', new Message(userId, 'list', 'insert', doc)); +}); + +Lists.before.upsert(function (userId, selector, modifier, options) { + Winston.log('info', new Message(userId, 'list', 'update', null, selector, null, modifier)); +}); + + +//------------- CARD COMMENTS -------------------- +CardComments.before.update(function (userId, doc, fieldNames, modifier, options) { + Winston.log('info', new Message(userId, 'card-comments', 'update', doc, null, fieldNames, modifier)); +}); + +CardComments.before.remove(function (userId, doc) { + Winston.log('info', new Message(userId, 'card-comments', 'remove', doc)); +}); + +CardComments.before.insert(function (userId, doc) { + Winston.log('info', new Message(userId, 'card-comments', 'insert', doc)); +}); + +CardComments.before.upsert(function (userId, selector, modifier, options) { + Winston.log('info', new Message(userId, 'card-comments', 'update', null, selector, null, modifier)); +}); + + +//------------- USERS -------------------- +Users.before.update(function (userId, doc, fieldNames, modifier, options) { + Winston.log('info', new Message(userId, 'user', 'update', doc, null, fieldNames, modifier)); +}); + +Users.before.remove(function (userId, doc) { + Winston.log('info', new Message(userId, 'user', 'remove', doc)); +}); + +Users.before.insert(function (userId, doc) { + Winston.log('info', new Message(userId, 'user', 'insert', doc)); +}); + +Users.before.upsert(function (userId, selector, modifier, options) { + Winston.log('info', new Message(userId, 'user', 'update', null, selector, null, modifier)); +}); |