diff options
author | Benjamin Tissoires <benjamin.tissoires@redhat.com> | 2018-10-26 07:27:24 +0200 |
---|---|---|
committer | Benjamin Tissoires <benjamin.tissoires@redhat.com> | 2019-01-18 17:02:56 +0100 |
commit | ff467402c0c24981078f1f8e2b92b26b0d67d00a (patch) | |
tree | 289b4647e571f7896172028068b2bd130cbfa757 | |
parent | 49d3eb5a3f21fad1eb2952eb3da2f93c5c5d6272 (diff) | |
download | wekan-ff467402c0c24981078f1f8e2b92b26b0d67d00a.tar.gz wekan-ff467402c0c24981078f1f8e2b92b26b0d67d00a.tar.bz2 wekan-ff467402c0c24981078f1f8e2b92b26b0d67d00a.zip |
RESTAPI: Add some JSDoc
So we can have a decent REST API documentation generated.
-rw-r--r-- | models/boards.js | 176 | ||||
-rw-r--r-- | models/cardComments.js | 56 | ||||
-rw-r--r-- | models/cards.js | 169 | ||||
-rw-r--r-- | models/checklistItems.js | 55 | ||||
-rw-r--r-- | models/checklists.js | 63 | ||||
-rw-r--r-- | models/customFields.js | 73 | ||||
-rw-r--r-- | models/export.js | 15 | ||||
-rw-r--r-- | models/integrations.js | 99 | ||||
-rw-r--r-- | models/lists.js | 68 | ||||
-rw-r--r-- | models/swimlanes.js | 59 | ||||
-rw-r--r-- | models/users.js | 172 |
11 files changed, 994 insertions, 11 deletions
diff --git a/models/boards.js b/models/boards.js index db3b1149..99480ca7 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1,10 +1,19 @@ Boards = new Mongo.Collection('boards'); +/** + * This is a Board. + */ Boards.attachSchema(new SimpleSchema({ title: { + /** + * The title of the board + */ type: String, }, slug: { + /** + * The title slugified. + */ type: String, autoValue() { // eslint-disable-line consistent-return // XXX We need to improve slug management. Only the id should be necessary @@ -24,6 +33,9 @@ Boards.attachSchema(new SimpleSchema({ }, }, archived: { + /** + * Is the board archived? + */ type: Boolean, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -32,6 +44,9 @@ Boards.attachSchema(new SimpleSchema({ }, }, createdAt: { + /** + * Creation time of the board + */ type: Date, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -43,6 +58,9 @@ Boards.attachSchema(new SimpleSchema({ }, // XXX Inconsistent field naming modifiedAt: { + /** + * Last modification time of the board + */ type: Date, optional: true, autoValue() { // eslint-disable-line consistent-return @@ -55,6 +73,9 @@ Boards.attachSchema(new SimpleSchema({ }, // De-normalized number of users that have starred this board stars: { + /** + * How many stars the board has + */ type: Number, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -64,6 +85,9 @@ Boards.attachSchema(new SimpleSchema({ }, // De-normalized label system 'labels': { + /** + * List of labels attached to a board + */ type: [Object], autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -78,6 +102,9 @@ Boards.attachSchema(new SimpleSchema({ }, }, 'labels.$._id': { + /** + * Unique id of a label + */ // We don't specify that this field must be unique in the board because that // will cause performance penalties and is not necessary since this field is // always set on the server. @@ -86,10 +113,22 @@ Boards.attachSchema(new SimpleSchema({ type: String, }, 'labels.$.name': { + /** + * Name of a label + */ type: String, optional: true, }, 'labels.$.color': { + /** + * color of a label. + * + * Can be amongst `green`, `yellow`, `orange`, `red`, `purple`, + * `blue`, `sky`, `lime`, `pink`, `black`, + * `silver`, `peachpuff`, `crimson`, `plum`, `darkgreen`, + * `slateblue`, `magenta`, `gold`, `navy`, `gray`, + * `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo` + */ type: String, allowedValues: [ 'green', 'yellow', 'orange', 'red', 'purple', @@ -103,6 +142,9 @@ Boards.attachSchema(new SimpleSchema({ // documents like de-normalized meta-data (the date the member joined the // board, the number of contributions, etc.). 'members': { + /** + * List of members of a board + */ type: [Object], autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -117,27 +159,48 @@ Boards.attachSchema(new SimpleSchema({ }, }, 'members.$.userId': { + /** + * The uniq ID of the member + */ type: String, }, 'members.$.isAdmin': { + /** + * Is the member an admin of the board? + */ type: Boolean, }, 'members.$.isActive': { + /** + * Is the member active? + */ type: Boolean, }, 'members.$.isNoComments': { + /** + * Is the member not allowed to make comments + */ type: Boolean, optional: true, }, 'members.$.isCommentOnly': { + /** + * Is the member only allowed to comment on the board + */ type: Boolean, optional: true, }, permission: { + /** + * visibility of the board + */ type: String, allowedValues: ['public', 'private'], }, color: { + /** + * The color of the board. + */ type: String, allowedValues: [ 'belize', @@ -154,24 +217,45 @@ Boards.attachSchema(new SimpleSchema({ }, }, description: { + /** + * The description of the board + */ type: String, optional: true, }, subtasksDefaultBoardId: { + /** + * The default board ID assigned to subtasks. + */ type: String, optional: true, defaultValue: null, }, subtasksDefaultListId: { + /** + * The default List ID assigned to subtasks. + */ type: String, optional: true, defaultValue: null, }, allowsSubtasks: { + /** + * Does the board allows subtasks? + */ type: Boolean, defaultValue: true, }, presentParentTask: { + /** + * Controls how to present the parent task: + * + * - `prefix-with-full-path`: add a prefix with the full path + * - `prefix-with-parent`: add a prefisx with the parent name + * - `subtext-with-full-path`: add a subtext with the full path + * - `subtext-with-parent`: add a subtext with the parent name + * - `no-parent`: does not show the parent at all + */ type: String, allowedValues: [ 'prefix-with-full-path', @@ -184,23 +268,38 @@ Boards.attachSchema(new SimpleSchema({ defaultValue: 'no-parent', }, startAt: { + /** + * Starting date of the board. + */ type: Date, optional: true, }, dueAt: { + /** + * Due date of the board. + */ type: Date, optional: true, }, endAt: { + /** + * End date of the board. + */ type: Date, optional: true, }, spentTime: { + /** + * Time spent in the board. + */ type: Number, decimal: true, optional: true, }, isOvertime: { + /** + * Is the board overtimed? + */ type: Boolean, defaultValue: false, optional: true, @@ -774,6 +873,14 @@ if (Meteor.isServer) { //BOARDS REST API if (Meteor.isServer) { + /** + * @operation get_boards_from_user + * @summary Get all boards attached to a user + * + * @param {string} userId the ID of the user to retrieve the data + * @return_type [{_id: string, + title: string}] + */ JsonRoutes.add('GET', '/api/users/:userId/boards', function (req, res) { try { Authentication.checkLoggedIn(req.userId); @@ -804,6 +911,13 @@ if (Meteor.isServer) { } }); + /** + * @operation get_public_boards + * @summary Get all public boards + * + * @return_type [{_id: string, + title: string}] + */ JsonRoutes.add('GET', '/api/boards', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -825,6 +939,13 @@ if (Meteor.isServer) { } }); + /** + * @operation get_board + * @summary Get the board with that particular ID + * + * @param {string} boardId the ID of the board to retrieve the data + * @return_type Boards + */ JsonRoutes.add('GET', '/api/boards/:boardId', function (req, res) { try { const id = req.params.boardId; @@ -843,6 +964,31 @@ if (Meteor.isServer) { } }); + /** + * @operation new_board + * @summary Create a board + * + * @description This allows to create a board. + * + * The color has to be chosen between `belize`, `nephritis`, `pomegranate`, + * `pumpkin`, `wisteria`, `midnight`: + * + * <img src="https://wekan.github.io/board-colors.png" width="40%" alt="Wekan logo" /> + * + * @param {string} title the new title of the board + * @param {string} owner "ABCDE12345" <= User ID in Wekan. + * (Not username or email) + * @param {boolean} [isAdmin] is the owner an admin of the board (default true) + * @param {boolean} [isActive] is the board active (default true) + * @param {boolean} [isNoComments] disable comments (default false) + * @param {boolean} [isCommentOnly] only enable comments (default false) + * @param {string} [permission] "private" board <== Set to "public" if you + * want public Wekan board + * @param {string} [color] the color of the board + * + * @return_type {_id: string, + defaultSwimlaneId: string} + */ JsonRoutes.add('POST', '/api/boards', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -880,6 +1026,12 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_board + * @summary Delete a board + * + * @param {string} boardId the ID of the board + */ JsonRoutes.add('DELETE', '/api/boards/:boardId', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -900,6 +1052,19 @@ if (Meteor.isServer) { } }); + /** + * @operation add_board_label + * @summary Add a label to a board + * + * @description If the board doesn't have the name/color label, this function + * adds the label to the board. + * + * @param {string} boardId the board + * @param {string} color the color of the new label + * @param {string} name the name of the new label + * + * @return_type string + */ JsonRoutes.add('PUT', '/api/boards/:boardId/labels', function (req, res) { Authentication.checkUserId(req.userId); const id = req.params.boardId; @@ -929,6 +1094,17 @@ if (Meteor.isServer) { } }); + /** + * @operation set_board_member_permission + * @tag Users + * @summary Change the permission of a member of a board + * + * @param {string} boardId the ID of the board that we are changing + * @param {string} memberId the ID of the user to change permissions + * @param {boolean} isAdmin admin capability + * @param {boolean} isNoComments NoComments capability + * @param {boolean} isCommentOnly CommentsOnly capability + */ JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function (req, res) { try { const boardId = req.params.boardId; diff --git a/models/cardComments.js b/models/cardComments.js index b6cb10fa..974c5ec9 100644 --- a/models/cardComments.js +++ b/models/cardComments.js @@ -1,19 +1,34 @@ CardComments = new Mongo.Collection('card_comments'); +/** + * A comment on a card + */ CardComments.attachSchema(new SimpleSchema({ boardId: { + /** + * the board ID + */ type: String, }, cardId: { + /** + * the card ID + */ type: String, }, // XXX Rename in `content`? `text` is a bit vague... text: { + /** + * the text of the comment + */ type: String, }, // XXX We probably don't need this information here, since we already have it // in the associated comment creation activity createdAt: { + /** + * when was the comment created + */ type: Date, denyUpdate: false, autoValue() { // eslint-disable-line consistent-return @@ -26,6 +41,9 @@ CardComments.attachSchema(new SimpleSchema({ }, // XXX Should probably be called `authorId` userId: { + /** + * the author ID of the comment + */ type: String, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -87,6 +105,16 @@ if (Meteor.isServer) { //CARD COMMENT REST API if (Meteor.isServer) { + /** + * @operation get_all_comments + * @summary Get all comments attached to a card + * + * @param {string} boardId the board ID of the card + * @param {string} cardId the ID of the card + * @return_type [{_id: string, + * comment: string, + * authorId: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', function (req, res) { try { Authentication.checkUserId( req.userId); @@ -111,6 +139,15 @@ if (Meteor.isServer) { } }); + /** + * @operation get_comment + * @summary Get a comment on a card + * + * @param {string} boardId the board ID of the card + * @param {string} cardId the ID of the card + * @param {string} commentId the ID of the comment to retrieve + * @return_type CardComments + */ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments/:commentId', function (req, res) { try { Authentication.checkUserId( req.userId); @@ -130,6 +167,16 @@ if (Meteor.isServer) { } }); + /** + * @operation new_comment + * @summary Add a comment on a card + * + * @param {string} boardId the board ID of the card + * @param {string} cardId the ID of the card + * @param {string} authorId the user who 'posted' the comment + * @param {string} text the content of the comment + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/cards/:cardId/comments', function (req, res) { try { Authentication.checkUserId( req.userId); @@ -160,6 +207,15 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_comment + * @summary Delete a comment on a card + * + * @param {string} boardId the board ID of the card + * @param {string} cardId the ID of the card + * @param {string} commentId the ID of the comment to delete + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/comments/:commentId', function (req, res) { try { Authentication.checkUserId( req.userId); diff --git a/models/cards.js b/models/cards.js index 7b05e4b5..aa0bf93e 100644 --- a/models/cards.js +++ b/models/cards.js @@ -5,11 +5,17 @@ Cards = new Mongo.Collection('cards'); // of comments just to display the number of them in the board view. Cards.attachSchema(new SimpleSchema({ title: { + /** + * the title of the card + */ type: String, optional: true, defaultValue: '', }, archived: { + /** + * is the card archived + */ type: Boolean, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -18,33 +24,51 @@ Cards.attachSchema(new SimpleSchema({ }, }, parentId: { + /** + * ID of the parent card + */ type: String, optional: true, defaultValue: '', }, listId: { + /** + * List ID where the card is + */ type: String, optional: true, defaultValue: '', }, swimlaneId: { + /** + * Swimlane ID where the card is + */ type: String, }, // The system could work without this `boardId` information (we could deduce // the board identifier from the card), but it would make the system more // difficult to manage and less efficient. boardId: { + /** + * Board ID of the card + */ type: String, optional: true, defaultValue: '', }, coverId: { + /** + * Cover ID of the card + */ type: String, optional: true, defaultValue: '', }, createdAt: { + /** + * creation date + */ type: Date, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -55,6 +79,9 @@ Cards.attachSchema(new SimpleSchema({ }, }, customFields: { + /** + * list of custom fields + */ type: [Object], optional: true, defaultValue: [], @@ -62,11 +89,17 @@ Cards.attachSchema(new SimpleSchema({ 'customFields.$': { type: new SimpleSchema({ _id: { + /** + * the ID of the related custom field + */ type: String, optional: true, defaultValue: '', }, value: { + /** + * value attached to the custom field + */ type: Match.OneOf(String, Number, Boolean, Date), optional: true, defaultValue: '', @@ -74,59 +107,95 @@ Cards.attachSchema(new SimpleSchema({ }), }, dateLastActivity: { + /** + * Date of last activity + */ type: Date, autoValue() { return new Date(); }, }, description: { + /** + * description of the card + */ type: String, optional: true, defaultValue: '', }, requestedBy: { + /** + * who requested the card (ID of the user) + */ type: String, optional: true, defaultValue: '', }, assignedBy: { + /** + * who assigned the card (ID of the user) + */ type: String, optional: true, defaultValue: '', }, labelIds: { + /** + * list of labels ID the card has + */ type: [String], optional: true, defaultValue: [], }, members: { + /** + * list of members (user IDs) + */ type: [String], optional: true, defaultValue: [], }, receivedAt: { + /** + * Date the card was received + */ type: Date, optional: true, }, startAt: { + /** + * Date the card was started to be worked on + */ type: Date, optional: true, }, dueAt: { + /** + * Date the card is due + */ type: Date, optional: true, }, endAt: { + /** + * Date the card ended + */ type: Date, optional: true, }, spentTime: { + /** + * How much time has been spent on this + */ type: Number, decimal: true, optional: true, defaultValue: 0, }, isOvertime: { + /** + * is the card over time? + */ type: Boolean, defaultValue: false, optional: true, @@ -134,6 +203,9 @@ Cards.attachSchema(new SimpleSchema({ // XXX Should probably be called `authorId`. Is it even needed since we have // the `members` field? userId: { + /** + * user ID of the author of the card + */ type: String, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -142,21 +214,33 @@ Cards.attachSchema(new SimpleSchema({ }, }, sort: { + /** + * Sort value + */ type: Number, decimal: true, defaultValue: '', }, subtaskSort: { + /** + * subtask sort value + */ type: Number, decimal: true, defaultValue: -1, optional: true, }, type: { + /** + * type of the card + */ type: String, defaultValue: '', }, linkedId: { + /** + * ID of the linked card + */ type: String, optional: true, defaultValue: '', @@ -1309,6 +1393,17 @@ if (Meteor.isServer) { } //SWIMLANES REST API if (Meteor.isServer) { + /** + * @operation get_swimlane_cards + * @summary get all cards attached to a swimlane + * + * @param {string} boardId the board ID + * @param {string} swimlaneId the swimlane ID + * @return_type [{_id: string, + * title: string, + * description: string, + * listId: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId/cards', function(req, res) { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; @@ -1332,6 +1427,16 @@ if (Meteor.isServer) { } //LISTS REST API if (Meteor.isServer) { + /** + * @operation get_all_cards + * @summary get all cards attached to a list + * + * @param {string} boardId the board ID + * @param {string} listId the list ID + * @return_type [{_id: string, + * title: string, + * description: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; @@ -1352,6 +1457,15 @@ if (Meteor.isServer) { }); }); + /** + * @operation get_card + * @summary get a card + * + * @param {string} boardId the board ID + * @param {string} listId the list ID of the card + * @param {string} cardId the card ID + * @return_type Cards + */ JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; @@ -1368,6 +1482,19 @@ if (Meteor.isServer) { }); }); + /** + * @operation new_card + * @summary creates a new card + * + * @param {string} boardId the board ID of the new card + * @param {string} listId the list ID of the new card + * @param {string} authorID the user ID of the person owning the card + * @param {string} title the title of the new card + * @param {string} description the description of the new card + * @param {string} swimlaneId the swimlane ID of the new card + * @param {string} [members] the member IDs list of the new card + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; @@ -1406,6 +1533,36 @@ if (Meteor.isServer) { } }); + /* + * Note for the JSDoc: + * 'list' will be interpreted as the path parameter + * 'listID' will be interpreted as the body parameter + */ + /** + * @operation edit_card + * @summary edit fields in a card + * + * @param {string} boardId the board ID of the card + * @param {string} list the list ID of the card + * @param {string} cardId the ID of the card + * @param {string} [title] the new title of the card + * @param {string} [listId] the new list ID of the card (move operation) + * @param {string} [description] the new description of the card + * @param {string} [authorId] change the owner of the card + * @param {string} [labelIds] the new list of label IDs attached to the card + * @param {string} [swimlaneId] the new swimlane ID of the card + * @param {string} [members] the new list of member IDs attached to the card + * @param {string} [requestedBy] the new requestedBy field of the card + * @param {string} [assignedBy] the new assignedBy field of the card + * @param {string} [receivedAt] the new receivedAt field of the card + * @param {string} [assignBy] the new assignBy field of the card + * @param {string} [startAt] the new startAt field of the card + * @param {string} [dueAt] the new dueAt field of the card + * @param {string} [endAt] the new endAt field of the card + * @param {string} [spentTime] the new spentTime field of the card + * @param {boolean} [isOverTime] the new isOverTime field of the card + * @param {string} [customFields] the new customFields value of the card + */ JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; @@ -1551,6 +1708,18 @@ if (Meteor.isServer) { }); }); + /** + * @operation delete_card + * @summary Delete a card from a board + * + * @description This operation **deletes** a card, and therefore the card + * is not put in the recycle bin. + * + * @param {string} boardId the board ID of the card + * @param {string} list the list ID of the card + * @param {string} cardId the ID of the card + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; diff --git a/models/checklistItems.js b/models/checklistItems.js index 9867dd94..35b18ed7 100644 --- a/models/checklistItems.js +++ b/models/checklistItems.js @@ -1,21 +1,39 @@ ChecklistItems = new Mongo.Collection('checklistItems'); +/** + * An item in a checklist + */ ChecklistItems.attachSchema(new SimpleSchema({ title: { + /** + * the text of the item + */ type: String, }, sort: { + /** + * the sorting field of the item + */ type: Number, decimal: true, }, isFinished: { + /** + * Is the item checked? + */ type: Boolean, defaultValue: false, }, checklistId: { + /** + * the checklist ID the item is attached to + */ type: String, }, cardId: { + /** + * the card ID the item is attached to + */ type: String, }, })); @@ -193,6 +211,17 @@ if (Meteor.isServer) { } if (Meteor.isServer) { + /** + * @operation get_checklist_item + * @tag Checklists + * @summary Get a checklist item + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} checklistId the checklist ID + * @param {string} itemId the ID of the item + * @return_type ChecklistItems + */ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) { Authentication.checkUserId( req.userId); const paramItemId = req.params.itemId; @@ -209,6 +238,19 @@ if (Meteor.isServer) { } }); + /** + * @operation edit_checklist_item + * @tag Checklists + * @summary Edit a checklist item + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} checklistId the checklist ID + * @param {string} itemId the ID of the item + * @param {string} [isFinished] is the item checked? + * @param {string} [title] the new text of the item + * @return_type {_id: string} + */ JsonRoutes.add('PUT', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) { Authentication.checkUserId( req.userId); @@ -229,6 +271,19 @@ if (Meteor.isServer) { }); }); + /** + * @operation delete_checklist_item + * @tag Checklists + * @summary Delete a checklist item + * + * @description Note: this operation can't be reverted. + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} checklistId the checklist ID + * @param {string} itemId the ID of the item to be removed + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) { Authentication.checkUserId( req.userId); const paramItemId = req.params.itemId; diff --git a/models/checklists.js b/models/checklists.js index 425a10b2..a372fafa 100644 --- a/models/checklists.js +++ b/models/checklists.js @@ -1,18 +1,33 @@ Checklists = new Mongo.Collection('checklists'); +/** + * A Checklist + */ Checklists.attachSchema(new SimpleSchema({ cardId: { + /** + * The ID of the card the checklist is in + */ type: String, }, title: { + /** + * the title of the checklist + */ type: String, defaultValue: 'Checklist', }, finishedAt: { + /** + * When was the checklist finished + */ type: Date, optional: true, }, createdAt: { + /** + * Creation date of the checklist + */ type: Date, denyUpdate: false, autoValue() { // eslint-disable-line consistent-return @@ -24,6 +39,9 @@ Checklists.attachSchema(new SimpleSchema({ }, }, sort: { + /** + * sorting value of the checklist + */ type: Number, decimal: true, }, @@ -128,6 +146,15 @@ if (Meteor.isServer) { } if (Meteor.isServer) { + /** + * @operation get_all_checklists + * @summary Get the list of checklists attached to a card + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @return_type [{_id: string, + * title: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res) { Authentication.checkUserId( req.userId); const paramCardId = req.params.cardId; @@ -149,6 +176,22 @@ if (Meteor.isServer) { } }); + /** + * @operation get_checklist + * @summary Get a checklist + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} checklistId the ID of the checklist + * @return_type {cardId: string, + * title: string, + * finishedAt: string, + * createdAt: string, + * sort: number, + * items: [{_id: string, + * title: string, + * isFinished: boolean}]} + */ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res) { Authentication.checkUserId( req.userId); const paramChecklistId = req.params.checklistId; @@ -173,6 +216,15 @@ if (Meteor.isServer) { } }); + /** + * @operation new_checklist + * @summary create a new checklist + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} title the title of the new checklist + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res) { Authentication.checkUserId( req.userId); @@ -204,6 +256,17 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_checklist + * @summary Delete a checklist + * + * @description The checklist will be removed, not put in the recycle bin. + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} checklistId the ID of the checklist to remove + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res) { Authentication.checkUserId( req.userId); const paramChecklistId = req.params.checklistId; diff --git a/models/customFields.js b/models/customFields.js index 5bb5e743..3e8aa250 100644 --- a/models/customFields.js +++ b/models/customFields.js @@ -1,40 +1,73 @@ CustomFields = new Mongo.Collection('customFields'); +/** + * A custom field on a card in the board + */ CustomFields.attachSchema(new SimpleSchema({ boardId: { + /** + * the ID of the board + */ type: String, }, name: { + /** + * name of the custom field + */ type: String, }, type: { + /** + * type of the custom field + */ type: String, allowedValues: ['text', 'number', 'date', 'dropdown'], }, settings: { + /** + * settings of the custom field + */ type: Object, }, 'settings.dropdownItems': { + /** + * list of drop down items objects + */ type: [Object], optional: true, }, 'settings.dropdownItems.$': { type: new SimpleSchema({ _id: { + /** + * ID of the drop down item + */ type: String, }, name: { + /** + * name of the drop down item + */ type: String, }, }), }, showOnCard: { + /** + * should we show on the cards this custom field + */ type: Boolean, }, automaticallyOnCard: { + /** + * should the custom fields automatically be added on cards? + */ type: Boolean, }, showLabelOnMiniCard: { + /** + * should the label of the custom field be shown on minicards? + */ type: Boolean, }, })); @@ -88,6 +121,15 @@ if (Meteor.isServer) { //CUSTOM FIELD REST API if (Meteor.isServer) { + /** + * @operation get_all_custom_fields + * @summary Get the list of Custom Fields attached to a board + * + * @param {string} boardID the ID of the board + * @return_type [{_id: string, + * name: string, + * type: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields', function (req, res) { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; @@ -103,6 +145,14 @@ if (Meteor.isServer) { }); }); + /** + * @operation get_custom_field + * @summary Get a Custom Fields attached to a board + * + * @param {string} boardID the ID of the board + * @param {string} customFieldId the ID of the custom field + * @return_type CustomFields + */ JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields/:customFieldId', function (req, res) { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; @@ -113,6 +163,19 @@ if (Meteor.isServer) { }); }); + /** + * @operation new_custom_field + * @summary Create a Custom Field + * + * @param {string} boardID the ID of the board + * @param {string} name the name of the custom field + * @param {string} type the type of the custom field + * @param {string} settings the settings object of the custom field + * @param {boolean} showOnCard should we show the custom field on cards? + * @param {boolean} automaticallyOnCard should the custom fields automatically be added on cards? + * @param {boolean} showLabelOnMiniCard should the label of the custom field be shown on minicards? + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/custom-fields', function (req, res) { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; @@ -137,6 +200,16 @@ if (Meteor.isServer) { }); }); + /** + * @operation delete_custom_field + * @summary Delete a Custom Fields attached to a board + * + * @description The Custom Field can't be retrieved after this operation + * + * @param {string} boardID the ID of the board + * @param {string} customFieldId the ID of the custom field + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/custom-fields/:customFieldId', function (req, res) { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; diff --git a/models/export.js b/models/export.js index 62d2687a..fa4894d9 100644 --- a/models/export.js +++ b/models/export.js @@ -6,13 +6,20 @@ if (Meteor.isServer) { // `ApiRoutes.path('boards/export', boardId)`` // on the client instead of copy/pasting the route path manually between the // client and the server. - /* - * This route is used to export the board FROM THE APPLICATION. - * If user is already logged-in, pass loginToken as param "authToken": - * '/api/boards/:boardId/export?authToken=:token' + /** + * @operation export + * @tag Boards + * + * @summary This route is used to export the board **FROM THE APPLICATION**. + * + * @description If user is already logged-in, pass loginToken as param + * "authToken": '/api/boards/:boardId/export?authToken=:token' * * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ * for detailed explanations + * + * @param {string} boardId the ID of the board we are exporting + * @param {string} authToken the loginToken */ JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) { const boardId = req.params.boardId; diff --git a/models/integrations.js b/models/integrations.js index 1062b93b..1c473b57 100644 --- a/models/integrations.js +++ b/models/integrations.js @@ -1,33 +1,60 @@ Integrations = new Mongo.Collection('integrations'); +/** + * Integration with third-party applications + */ Integrations.attachSchema(new SimpleSchema({ enabled: { + /** + * is the integration enabled? + */ type: Boolean, defaultValue: true, }, title: { + /** + * name of the integration + */ type: String, optional: true, }, type: { + /** + * type of the integratation (Default to 'outgoing-webhooks') + */ type: String, defaultValue: 'outgoing-webhooks', }, activities: { + /** + * activities the integration gets triggered (list) + */ type: [String], defaultValue: ['all'], }, url: { // URL validation regex (https://mathiasbynens.be/demo/url-regex) + /** + * URL validation regex (https://mathiasbynens.be/demo/url-regex) + */ type: String, }, token: { + /** + * token of the integration + */ type: String, optional: true, }, boardId: { + /** + * Board ID of the integration + */ type: String, }, createdAt: { + /** + * Creation date of the integration + */ type: Date, denyUpdate: false, autoValue() { // eslint-disable-line consistent-return @@ -39,6 +66,9 @@ Integrations.attachSchema(new SimpleSchema({ }, }, userId: { + /** + * user ID who created the interation + */ type: String, }, })); @@ -58,7 +88,13 @@ Integrations.allow({ //INTEGRATIONS REST API if (Meteor.isServer) { - // Get all integrations in board + /** + * @operation get_all_integrations + * @summary Get all integrations in board + * + * @param {string} boardId the board ID + * @return_type [Integrations] + */ JsonRoutes.add('GET', '/api/boards/:boardId/integrations', function(req, res) { try { const paramBoardId = req.params.boardId; @@ -78,7 +114,14 @@ if (Meteor.isServer) { } }); - // Get a single integration in board + /** + * @operation get_integration + * @summary Get a single integration in board + * + * @param {string} boardId the board ID + * @param {string} intId the integration ID + * @return_type Integrations + */ JsonRoutes.add('GET', '/api/boards/:boardId/integrations/:intId', function(req, res) { try { const paramBoardId = req.params.boardId; @@ -98,7 +141,14 @@ if (Meteor.isServer) { } }); - // Create a new integration + /** + * @operation new_integration + * @summary Create a new integration + * + * @param {string} boardId the board ID + * @param {string} url the URL of the integration + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/integrations', function(req, res) { try { const paramBoardId = req.params.boardId; @@ -125,7 +175,19 @@ if (Meteor.isServer) { } }); - // Edit integration data + /** + * @operation edit_integration + * @summary Edit integration data + * + * @param {string} boardId the board ID + * @param {string} intId the integration ID + * @param {string} [enabled] is the integration enabled? + * @param {string} [title] new name of the integration + * @param {string} [url] new URL of the integration + * @param {string} [token] new token of the integration + * @param {string} [activities] new list of activities of the integration + * @return_type {_id: string} + */ JsonRoutes.add('PUT', '/api/boards/:boardId/integrations/:intId', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -173,7 +235,15 @@ if (Meteor.isServer) { } }); - // Delete subscribed activities + /** + * @operation delete_integration_activities + * @summary Delete subscribed activities + * + * @param {string} boardId the board ID + * @param {string} intId the integration ID + * @param {string} newActivities the activities to remove from the integration + * @return_type Integrations + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId/activities', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -197,7 +267,15 @@ if (Meteor.isServer) { } }); - // Add subscribed activities + /** + * @operation new_integration_activities + * @summary Add subscribed activities + * + * @param {string} boardId the board ID + * @param {string} intId the integration ID + * @param {string} newActivities the activities to add to the integration + * @return_type Integrations + */ JsonRoutes.add('POST', '/api/boards/:boardId/integrations/:intId/activities', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -221,7 +299,14 @@ if (Meteor.isServer) { } }); - // Delete integration + /** + * @operation delete_integration + * @summary Delete integration + * + * @param {string} boardId the board ID + * @param {string} intId the integration ID + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId', function (req, res) { try { const paramBoardId = req.params.boardId; diff --git a/models/lists.js b/models/lists.js index b99fe8f5..0e1ba801 100644 --- a/models/lists.js +++ b/models/lists.js @@ -1,10 +1,19 @@ Lists = new Mongo.Collection('lists'); +/** + * A list (column) in the Wekan board. + */ Lists.attachSchema(new SimpleSchema({ title: { + /** + * the title of the list + */ type: String, }, archived: { + /** + * is the list archived + */ type: Boolean, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -13,9 +22,15 @@ Lists.attachSchema(new SimpleSchema({ }, }, boardId: { + /** + * the board associated to this list + */ type: String, }, createdAt: { + /** + * creation date + */ type: Date, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -26,12 +41,18 @@ Lists.attachSchema(new SimpleSchema({ }, }, sort: { + /** + * is the list sorted + */ type: Number, decimal: true, // XXX We should probably provide a default optional: true, }, updatedAt: { + /** + * last update of the list + */ type: Date, optional: true, autoValue() { // eslint-disable-line consistent-return @@ -43,19 +64,31 @@ Lists.attachSchema(new SimpleSchema({ }, }, wipLimit: { + /** + * WIP object, see below + */ type: Object, optional: true, }, 'wipLimit.value': { + /** + * value of the WIP + */ type: Number, decimal: false, defaultValue: 1, }, 'wipLimit.enabled': { + /** + * is the WIP enabled + */ type: Boolean, defaultValue: false, }, 'wipLimit.soft': { + /** + * is the WIP a soft or hard requirement + */ type: Boolean, defaultValue: false, }, @@ -212,6 +245,14 @@ if (Meteor.isServer) { //LISTS REST API if (Meteor.isServer) { + /** + * @operation get_all_lists + * @summary Get the list of Lists attached to a board + * + * @param {string} boardId the board ID + * @return_type [{_id: string, + * title: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/lists', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -235,6 +276,14 @@ if (Meteor.isServer) { } }); + /** + * @operation get_list + * @summary Get a List attached to a board + * + * @param {string} boardId the board ID + * @param {string} listId the List ID + * @return_type Lists + */ JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -253,6 +302,14 @@ if (Meteor.isServer) { } }); + /** + * @operation new_list + * @summary Add a List to a board + * + * @param {string} boardId the board ID + * @param {string} title the title of the List + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/lists', function (req, res) { try { Authentication.checkUserId( req.userId); @@ -276,6 +333,17 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_list + * @summary Delete a List + * + * @description This **deletes** a list from a board. + * The list is not put in the recycle bin. + * + * @param {string} boardId the board ID + * @param {string} listId the ID of the list to remove + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId', function (req, res) { try { Authentication.checkUserId( req.userId); diff --git a/models/swimlanes.js b/models/swimlanes.js index 3559bcd2..fa5245da 100644 --- a/models/swimlanes.js +++ b/models/swimlanes.js @@ -1,10 +1,19 @@ Swimlanes = new Mongo.Collection('swimlanes'); +/** + * A swimlane is an line in the kaban board. + */ Swimlanes.attachSchema(new SimpleSchema({ title: { + /** + * the title of the swimlane + */ type: String, }, archived: { + /** + * is the swimlane archived? + */ type: Boolean, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -13,9 +22,15 @@ Swimlanes.attachSchema(new SimpleSchema({ }, }, boardId: { + /** + * the ID of the board the swimlane is attached to + */ type: String, }, createdAt: { + /** + * creation date of the swimlane + */ type: Date, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -26,12 +41,18 @@ Swimlanes.attachSchema(new SimpleSchema({ }, }, sort: { + /** + * the sort value of the swimlane + */ type: Number, decimal: true, // XXX We should probably provide a default optional: true, }, updatedAt: { + /** + * when was the swimlane last edited + */ type: Date, optional: true, autoValue() { // eslint-disable-line consistent-return @@ -131,6 +152,15 @@ if (Meteor.isServer) { //SWIMLANE REST API if (Meteor.isServer) { + /** + * @operation get_all_swimlanes + * + * @summary Get the list of swimlanes attached to a board + * + * @param {string} boardId the ID of the board + * @return_type [{_id: string, + * title: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -154,6 +184,15 @@ if (Meteor.isServer) { } }); + /** + * @operation get_swimlane + * + * @summary Get a swimlane + * + * @param {string} boardId the ID of the board + * @param {string} swimlaneId the ID of the swimlane + * @return_type Swimlanes + */ JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -172,6 +211,15 @@ if (Meteor.isServer) { } }); + /** + * @operation new_swimlane + * + * @summary Add a swimlane to a board + * + * @param {string} boardId the ID of the board + * @param {string} title the new title of the swimlane + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/swimlanes', function (req, res) { try { Authentication.checkUserId( req.userId); @@ -195,6 +243,17 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_swimlane + * + * @summary Delete a swimlane + * + * @description The swimlane will be deleted, not moved to the recycle bin + * + * @param {string} boardId the ID of the board + * @param {string} swimlaneId the ID of the swimlane + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/swimlanes/:swimlaneId', function (req, res) { try { Authentication.checkUserId( req.userId); diff --git a/models/users.js b/models/users.js index d4c678b7..56643848 100644 --- a/models/users.js +++ b/models/users.js @@ -4,8 +4,14 @@ const isSandstorm = Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm; Users = Meteor.users; +/** + * A User in wekan + */ Users.attachSchema(new SimpleSchema({ username: { + /** + * the username of the user + */ type: String, optional: true, autoValue() { // eslint-disable-line consistent-return @@ -18,17 +24,29 @@ Users.attachSchema(new SimpleSchema({ }, }, emails: { + /** + * the list of emails attached to a user + */ type: [Object], optional: true, }, 'emails.$.address': { + /** + * The email address + */ type: String, regEx: SimpleSchema.RegEx.Email, }, 'emails.$.verified': { + /** + * Has the email been verified + */ type: Boolean, }, createdAt: { + /** + * creation date of the user + */ type: Date, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -39,6 +57,9 @@ Users.attachSchema(new SimpleSchema({ }, }, profile: { + /** + * profile settings + */ type: Object, optional: true, autoValue() { // eslint-disable-line consistent-return @@ -50,50 +71,86 @@ Users.attachSchema(new SimpleSchema({ }, }, 'profile.avatarUrl': { + /** + * URL of the avatar of the user + */ type: String, optional: true, }, 'profile.emailBuffer': { + /** + * list of email buffers of the user + */ type: [String], optional: true, }, 'profile.fullname': { + /** + * full name of the user + */ type: String, optional: true, }, 'profile.hiddenSystemMessages': { + /** + * does the user wants to hide system messages? + */ type: Boolean, optional: true, }, 'profile.initials': { + /** + * initials of the user + */ type: String, optional: true, }, 'profile.invitedBoards': { + /** + * board IDs the user has been invited to + */ type: [String], optional: true, }, 'profile.language': { + /** + * language of the user + */ type: String, optional: true, }, 'profile.notifications': { + /** + * enabled notifications for the user + */ type: [String], optional: true, }, 'profile.showCardsCountAt': { + /** + * showCardCountAt field of the user + */ type: Number, optional: true, }, 'profile.starredBoards': { + /** + * list of starred board IDs + */ type: [String], optional: true, }, 'profile.icode': { + /** + * icode + */ type: String, optional: true, }, 'profile.boardView': { + /** + * boardView field of the user + */ type: String, optional: true, allowedValues: [ @@ -103,27 +160,45 @@ Users.attachSchema(new SimpleSchema({ ], }, services: { + /** + * services field of the user + */ type: Object, optional: true, blackbox: true, }, heartbeat: { + /** + * last time the user has been seen + */ type: Date, optional: true, }, isAdmin: { + /** + * is the user an admin of the board? + */ type: Boolean, optional: true, }, createdThroughApi: { + /** + * was the user created through the API? + */ type: Boolean, optional: true, }, loginDisabled: { + /** + * loginDisabled field of the user + */ type: Boolean, optional: true, }, 'authenticationMethod': { + /** + * authentication method of the user + */ type: String, optional: false, defaultValue: 'password', @@ -681,6 +756,12 @@ if (Meteor.isServer) { } }); + /** + * @operation get_current_user + * + * @summary returns the current user + * @return_type Users + */ JsonRoutes.add('GET', '/api/user', function(req, res) { try { Authentication.checkLoggedIn(req.userId); @@ -699,6 +780,15 @@ if (Meteor.isServer) { } }); + /** + * @operation get_all_users + * + * @summary return all the users + * + * @description Only the admin user (the first user) can call the REST API. + * @return_type [{ _id: string, + * username: string}] + */ JsonRoutes.add('GET', '/api/users', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -717,6 +807,16 @@ if (Meteor.isServer) { } }); + /** + * @operation get_user + * + * @summary get a given user + * + * @description Only the admin user (the first user) can call the REST API. + * + * @param {string} userId the user ID + * @return_type Users + */ JsonRoutes.add('GET', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -734,6 +834,23 @@ if (Meteor.isServer) { } }); + /** + * @operation edit_user + * + * @summary edit a given user + * + * @description Only the admin user (the first user) can call the REST API. + * + * Possible values for *action*: + * - `takeOwnership`: The admin takes the ownership of ALL boards of the user (archived and not archived) where the user is admin on. + * - `disableLogin`: Disable a user (the user is not allowed to login and his login tokens are purged) + * - `enableLogin`: Enable a user + * + * @param {string} userId the user ID + * @param {string} action the action + * @return_type {_id: string, + * title: string} + */ JsonRoutes.add('PUT', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -777,6 +894,25 @@ if (Meteor.isServer) { } }); + /** + * @operation add_board_member + * @tag Boards + * + * @summary Add New Board Member with Role + * + * @description Only the admin user (the first user) can call the REST API. + * + * **Note**: see [Boards.set_board_member_permission](#set_board_member_permission) + * to later change the permissions. + * + * @param {string} boardId the board ID + * @param {string} userId the user ID + * @param {boolean} isAdmin is the user an admin of the board + * @param {boolean} isNoComments disable comments + * @param {boolean} isCommentOnly only enable comments + * @return_type {_id: string, + * title: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/members/:userId/add', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -817,6 +953,20 @@ if (Meteor.isServer) { } }); + /** + * @operation remove_board_member + * @tag Boards + * + * @summary Remove Member from Board + * + * @description Only the admin user (the first user) can call the REST API. + * + * @param {string} boardId the board ID + * @param {string} userId the user ID + * @param {string} action the action (needs to be `remove`) + * @return_type {_id: string, + * title: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/members/:userId/remove', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -852,6 +1002,18 @@ if (Meteor.isServer) { } }); + /** + * @operation new_user + * + * @summary Create a new user + * + * @description Only the admin user (the first user) can call the REST API. + * + * @param {string} username the new username + * @param {string} email the email of the new user + * @param {string} password the password of the new user + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/users/', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -876,6 +1038,16 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_user + * + * @summary Delete a user + * + * @description Only the admin user (the first user) can call the REST API. + * + * @param {string} userId the ID of the user to delete + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); |