text: import {
    ALLOWED_BOARD_COLORS,
    ALLOWED_COLORS,
    TYPE_BOARD,
    TYPE_TEMPLATE_BOARD,
    TYPE_TEMPLATE_CONTAINER,
  } from '/config/const';
  import Users from "./users";
  
  const escapeForRegex = require('escape-string-regexp');
  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,
        // eslint-disable-next-line consistent-return
        autoValue() {
          // In some cases (Chinese and Japanese for instance) the `getSlug` function
          // return an empty string. This is causes bugs in our application so we set
          // a default slug in this case.
          // Improvment would be to change client URL after slug is changed
          const title = this.field('title');
          if (title.isSet && !this.isSet) {
            let slug = 'board';
            slug = getSlug(title.value) || slug;
            return slug;
          }
        },
      },
      archived: {
        /**
         * Is the board archived?
         */
        type: Boolean,
        // eslint-disable-next-line consistent-return
        autoValue() {
          if (this.isInsert && !this.isSet) {
            return false;
          }
        },
      },
      archivedAt: {
        /**
         * Latest archiving time of the board
         */
        type: Date,
        optional: true,
      },
      createdAt: {
        /**
         * Creation time of the board
         */
        type: Date,
        // eslint-disable-next-line consistent-return
        autoValue() {
          if (this.isInsert) {
            return new Date();
          } else if (this.isUpsert) {
            return { $setOnInsert: new Date() };
          } else {
            this.unset();
          }
        },
      },
      // XXX Inconsistent field naming
      modifiedAt: {
        /**
         * Last modification time of the board
         */
        type: Date,
        optional: true,
        // eslint-disable-next-line consistent-return
        autoValue() {
          if (this.isInsert || this.isUpsert || this.isUpdate) {
            return new Date();
          } else {
            this.unset();
          }
        },
      },
      // De-normalized number of users that have starred this board
      stars: {
        /**
         * How many stars the board has
         */
        type: Number,
        // eslint-disable-next-line consistent-return
        autoValue() {
          if (this.isInsert) {
            return 0;
          }
        },
      },
      // De-normalized label system
      labels: {
        /**
         * List of labels attached to a board
         */
        type: [Object],
        optional: true,
        /* Commented out, so does not create labels to new boards.
        // eslint-disable-next-line consistent-return
        autoValue() {
          if (this.isInsert && !this.isSet) {
            const colors = Boards.simpleSchema()._schema['labels.$.color']
              .allowedValues;
            const defaultLabelsColors = _.clone(colors).splice(0, 6);
            return defaultLabelsColors.map(color => ({
              color,
              _id: Random.id(6),
              name: '',
            }));
          }
        },
        */
      },
      '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.
        // XXX Actually if we create a new label, the `_id` is set on the client
        // without being overwritten by the server, could it be a problem?
        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: ALLOWED_COLORS,
      },
      // XXX We might want to maintain more informations under the member sub-
      // 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],
        // eslint-disable-next-line consistent-return
        autoValue() {
          if (this.isInsert && !this.isSet) {
            return [
              {
                userId: this.userId,
                isAdmin: true,
                isActive: true,
                isNoComments: false,
                isCommentOnly: false,
                isWorker: false,
              },
            ];
          }
        },
      },
      '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,
      },
      'members.$.isWorker': {
        /**
         * Is the member only allowed to move card, assign himself to card and comment
         */
        type: Boolean,
        optional: true,
      },
      permission: {
        /**
         * visibility of the board
         */
        type: String,
        allowedValues: ['public', 'private'],
      },
      orgs: {
        /**
         * the list of organizations that a board belongs to
         */
         type: [Object],
         optional: true,
      },
      'orgs.$.orgId':{
        /**
         * The uniq ID of the organization
         */
         type: String,
      },
      'orgs.$.orgDisplayName':{
        /**
         * The display name of the organization
         */
         type: String,
      },
      'orgs.$.isActive': {
        /**
         * Is the organization active?
         */
        type: Boolean,
      },
      teams: {
        /**
         * the list of teams that a board belongs to
         */
         type: [Object],
         optional: true,
      },
      'teams.$.teamId':{
        /**
         * The uniq ID of the team
         */
         type: String,
      },
      'teams.$.teamDisplayName':{
        /**
         * The display name of the team
         */
         type: String,
      },
      'teams.$.isActive': {
        /**
         * Is the team active?
         */
        type: Boolean,
      },
      color: {
        /**
         * The color of the board.
         */
        type: String,
        allowedValues: ALLOWED_BOARD_COLORS,
        // eslint-disable-next-line consistent-return
        autoValue() {
          if (this.isInsert && !this.isSet) {
            return ALLOWED_BOARD_COLORS[0];
          }
        },
      },
      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,
      },
  
      dateSettingsDefaultBoardId: {
        type: String,
        optional: true,
        defaultValue: null,
      },
  
      dateSettingsDefaultListId: {
        type: String,
        optional: true,
        defaultValue: null,
      },
  
      allowsSubtasks: {
        /**
         * Does the board allows subtasks?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsAttachments: {
        /**
         * Does the board allows attachments?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsChecklists: {
        /**
         * Does the board allows checklists?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsComments: {
        /**
         * Does the board allows comments?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsDescriptionTitle: {
        /**
         * Does the board allows description title?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsDescriptionText: {
        /**
         * Does the board allows description text?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsCardNumber: {
        /**
         * Does the board allows card numbers?
         */
        type: Boolean,
        defaultValue: false,
      },
  
      allowsActivities: {
        /**
         * Does the board allows comments?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsLabels: {
        /**
         * Does the board allows labels?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsCreator: {
        /**
         * Does the board allow creator?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsAssignee: {
        /**
         * Does the board allows assignee?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsMembers: {
        /**
         * Does the board allows members?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsRequestedBy: {
        /**
         * Does the board allows requested by?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsCardSortingByNumber: {
        /**
         * Does the board allows card sorting by number?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsShowLists: {
        /**
         * Does the board allows show lists on the card?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsAssignedBy: {
        /**
         * Does the board allows requested by?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsReceivedDate: {
        /**
         * Does the board allows received date?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsStartDate: {
        /**
         * Does the board allows start date?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsEndDate: {
        /**
         * Does the board allows end date?
         */
        type: Boolean,
        defaultValue: true,
      },
  
      allowsDueDate: {
        /**
         * Does the board allows due date?
         */
        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',
          'prefix-with-parent',
          'subtext-with-full-path',
          'subtext-with-parent',
          'no-parent',
        ],
        optional: true,
        defaultValue: 'no-parent',
      },
      receivedAt: {
        /**
         * Date the card was received
         */
        type: Date,
        optional: true,
      },
      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,
      },
      type: {
        /**
         * The type of board
         * possible values: board, template-board, template-container
         */
        type: String,
        defaultValue: TYPE_BOARD,
        allowedValues: [TYPE_BOARD, TYPE_TEMPLATE_BOARD, TYPE_TEMPLATE_CONTAINER],
      },
      sort: {
        /**
         * Sort value
         */
        type: Number,
        decimal: true,
        defaultValue: -1,
      },
    }),
  );
  
  Boards.helpers({
    copy() {
      const oldId = this._id;
      delete this._id;
      delete this.slug;
      this.title = this.copyTitle();
      const _id = Boards.insert(this);
  
      // Copy all swimlanes in board
      Swimlanes.find({
        boardId: oldId,
        archived: false,
      }).forEach(swimlane => {
        swimlane.type = 'swimlane';
        swimlane.copy(_id);
      });
  
      // copy custom field definitions
      const cfMap = {};
      CustomFields.find({ boardIds: oldId }).forEach(cf => {
        const id = cf._id;
        delete cf._id;
        cf.boardIds = [_id];
        cfMap[id] = CustomFields.insert(cf);
      });
      Cards.find({ boardId: _id }).forEach(card => {
        Cards.update(card._id, {
          $set: {
            customFields: card.customFields.map(cf => {
              cf._id = cfMap[cf._id];
              return cf;
            }),
          },
        });
      });
  
      // copy rules, actions, and triggers
      const actionsMap = {};
      Actions.find({ boardId: oldId }).forEach(action => {
        const id = action._id;
        delete action._id;
        action.boardId = _id;
        actionsMap[id] = Actions.insert(action);
      });
      const triggersMap = {};
      Triggers.find({ boardId: oldId }).forEach(trigger => {
        const id = trigger._id;
        delete trigger._id;
        trigger.boardId = _id;
        triggersMap[id] = Triggers.insert(trigger);
      });
      Rules.find({ boardId: oldId }).forEach(rule => {
        delete rule._id;
        rule.boardId = _id;
        rule.actionId = actionsMap[rule.actionId];
        rule.triggerId = triggersMap[rule.triggerId];
        Rules.insert(rule);
      });
      return _id;
    },
    /**
     * Return a unique title based on the current title
     *
     * @returns {string|null}
     */
    copyTitle() {
      return Boards.uniqueTitle(this.title);
    },
  
    /**
     * Is supplied user authorized to view this board?
     */
    isVisibleBy(user) {
      if (this.isPublic()) {
        // public boards are visible to everyone
        return true;
      } else {
        // otherwise you have to be logged-in and active member
        return user && this.isActiveMember(user._id);
      }
    },
  
    /**
     * Is the user one of the active members of the board?
     *
     * @param userId
     * @returns {boolean} the member that matches, or undefined/false
     */
    isActiveMember(userId) {
      if (userId) {
        return this.members.find(
          member => member.userId === userId && member.isActive,
        );
      } else {
        return false;
      }
    },
  
    isPublic() {
      return this.permission === 'public';
    },
  
    cards() {
      return Cards.find(
        { boardId: this._id, archived: false },
        { sort: { title: 1 } },
      );
    },
  
    lists() {
      //currentUser = Meteor.user();
      //if (currentUser) {
      //  enabled = Meteor.user().hasSortBy();
      //}
      //return enabled ? this.newestLists() : this.draggableLists();
      return this.draggableLists();
    },
  
    newestLists() {
      // sorted lists from newest to the oldest, by its creation date or its cards' last modification date
      const value = Meteor.user()._getListSortBy();
      const sortKey = { starred: -1, [value[0]]: value[1] }; // [["starred",-1],value];
      return Lists.find(
        {
          boardId: this._id,
          archived: false,
        },
        { sort: sortKey },
      );
    },
  
    draggableLists() {
      return Lists.find({ boardId: this._id }, { sort: { sort: 1 } });
    },
  
    /** returns the last list
     * @returns Document the last list
     */
    getLastList() {
      const ret = Lists.findOne({ boardId: this._id }, { sort: { sort: 'desc' } });
      return ret;
    },
  
    nullSortLists() {
      return Lists.find({
        boardId: this._id,
        archived: false,
        sort: { $eq: null },
      });
    },
  
    swimlanes() {
      return Swimlanes.find(
        { boardId: this._id, archived: false },
        { sort: { sort: 1 } },
      );
    },
  
    nextSwimlane(swimlane) {
      return Swimlanes.findOne(
        {
          boardId: this._id,
          archived: false,
          sort: { $gte: swimlane.sort },
          _id: { $ne: swimlane._id },
        },
        {
          sort: { sort: 1 },
        },
      );
    },
  
    nullSortSwimlanes() {
      return Swimlanes.find({
        boardId: this._id,
        archived: false,
        sort: { $eq: null },
      });
    },
  
    hasOvertimeCards() {
      const card = Cards.findOne({
        isOvertime: true,
        boardId: this._id,
        archived: false,
      });
      return card !== undefined;
    },
  
    hasSpentTimeCards() {
      const card = Cards.findOne({
        spentTime: { $gt: 0 },
        boardId: this._id,
        archived: false,
      });
      return card !== undefined;
    },
  
    activities() {
      return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 } });
    },
  
    activeMembers(){
      return _.where(this.members, { isActive: true });
    },
  
    activeMembers2(members, boardTeamUsers) {
      let allMembers = members;
      if(this.teams !== undefined && this.teams.length > 0){
        let index;
        if(boardTeamUsers && boardTeamUsers.count() > 0){
          boardTeamUsers.forEach((u) => {
            index = allMembers.findIndex(function(m){ return m.userId == u._id});
            if(index == -1){
              allMembers.push({
                "isActive": true,
                "isAdmin": u.isAdmin !== undefined ? u.isAdmin : false,
                "isCommentOnly" : false,
                "isNoComments" : false,
                "userId": u._id,
              });
            }
          });
        }
      }
  
      return allMembers;
    },
  
    activeOrgs() {
      return _.where(this.orgs, { isActive: true });
    },
  
    // hasNotAnyOrg(){
    //   return this.orgs === undefined || this.orgs.length <= 0;
    // },
  
    activeTeams() {
      return _.where(this.teams, { isActive: true });
    },
  
    // hasNotAnyTeam(){
    //   return this.teams === undefined || this.teams.length <= 0;
    // },
  
    activeAdmins() {
      return _.where(this.members, { isActive: true, isAdmin: true });
    },
  
    memberUsers() {
      return Users.find({ _id: { $in: _.pluck(this.members, 'userId') } });
    },
  
    getLabel(name, color) {
      return _.findWhere(this.labels, { name, color });
    },
  
    getLabelById(labelId) {
      return _.findWhere(this.labels, { _id: labelId });
    },
  
    labelIndex(labelId) {
      return _.pluck(this.labels, '_id').indexOf(labelId);
    },
  
    memberIndex(memberId) {
      return _.pluck(this.members, 'userId').indexOf(memberId);
    },
  
    hasMember(memberId) {
      return !!_.findWhere(this.members, { userId: memberId, isActive: true });
    },
  
    hasAdmin(memberId) {
      return !!_.findWhere(this.members, {
        userId: memberId,
        isActive: true,
        isAdmin: true,
      });
    },
  
    hasNoComments(memberId) {
      return !!_.findWhere(this.members, {
        userId: memberId,
        isActive: true,
        isAdmin: false,
        isNoComments: true,
        isWorker: false,
      });
    },
  
    hasCommentOnly(memberId) {
      return !!_.findWhere(this.members, {
        userId: memberId,
        isActive: true,
        isAdmin: false,
        isCommentOnly: true,
        isWorker: false,
      });
    },
  
    hasWorker(memberId) {
      return !!_.findWhere(this.members, {
        userId: memberId,
        isActive: true,
        isAdmin: false,
        isCommentOnly: false,
        isWorker: true,
      });
    },
  
    hasAnyAllowsDate() {
      const ret = this.allowsReceivedDate || this.allowsStartDate || this.allowsDueDate || this.allowsEndDate;
      return ret;
    },
  
    hasAnyAllowsUser() {
      const ret = this.allowsCreator || this.allowsMembers || this.allowsAssignee || this.allowsRequestedBy || this.allowsAssignedBy;
      return ret;
    },
  
    absoluteUrl() {
      return FlowRouter.url('board', { id: this._id, slug: this.slug });
    },
    originRelativeUrl() {
      return FlowRouter.path('board', { id: this._id, slug: this.slug });
    },
  
    colorClass() {
      return `board-color-${this.color}`;
    },
  
    customFields() {
      return CustomFields.find(
        { boardIds: { $in: [this._id] } },
        { sort: { name: 1 } },
      );
    },
  
    // XXX currently mutations return no value so we have an issue when using addLabel in import
    // 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 } } });
      return _id;
    },
  
    /** sets the new label order
     * @param newLabelOrderOnlyIds new order array of _id, e.g. Array(4) [ "FvtD34", "PAEgDP", "LjRBxH", "YJ8sZz" ]
     */
    setNewLabelOrder(newLabelOrderOnlyIds) {
      if (this.labels.length == newLabelOrderOnlyIds.length) {
        if (this.labels.every(_label => newLabelOrderOnlyIds.indexOf(_label._id) >= 0)) {
          const newLabels = _.sortBy(this.labels, _label => newLabelOrderOnlyIds.indexOf(_label._id));
          if (this.labels.length == newLabels.length) {
            Boards.direct.update(this._id, {$set: {labels: newLabels}});
          }
        }
      }
    },
  
    searchBoards(term) {
      check(term, Match.OneOf(String, null, undefined));
  
      const query = { boardId: this._id };
      query.type = 'cardType-linkedBoard';
      query.archived = false;
  
      const projection = { limit: 10, sort: { createdAt: -1 } };
  
      if (term) {
        const regex = new RegExp(term, 'i');
  
        query.$or = [{ title: regex }, { description: regex }];
      }
  
      return Cards.find(query, projection);
    },
  
    searchSwimlanes(term) {
      check(term, Match.OneOf(String, null, undefined));
  
      const query = { boardId: this._id };
      if (this.isTemplatesBoard()) {
        query.type = 'template-swimlane';
        query.archived = false;
      } else {
        query.type = { $nin: ['template-swimlane'] };
      }
      const projection = { limit: 10, sort: { createdAt: -1 } };
  
      if (term) {
        const regex = new RegExp(term, 'i');
  
        query.$or = [{ title: regex }, { description: regex }];
      }
  
      return Swimlanes.find(query, projection);
    },
  
    searchLists(term) {
      let ret = null;
      if (term) {
        check(term, Match.OneOf(String));
        term = term.trim();
      }
      if (term) {
        const query = { boardId: this._id };
        if (this.isTemplatesBoard()) {
          query.type = 'template-list';
          query.archived = false;
        } else {
          query.type = { $nin: ['template-list'] };
        }
        const projection = { sort: { createdAt: -1 } };
  
        if (term) {
          const regex = new RegExp(term, 'i');
  
          query.$or = [{ title: regex }, { description: regex }];
        }
  
        ret = Lists.find(query, projection);
      }
      return ret;
    },
  
    searchCards(term, excludeLinked) {
      let ret = null;
      if (term) {
        check(term, Match.OneOf(String));
        term = term.trim();
      }
      if (term) {
        const query = { boardId: this._id };
        if (excludeLinked) {
          query.linkedId = null;
        }
        if (this.isTemplatesBoard()) {
          query.type = 'template-card';
          query.archived = false;
        } else {
          query.type = { $nin: ['template-card'] };
        }
        const projection = { sort: { createdAt: -1 } };
  
        const regex = new RegExp(term, 'i');
  
        query.$or = [
          { title: regex },
          { description: regex },
          { customFields: { $elemMatch: { value: regex } } },
        ];
        ret = Cards.find(query, projection);
      }
      return ret;
    },
    // A board alwasy has another board where it deposits subtasks of thasks
    // that belong to itself.
    getDefaultSubtasksBoardId() {
      if (
        this.subtasksDefaultBoardId === null ||
        this.subtasksDefaultBoardId === undefined
      ) {
        this.subtasksDefaultBoardId = Boards.insert({
          title: `^${this.title}^`,
          permission: this.permission,
          members: this.members,
          color: this.color,
          description: TAPi18n.__('default-subtasks-board', {
            board: this.title,
          }),
        });
  
        Swimlanes.insert({
          title: TAPi18n.__('default'),
          boardId: this.subtasksDefaultBoardId,
        });
        Boards.update(this._id, {
          $set: {
            subtasksDefaultBoardId: this.subtasksDefaultBoardId,
          },
        });
      }
      return this.subtasksDefaultBoardId;
    },
  
    getDefaultSubtasksBoard() {
      return Boards.findOne(this.getDefaultSubtasksBoardId());
    },
  
    //Date Settings option such as received date, start date and so on.
    getDefaultDateSettingsBoardId() {
      if (
        this.dateSettingsDefaultBoardId === null ||
        this.dateSettingsDefaultBoardId === undefined
      ) {
        this.dateSettingsDefaultBoardId = Boards.insert({
          title: `^${this.title}^`,
          permission: this.permission,
          members: this.members,
          color: this.color,
          description: TAPi18n.__('default-dates-board', {
            board: this.title,
          }),
        });
  
        Swimlanes.insert({
          title: TAPi18n.__('default'),
          boardId: this.dateSettingsDefaultBoardId,
        });
        Boards.update(this._id, {
          $set: {
            dateSettingsDefaultBoardId: this.dateSettingsDefaultBoardId,
          },
        });
      }
      return this.dateSettingsDefaultBoardId;
    },
  
    getDefaultDateSettingsBoard() {
      return Boards.findOne(this.getDefaultDateSettingsBoardId());
    },
  
    getDefaultSubtasksListId() {
      if (
        this.subtasksDefaultListId === null ||
        this.subtasksDefaultListId === undefined
      ) {
        this.subtasksDefaultListId = Lists.insert({
          title: TAPi18n.__('queue'),
          boardId: this._id,
        });
        this.setSubtasksDefaultListId(this.subtasksDefaultListId);
      }
      return this.subtasksDefaultListId;
    },
  
    getDefaultSubtasksList() {
      return Lists.findOne(this.getDefaultSubtasksListId());
    },
  
    getDefaultDateSettingsListId() {
      if (
        this.dateSettingsDefaultListId === null ||
        this.dateSettingsDefaultListId === undefined
      ) {
        this.dateSettingsDefaultListId = Lists.insert({
          title: TAPi18n.__('queue'),
          boardId: this._id,
        });
        this.setDateSettingsDefaultListId(this.dateSettingsDefaultListId);
      }
      return this.dateSettingsDefaultListId;
    },
  
    getDefaultDateSettingsList() {
      return Lists.findOne(this.getDefaultDateSettingsListId());
    },
  
    getDefaultSwimline() {
      let result = Swimlanes.findOne({ boardId: this._id });
      if (result === undefined) {
        Swimlanes.insert({
          title: TAPi18n.__('default'),
          boardId: this._id,
        });
        result = Swimlanes.findOne({ boardId: this._id });
      }
      return result;
    },
  
    getNextCardNumber() {
      const boardCards = Cards.find(
        {
          boardId: this._id
        },
        {
          sort: { cardNumber: -1 },
          limit: 1
        }
      ).fetch();
  
      // If no card is assigned to the board, return 1
      if (!boardCards || boardCards.length === 0) {
        return 1;
      }
  
      const maxCardNr = !!boardCards[0].cardNumber ? boardCards[0].cardNumber : 0;
      return maxCardNr + 1;
    },
  
    cardsDueInBetween(start, end) {
      return Cards.find({
        boardId: this._id,
        dueAt: { $gte: start, $lte: end },
      });
    },
  
    cardsInInterval(start, end) {
      return Cards.find({
        boardId: this._id,
        $or: [
          {
            startAt: {
              $lte: start,
            },
            endAt: {
              $gte: start,
            },
          },
          {
            startAt: {
              $lte: end,
            },
            endAt: {
              $gte: end,
            },
          },
          {
            startAt: {
              $gte: start,
            },
            endAt: {
              $lte: end,
            },
          },
        ],
      });
    },
  
    isTemplateBoard() {
      return this.type === 'template-board';
    },
  
    isTemplatesBoard() {
      return this.type === 'template-container';
    },
  });
  
  Boards.mutations({
    archive() {
      return { $set: { archived: true, archivedAt: new Date() } };
    },
  
    restore() {
      return { $set: { archived: false } };
    },
  
    rename(title) {
      return { $set: { title } };
    },
  
    setDescription(description) {
      return { $set: { description } };
    },
  
    setColor(color) {
      return { $set: { color } };
    },
  
    setVisibility(visibility) {
      return { $set: { permission: visibility } };
    },
  
    addLabel(name, color) {
      // If label with the same name and color already exists we don't want to
      // create another one because they would be indistinguishable in the UI
      // (they would still have different `_id` but that is not exposed to the
      // user).
      if (!this.getLabel(name, color)) {
        const _id = Random.id(6);
        return { $push: { labels: { _id, name, color } } };
      }
      return {};
    },
  
    editLabel(labelId, name, color) {
      if (!this.getLabel(name, color)) {
        const labelIndex = this.labelIndex(labelId);
        return {
          $set: {
            [`labels.${labelIndex}.name`]: name,
            [`labels.${labelIndex}.color`]: color,
          },
        };
      }
      return {};
    },
  
    removeLabel(labelId) {
      return { $pull: { labels: { _id: labelId } } };
    },
  
    changeOwnership(fromId, toId) {
      const memberIndex = this.memberIndex(fromId);
      return {
        $set: {
          [`members.${memberIndex}.userId`]: toId,
        },
      };
    },
  
    addMember(memberId) {
      const memberIndex = this.memberIndex(memberId);
      if (memberIndex >= 0) {
        return {
          $set: {
            [`members.${memberIndex}.isActive`]: true,
          },
        };
      }
  
      return {
        $push: {
          members: {
            userId: memberId,
            isAdmin: false,
            isActive: true,
            isNoComments: false,
            isCommentOnly: false,
            isWorker: false,
          },
        },
      };
    },
  
    removeMember(memberId) {
      const memberIndex = this.memberIndex(memberId);
  
      // we do not allow the only one admin to be removed
      const allowRemove =
        !this.members[memberIndex].isAdmin || this.activeAdmins().length > 1;
      if (!allowRemove) {
        return {
          $set: {
            [`members.${memberIndex}.isActive`]: true,
          },
        };
      }
  
      return {
        $set: {
          [`members.${memberIndex}.isActive`]: false,
          [`members.${memberIndex}.isAdmin`]: false,
        },
      };
    },
  
    setMemberPermission(
      memberId,
      isAdmin,
      isNoComments,
      isCommentOnly,
      isWorker,
      currentUserId = Meteor.userId(),
    ) {
      const memberIndex = this.memberIndex(memberId);
      // do not allow change permission of self
      if (memberId === currentUserId) {
        isAdmin = this.members[memberIndex].isAdmin;
      }
  
      return {
        $set: {
          [`members.${memberIndex}.isAdmin`]: isAdmin,
          [`members.${memberIndex}.isNoComments`]: isNoComments,
          [`members.${memberIndex}.isCommentOnly`]: isCommentOnly,
          [`members.${memberIndex}.isWorker`]: isWorker,
        },
      };
    },
  
    setAllowsSubtasks(allowsSubtasks) {
      return { $set: { allowsSubtasks } };
    },
  
    setAllowsCreator(allowsCreator) {
      return { $set: { allowsCreator } };
    },
  
    setAllowsMembers(allowsMembers) {
      return { $set: { allowsMembers } };
    },
  
    setAllowsChecklists(allowsChecklists) {
      return { $set: { allowsChecklists } };
    },
  
    setAllowsAssignee(allowsAssignee) {
      return { $set: { allowsAssignee } };
    },
  
    setAllowsAssignedBy(allowsAssignedBy) {
      return { $set: { allowsAssignedBy } };
    },
  
    setAllowsRequestedBy(allowsRequestedBy) {
      return { $set: { allowsRequestedBy } };
    },
  
    setAllowsCardSortingByNumber(allowsCardSortingByNumber) {
      return { $set: { allowsCardSortingByNumber } };
    },
  
    setAllowsShowLists(allowsShowLists) {
      return { $set: { allowsShowLists } };
    },
  
    setAllowsAttachments(allowsAttachments) {
      return { $set: { allowsAttachments } };
    },
  
    setAllowsLabels(allowsLabels) {
      return { $set: { allowsLabels } };
    },
  
    setAllowsComments(allowsComments) {
      return { $set: { allowsComments } };
    },
  
    setAllowsDescriptionTitle(allowsDescriptionTitle) {
      return { $set: { allowsDescriptionTitle } };
    },
  
    setAllowsCardNumber(allowsCardNumber) {
      return { $set: { allowsCardNumber } };
    },
  
    setAllowsDescriptionText(allowsDescriptionText) {
      return { $set: { allowsDescriptionText } };
    },
  
    setAllowsActivities(allowsActivities) {
      return { $set: { allowsActivities } };
    },
  
    setAllowsReceivedDate(allowsReceivedDate) {
      return { $set: { allowsReceivedDate } };
    },
  
    setAllowsStartDate(allowsStartDate) {
      return { $set: { allowsStartDate } };
    },
  
    setAllowsEndDate(allowsEndDate) {
      return { $set: { allowsEndDate } };
    },
  
    setAllowsDueDate(allowsDueDate) {
      return { $set: { allowsDueDate } };
    },
  
    setSubtasksDefaultBoardId(subtasksDefaultBoardId) {
      return { $set: { subtasksDefaultBoardId } };
    },
  
    setSubtasksDefaultListId(subtasksDefaultListId) {
      return { $set: { subtasksDefaultListId } };
    },
  
    setPresentParentTask(presentParentTask) {
      return { $set: { presentParentTask } };
    },
  
    move(sortIndex) {
      return { $set: { sort: sortIndex } };
    },
  });
  
  function boardRemover(userId, doc) {
    [Cards, Lists, Swimlanes, Integrations, Rules, Activities].forEach(
      element => {
        element.remove({ boardId: doc._id });
      },
    );
  }
  
  Boards.uniqueTitle = title => {
    const m = title.match(
      new RegExp('^(?<title>.*?)\\s*(\\[(?<num>\\d+)]\\s*$|\\s*$)'),
    );
    const base = escapeForRegex(m.groups.title);
    let num = 0;
    Boards.find({ title: new RegExp(`^${base}\\s*\\[\\d+]\\s*$`) }).forEach(
      board => {
        const m = board.title.match(
          new RegExp('^(?<title>.*?)\\s*\\[(?<num>\\d+)]\\s*$'),
        );
        if (m) {
          const n = parseInt(m.groups.num, 10);
          num = num < n ? n : num;
        }
      },
    );
  
    if (num > 0) {
      return `${base} [${num + 1}]`;
    }
  
    return title;
  };
  
  Boards.userSearch = (
    userId,
    selector = {},
    projection = {},
    // includeArchived = false,
  ) => {
    // if (!includeArchived) {
    //   selector.archived = false;
    // }
    selector.$or = [{ permission: 'public' }];
  
    if (userId) {
      selector.$or.push({ members: { $elemMatch: { userId, isActive: true } } });
    }
    return Boards.find(selector, projection);
  };
  
  Boards.userBoards = (
    userId,
    archived = false,
    selector = {},
    projection = {},
  ) => {
    const user = Users.findOne(userId);
    if (!user) {
      return [];
    }
  
    if (typeof archived === 'boolean') {
      selector.archived = archived;
    }
    if (!selector.type) {
      selector.type = 'board';
    }
  
    selector.$or = [
      { permission: 'public' },
      { members: { $elemMatch: { userId, isActive: true } } },
      { orgs: { $elemMatch: { orgId: { $in: user.orgIds() }, isActive: true } } },
      { teams: { $elemMatch: { teamId: { $in: user.teamIds() }, isActive: true } } },
    ];
  
    return Boards.find(selector, projection);
  };
  
  Boards.userBoardIds = (userId, archived = false, selector = {}) => {
    return Boards.userBoards(userId, archived, selector, {
      fields: { _id: 1 },
    }).map(board => {
      return board._id;
    });
  };
  
  Boards.colorMap = () => {
    const colors = {};
    for (const color of Boards.labelColors()) {
      colors[TAPi18n.__(`color-${color}`)] = color;
    }
    return colors;
  };
  
  Boards.labelColors = () => {
    return ALLOWED_COLORS;
  };
  
  if (Meteor.isServer) {
    Boards.allow({
      insert: Meteor.userId,
      update: allowIsBoardAdmin,
      remove: allowIsBoardAdmin,
      fetch: ['members'],
    });
  
    // All logged in users are allowed to reorder boards by dragging at All Boards page and Public Boards page.
    Boards.allow({
      update(userId, board, fieldNames) {
        return _.contains(fieldNames, 'sort');
      },
      fetch: [],
    });
  
    // The number of users that have starred this board is managed by trusted code
    // and the user is not allowed to update it
    Boards.deny({
      update(userId, board, fieldNames) {
        return _.contains(fieldNames, 'stars');
      },
      fetch: [],
    });
  
    // We can't remove a member if it is the last administrator
    Boards.deny({
      update(userId, doc, fieldNames, modifier) {
        if (!_.contains(fieldNames, 'members')) return false;
  
        // We only care in case of a $pull operation, ie remove a member
        if (!_.isObject(modifier.$pull && modifier.$pull.members)) 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;
        if (nbAdmins > 1) return false;
  
        // If all the previous conditions were verified, we can't remove
        // a user if it's an admin
        const removedMemberId = modifier.$pull.members.userId;
        return Boolean(
          _.findWhere(doc.members, {
            userId: removedMemberId,
            isAdmin: true,
          }),
        );
      },
      fetch: ['members'],
    });
  
    Meteor.methods({
      quitBoard(boardId) {
        check(boardId, String);
        const board = Boards.findOne(boardId);
        if (board) {
          const userId = Meteor.userId();
          const index = board.memberIndex(userId);
          if (index >= 0) {
            board.removeMember(userId);
            return true;
          } else throw new Meteor.Error('error-board-notAMember');
        } else throw new Meteor.Error('error-board-doesNotExist');
      },
      acceptInvite(boardId) {
        check(boardId, String);
        const board = Boards.findOne(boardId);
        if (!board) {
          throw new Meteor.Error('error-board-doesNotExist');
        }
  
        Meteor.users.update(Meteor.userId(), {
          $pull: {
            'profile.invitedBoards': boardId,
          },
        });
      },
      myLabelNames() {
        let names = [];
        Boards.userBoards(Meteor.userId()).forEach(board => {
          // Only return labels when they exist.
          if (board.labels !== undefined) {
            names = names.concat(
              board.labels
                .filter(label => !!label.name)
                .map(label => {
                  return label.name;
                }),
            );
          } else {
            return [];
          }
        });
        return _.uniq(names).sort();
      },
      myBoardNames() {
        return _.uniq(
          Boards.userBoards(Meteor.userId()).map(board => {
            return board.title;
          }),
        ).sort();
      },
    });
  
    Meteor.methods({
      archiveBoard(boardId) {
        check(boardId, String);
        const board = Boards.findOne(boardId);
        if (board) {
          const userId = Meteor.userId();
          const index = board.memberIndex(userId);
          if (index >= 0) {
            board.archive();
            return true;
          } else throw new Meteor.Error('error-board-notAMember');
        } else throw new Meteor.Error('error-board-doesNotExist');
      },
      setBoardOrgs(boardOrgsArray, currBoardId){
        check(boardOrgsArray, Array);
        check(currBoardId, String);
        Boards.update(currBoardId, {
          $set: {
            orgs: boardOrgsArray,
          },
        });
      },
      setBoardTeams(boardTeamsArray, membersArray, currBoardId){
        check(boardTeamsArray, Array);
        check(membersArray, Array);
        check(currBoardId, String);
        Boards.update(currBoardId, {
          $set: {
            members: membersArray,
            teams: boardTeamsArray,
          },
        });
      },
    });
  }
  
  // Insert new board at last position in sort order.
  Boards.before.insert((userId, doc) => {
    const lastBoard = Boards.findOne(
      { sort: { $exists: true } },
      { sort: { sort: -1 } },
    );
    if (lastBoard && typeof lastBoard.sort !== 'undefined') {
      doc.sort = lastBoard.sort + 1;
    }
  });
  
  if (Meteor.isServer) {
    // Let MongoDB ensure that a member is not included twice in the same board
    Meteor.startup(() => {
      Boards._collection._ensureIndex({ modifiedAt: -1 });
      Boards._collection._ensureIndex(
        {
          _id: 1,
          'members.userId': 1,
        },
        { unique: true },
      );
      Boards._collection._ensureIndex({ 'members.userId': 1 });
    });
  
    // Genesis: the first activity of the newly created board
    Boards.after.insert((userId, doc) => {
      Activities.insert({
        userId,
        type: 'board',
        activityTypeId: doc._id,
        activityType: 'createBoard',
        boardId: doc._id,
      });
    });
  
    // If the user remove one label from a board, we cant to remove reference of
    // this label in any card of this board.
    Boards.after.update((userId, doc, fieldNames, modifier) => {
      if (
        !_.contains(fieldNames, 'labels') ||
        !modifier.$pull ||
        !modifier.$pull.labels ||
        !modifier.$pull.labels._id
      ) {
        return;
      }
  
      const removedLabelId = modifier.$pull.labels._id;
      Cards.update(
        { boardId: doc._id },
        {
          $pull: {
            labelIds: removedLabelId,
          },
        },
        { multi: true },
      );
    });
  
    const foreachRemovedMember = (doc, modifier, callback) => {
      Object.keys(modifier).forEach(set => {
        if (modifier[set] !== false) {
          return;
        }
  
        const parts = set.split('.');
        if (
          parts.length === 3 &&
          parts[0] === 'members' &&
          parts[2] === 'isActive'
        ) {
          callback(doc.members[parts[1]].userId);
        }
      });
    };
  
    // Remove a member from all objects of the board before leaving the board
    Boards.before.update((userId, doc, fieldNames, modifier) => {
      if (!_.contains(fieldNames, 'members')) {
        return;
      }
  
      if (modifier.$set) {
        const boardId = doc._id;
        foreachRemovedMember(doc, modifier.$set, memberId => {
          Cards.update(
            { boardId },
            {
              $pull: {
                members: memberId,
                watchers: memberId,
              },
            },
            { multi: true },
          );
  
          Lists.update(
            { boardId },
            {
              $pull: {
                watchers: memberId,
              },
            },
            { multi: true },
          );
  
          const board = Boards._transform(doc);
          board.setWatcher(memberId, false);
  
          // Remove board from users starred list
          if (!board.isPublic()) {
            Users.update(memberId, {
              $pull: {
                'profile.starredBoards': boardId,
              },
            });
          }
        });
      }
    });
  
    Boards.before.remove((userId, doc) => {
      boardRemover(userId, doc);
      // Add removeBoard activity to keep it
      Activities.insert({
        userId,
        type: 'board',
        activityTypeId: doc._id,
        activityType: 'removeBoard',
        boardId: doc._id,
      });
    });
  
    // Add a new activity if we add or remove a member to the board
    Boards.after.update((userId, doc, fieldNames, modifier) => {
      if (!_.contains(fieldNames, 'members')) {
        return;
      }
  
      // Say hello to the new member
      if (modifier.$push && modifier.$push.members) {
        const memberId = modifier.$push.members.userId;
        Activities.insert({
          userId,
          memberId,
          type: 'member',
          activityType: 'addBoardMember',
          boardId: doc._id,
        });
      }
  
      // Say goodbye to the former member
      if (modifier.$set) {
        foreachRemovedMember(doc, modifier.$set, memberId => {
          Activities.insert({
            userId,
            memberId,
            type: 'member',
            activityType: 'removeBoardMember',
            boardId: doc._id,
          });
        });
      }
    });
  }
  
  //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);
        const paramUserId = req.params.userId;
        // A normal user should be able to see their own boards,
        // admins can access boards of any user
        Authentication.checkAdminOrCondition(
          req.userId,
          req.userId === paramUserId,
        );
  
        const data = Boards.find(
          {
            archived: false,
            'members.userId': paramUserId,
          },
          {
            sort: { sort: 1 /* boards default sorting */ },
          },
        ).map(function(board) {
          return {
            _id: board._id,
            title: board.title,
          };
        });
  
        JsonRoutes.sendResult(res, { code: 200, data });
      } catch (error) {
        JsonRoutes.sendResult(res, {
          code: 200,
          data: error,
        });
      }
    });
  
    /**
     * @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);
        JsonRoutes.sendResult(res, {
          code: 200,
          data: Boards.find(
            { permission: 'public' },
            {
              sort: { sort: 1 /* boards default sorting */ },
            },
          ).map(function(doc) {
            return {
              _id: doc._id,
              title: doc.title,
            };
          }),
        });
      } catch (error) {
        JsonRoutes.sendResult(res, {
          code: 200,
          data: error,
        });
      }
    });
  
    /**
     * @operation get_boards_count
     * @summary Get public and private boards count
     *
     * @return_type {private: integer, public: integer}
     */
    JsonRoutes.add('GET', '/api/boards_count', function(req, res) {
      try {
        Authentication.checkUserId(req.userId);
        JsonRoutes.sendResult(res, {
          code: 200,
          data: {
            private: Boards.find({ permission: 'private' }).count(),
            public: Boards.find({ permission: 'public' }).count(),
          },
        });
      } catch (error) {
        JsonRoutes.sendResult(res, {
          code: 200,
          data: error,
        });
      }
    });
  
    /**
     * @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 {
        Authentication.checkUserId(req.userId);
        const id = req.params.boardId;
  
        JsonRoutes.sendResult(res, {
          code: 200,
          data: Boards.findOne({ _id: id }),
        });
      } catch (error) {
        JsonRoutes.sendResult(res, {
          code: 200,
          data: error,
        });
      }
    });
  
    /**
     * @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`, `moderatepink`, `strongcyan`,
     * `limegreen`, `midnight`, `dark`, `relax`, `corteza`:
     *
     * <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 {boolean} [isWorker] only move cards, assign himself to card and comment (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);
        const id = Boards.insert({
          title: req.body.title,
          members: [
            {
              userId: req.body.owner,
              isAdmin: req.body.isAdmin || true,
              isActive: req.body.isActive || true,
              isNoComments: req.body.isNoComments || false,
              isCommentOnly: req.body.isCommentOnly || false,
              isWorker: req.body.isWorker || false,
            },
          ],
          permission: req.body.permission || 'private',
          color: req.body.color || 'belize',
        });
        const swimlaneId = Swimlanes.insert({
          title: TAPi18n.__('default'),
          boardId: id,
        });
        JsonRoutes.sendResult(res, {
          code: 200,
          data: {
            _id: id,
            defaultSwimlaneId: swimlaneId,
          },
        });
      } catch (error) {
        JsonRoutes.sendResult(res, {
          code: 200,
          data: error,
        });
      }
    });
  
    /**
     * @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);
        const id = req.params.boardId;
        Boards.remove({ _id: id });
        JsonRoutes.sendResult(res, {
          code: 200,
          data: {
            _id: id,
          },
        });
      } catch (error) {
        JsonRoutes.sendResult(res, {
          code: 200,
          data: error,
        });
      }
    });
  
    /**
     * @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;
      try {
        if (req.body.hasOwnProperty('label')) {
          const board = Boards.findOne({ _id: id });
          const color = req.body.label.color;
          const name = req.body.label.name;
          const labelId = Random.id(6);
          if (!board.getLabel(name, color)) {
            Boards.direct.update(
              { _id: id },
              { $push: { labels: { _id: labelId, name, color } } },
            );
            JsonRoutes.sendResult(res, {
              code: 200,
              data: labelId,
            });
          } else {
            JsonRoutes.sendResult(res, {
              code: 200,
            });
          }
        }
      } catch (error) {
        JsonRoutes.sendResult(res, {
          data: error,
        });
      }
    });
  
    /**
     * @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
     * @param {boolean} isWorker Worker capability
     */
    JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function(
      req,
      res,
    ) {
      try {
        Authentication.checkUserId(req.userId);
        const boardId = req.params.boardId;
        const memberId = req.params.memberId;
        const { isAdmin, isNoComments, isCommentOnly, isWorker } = req.body;
        const board = Boards.findOne({ _id: boardId });
        function isTrue(data) {
          try {
            return data.toLowerCase() === 'true';
          } catch (error) {
            return data;
          }
        }
        const query = board.setMemberPermission(
          memberId,
          isTrue(isAdmin),
          isTrue(isNoComments),
          isTrue(isCommentOnly),
          isTrue(isWorker),
          req.userId,
        );
  
        JsonRoutes.sendResult(res, {
          code: 200,
          data: query,
        });
      } catch (error) {
        JsonRoutes.sendResult(res, {
          code: 200,
          data: error,
        });
      }
    });
  
    //ATTACHMENTS REST API
    /**
     * @operation get_board_attachments
     * @summary Get the list of attachments of a board
     *
     * @param {string} boardId the board ID
     * @return_type [{attachmentId: string,
     *                attachmentName: string,
     *                attachmentType: string,
     *                cardId: string,
     *                listId: string,
     *                swimlaneId: string}]
     */
    JsonRoutes.add('GET', '/api/boards/:boardId/attachments', function(req, res) {
      Authentication.checkUserId(req.userId);
      const paramBoardId = req.params.boardId;
      JsonRoutes.sendResult(res, {
        code: 200,
        data: Attachments.files
          .find({ boardId: paramBoardId }, { fields: { boardId: 0 } })
          .map(function(doc) {
            return {
              attachmentId: doc._id,
              attachmentName: doc.original.name,
              attachmentType: doc.original.type,
              url: FlowRouter.url(doc.url()),
              urlDownload: `${FlowRouter.url(doc.url())}?download=true&token=`,
              cardId: doc.cardId,
              listId: doc.listId,
              swimlaneId: doc.swimlaneId,
            };
          }),
      });
    });
  }
  
  export default Boards;
  