Files
overleaf-cep/services/web/app/src/Features/Project/ProjectEntityHandler.mjs
Andrew Rumble 394c60f2cf Merge pull request #29659 from overleaf/revert-29656-revert-29521-ar-models-es-conversion
Revert "Revert "[web] Convert models and self-referential test files to ESM ""

GitOrigin-RevId: f64000ae31d298b075a8722dfc51f294c71bc021
2025-11-18 09:04:56 +00:00

221 lines
6.0 KiB
JavaScript

import path from 'node:path'
import DocstoreManager from '../Docstore/DocstoreManager.mjs'
import Errors from '../Errors/Errors.js'
import ProjectGetter from './ProjectGetter.mjs'
import { callbackifyAll } from '@overleaf/promise-utils'
import OError from '@overleaf/o-error'
import { iterablePaths } from './IterablePath.mjs'
async function getAllDocs(projectId) {
// We get the path and name info from the project, and the lines and
// version info from the doc store.
const docContentsArray = await DocstoreManager.promises.getAllDocs(projectId)
// Turn array from docstore into a dictionary based on doc id
const docContents = {}
for (const docContent of docContentsArray) {
docContents[docContent._id] = docContent
}
const folders = await _getAllFolders(projectId)
const docs = {}
for (const { path: folderPath, folder } of folders) {
for (const doc of iterablePaths(folder, 'docs')) {
const content = docContents[doc._id.toString()]
if (content != null) {
docs[path.join(folderPath, doc.name)] = {
_id: doc._id,
name: doc.name,
lines: content.lines,
rev: content.rev,
folder,
}
}
}
}
return docs
}
async function getAllFiles(projectId) {
const folders = await _getAllFolders(projectId)
const files = {}
for (const { path: folderPath, folder } of folders) {
for (const file of iterablePaths(folder, 'fileRefs')) {
if (file != null) {
files[path.join(folderPath, file.name)] = { ...file, folder }
}
}
}
return files
}
async function getAllEntities(projectId) {
const project = await ProjectGetter.promises.getProject(projectId)
if (project == null) {
throw new Errors.NotFoundError('project not found')
}
const entities = getAllEntitiesFromProject(project)
return entities
}
function getAllEntitiesFromProject(project) {
const folders = _getAllFoldersFromProject(project)
const docs = []
const files = []
for (const { path: folderPath, folder } of folders) {
for (const doc of iterablePaths(folder, 'docs')) {
if (doc != null) {
docs.push({ path: path.join(folderPath, doc.name), doc })
}
}
for (const file of iterablePaths(folder, 'fileRefs')) {
if (file != null) {
files.push({ path: path.join(folderPath, file.name), file })
}
}
}
return { docs, files, folders }
}
async function getAllDocPathsFromProjectById(projectId) {
const project =
await ProjectGetter.promises.getProjectWithoutDocLines(projectId)
if (project == null) {
throw new Errors.NotFoundError('no project')
}
const docPaths = getAllDocPathsFromProject(project)
return docPaths
}
function getAllDocPathsFromProject(project) {
const folders = _getAllFoldersFromProject(project)
const docPath = {}
for (const { path: folderPath, folder } of folders) {
for (const doc of iterablePaths(folder, 'docs')) {
docPath[doc._id] = path.join(folderPath, doc.name)
}
}
return docPath
}
/**
*
* @param {string} projectId
* @param {string} docId
* @param {{peek?: boolean, include_deleted?: boolean}} options
* @return {Promise<{lines: *, rev: *, version: *, ranges: *}>}
*/
async function getDoc(projectId, docId, options = {}) {
const { lines, rev, version, ranges } = await DocstoreManager.promises.getDoc(
projectId,
docId,
options
)
return { lines, rev, version, ranges }
}
/**
* @param {ObjectId | string} projectId
* @param {ObjectId | string} docId
*/
async function getDocPathByProjectIdAndDocId(projectId, docId) {
const project =
await ProjectGetter.promises.getProjectWithoutDocLines(projectId)
if (project == null) {
throw new Errors.NotFoundError('no project')
}
const docPath = await getDocPathFromProjectByDocId(project, docId)
if (docPath == null) {
throw new Errors.NotFoundError('no doc')
}
return docPath
}
function _recursivelyFindDocInFolder(basePath, docId, folder) {
const docInCurrentFolder = (folder.docs || []).find(
currentDoc => currentDoc._id.toString() === docId.toString()
)
if (docInCurrentFolder != null) {
return path.join(basePath, docInCurrentFolder.name)
} else {
let docPath, childFolder
for (childFolder of iterablePaths(folder, 'folders')) {
docPath = _recursivelyFindDocInFolder(
path.join(basePath, childFolder.name),
docId,
childFolder
)
if (docPath != null) {
return docPath
}
}
return null
}
}
/**
* @param {Project} project
* @param {ObjectId | string} docId
* @param {Function} callback
*/
async function getDocPathFromProjectByDocId(project, docId) {
const docPath = _recursivelyFindDocInFolder('/', docId, project.rootFolder[0])
return docPath
}
async function _getAllFolders(projectId) {
const project =
await ProjectGetter.promises.getProjectWithoutDocLines(projectId)
if (project == null) {
throw new Errors.NotFoundError('no project')
}
const folders = _getAllFoldersFromProject(project)
return folders
}
function _getAllFoldersFromProject(project) {
const folders = []
try {
const processFolder = (basePath, folder) => {
folders.push({ path: basePath, folder })
if (folder.folders) {
for (const childFolder of iterablePaths(folder, 'folders')) {
if (childFolder.name != null) {
const childPath = path.join(basePath, childFolder.name)
processFolder(childPath, childFolder)
}
}
}
}
processFolder('/', project.rootFolder[0])
return folders
} catch (err) {
throw OError.tag(err, 'Error getting folders', { projectId: project._id })
}
}
const ProjectEntityHandler = {
getAllDocs,
getAllFiles,
getAllEntities,
getAllDocPathsFromProjectById,
getDoc,
getDocPathByProjectIdAndDocId,
getDocPathFromProjectByDocId,
_getAllFolders,
}
export default {
...callbackifyAll(ProjectEntityHandler, {
multiResult: {
getDoc: ['lines', 'rev', 'version', 'ranges'],
},
}),
promises: ProjectEntityHandler,
getAllEntitiesFromProject,
getAllDocPathsFromProject,
_getAllFoldersFromProject,
}