Created
July 14, 2017 06:19
-
-
Save crapthings/51f86bc605e70160d23baec6d58d5e6a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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