| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- import ProjectDeleter from '../Project/ProjectDeleter.js'
- import EditorController from './EditorController.js'
- import ProjectGetter from '../Project/ProjectGetter.js'
- import AuthorizationManager from '../Authorization/AuthorizationManager.js'
- import ProjectEditorHandler from '../Project/ProjectEditorHandler.js'
- import Metrics from '@overleaf/metrics'
- import CollaboratorsInviteGetter from '../Collaborators/CollaboratorsInviteGetter.js'
- import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
- import SessionManager from '../Authentication/SessionManager.js'
- import Errors from '../Errors/Errors.js'
- import { expressify } from '@overleaf/promise-utils'
- import Settings from '@overleaf/settings'
- import CollaboratorsGetter from '../Collaborators/CollaboratorsGetter.js'
- const ProjectAccess = CollaboratorsGetter.ProjectAccess
- export default {
- joinProject: expressify(joinProject),
- addDoc: expressify(addDoc),
- addFolder: expressify(addFolder),
- renameEntity: expressify(renameEntity),
- moveEntity: expressify(moveEntity),
- deleteDoc: expressify(deleteDoc),
- deleteFile: expressify(deleteFile),
- deleteFolder: expressify(deleteFolder),
- deleteEntity: expressify(deleteEntity),
- _nameIsAcceptableLength,
- }
- async function joinProject(req, res, next) {
- const projectId = req.params.Project_id
- let userId = req.body.userId // keep schema in sync with router
- if (userId === 'anonymous-user') {
- userId = null
- }
- Metrics.inc('editor.join-project')
- const {
- project,
- privilegeLevel,
- isRestrictedUser,
- isTokenMember,
- isInvitedMember,
- } = await _buildJoinProjectView(req, projectId, userId)
- if (!project) {
- return res.sendStatus(403)
- }
- // Only show the 'renamed or deleted' message once
- if (project.deletedByExternalDataSource) {
- await ProjectDeleter.promises.unmarkAsDeletedByExternalSource(projectId)
- }
- if (project.spellCheckLanguage) {
- project.spellCheckLanguage = await chooseSpellCheckLanguage(
- project.spellCheckLanguage
- )
- }
- res.json({
- project,
- privilegeLevel,
- isRestrictedUser,
- isTokenMember,
- isInvitedMember,
- })
- }
- async function _buildJoinProjectView(req, projectId, userId) {
- const project =
- await ProjectGetter.promises.getProjectWithoutDocLines(projectId)
- if (project == null) {
- throw new Errors.NotFoundError('project not found')
- }
- const projectAccess = new ProjectAccess(project)
- const token = req.body.anonymousAccessToken
- const privilegeLevel =
- await AuthorizationManager.promises.getPrivilegeLevelForProjectWithProjectAccess(
- userId,
- projectId,
- token,
- projectAccess
- )
- if (privilegeLevel == null || privilegeLevel === PrivilegeLevels.NONE) {
- return { project: null, privilegeLevel: null, isRestrictedUser: false }
- }
- const isTokenMember = projectAccess.isUserTokenMember(userId)
- const isInvitedMember = projectAccess.isUserInvitedMember(userId)
- const isRestrictedUser = AuthorizationManager.isRestrictedUser(
- userId,
- privilegeLevel,
- isTokenMember,
- isInvitedMember
- )
- let ownerMember
- let members = []
- let invites = []
- if (isRestrictedUser) {
- ownerMember = await projectAccess.loadOwner()
- } else {
- ;({ ownerMember, members } =
- await projectAccess.loadOwnerAndInvitedMembers())
- invites = await CollaboratorsInviteGetter.promises.getAllInvites(projectId)
- }
- return {
- project: ProjectEditorHandler.buildProjectModelView(
- project,
- ownerMember,
- members,
- invites,
- isRestrictedUser
- ),
- privilegeLevel,
- isTokenMember,
- isInvitedMember,
- isRestrictedUser,
- }
- }
- function _nameIsAcceptableLength(name) {
- return name != null && name.length < 150 && name.length !== 0
- }
- async function addDoc(req, res, next) {
- const projectId = req.params.Project_id
- const { name } = req.body
- const parentFolderId = req.body.parent_folder_id
- const userId = SessionManager.getLoggedInUserId(req.session)
- if (!_nameIsAcceptableLength(name)) {
- return res.sendStatus(400)
- }
- try {
- const doc = await EditorController.promises.addDoc(
- projectId,
- parentFolderId,
- name,
- [],
- 'editor',
- userId
- )
- res.json(doc)
- } catch (err) {
- if (err.message === 'project_has_too_many_files') {
- res.status(400).json(req.i18n.translate('project_has_too_many_files'))
- } else {
- next(err)
- }
- }
- }
- async function addFolder(req, res, next) {
- const projectId = req.params.Project_id
- const { name } = req.body
- const parentFolderId = req.body.parent_folder_id
- const userId = SessionManager.getLoggedInUserId(req.session)
- if (!_nameIsAcceptableLength(name)) {
- return res.sendStatus(400)
- }
- try {
- const doc = await EditorController.promises.addFolder(
- projectId,
- parentFolderId,
- name,
- 'editor',
- userId
- )
- res.json(doc)
- } catch (err) {
- if (err.message === 'project_has_too_many_files') {
- res.status(400).json(req.i18n.translate('project_has_too_many_files'))
- } else if (err.message === 'invalid element name') {
- res.status(400).json(req.i18n.translate('invalid_file_name'))
- } else {
- next(err)
- }
- }
- }
- async function renameEntity(req, res, next) {
- const projectId = req.params.Project_id
- const entityId = req.params.entity_id
- const entityType = req.params.entity_type
- const { name, source = 'editor' } = req.body
- if (!_nameIsAcceptableLength(name)) {
- return res.sendStatus(400)
- }
- const userId = SessionManager.getLoggedInUserId(req.session)
- await EditorController.promises.renameEntity(
- projectId,
- entityId,
- entityType,
- name,
- userId,
- source
- )
- res.sendStatus(204)
- }
- async function moveEntity(req, res, next) {
- const projectId = req.params.Project_id
- const entityId = req.params.entity_id
- const entityType = req.params.entity_type
- const folderId = req.body.folder_id
- const source = req.body.source ?? 'editor'
- const userId = SessionManager.getLoggedInUserId(req.session)
- await EditorController.promises.moveEntity(
- projectId,
- entityId,
- folderId,
- entityType,
- userId,
- source
- )
- res.sendStatus(204)
- }
- async function deleteDoc(req, res, next) {
- req.params.entity_type = 'doc'
- await deleteEntity(req, res, next)
- }
- async function deleteFile(req, res, next) {
- req.params.entity_type = 'file'
- await deleteEntity(req, res, next)
- }
- async function deleteFolder(req, res, next) {
- req.params.entity_type = 'folder'
- await deleteEntity(req, res, next)
- }
- async function deleteEntity(req, res, next) {
- const projectId = req.params.Project_id
- const entityId = req.params.entity_id
- const entityType = req.params.entity_type
- const userId = SessionManager.getLoggedInUserId(req.session)
- await EditorController.promises.deleteEntity(
- projectId,
- entityId,
- entityType,
- 'editor',
- userId
- )
- res.sendStatus(204)
- }
- const supportedSpellCheckLanguages = new Set(
- Settings.languages
- // only include spell-check languages that are available in the client
- .filter(language => language.dic !== undefined)
- .map(language => language.code)
- )
- async function chooseSpellCheckLanguage(spellCheckLanguage) {
- if (supportedSpellCheckLanguages.has(spellCheckLanguage)) {
- return spellCheckLanguage
- }
- // Preserve the value in the database so they can use it again once we add back support.
- // Map some server-only languages to a specific variant, or disable spell checking for currently unsupported spell check languages.
- switch (spellCheckLanguage) {
- case 'en':
- // map "English" to "English (American)"
- return 'en_US'
- case 'no':
- // map "Norwegian" to "Norwegian (Bokmål)"
- return 'nb_NO'
- default:
- // map anything else to "off"
- return ''
- }
- }
|