summaryrefslogtreecommitdiffstats
path: root/server/publications/boards.js
blob: 17d87f3ae07526f78c37373b8fc0f18586e5197b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// This is the publication used to display the board list. We publish all the
// non-archived boards:
// 1. that the user is a member of
// 2. the user has starred
Meteor.publish('boards', function() {
  // Ensure that the user is connected. If it is not, we need to return an empty
  // array to tell the client to remove the previously published docs.
  if (!Match.test(this.userId, String))
    return [];

  // Defensive programming to verify that starredBoards has the expected
  // format -- since the field is in the `profile` a user can modify it.
  const {starredBoards = []} = Users.findOne(this.userId).profile;
  check(starredBoards, [String]);

  return Boards.find({
    archived: false,
    $or: [
      {
        _id: { $in: starredBoards },
        permission: 'public',
      },
      { members: { $elemMatch: { userId: this.userId, isActive: true }}},
    ],
  }, {
    fields: {
      _id: 1,
      archived: 1,
      slug: 1,
      title: 1,
      description: 1,
      color: 1,
      members: 1,
      permission: 1,
    },
  });
});

Meteor.publish('archivedBoards', function() {
  if (!Match.test(this.userId, String))
    return [];

  return Boards.find({
    archived: true,
    members: {
      $elemMatch: {
        userId: this.userId,
        isAdmin: true,
      },
    },
  }, {
    fields: {
      _id: 1,
      archived: 1,
      slug: 1,
      title: 1,
    },
  });
});

Meteor.publishRelations('board', function(boardId) {
  check(boardId, String);
  const thisUserId = this.userId;

  this.cursor(Boards.find({
    _id: boardId,
    archived: false,
    // If the board is not public the user has to be a member of it to see
    // it.
    $or: [
      { permission: 'public' },
      { members: { $elemMatch: { userId: this.userId, isActive: true }}},
    ],
  }, { limit: 1 }), function(boardId, board) {
    this.cursor(Lists.find({ boardId }));
    this.cursor(Swimlanes.find({ boardId }));
    this.cursor(Integrations.find({ boardId }));

    // Cards and cards comments
    // XXX Originally we were publishing the card documents as a child of the
    // list publication defined above using the following selector `{ listId:
    // list._id }`. But it was causing a race condition in publish-composite,
    // that I documented here:
    //
    //   https://github.com/englue/meteor-publish-composite/issues/29
    //
    // cottz:publish had a similar problem:
    //
    //   https://github.com/Goluis/cottz-publish/issues/4
    //
    // The current state of relational publishing in meteor is a bit sad,
    // there are a lot of various packages, with various APIs, some of them
    // are unmaintained. Fortunately this is something that will be fixed by
    // meteor-core at some point:
    //
    //   https://trello.com/c/BGvIwkEa/48-easy-joins-in-subscriptions
    //
    // And in the meantime our code below works pretty well -- it's not even a
    // hack!
    this.cursor(Cards.find({ boardId }), function(cardId) {
      this.cursor(CardComments.find({ cardId }));
      this.cursor(Attachments.find({ cardId }));
      this.cursor(Checklists.find({ cardId }));
      this.cursor(ChecklistItems.find({ cardId }));
    });

    if (board.members) {
      // Board members. This publication also includes former board members that
      // aren't members anymore but may have some activities attached to them in
      // the history.
      const memberIds = _.pluck(board.members, 'userId');

      // We omit the current user because the client should already have that data,
      // and sending it triggers a subtle bug:
      // https://github.com/wefork/wekan/issues/15
      this.cursor(Users.find({
        _id: { $in: _.without(memberIds, thisUserId)},
      }, { fields: {
        'username': 1,
        'profile.fullname': 1,
        'profile.avatarUrl': 1,
      }}));

      this.cursor(presences.find({ userId: { $in: memberIds } }));
    }
  });

  return this.ready();
});