mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-08 00:29:04 +02:00
Admin Tools: Manage users and Manage projects pages
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
import _ from 'lodash'
|
||||
import Path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { expressify } from '@overleaf/promise-utils'
|
||||
import logger from '@overleaf/logger'
|
||||
import Metrics from '@overleaf/metrics'
|
||||
import ProjectHelper from '../../../../app/src/Features/Project/ProjectHelper.js'
|
||||
import ProjectGetter from '../../../../app/src/Features/Project/ProjectGetter.mjs'
|
||||
import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.js'
|
||||
import SessionManager from '../../../../app/src/Features/Authentication/SessionManager.js'
|
||||
import UserGetter from '../../../../app/src/Features/User/UserGetter.js'
|
||||
import { OError } from '../../../../app/src/Features/Errors/Errors.js'
|
||||
import { User } from '../../../../app/src/models/User.js'
|
||||
import { Project } from '../../../../app/src/models/Project.js'
|
||||
import { DeletedProject } from '../../../../app/src/models/DeletedProject.js'
|
||||
import ProjectDeleter from '../../../../app/src/Features/Project/ProjectDeleter.mjs'
|
||||
import HttpErrorHandler from '../../../../app/src/Features/Errors/HttpErrorHandler.js'
|
||||
|
||||
const __dirname = Path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
async function manageProjectsPage(req, res, next) {
|
||||
const projectsBlobPending = _getProjects().catch(err => {
|
||||
logger.err({ err }, 'projects listing in background failed')
|
||||
return undefined
|
||||
})
|
||||
|
||||
const prefetchedProjectsBlob = await projectsBlobPending
|
||||
|
||||
Metrics.inc('project-list-prefetch-projects', 1, {
|
||||
status: prefetchedProjectsBlob ? 'success' : 'error',
|
||||
})
|
||||
|
||||
res.render(Path.resolve(__dirname, '../views/manage-projects-react'), {
|
||||
title: 'Manage Projects',
|
||||
prefetchedProjectsBlob,
|
||||
})
|
||||
}
|
||||
|
||||
async function getProjectsJson(req, res) {
|
||||
const { filters, page, sort } = req.body
|
||||
const { userId } = req.params
|
||||
|
||||
const projectsPage = await _getProjects(userId, filters, sort, page)
|
||||
res.json(projectsPage)
|
||||
}
|
||||
|
||||
async function _getProjects(
|
||||
userId = null,
|
||||
filters = {},
|
||||
sort = { by: 'lastUpdated', order: 'desc' },
|
||||
page = { size: 20 }
|
||||
) {
|
||||
|
||||
const projection = {
|
||||
_id: 1,
|
||||
name: 1,
|
||||
lastUpdated: 1,
|
||||
lastUpdatedBy: 1,
|
||||
lastOpened: 1,
|
||||
trashed: 1,
|
||||
owner_ref: 1,
|
||||
}
|
||||
|
||||
const actualProjects = await Project.find(
|
||||
userId == null ? {} : { owner_ref: userId },
|
||||
projection,
|
||||
).lean().exec()
|
||||
|
||||
const delProjection = Object.fromEntries(
|
||||
Object.keys(projection).map(k => [`project.${k}`, 1])
|
||||
)
|
||||
delProjection['deleterData.deletedAt'] = 1
|
||||
delProjection['deleterData.deleterId'] = 1
|
||||
|
||||
const deletedProjects = await DeletedProject.find(
|
||||
userId == null ? { project: { $type: 'object' } } : { 'project.owner_ref': userId },
|
||||
delProjection
|
||||
).lean().exec()
|
||||
|
||||
const formattedActualProjects = _formatProjects(actualProjects, _formatProjectInfo)
|
||||
const formattedDeletedProjects = _formatProjects(deletedProjects, _formatDeletedProjectInfo)
|
||||
const formattedProjects = [...formattedActualProjects, ...formattedDeletedProjects]
|
||||
const filteredProjects = _applyFilters(formattedProjects, filters)
|
||||
const projects = _sortAndPaginate(filteredProjects, sort, page)
|
||||
|
||||
return {
|
||||
totalSize: filteredProjects.length,
|
||||
projects,
|
||||
}
|
||||
}
|
||||
|
||||
function _formatProjects(projects, formatProjectInfo) {
|
||||
const yearAgo = new Date()
|
||||
yearAgo.setFullYear(yearAgo.getFullYear() - 1)
|
||||
const formattedProjects = []
|
||||
|
||||
for (const project of projects) {
|
||||
formattedProjects.push(
|
||||
formatProjectInfo(project, yearAgo)
|
||||
)
|
||||
}
|
||||
return formattedProjects
|
||||
}
|
||||
|
||||
function _applyFilters(projects, filters) {
|
||||
if (!_hasActiveFilter(filters)) {
|
||||
return projects
|
||||
}
|
||||
return projects.filter(project => _matchesFilters(project, filters))
|
||||
}
|
||||
|
||||
function _sortAndPaginate(projects, sort, page) {
|
||||
if (
|
||||
(sort.by && !['lastUpdated', 'title', 'deletedAt'].includes(sort.by)) ||
|
||||
(sort.order && !['asc', 'desc'].includes(sort.order))
|
||||
) {
|
||||
throw new OError('Invalid sorting criteria', { sort })
|
||||
}
|
||||
const sortedProjects = _.orderBy(
|
||||
projects,
|
||||
[sort.by || 'lastUpdated'],
|
||||
[sort.order || 'desc']
|
||||
)
|
||||
return sortedProjects
|
||||
}
|
||||
|
||||
function _formatProjectInfo(project, maxDate) {
|
||||
const owner_ref = project.owner_ref
|
||||
const trashed = owner_ref ? ProjectHelper.isTrashed(project, owner_ref) : false
|
||||
|
||||
return {
|
||||
id: project._id.toString(),
|
||||
name: project.name,
|
||||
owner: project.owner_ref,
|
||||
lastUpdated: project.lastUpdated?.toISOString(),
|
||||
lastUpdatedBy: project.lastUpdatedBy,
|
||||
inactive: project.lastOpened < maxDate,
|
||||
trashed,
|
||||
deleted: false,
|
||||
}
|
||||
}
|
||||
|
||||
function _formatDeletedProjectInfo(deletedProject, maxDate) {
|
||||
const project = deletedProject.project
|
||||
const owner_ref = project.owner_ref
|
||||
const trashed = owner_ref ? ProjectHelper.isTrashed(project, owner_ref) : false
|
||||
|
||||
return {
|
||||
id: project._id.toString(),
|
||||
name: project.name,
|
||||
owner: owner_ref,
|
||||
lastUpdated: project.lastUpdated?.toISOString(),
|
||||
lastUpdatedBy: project.lastUpdatedBy,
|
||||
inactive: project.lastOpened < maxDate,
|
||||
trashed,
|
||||
deleted: true,
|
||||
deletedAt: deletedProject.deleterData?.deletedAt?.toISOString(),
|
||||
deletedBy: deletedProject.deleterData?.deleterId,
|
||||
}
|
||||
}
|
||||
|
||||
function _matchesFilters(project, filters) {
|
||||
if (filters.owned && (project.trashed || project.deleted)) {
|
||||
return false
|
||||
}
|
||||
if (filters.trashed && (!project.trashed || project.deleted)) {
|
||||
return false
|
||||
}
|
||||
if (filters.deleted && !project.deleted) {
|
||||
return false
|
||||
}
|
||||
if (filters.inactive && (project.trashed || project.deleted || !project.inactive)) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
filters.search?.length &&
|
||||
project.name.toLowerCase().indexOf(filters.search.toLowerCase()) === -1
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function _hasActiveFilter(filters) {
|
||||
return Boolean(
|
||||
filters.owned ||
|
||||
filters.inactive ||
|
||||
filters.trashed ||
|
||||
filters.deleted ||
|
||||
filters.search?.length
|
||||
)
|
||||
}
|
||||
|
||||
async function trashProjectForUser(req, res) {
|
||||
const projectId = req.params.project_id
|
||||
const { userId } = req.body
|
||||
await ProjectDeleter.promises.trashProject(projectId, userId)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
async function untrashProjectForUser(req, res) {
|
||||
const projectId = req.params.project_id
|
||||
const { userId } = req.body
|
||||
await ProjectDeleter.promises.untrashProject(projectId, userId)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
async function undeleteProject(req, res) {
|
||||
const projectId = req.params.project_id
|
||||
const { userId } = req.body
|
||||
const undelededProject = await ProjectDeleter.promises.undeleteProject(projectId, { userId })
|
||||
await ProjectDeleter.promises.untrashProject(projectId, userId)
|
||||
|
||||
return res.json({
|
||||
name: undelededProject.name,
|
||||
})
|
||||
}
|
||||
|
||||
async function purgeDeletedProject(req, res) {
|
||||
const projectId = req.params.project_id
|
||||
await ProjectDeleter.promises.expireDeletedProject(projectId)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
export default {
|
||||
manageProjectsPage: expressify(manageProjectsPage),
|
||||
getProjectsJson: expressify(getProjectsJson),
|
||||
undeleteProject: expressify(undeleteProject),
|
||||
purgeDeletedProject: expressify(purgeDeletedProject),
|
||||
trashProjectForUser: expressify(trashProjectForUser),
|
||||
untrashProjectForUser: expressify(untrashProjectForUser),
|
||||
}
|
||||
Reference in New Issue
Block a user