|
@@ -1,3 +1,4 @@
|
|
|
|
|
+// @ts-check
|
|
|
const { callbackify } = require('util')
|
|
const { callbackify } = require('util')
|
|
|
const pLimit = require('p-limit')
|
|
const pLimit = require('p-limit')
|
|
|
const { ObjectId } = require('mongodb-legacy')
|
|
const { ObjectId } = require('mongodb-legacy')
|
|
@@ -50,7 +51,155 @@ module.exports = {
|
|
|
},
|
|
},
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function getMemberIdsWithPrivilegeLevels(projectId) {
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * @typedef ProjectMember
|
|
|
|
|
+ * @property {string} id
|
|
|
|
|
+ * @property {typeof PrivilegeLevels[keyof PrivilegeLevels]} privilegeLevel
|
|
|
|
|
+ * @property {typeof Sources[keyof Sources]} source
|
|
|
|
|
+ * @property {boolean} [pendingEditor]
|
|
|
|
|
+ * @property {boolean} [pendingReviewer]
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @typedef LoadedProjectMember
|
|
|
|
|
+ * @property {typeof PrivilegeLevels[keyof PrivilegeLevels]} privilegeLevel
|
|
|
|
|
+ * @property {{_id: ObjectId, email: string, features: any, first_name: string, last_name: string, signUpDate: Date}} user
|
|
|
|
|
+ * @property {boolean} [pendingEditor]
|
|
|
|
|
+ * @property {boolean} [pendingReviewer]
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+// Wrapper for determining multiple dimensions of project access.
|
|
|
|
|
+class ProjectAccess {
|
|
|
|
|
+ /** @type {ProjectMember[]} */
|
|
|
|
|
+ #members
|
|
|
|
|
+
|
|
|
|
|
+ /** @type {typeof PublicAccessLevels[keyof PublicAccessLevels]} */
|
|
|
|
|
+ #publicAccessLevel
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param {{ owner_ref: ObjectId; collaberator_refs: ObjectId[]; readOnly_refs: ObjectId[]; tokenAccessReadAndWrite_refs: ObjectId[]; tokenAccessReadOnly_refs: ObjectId[]; publicAccesLevel: typeof PublicAccessLevels[keyof PublicAccessLevels]; pendingEditor_refs: ObjectId[]; reviewer_refs: ObjectId[]; pendingReviewer_refs: ObjectId[]; }} project
|
|
|
|
|
+ */
|
|
|
|
|
+ constructor(project) {
|
|
|
|
|
+ this.#members = _getMemberIdsWithPrivilegeLevelsFromFields(
|
|
|
|
|
+ project.owner_ref,
|
|
|
|
|
+ project.collaberator_refs,
|
|
|
|
|
+ project.readOnly_refs,
|
|
|
|
|
+ project.tokenAccessReadAndWrite_refs,
|
|
|
|
|
+ project.tokenAccessReadOnly_refs,
|
|
|
|
|
+ project.publicAccesLevel,
|
|
|
|
|
+ project.pendingEditor_refs,
|
|
|
|
|
+ project.reviewer_refs,
|
|
|
|
|
+ project.pendingReviewer_refs
|
|
|
|
|
+ )
|
|
|
|
|
+ this.#publicAccessLevel = project.publicAccesLevel
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return {Promise<LoadedProjectMember[]>}
|
|
|
|
|
+ */
|
|
|
|
|
+ async loadInvitedMembers() {
|
|
|
|
|
+ return _loadMembers(this.#members.filter(m => m.source !== Sources.TOKEN))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return {ProjectMember[]}
|
|
|
|
|
+ */
|
|
|
|
|
+ allMembers() {
|
|
|
|
|
+ return this.#members
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return {typeof PublicAccessLevels[keyof PublicAccessLevels]}
|
|
|
|
|
+ */
|
|
|
|
|
+ publicAccessLevel() {
|
|
|
|
|
+ return this.#publicAccessLevel
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return {string[]}
|
|
|
|
|
+ */
|
|
|
|
|
+ memberIds() {
|
|
|
|
|
+ return this.#members.map(m => m.id)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return {string[]}
|
|
|
|
|
+ */
|
|
|
|
|
+ invitedMemberIds() {
|
|
|
|
|
+ return this.#members.filter(m => m.source !== Sources.TOKEN).map(m => m.id)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param {string | ObjectId} userId
|
|
|
|
|
+ * @return {typeof PrivilegeLevels[keyof PrivilegeLevels]}
|
|
|
|
|
+ */
|
|
|
|
|
+ privilegeLevelForUser(userId) {
|
|
|
|
|
+ for (const member of this.#members) {
|
|
|
|
|
+ if (member.id === userId.toString()) {
|
|
|
|
|
+ return member.privilegeLevel
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return PrivilegeLevels.NONE
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param {string | ObjectId} userId
|
|
|
|
|
+ * @return {boolean}
|
|
|
|
|
+ */
|
|
|
|
|
+ isUserInvitedMember(userId) {
|
|
|
|
|
+ for (const member of this.#members) {
|
|
|
|
|
+ if (member.id === userId.toString() && member.source !== Sources.TOKEN) {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param {string | ObjectId} userId
|
|
|
|
|
+ * @return {boolean}
|
|
|
|
|
+ */
|
|
|
|
|
+ isUserInvitedReadWriteMember(userId) {
|
|
|
|
|
+ for (const member of this.#members) {
|
|
|
|
|
+ if (
|
|
|
|
|
+ member.id.toString() === userId.toString() &&
|
|
|
|
|
+ member.source !== Sources.TOKEN &&
|
|
|
|
|
+ member.privilegeLevel === PrivilegeLevels.READ_AND_WRITE
|
|
|
|
|
+ ) {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Counts invited members with editor or reviewer roles
|
|
|
|
|
+ * @return {number}
|
|
|
|
|
+ */
|
|
|
|
|
+ countInvitedEditCollaborators() {
|
|
|
|
|
+ return this.#members.filter(
|
|
|
|
|
+ m =>
|
|
|
|
|
+ m.source === Sources.INVITE &&
|
|
|
|
|
+ (m.privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
|
|
|
|
|
+ m.privilegeLevel === PrivilegeLevels.REVIEW)
|
|
|
|
|
+ ).length
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Counts invited members that are readonly pending editors or pending reviewers
|
|
|
|
|
+ * @return {number}
|
|
|
|
|
+ */
|
|
|
|
|
+ countInvitedPendingEditors() {
|
|
|
|
|
+ return this.#members.filter(
|
|
|
|
|
+ m =>
|
|
|
|
|
+ m.source === Sources.INVITE &&
|
|
|
|
|
+ m.privilegeLevel === PrivilegeLevels.READ_ONLY &&
|
|
|
|
|
+ (m.pendingEditor || m.pendingReviewer)
|
|
|
|
|
+ ).length
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getProjectAccess(projectId) {
|
|
|
const project = await ProjectGetter.promises.getProject(projectId, {
|
|
const project = await ProjectGetter.promises.getProject(projectId, {
|
|
|
owner_ref: 1,
|
|
owner_ref: 1,
|
|
|
collaberator_refs: 1,
|
|
collaberator_refs: 1,
|
|
@@ -65,34 +214,23 @@ async function getMemberIdsWithPrivilegeLevels(projectId) {
|
|
|
if (!project) {
|
|
if (!project) {
|
|
|
throw new Errors.NotFoundError(`no project found with id ${projectId}`)
|
|
throw new Errors.NotFoundError(`no project found with id ${projectId}`)
|
|
|
}
|
|
}
|
|
|
- const memberIds = _getMemberIdsWithPrivilegeLevelsFromFields(
|
|
|
|
|
- project.owner_ref,
|
|
|
|
|
- project.collaberator_refs,
|
|
|
|
|
- project.readOnly_refs,
|
|
|
|
|
- project.tokenAccessReadAndWrite_refs,
|
|
|
|
|
- project.tokenAccessReadOnly_refs,
|
|
|
|
|
- project.publicAccesLevel,
|
|
|
|
|
- project.pendingEditor_refs,
|
|
|
|
|
- project.reviewer_refs,
|
|
|
|
|
- project.pendingReviewer_refs
|
|
|
|
|
- )
|
|
|
|
|
- return memberIds
|
|
|
|
|
|
|
+ return new ProjectAccess(project)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getMemberIdsWithPrivilegeLevels(projectId) {
|
|
|
|
|
+ return (await getProjectAccess(projectId)).allMembers()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function getMemberIds(projectId) {
|
|
async function getMemberIds(projectId) {
|
|
|
- const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
|
|
|
|
- return members.map(m => m.id)
|
|
|
|
|
|
|
+ return (await getProjectAccess(projectId)).memberIds()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function getInvitedMemberIds(projectId) {
|
|
async function getInvitedMemberIds(projectId) {
|
|
|
- const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
|
|
|
|
- return members.filter(m => m.source !== Sources.TOKEN).map(m => m.id)
|
|
|
|
|
|
|
+ return (await getProjectAccess(projectId)).invitedMemberIds()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function getInvitedMembersWithPrivilegeLevels(projectId) {
|
|
async function getInvitedMembersWithPrivilegeLevels(projectId) {
|
|
|
- let members = await getMemberIdsWithPrivilegeLevels(projectId)
|
|
|
|
|
- members = members.filter(m => m.source !== Sources.TOKEN)
|
|
|
|
|
- return _loadMembers(members)
|
|
|
|
|
|
|
+ return await (await getProjectAccess(projectId)).loadInvitedMembers()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function getInvitedMembersWithPrivilegeLevelsFromFields(
|
|
async function getInvitedMembersWithPrivilegeLevelsFromFields(
|
|
@@ -107,7 +245,7 @@ async function getInvitedMembersWithPrivilegeLevelsFromFields(
|
|
|
readOnlyIds,
|
|
readOnlyIds,
|
|
|
[],
|
|
[],
|
|
|
[],
|
|
[],
|
|
|
- null,
|
|
|
|
|
|
|
+ 'private',
|
|
|
[],
|
|
[],
|
|
|
reviewerIds,
|
|
reviewerIds,
|
|
|
[]
|
|
[]
|
|
@@ -121,69 +259,31 @@ async function getMemberIdPrivilegeLevel(userId, projectId) {
|
|
|
if (userId == null) {
|
|
if (userId == null) {
|
|
|
return PrivilegeLevels.NONE
|
|
return PrivilegeLevels.NONE
|
|
|
}
|
|
}
|
|
|
- const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
|
|
|
|
- for (const member of members) {
|
|
|
|
|
- if (member.id === userId.toString()) {
|
|
|
|
|
- return member.privilegeLevel
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return PrivilegeLevels.NONE
|
|
|
|
|
|
|
+ return (await getProjectAccess(projectId)).privilegeLevelForUser(userId)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function getInvitedEditCollaboratorCount(projectId) {
|
|
async function getInvitedEditCollaboratorCount(projectId) {
|
|
|
- // Counts invited members with editor or reviewer roles
|
|
|
|
|
- const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
|
|
|
|
- return members.filter(
|
|
|
|
|
- m =>
|
|
|
|
|
- m.source === Sources.INVITE &&
|
|
|
|
|
- (m.privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
|
|
|
|
|
- m.privilegeLevel === PrivilegeLevels.REVIEW)
|
|
|
|
|
- ).length
|
|
|
|
|
|
|
+ return (await getProjectAccess(projectId)).countInvitedEditCollaborators()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function getInvitedPendingEditorCount(projectId) {
|
|
async function getInvitedPendingEditorCount(projectId) {
|
|
|
- // Only counts invited members that are readonly pending editors or pending
|
|
|
|
|
- // reviewers
|
|
|
|
|
- const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
|
|
|
|
- return members.filter(
|
|
|
|
|
- m =>
|
|
|
|
|
- m.source === Sources.INVITE &&
|
|
|
|
|
- m.privilegeLevel === PrivilegeLevels.READ_ONLY &&
|
|
|
|
|
- (m.pendingEditor || m.pendingReviewer)
|
|
|
|
|
- ).length
|
|
|
|
|
|
|
+ return (await getProjectAccess(projectId)).countInvitedPendingEditors()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function isUserInvitedMemberOfProject(userId, projectId) {
|
|
async function isUserInvitedMemberOfProject(userId, projectId) {
|
|
|
if (!userId) {
|
|
if (!userId) {
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
- const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
|
|
|
|
- for (const member of members) {
|
|
|
|
|
- if (
|
|
|
|
|
- member.id.toString() === userId.toString() &&
|
|
|
|
|
- member.source !== Sources.TOKEN
|
|
|
|
|
- ) {
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return false
|
|
|
|
|
|
|
+ return (await getProjectAccess(projectId)).isUserInvitedMember(userId)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function isUserInvitedReadWriteMemberOfProject(userId, projectId) {
|
|
async function isUserInvitedReadWriteMemberOfProject(userId, projectId) {
|
|
|
if (!userId) {
|
|
if (!userId) {
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
- const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
|
|
|
|
- for (const member of members) {
|
|
|
|
|
- if (
|
|
|
|
|
- member.id.toString() === userId.toString() &&
|
|
|
|
|
- member.source !== Sources.TOKEN &&
|
|
|
|
|
- member.privilegeLevel === PrivilegeLevels.READ_AND_WRITE
|
|
|
|
|
- ) {
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return false
|
|
|
|
|
|
|
+ return (await getProjectAccess(projectId)).isUserInvitedReadWriteMember(
|
|
|
|
|
+ userId
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function getPublicShareTokens(userId, projectId) {
|
|
async function getPublicShareTokens(userId, projectId) {
|
|
@@ -209,10 +309,13 @@ async function getPublicShareTokens(userId, projectId) {
|
|
|
return null
|
|
return null
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // @ts-ignore
|
|
|
if (memberInfo.isOwner) {
|
|
if (memberInfo.isOwner) {
|
|
|
return memberInfo.tokens
|
|
return memberInfo.tokens
|
|
|
|
|
+ // @ts-ignore
|
|
|
} else if (memberInfo.hasTokenReadOnlyAccess) {
|
|
} else if (memberInfo.hasTokenReadOnlyAccess) {
|
|
|
return {
|
|
return {
|
|
|
|
|
+ // @ts-ignore
|
|
|
readOnly: memberInfo.tokens.readOnly,
|
|
readOnly: memberInfo.tokens.readOnly,
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
@@ -224,6 +327,7 @@ async function getPublicShareTokens(userId, projectId) {
|
|
|
// excluding projects where the user is listed in the token access fields when
|
|
// excluding projects where the user is listed in the token access fields when
|
|
|
// token access has been disabled.
|
|
// token access has been disabled.
|
|
|
async function getProjectsUserIsMemberOf(userId, fields) {
|
|
async function getProjectsUserIsMemberOf(userId, fields) {
|
|
|
|
|
+ // @ts-ignore
|
|
|
const limit = pLimit(2)
|
|
const limit = pLimit(2)
|
|
|
const [readAndWrite, review, readOnly, tokenReadAndWrite, tokenReadOnly] =
|
|
const [readAndWrite, review, readOnly, tokenReadAndWrite, tokenReadOnly] =
|
|
|
await Promise.all([
|
|
await Promise.all([
|
|
@@ -274,9 +378,9 @@ async function dangerouslyGetAllProjectsUserIsMemberOf(userId, fields) {
|
|
|
|
|
|
|
|
async function getAllInvitedMembers(projectId) {
|
|
async function getAllInvitedMembers(projectId) {
|
|
|
try {
|
|
try {
|
|
|
- const rawMembers = await getInvitedMembersWithPrivilegeLevels(projectId)
|
|
|
|
|
- const { members } =
|
|
|
|
|
- ProjectEditorHandler.buildOwnerAndMembersViews(rawMembers)
|
|
|
|
|
|
|
+ const { members } = ProjectEditorHandler.buildOwnerAndMembersViews(
|
|
|
|
|
+ await (await getProjectAccess(projectId)).loadInvitedMembers()
|
|
|
|
|
+ )
|
|
|
return members
|
|
return members
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
throw OError.tag(err, 'error getting members for project', { projectId })
|
|
throw OError.tag(err, 'error getting members for project', { projectId })
|
|
@@ -316,6 +420,19 @@ async function userIsReadWriteTokenMember(userId, projectId) {
|
|
|
return project != null
|
|
return project != null
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * @param {ObjectId} ownerId
|
|
|
|
|
+ * @param {ObjectId[]} collaboratorIds
|
|
|
|
|
+ * @param {ObjectId[]} readOnlyIds
|
|
|
|
|
+ * @param {ObjectId[]} tokenAccessIds
|
|
|
|
|
+ * @param {ObjectId[]} tokenAccessReadOnlyIds
|
|
|
|
|
+ * @param {typeof PublicAccessLevels[keyof PublicAccessLevels]} publicAccessLevel
|
|
|
|
|
+ * @param {ObjectId[]} pendingEditorIds
|
|
|
|
|
+ * @param {ObjectId[]} reviewerIds
|
|
|
|
|
+ * @param {ObjectId[]} pendingReviewerIds
|
|
|
|
|
+ * @return {ProjectMember[]}
|
|
|
|
|
+ * @private
|
|
|
|
|
+ */
|
|
|
function _getMemberIdsWithPrivilegeLevelsFromFields(
|
|
function _getMemberIdsWithPrivilegeLevelsFromFields(
|
|
|
ownerId,
|
|
ownerId,
|
|
|
collaboratorIds,
|
|
collaboratorIds,
|
|
@@ -384,6 +501,11 @@ function _getMemberIdsWithPrivilegeLevelsFromFields(
|
|
|
return members
|
|
return members
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * @param {ProjectMember[]} members
|
|
|
|
|
+ * @return {Promise<LoadedProjectMember[]>}
|
|
|
|
|
+ * @private
|
|
|
|
|
+ */
|
|
|
async function _loadMembers(members) {
|
|
async function _loadMembers(members) {
|
|
|
const userIds = Array.from(new Set(members.map(m => m.id)))
|
|
const userIds = Array.from(new Set(members.map(m => m.id)))
|
|
|
const users = new Map()
|
|
const users = new Map()
|