overleaf/services/web/app/src/Features/Project/ProjectGetter.js
Tim Down 079a0dcae4 Merge pull request #10667 from overleaf/ii-dashboard-duplicate-projects
[web] Remove duplicate projects when fetching all users projects

GitOrigin-RevId: b850cd6ea5a03f01ba82eaaba101afd21a5098cc
2022-11-30 09:04:19 +00:00

196 lines
5.8 KiB
JavaScript

const { db } = require('../../infrastructure/mongodb')
const { normalizeQuery } = require('../Helpers/Mongo')
const OError = require('@overleaf/o-error')
const metrics = require('@overleaf/metrics')
const { promisifyAll } = require('../../util/promises')
const { Project } = require('../../models/Project')
const logger = require('@overleaf/logger')
const LockManager = require('../../infrastructure/LockManager')
const { DeletedProject } = require('../../models/DeletedProject')
const ProjectGetter = {
EXCLUDE_DEPTH: 8,
getProjectWithoutDocLines(projectId, callback) {
const excludes = {}
for (let i = 1; i <= ProjectGetter.EXCLUDE_DEPTH; i++) {
excludes[`rootFolder${Array(i).join('.folders')}.docs.lines`] = 0
}
ProjectGetter.getProject(projectId, excludes, callback)
},
getProjectWithOnlyFolders(projectId, callback) {
const excludes = {}
for (let i = 1; i <= ProjectGetter.EXCLUDE_DEPTH; i++) {
excludes[`rootFolder${Array(i).join('.folders')}.docs`] = 0
excludes[`rootFolder${Array(i).join('.folders')}.fileRefs`] = 0
}
ProjectGetter.getProject(projectId, excludes, callback)
},
getProject(projectId, projection, callback) {
if (typeof projection === 'function' && callback == null) {
callback = projection
projection = {}
}
if (projectId == null) {
return callback(new Error('no project id provided'))
}
if (typeof projection !== 'object') {
return callback(new Error('projection is not an object'))
}
if (projection.rootFolder || Object.keys(projection).length === 0) {
const ProjectEntityMongoUpdateHandler = require('./ProjectEntityMongoUpdateHandler')
LockManager.runWithLock(
ProjectEntityMongoUpdateHandler.LOCK_NAMESPACE,
projectId,
cb => ProjectGetter.getProjectWithoutLock(projectId, projection, cb),
callback
)
} else {
ProjectGetter.getProjectWithoutLock(projectId, projection, callback)
}
},
getProjectWithoutLock(projectId, projection, callback) {
if (typeof projection === 'function' && callback == null) {
callback = projection
projection = {}
}
if (projectId == null) {
return callback(new Error('no project id provided'))
}
if (typeof projection !== 'object') {
return callback(new Error('projection is not an object'))
}
let query
try {
query = normalizeQuery(projectId)
} catch (err) {
return callback(err)
}
db.projects.findOne(query, { projection }, function (err, project) {
if (err) {
OError.tag(err, 'error getting project', {
query,
projection,
})
return callback(err)
}
callback(null, project)
})
},
getProjectIdByReadAndWriteToken(token, callback) {
Project.findOne(
{ 'tokens.readAndWrite': token },
{ _id: 1 },
function (err, project) {
if (err) {
return callback(err)
}
if (project == null) {
return callback()
}
callback(null, project._id)
}
)
},
findAllUsersProjects(userId, fields, callback) {
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
Project.find(
{ owner_ref: userId },
fields,
function (error, ownedProjects) {
if (error) {
return callback(error)
}
CollaboratorsGetter.getProjectsUserIsMemberOf(
userId,
fields,
function (error, projects) {
if (error) {
return callback(error)
}
const result = {
owned: ownedProjects || [],
readAndWrite: projects.readAndWrite || [],
readOnly: projects.readOnly || [],
tokenReadAndWrite: projects.tokenReadAndWrite || [],
tokenReadOnly: projects.tokenReadOnly || [],
}
// Remove duplicate projects. The order of result values is determined by the order they occur.
const tempAddedProjectsIds = new Set()
const filteredProjects = Object.entries(result).reduce(
(prev, current) => {
const [key, projects] = current
prev[key] = []
projects.forEach(project => {
const projectId = project._id.toString()
if (!tempAddedProjectsIds.has(projectId)) {
prev[key].push(project)
tempAddedProjectsIds.add(projectId)
}
})
return prev
},
{}
)
callback(null, filteredProjects)
}
)
}
)
},
/**
* Return all projects with the given name that belong to the given user.
*
* Projects include the user's own projects as well as collaborations with
* read/write access.
*/
findUsersProjectsByName(userId, projectName, callback) {
ProjectGetter.findAllUsersProjects(
userId,
'name archived trashed',
(err, allProjects) => {
if (err != null) {
return callback(err)
}
const { owned, readAndWrite } = allProjects
const projects = owned.concat(readAndWrite)
const lowerCasedProjectName = projectName.toLowerCase()
const matches = projects.filter(
project => project.name.toLowerCase() === lowerCasedProjectName
)
callback(null, matches)
}
)
},
getUsersDeletedProjects(userId, callback) {
DeletedProject.find(
{
'deleterData.deletedProjectOwnerId': userId,
},
callback
)
},
}
;['getProject', 'getProjectWithoutDocLines'].map(method =>
metrics.timeAsyncMethod(ProjectGetter, method, 'mongo.ProjectGetter', logger)
)
ProjectGetter.promises = promisifyAll(ProjectGetter)
module.exports = ProjectGetter