|
@@ -1,3 +1,5 @@
|
|
|
+const escapeForRegex = require('escape-string-regexp');
|
|
|
+
|
|
|
Meteor.publish('card', cardId => {
|
|
|
check(cardId, String);
|
|
|
return Cards.find({ _id: cardId });
|
|
@@ -177,18 +179,363 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|
|
check(sessionId, String);
|
|
|
check(queryParams, Object);
|
|
|
|
|
|
+ const userId = Meteor.userId();
|
|
|
// eslint-disable-next-line no-console
|
|
|
- // console.log('queryParams:', queryParams);
|
|
|
+ // console.log('userId:', userId);
|
|
|
+
|
|
|
+ const errors = new (class {
|
|
|
+ constructor() {
|
|
|
+ this.notFound = {
|
|
|
+ boards: [],
|
|
|
+ swimlanes: [],
|
|
|
+ lists: [],
|
|
|
+ labels: [],
|
|
|
+ users: [],
|
|
|
+ members: [],
|
|
|
+ assignees: [],
|
|
|
+ is: [],
|
|
|
+ comments: [],
|
|
|
+ };
|
|
|
+
|
|
|
+ this.colorMap = {};
|
|
|
+ for (const color of Boards.simpleSchema()._schema['labels.$.color']
|
|
|
+ .allowedValues) {
|
|
|
+ this.colorMap[TAPi18n.__(`color-${color}`)] = color;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ hasErrors() {
|
|
|
+ for (const prop in this.notFound) {
|
|
|
+ if (this.notFound[prop].length) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
- const results = Cards.globalSearch(queryParams);
|
|
|
- const cards = results.cards;
|
|
|
+ errorMessages() {
|
|
|
+ const messages = [];
|
|
|
+
|
|
|
+ this.notFound.boards.forEach(board => {
|
|
|
+ messages.push({ tag: 'board-title-not-found', value: board });
|
|
|
+ });
|
|
|
+ this.notFound.swimlanes.forEach(swim => {
|
|
|
+ messages.push({ tag: 'swimlane-title-not-found', value: swim });
|
|
|
+ });
|
|
|
+ this.notFound.lists.forEach(list => {
|
|
|
+ messages.push({ tag: 'list-title-not-found', value: list });
|
|
|
+ });
|
|
|
+ this.notFound.comments.forEach(comments => {
|
|
|
+ comments.forEach(text => {
|
|
|
+ messages.push({ tag: 'comment-not-found', value: text });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ this.notFound.labels.forEach(label => {
|
|
|
+ messages.push({ tag: 'label-not-found', value: label, color: true });
|
|
|
+ });
|
|
|
+ this.notFound.users.forEach(user => {
|
|
|
+ messages.push({ tag: 'user-username-not-found', value: user });
|
|
|
+ });
|
|
|
+ this.notFound.members.forEach(user => {
|
|
|
+ messages.push({ tag: 'user-username-not-found', value: user });
|
|
|
+ });
|
|
|
+ this.notFound.assignees.forEach(user => {
|
|
|
+ messages.push({ tag: 'user-username-not-found', value: user });
|
|
|
+ });
|
|
|
+
|
|
|
+ return messages;
|
|
|
+ }
|
|
|
+ })();
|
|
|
+
|
|
|
+ const selector = {
|
|
|
+ archived: false,
|
|
|
+ type: 'cardType-card',
|
|
|
+ boardId: { $in: Boards.userBoardIds(userId) },
|
|
|
+ swimlaneId: { $nin: Swimlanes.archivedSwimlaneIds() },
|
|
|
+ listId: { $nin: Lists.archivedListIds() },
|
|
|
+ };
|
|
|
+
|
|
|
+ if (queryParams.boards.length) {
|
|
|
+ const queryBoards = [];
|
|
|
+ queryParams.boards.forEach(query => {
|
|
|
+ const boards = Boards.userSearch(userId, {
|
|
|
+ title: new RegExp(escapeForRegex(query), 'i'),
|
|
|
+ });
|
|
|
+ if (boards.count()) {
|
|
|
+ boards.forEach(board => {
|
|
|
+ queryBoards.push(board._id);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ errors.notFound.boards.push(query);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ selector.boardId.$in = queryBoards;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (queryParams.swimlanes.length) {
|
|
|
+ const querySwimlanes = [];
|
|
|
+ queryParams.swimlanes.forEach(query => {
|
|
|
+ const swimlanes = Swimlanes.find({
|
|
|
+ title: new RegExp(escapeForRegex(query), 'i'),
|
|
|
+ });
|
|
|
+ if (swimlanes.count()) {
|
|
|
+ swimlanes.forEach(swim => {
|
|
|
+ querySwimlanes.push(swim._id);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ errors.notFound.swimlanes.push(query);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ selector.swimlaneId.$in = querySwimlanes;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (queryParams.lists.length) {
|
|
|
+ const queryLists = [];
|
|
|
+ queryParams.lists.forEach(query => {
|
|
|
+ const lists = Lists.find({
|
|
|
+ title: new RegExp(escapeForRegex(query), 'i'),
|
|
|
+ });
|
|
|
+ if (lists.count()) {
|
|
|
+ lists.forEach(list => {
|
|
|
+ queryLists.push(list._id);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ errors.notFound.lists.push(query);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ selector.listId.$in = queryLists;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (queryParams.comments.length) {
|
|
|
+ const cardIds = CardComments.textSearch(userId, queryParams.comments).map(
|
|
|
+ com => {
|
|
|
+ return com.cardId;
|
|
|
+ },
|
|
|
+ );
|
|
|
+ if (cardIds.length) {
|
|
|
+ selector._id = { $in: cardIds };
|
|
|
+ } else {
|
|
|
+ errors.notFound.comments.push(queryParams.comments);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (queryParams.dueAt !== null) {
|
|
|
+ selector.dueAt = { $lte: new Date(queryParams.dueAt) };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (queryParams.createdAt !== null) {
|
|
|
+ selector.createdAt = { $gte: new Date(queryParams.createdAt) };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (queryParams.modifiedAt !== null) {
|
|
|
+ selector.modifiedAt = { $gte: new Date(queryParams.modifiedAt) };
|
|
|
+ }
|
|
|
+
|
|
|
+ const queryMembers = [];
|
|
|
+ const queryAssignees = [];
|
|
|
+ if (queryParams.users.length) {
|
|
|
+ queryParams.users.forEach(query => {
|
|
|
+ const users = Users.find({
|
|
|
+ username: query,
|
|
|
+ });
|
|
|
+ if (users.count()) {
|
|
|
+ users.forEach(user => {
|
|
|
+ queryMembers.push(user._id);
|
|
|
+ queryAssignees.push(user._id);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ errors.notFound.users.push(query);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (queryParams.members.length) {
|
|
|
+ queryParams.members.forEach(query => {
|
|
|
+ const users = Users.find({
|
|
|
+ username: query,
|
|
|
+ });
|
|
|
+ if (users.count()) {
|
|
|
+ users.forEach(user => {
|
|
|
+ queryMembers.push(user._id);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ errors.notFound.members.push(query);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (queryParams.assignees.length) {
|
|
|
+ queryParams.assignees.forEach(query => {
|
|
|
+ const users = Users.find({
|
|
|
+ username: query,
|
|
|
+ });
|
|
|
+ if (users.count()) {
|
|
|
+ users.forEach(user => {
|
|
|
+ queryAssignees.push(user._id);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ errors.notFound.assignees.push(query);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (queryMembers.length && queryAssignees.length) {
|
|
|
+ selector.$or = [
|
|
|
+ { members: { $in: queryMembers } },
|
|
|
+ { assignees: { $in: queryAssignees } },
|
|
|
+ ];
|
|
|
+ } else if (queryMembers.length) {
|
|
|
+ selector.members = { $in: queryMembers };
|
|
|
+ } else if (queryAssignees.length) {
|
|
|
+ selector.assignees = { $in: queryAssignees };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (queryParams.labels.length) {
|
|
|
+ queryParams.labels.forEach(label => {
|
|
|
+ const queryLabels = [];
|
|
|
+
|
|
|
+ let boards = Boards.userSearch(userId, {
|
|
|
+ labels: { $elemMatch: { color: label.toLowerCase() } },
|
|
|
+ });
|
|
|
+
|
|
|
+ if (boards.count()) {
|
|
|
+ boards.forEach(board => {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ // console.log('board:', board);
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ // console.log('board.labels:', board.labels);
|
|
|
+ board.labels
|
|
|
+ .filter(boardLabel => {
|
|
|
+ return boardLabel.color === label.toLowerCase();
|
|
|
+ })
|
|
|
+ .forEach(boardLabel => {
|
|
|
+ queryLabels.push(boardLabel._id);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ // console.log('label:', label);
|
|
|
+ const reLabel = new RegExp(escapeForRegex(label), 'i');
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ // console.log('reLabel:', reLabel);
|
|
|
+ boards = Boards.userSearch(userId, {
|
|
|
+ labels: { $elemMatch: { name: reLabel } },
|
|
|
+ });
|
|
|
+
|
|
|
+ if (boards.count()) {
|
|
|
+ boards.forEach(board => {
|
|
|
+ board.labels
|
|
|
+ .filter(boardLabel => {
|
|
|
+ return boardLabel.name.match(reLabel);
|
|
|
+ })
|
|
|
+ .forEach(boardLabel => {
|
|
|
+ queryLabels.push(boardLabel._id);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ errors.notFound.labels.push(label);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ selector.labelIds = { $in: queryLabels };
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ let cards = null;
|
|
|
+
|
|
|
+ if (!errors.hasErrors()) {
|
|
|
+ if (queryParams.text) {
|
|
|
+ const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
|
|
|
+
|
|
|
+ selector.$or = [
|
|
|
+ { title: regex },
|
|
|
+ { description: regex },
|
|
|
+ { customFields: { $elemMatch: { value: regex } } },
|
|
|
+ {
|
|
|
+ _id: {
|
|
|
+ $in: CardComments.textSearch(userId, [queryParams.text]).map(
|
|
|
+ com => com.cardId,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ // console.log('selector:', selector);
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ // console.log('selector.$or:', selector.$or);
|
|
|
+
|
|
|
+ const projection = {
|
|
|
+ fields: {
|
|
|
+ _id: 1,
|
|
|
+ archived: 1,
|
|
|
+ boardId: 1,
|
|
|
+ swimlaneId: 1,
|
|
|
+ listId: 1,
|
|
|
+ title: 1,
|
|
|
+ type: 1,
|
|
|
+ sort: 1,
|
|
|
+ members: 1,
|
|
|
+ assignees: 1,
|
|
|
+ colors: 1,
|
|
|
+ dueAt: 1,
|
|
|
+ createdAt: 1,
|
|
|
+ modifiedAt: 1,
|
|
|
+ labelIds: 1,
|
|
|
+ },
|
|
|
+ limit: 50,
|
|
|
+ };
|
|
|
+
|
|
|
+ if (queryParams.sort === 'due') {
|
|
|
+ projection.sort = {
|
|
|
+ dueAt: 1,
|
|
|
+ boardId: 1,
|
|
|
+ swimlaneId: 1,
|
|
|
+ listId: 1,
|
|
|
+ sort: 1,
|
|
|
+ };
|
|
|
+ } else if (queryParams.sort === 'modified') {
|
|
|
+ projection.sort = {
|
|
|
+ modifiedAt: -1,
|
|
|
+ boardId: 1,
|
|
|
+ swimlaneId: 1,
|
|
|
+ listId: 1,
|
|
|
+ sort: 1,
|
|
|
+ };
|
|
|
+ } else if (queryParams.sort === 'created') {
|
|
|
+ projection.sort = {
|
|
|
+ createdAt: -1,
|
|
|
+ boardId: 1,
|
|
|
+ swimlaneId: 1,
|
|
|
+ listId: 1,
|
|
|
+ sort: 1,
|
|
|
+ };
|
|
|
+ } else if (queryParams.sort === 'system') {
|
|
|
+ projection.sort = {
|
|
|
+ boardId: 1,
|
|
|
+ swimlaneId: 1,
|
|
|
+ listId: 1,
|
|
|
+ modifiedAt: 1,
|
|
|
+ sort: 1,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ cards = Cards.find(selector, projection);
|
|
|
+
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ // console.log('count:', cards.count());
|
|
|
+ }
|
|
|
|
|
|
const update = {
|
|
|
$set: {
|
|
|
totalHits: 0,
|
|
|
lastHit: 0,
|
|
|
cards: [],
|
|
|
- errorMessages: results.errors.errorMessages(),
|
|
|
+ errors: errors.errorMessages(),
|
|
|
},
|
|
|
};
|
|
|
|
|
@@ -202,12 +549,12 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|
|
|
|
|
SessionData.upsert({ userId: this.userId, sessionId }, update);
|
|
|
|
|
|
- const boards = [];
|
|
|
- const swimlanes = [];
|
|
|
- const lists = [];
|
|
|
- const users = [this.userId];
|
|
|
-
|
|
|
if (cards) {
|
|
|
+ const boards = [];
|
|
|
+ const swimlanes = [];
|
|
|
+ const lists = [];
|
|
|
+ const users = [this.userId];
|
|
|
+
|
|
|
cards.forEach(card => {
|
|
|
if (card.boardId) boards.push(card.boardId);
|
|
|
if (card.swimlaneId) swimlanes.push(card.swimlaneId);
|
|
@@ -223,28 +570,24 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
- }
|
|
|
|
|
|
- const fields = {
|
|
|
- _id: 1,
|
|
|
- title: 1,
|
|
|
- archived: 1,
|
|
|
- };
|
|
|
- // eslint-disable-next-line no-console
|
|
|
- // console.log('users:', users);
|
|
|
- const cursors = [
|
|
|
- Boards.find({ _id: { $in: boards } }, { fields }),
|
|
|
- Swimlanes.find({ _id: { $in: swimlanes } }, { fields }),
|
|
|
- Lists.find({ _id: { $in: lists } }, { fields }),
|
|
|
- Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
|
|
|
- SessionData.find({ userId: this.userId, sessionId }),
|
|
|
- ];
|
|
|
-
|
|
|
- if (cards) {
|
|
|
- cursors.push(cards);
|
|
|
+ const fields = {
|
|
|
+ _id: 1,
|
|
|
+ title: 1,
|
|
|
+ archived: 1,
|
|
|
+ };
|
|
|
+
|
|
|
+ return [
|
|
|
+ cards,
|
|
|
+ Boards.find({ _id: { $in: boards } }, { fields }),
|
|
|
+ Swimlanes.find({ _id: { $in: swimlanes } }, { fields }),
|
|
|
+ Lists.find({ _id: { $in: lists } }, { fields }),
|
|
|
+ Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
|
|
|
+ SessionData.find({ userId: this.userId, sessionId }),
|
|
|
+ ];
|
|
|
}
|
|
|
|
|
|
- return cursors;
|
|
|
+ return [SessionData.find({ userId: this.userId, sessionId })];
|
|
|
});
|
|
|
|
|
|
Meteor.publish('brokenCards', function() {
|