Răsfoiți Sursa

[web] add ProjectAccess helper class (#25663)

* [web] add ProjectAccess helper class

* [web] remove ts-ignore for calling OError.tag with try/catch error

GitOrigin-RevId: e097a95b4d929a3927a3eeb70635590680c93007
Jakob Ackermann 1 an în urmă
părinte
comite
2e50e0ffa1
1 a modificat fișierele cu 191 adăugiri și 69 ștergeri
  1. 191 69
      services/web/app/src/Features/Collaborators/CollaboratorsGetter.js

+ 191 - 69
services/web/app/src/Features/Collaborators/CollaboratorsGetter.js

@@ -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()