Files
overleaf-cep/services/web/app/src/Features/InactiveData/InactiveProjectManager.mjs
T
Antoine Clausse 0ef959d05b [web] Convert some Features files to ES modules (part 3) (#28494)
* Rename files to mjs

* Rename test files to mjs

* Update CODEOWNERS

* Update files to ESM

* Update test files to ESM

* Update RestoreManager.test.mjs

* Remove unused `AdminAuthorizationHelper` mock and stub

* Remove unnecessary return

GitOrigin-RevId: 2b9ef126de1d8964afbc6e5641cca36712655866
2025-09-17 08:05:02 +00:00

140 lines
3.8 KiB
JavaScript

import OError from '@overleaf/o-error'
import logger from '@overleaf/logger'
import DocstoreManager from '../Docstore/DocstoreManager.js'
import DocumentUpdaterHandler from '../DocumentUpdater/DocumentUpdaterHandler.js'
import ProjectGetter from '../Project/ProjectGetter.js'
import ProjectUpdateHandler from '../Project/ProjectUpdateHandler.js'
import { Project } from '../../models/Project.js'
import Modules from '../../infrastructure/Modules.js'
import { READ_PREFERENCE_SECONDARY } from '../../infrastructure/mongodb.js'
import { callbackifyAll } from '@overleaf/promise-utils'
import Metrics from '@overleaf/metrics'
const MILISECONDS_IN_DAY = 86400000
function findInactiveProjects(limit, daysOld) {
const oldProjectDate = new Date() - MILISECONDS_IN_DAY * daysOld
try {
// use $not $gt to catch non-opened projects where lastOpened is null
// return a cursor instead of executing the query
return Project.find({
lastOpened: { $not: { $gt: oldProjectDate } },
})
.where('active')
.equals(true)
.select(['_id', 'lastOpened'])
.limit(limit)
.read(READ_PREFERENCE_SECONDARY)
.cursor()
} catch (err) {
logger.err({ err }, 'could not get projects for deactivating')
throw err // Re-throw the error to be handled by the caller
}
}
const InactiveProjectManager = {
async reactivateProjectIfRequired(projectId) {
let project
try {
project = await ProjectGetter.promises.getProject(projectId, {
active: true,
})
} catch (err) {
OError.tag(err, 'error getting project', {
project_id: projectId,
})
throw err
}
logger.debug(
{ projectId, active: project.active },
'seeing if need to reactivate project'
)
if (project.active) {
return
}
try {
await DocstoreManager.promises.unarchiveProject(projectId)
} catch (err) {
OError.tag(err, 'error reactivating project in docstore', {
project_id: projectId,
})
throw err
}
await ProjectUpdateHandler.promises.markAsActive(projectId)
},
async deactivateOldProjects(limit, daysOld) {
if (limit == null) {
limit = 10
}
if (daysOld == null) {
daysOld = 360
}
logger.debug('deactivating projects')
const processedProjects = []
for await (const project of findInactiveProjects(limit, daysOld)) {
processedProjects.push(project)
try {
await InactiveProjectManager.deactivateProject(project._id)
} catch (err) {
logger.err(
{ projectId: project._id, err },
'unable to deactivate project'
)
}
}
logger.debug(
{ numberOfProjects: processedProjects.length },
'finished deactivating projects'
)
return processedProjects
},
async deactivateProject(projectId) {
logger.debug({ projectId }, 'deactivating inactive project')
// ensure project is removed from document updater (also flushes updates to history)
try {
await DocumentUpdaterHandler.promises.flushProjectToMongoAndDelete(
projectId
)
} catch (err) {
logger.warn(
{ err, projectId },
'error flushing project to mongo when archiving'
)
Metrics.inc('inactive-project', 1, {
method: 'archive',
status: 'flush-error',
})
throw err
}
await Modules.promises.hooks.fire('deactivateProject', projectId)
// now archive the project and mark it as inactive
try {
await DocstoreManager.promises.archiveProject(projectId)
await ProjectUpdateHandler.promises.markAsInactive(projectId)
} catch (err) {
logger.warn({ err, projectId }, 'error deactivating project')
throw err
}
},
}
export default {
...callbackifyAll(InactiveProjectManager),
promises: InactiveProjectManager,
findInactiveProjects,
}