Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save crapthings/51f86bc605e70160d23baec6d58d5e6a to your computer and use it in GitHub Desktop.
Save crapthings/51f86bc605e70160d23baec6d58d5e6a to your computer and use it in GitHub Desktop.
import utils from '/server/utils';
/* eslint-disable prefer-arrow-callback */
import { check, Match } from 'meteor/check';
import Collections from '../../lib/collections';
import cacheManager from '../cache.js';
import {checkIssueMembership} from '../../lib/is-member.js';
import _ from 'lodash';
const { Issues, Activities, Users, IssueMembers, Groups, Tags, Documents } = Collections;
const noPermissionField = { permissions: 0 };
// FIXME add error handling
// Not in use
utils.safePublishComposite('issues.group', (groupId, onlyOpen) => ({
find() {
return IssueMembers.find({ groupId, userId: this.userId, status: 'Active' });
},
children: [
{
find(issueMember) {
const selector = { _id: issueMember.issueId, groupId };
if (onlyOpen) selector.isOpen = true;
return Issues.find(selector);
},
children: [
{
find(issue) {
const userIds = [issue.createdById];
if (issue.lastActiveById) userIds.push(issue.lastActiveById);
return Users.find({ _id: { $in: userIds } }, { fields: Meteor.userPublicFields });
},
},
{
find(issue) {
return Tags.find({ _id: { $in: issue.tagIds } });
},
},
],
},
],
}));
/* !! Obsolete */
utils.safePublishComposite('issues.single', (issueId) => {
check(issueId, String);
return {
find() {
return IssueMembers.find({ userId: this.userId, issueId, status: 'Active' }, { fields: noPermissionField });
},
children: [
{
find(issueMember) {
return Issues.find({ _id: issueMember.issueId });
},
},
{
find(issueMember) {
return IssueMembers.find({ issueId: issueMember.issueId }, { fields: noPermissionField });
},
children: [
{
find(issueMember) {
return Users.find(issueMember.userId, { fields: Meteor.userPublicFields });
},
},
],
},
{
find(issueMember) {
return Activities.find({ issueId: issueMember.issueId });
},
children: [
{
find(activity) {
return Groups.find({ _id: activity.groupId });
},
},
{
find(activity) {
const userIds = [activity.initiatorId];
if (activity.assigneeId) userIds.push(activity.assigneeId);
if (activity.logById) userIds.push(activity.logById);
if (activity.participantIds.length) {
activity.participantIds.forEach(userId => userIds.push(userId));
}
return Users.find({ _id: { $in: userIds } }, { fields: Meteor.userPublicFields });
},
},
{
find(activity) {
return Tags.find({ _id: activity.tagId });
},
},
],
},
],
};
});
// SECURITY FLAW !! Check membership
utils.safePublishComposite('issues.single.timeline', (issueId) => {
check(issueId, String);
return {
find() {
return Activities.find({issueId}, {
fields: noPermissionField,
transform: (act) => {
act._sortingDate = act.endedAt || act.startedAt || act.createdAt;
return act;
}
});
},
};
});
utils.safePublishComposite('issues.members.activities', function groupMember(issueId, userId) {
// The subscriber must exist in the same group
if (!IssueMembers.findOne({userId: this.userId, issueId, status: 'Active'})) throw new Meteor.Error('Unauthorized');
// TODO allow content-admin
return {
find() {
return Activities.find({initiatorId: userId, issueId, isDeleted: {$ne: true}}, {sort: {createdAt: -1}, limit: 50});
}
};
});
utils.safePublishComposite('issues.single.basic', (issueId) => {
check(issueId, String);
return {
find() {
return Issues.find({_id: issueId});
},
children: [
{
find(issue) {
return Tags.find({_id: {$in: issue.tagIds}});
}
}
]
};
});
// FIXME add error handling
utils.safePublishComposite('issues.members', function issueMembers(issueId) {
check(issueId, String);
const issueMember = IssueMembers.findOne({ userId: this.userId, issueId, status: 'Active' });
if (!issueMember) return null;
return [{
find() {
return IssueMembers.find({ issueId }, { fields: noPermissionField });
},
children: [
{
find(subIssueMember) {
return Users.find({ _id: subIssueMember.userId }, { fields: Meteor.userPublicFields });
},
},
],
}, {
find() {
return Issues.find({ _id: issueId });
},
children: [
{
find(issue) {
return Groups.find({ _id: issue.groupId });
},
},
],
}];
});
// FIXME add error handling
utils.safePublishComposite('issues.members.profile', function groupMembers(memberId) {
const issueMember = IssueMembers.findOne(memberId);
if (!issueMember) return [];
return [{
find() {
return IssueMembers.find({ issueId: issueMember.issueId, status: 'Active' }, { fields: noPermissionField });
},
children: [
{
find(issueMemberItem) {
return Users.find({ _id: issueMemberItem.userId }, { fields: {...Meteor.userPublicFields, ...noPermissionField} });
},
},
],
},
{
find() {
return Activities.find({
issueId: issueMember.issueId,
initiatorId: issueMember.userId,
}, { limit: 10 });
},
}];
});
// Obsolete
utils.safePublish('issues.members.single', function issueMember({memberId, userId, issueId}) {
const selector = typeof memberId === 'string' ? {_id: memberId} : {userId, issueId};
return IssueMembers.find(selector);
});
/* Publish issue members for a single issue */
utils.safePublishComposite('issues.members.single.ex', function issueMember(issueId) {
check(issueId, String);
if (!checkIssueMembership(this.userId, issueId)) return null;
const cache = cacheManager();
return {
find() {
return IssueMembers.find({issueId}, {
fields: {userId: 1, issueId: 1, permissions: 1, status: 1},
transform: (doc) => {
const user = cache.find('Users', doc.userId);
doc._name = user.profile.name;
doc._avatar = user.profile.avatar;
doc._jobTitle = user.profile.jobTitle;
doc._role = user.role;
if (this.userId !== doc.userId) delete doc.permissions;
return doc;
}
});
}
};
});
// Obsolete !!
utils.safePublishTransformed('issues.list', function issueList({onlyOpen, limit, groupId, arrangedBy, tagId}) {
check(onlyOpen, Boolean);
check(limit, Match.Maybe(Number));
check(groupId, Match.Maybe(String));
check(arrangedBy, Match.Maybe(String));
const user = Users.findOne(this.userId);
if (!user) {
console.error(`[BUG] Unable to find user by id: "${this.userId})"`);
throw new Meteor.Error('InternalError', 'User not found in database', 'Issue #951');
}
const issueIds = user.issueIds();
const selector = { _id: { $in: issueIds }};
if (onlyOpen) selector.isOpen = true;
// BUSINESS LOGIC: Find inside any type of decendants groups along with this one
if (groupId) selector.groupId = {$in: Groups.findOne({_id: groupId}).subGroupIds(groupId)};
if (tagId) selector.tagIds = tagId;
const options = { sort: {lastActiveAt: -1}};
if (limit) options.limit = limit;
return Issues.find(selector, options).serverTransform({
_lastActiveBy(issue) {
return Users.findOne(issue.lastActiveById);
},
_createdBy(issue) {
return Users.findOne(issue.createdById);
},
_tags(issue) {
return Tags.find({ _id: { $in: issue.tagIds }}).fetch();
},
_arrangement(issue) {
if (!issue) return null;
switch (arrangedBy) {
case 'NONE':
return {
id: null,
};
case 'GROUP': {
const group = issue.group();
return {
id: issue.groupId,
caption: group._pathEx().pathCaption,
sortBy: group._pathEx().pathCode,
};
}
case 'AGE':
return {
id: issue.ageClass(),
caption: issue.ageClass(),
sortBy: issue.createdAt * -1,
};
case 'NEWS':
return {
id: issue.newsClass(),
caption: issue.newsClass(),
sortBy: issue.lastActiveAt * -1,
};
case 'ACTIVE':
return {
id: issue.activeClass().length,
caption: issue.activeClass(),
sortBy: issue.actCount,
};
case 'ASSIGNEE':
return {
id: issue.assigneeId,
caption: issue.assigneeId ? '责任人: ' + issue.assignee().profile.name : '未指定责任人',
sortBy: issue.lastActiveAt,
};
default:
throw new Meteor.Error('UnknownArrangementType');
}
}
});
});
function getGroupPathCaption(group) {
const collection = 'GroupPathCaptions';
const key = group._id;
const cache = cacheManager();
const hit = cache.findKey(collection, key);
if (!hit) {
const value = group._pathEx().pathCaption;
cache.put(collection, key, value);
return value;
}
return hit;
}
function getGroupPathCode(group) {
const collection = 'GroupPathCodes';
const key = group._id;
const cache = cacheManager();
const hit = cache.findKey(collection, key);
if (!hit) {
const value = group._pathEx().pathCode;
cache.put(collection, key, value);
return value;
}
return hit;
}
// XXX Duplicate code with lib/index.js
const oneDay = 24 * 3600 * 1000;
const oneWeek = 7 * oneDay;
const oneMonth = 30 * oneDay;
const oneYear = 365 * oneDay;
function getDurationClass(t) {
const diff = new Date() - t;
if (diff < oneDay) {
return '一天内';
} else if (diff < oneWeek) {
return '一周内';
} else if (diff < oneMonth) {
return '一个月内';
} else if (diff < oneYear) {
return '一年内';
} return '一年以上';
}
function getDurationSortCode(t) {
const diff = new Date() - t;
if (diff < oneDay) {
return 1;
} else if (diff < oneWeek) {
return 7;
} else if (diff < oneMonth) {
return 30;
} else if (diff < oneYear) {
return 365;
}
return Infinity;
}
function getArrangement(issue, arrangedBy, groups) {
switch (arrangedBy) {
case 'NONE':
return {
id: null,
};
case 'GROUP': {
const group = groups[issue.groupId];
return {
id: issue.groupId,
caption: getGroupPathCaption(group),
sortBy: getGroupPathCode(group),
};
}
case 'AGE':
return {
id: getDurationClass(issue.createdAt),
caption: getDurationClass(issue.createdAt),
sortBy: getDurationSortCode(issue.createdAt),
};
case 'NEWS':
return {
id: getDurationClass(issue.lastActiveAt),
caption: getDurationClass(issue.lastActiveAt),
sortBy: getDurationSortCode(issue.lastActiveAt),
};
case 'ACTIVE':
return {
id: issue.activeClass().length,
caption: issue.activeClass(),
sortBy: issue.actCount,
};
case 'ASSIGNEE':
return {
id: issue.assigneeId,
caption: issue.assigneeId ? '责任人: ' + issue.assignee().profile.name : '未指定责任人',
sortBy: issue.lastActiveAt,
};
default:
throw new Meteor.Error('UnknownArrangementType');
}
}
utils.safePublishComposite('issues.list.ex', function issueList({filter, limit, groupId, arrangedBy}) {
check(filter, Match.Maybe(Object));
check(limit, Match.Maybe(Number));
check(groupId, Match.Maybe(String));
check(arrangedBy, Match.Maybe(String));
const user = Users.findOne(this.userId);
if (!user) {
console.error(`[BUG] Unable to find user by id: "${this.userId})"`);
throw new Meteor.Error('InternalError', 'User not found in database', 'Issue #951');
}
return {
find() {
const selector = { _id: { $in: user.issueIds() }};
if (filter && filter.onlyOpen) selector.isOpen = true;
if (filter && filter.tagId) selector.tagIds = filter.tagId;
if (filter && filter.onlyRecent) {
const lastTwoMonth = new Date();
lastTwoMonth.setMonth(lastTwoMonth.getMonth() -2);
arrangedBy === 'AGE' ? selector.createdAt = {$gt: lastTwoMonth} : selector.lastActiveAt = {$gt: lastTwoMonth};
}
// BUSINESS LOGIC: Find inside any type of decendants groups along with this one
if (groupId) selector.groupId = {$in: Groups.findOne({_id: groupId}).subGroupIds()};
const options = { sort: {lastActiveAt: -1}, fields: {assigneeId: 0, showInReport: 0, isDeleted: 0}};
// if (limit) options.limit = limit;
// TODO Use a global function to generate url
const issues = Issues.find(selector, options);
const issuesUserIds = _.uniq(_.compact(_.concat(_.map(issues.fetch(), 'createdById'), _.map(issues.fetch(), 'lastActiveById'))));
const cachedIssuesUsers = _.keyBy(Meteor.users.find({ _id: { $in: issuesUserIds }}).fetch(), '_id');
const cachedGroups = _.keyBy(Groups.find({ _id: { $in: _.map(issues.fetch(), 'groupId') } }).fetch(), '_id');
// console.log(cachedGroups)
options.transform = (issue) => {
// issue.avatarURL = `https://cube-public.oss-cn-beijing.aliyuncs.com/avatars/${issue.createdById}.jpg`;
// const cache = cacheManager();
// const createdBy = cache.find('Users', issue.createdById);
const createdBy = cachedIssuesUsers[issue.createdById];
issue.avatarURL = createdBy.profile.avatar;
// const lastActiveBy = cache.find('Users', issue.lastActiveById);
const lastActiveBy = cachedIssuesUsers[issue.lastActiveById];
issue.lastActiveBy = issue.lastActiveById ? lastActiveBy.profile.name : null;
issue.arrangement = getArrangement(issue, arrangedBy, cachedGroups);
return issue;
};
return Issues.find(selector, options);
},
children: [{
find(issue) {
// Maximum of totally three tags are enough for an issue item in list
return Tags.find({_id: {$in: issue.tagIds}, isDelete: { $ne: true }}, {limit: 3, fields: { name: 1, color: 1, isIssueCategory: 1, codeName: 1 }});
}
}],
};
});
utils.safePublish('issues.requiredDocuments', function requiredDocs(issueId) {
const issue = Issues.findOne({_id: issueId});
if (!issue || !issue.requiredDocState) return this.ready();
const docIds = issue.requiredDocState.map((doc) =>
new Mongo.ObjectID(doc.docId));
return Documents.find({_id: {$in: docIds}});
});
const docsInIssueActivity = {
find(issue) {
return Activities.find({
$or: [{
issueId: issue._id,
$where: 'this.attachmentIds.length > 0'
}, {
issueId: issue._id,
$where: 'this.attachmentIds.length > 0',
attachmentId: {$exists: true}
}]});
},
children: [{
find(act, issue) {
const {attachmentIds = [], attachmentId} = act;
attachmentId && attachmentIds.push(attachmentId);
const docIds = attachmentIds.map(id => new Mongo.ObjectID(id));
// console.log(docIds);
return Documents.find({_id: {$in: docIds}}, {
transform: (doc) => {
doc.belong = {issueId: act.issueId, issueTitle: issue.title};
doc.belong.user = Users.findOne(_.get(doc, 'metadata.uploaderId'));
return doc;
}
});
}
}]
};
const docsInRequirments = {
find(issue, act) {
const requiredDocState = _.get(issue, 'requiredDocState', []);
const docIds = _.chain(requiredDocState)
.map((state) => _.get(state, 'docId'))
.map((docId) => docId && new Mongo.ObjectID(docId))
.value();
// console.log(docIds);
return Documents.find({_id: {$in: docIds}}, {
transform: (doc) => {
doc.belong = {issueId: issue._id, issueTitle: issue.title};
doc.belong.user = Users.findOne(_.get(doc, 'metadata.uploaderId'));
doc.ds = requiredDocState.find(ds => ds.docId === doc._id.valueOf());
return doc;
}
});
}
}
utils.safePublishComposite('issues.docSlots', function (issueId) {
return {
find() {
return Issues.find({_id: issueId});
},
children: [{
find(issue) {
const userIds = _.chain(issue)
.get('requiredDocState', [])
.map((state) => _.get(state, 'linkBy'))
.value();
// console.log(userIds);
return Users.find({_id: {$in: userIds}});
}
}, docsInIssueActivity, docsInRequirments, {
find(issue) {
return Activities.find({type: 'LINK_ISSUE', issueId: issue._id});
},
children: [{
find(act) {
return Issues.find({_id: act.comment});
},
children: [docsInIssueActivity, docsInRequirments]
}]
}]
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment