EditorHttpController.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. const HttpErrors = require('@overleaf/o-error/http')
  2. const ProjectDeleter = require('../Project/ProjectDeleter')
  3. const EditorController = require('./EditorController')
  4. const ProjectGetter = require('../Project/ProjectGetter')
  5. const AuthorizationManager = require('../Authorization/AuthorizationManager')
  6. const ProjectEditorHandler = require('../Project/ProjectEditorHandler')
  7. const Metrics = require('metrics-sharelatex')
  8. const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
  9. const CollaboratorsInviteHandler = require('../Collaborators/CollaboratorsInviteHandler')
  10. const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
  11. const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
  12. const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
  13. const AuthenticationController = require('../Authentication/AuthenticationController')
  14. const Errors = require('../Errors/Errors')
  15. const HttpErrorHandler = require('../Errors/HttpErrorHandler')
  16. const ProjectEntityUpdateHandler = require('../Project/ProjectEntityUpdateHandler')
  17. const { expressify } = require('../../util/promises')
  18. module.exports = {
  19. joinProject: expressify(joinProject),
  20. addDoc: expressify(addDoc),
  21. addFolder: expressify(addFolder),
  22. renameEntity: expressify(renameEntity),
  23. moveEntity: expressify(moveEntity),
  24. deleteDoc: expressify(deleteDoc),
  25. deleteFile: expressify(deleteFile),
  26. deleteFolder: expressify(deleteFolder),
  27. deleteEntity: expressify(deleteEntity),
  28. convertDocToFile: expressify(convertDocToFile),
  29. _nameIsAcceptableLength
  30. }
  31. const unsupportedSpellcheckLanguages = [
  32. 'am',
  33. 'hy',
  34. 'bn',
  35. 'gu',
  36. 'he',
  37. 'hi',
  38. 'hu',
  39. 'is',
  40. 'kn',
  41. 'ml',
  42. 'mr',
  43. 'or',
  44. 'ss',
  45. 'ta',
  46. 'te',
  47. 'uk',
  48. 'uz',
  49. 'zu',
  50. 'fi'
  51. ]
  52. async function joinProject(req, res, next) {
  53. const projectId = req.params.Project_id
  54. let userId = req.query.user_id
  55. if (userId === 'anonymous-user') {
  56. userId = null
  57. }
  58. Metrics.inc('editor.join-project')
  59. const {
  60. project,
  61. privilegeLevel,
  62. isRestrictedUser
  63. } = await _buildJoinProjectView(req, projectId, userId)
  64. if (!project) {
  65. return res.sendStatus(403)
  66. }
  67. // Hide access tokens if this is not the project owner
  68. TokenAccessHandler.protectTokens(project, privilegeLevel)
  69. if (isRestrictedUser) {
  70. project.owner = { _id: project.owner._id }
  71. }
  72. // Only show the 'renamed or deleted' message once
  73. if (project.deletedByExternalDataSource) {
  74. await ProjectDeleter.promises.unmarkAsDeletedByExternalSource(projectId)
  75. }
  76. // disable spellchecking for currently unsupported spell check languages
  77. // preserve the value in the db so they can use it again once we add back
  78. // support.
  79. if (
  80. unsupportedSpellcheckLanguages.indexOf(project.spellCheckLanguage) !== -1
  81. ) {
  82. project.spellCheckLanguage = ''
  83. }
  84. res.json({
  85. project,
  86. privilegeLevel,
  87. isRestrictedUser
  88. })
  89. }
  90. async function _buildJoinProjectView(req, projectId, userId) {
  91. const project = await ProjectGetter.promises.getProjectWithoutDocLines(
  92. projectId
  93. )
  94. if (project == null) {
  95. throw new Errors.NotFoundError('project not found')
  96. }
  97. const members = await CollaboratorsGetter.promises.getInvitedMembersWithPrivilegeLevels(
  98. projectId
  99. )
  100. const token = TokenAccessHandler.getRequestToken(req, projectId)
  101. const privilegeLevel = await AuthorizationManager.promises.getPrivilegeLevelForProject(
  102. userId,
  103. projectId,
  104. token
  105. )
  106. if (privilegeLevel == null || privilegeLevel === PrivilegeLevels.NONE) {
  107. return { project: null, privilegeLevel: null, isRestrictedUser: false }
  108. }
  109. const invites = await CollaboratorsInviteHandler.promises.getAllInvites(
  110. projectId
  111. )
  112. const isTokenMember = await CollaboratorsHandler.promises.userIsTokenMember(
  113. userId,
  114. projectId
  115. )
  116. const isRestrictedUser = AuthorizationManager.isRestrictedUser(
  117. userId,
  118. privilegeLevel,
  119. isTokenMember
  120. )
  121. return {
  122. project: ProjectEditorHandler.buildProjectModelView(
  123. project,
  124. members,
  125. invites
  126. ),
  127. privilegeLevel,
  128. isRestrictedUser
  129. }
  130. }
  131. function _nameIsAcceptableLength(name) {
  132. return name != null && name.length < 150 && name.length !== 0
  133. }
  134. async function addDoc(req, res, next) {
  135. const projectId = req.params.Project_id
  136. const { name } = req.body
  137. const parentFolderId = req.body.parent_folder_id
  138. const userId = AuthenticationController.getLoggedInUserId(req)
  139. if (!_nameIsAcceptableLength(name)) {
  140. return res.sendStatus(400)
  141. }
  142. try {
  143. const doc = await EditorController.promises.addDoc(
  144. projectId,
  145. parentFolderId,
  146. name,
  147. [],
  148. 'editor',
  149. userId
  150. )
  151. res.json(doc)
  152. } catch (err) {
  153. if (err.message === 'project_has_too_many_files') {
  154. res.status(400).json(req.i18n.translate('project_has_too_many_files'))
  155. } else {
  156. next(err)
  157. }
  158. }
  159. }
  160. async function addFolder(req, res, next) {
  161. const projectId = req.params.Project_id
  162. const { name } = req.body
  163. const parentFolderId = req.body.parent_folder_id
  164. if (!_nameIsAcceptableLength(name)) {
  165. return res.sendStatus(400)
  166. }
  167. try {
  168. const doc = await EditorController.promises.addFolder(
  169. projectId,
  170. parentFolderId,
  171. name,
  172. 'editor'
  173. )
  174. res.json(doc)
  175. } catch (err) {
  176. if (err.message === 'project_has_too_many_files') {
  177. res.status(400).json(req.i18n.translate('project_has_too_many_files'))
  178. } else if (err.message === 'invalid element name') {
  179. res.status(400).json(req.i18n.translate('invalid_file_name'))
  180. } else {
  181. next(err)
  182. }
  183. }
  184. }
  185. async function renameEntity(req, res, next) {
  186. const projectId = req.params.Project_id
  187. const entityId = req.params.entity_id
  188. const entityType = req.params.entity_type
  189. const { name } = req.body
  190. if (!_nameIsAcceptableLength(name)) {
  191. return res.sendStatus(400)
  192. }
  193. const userId = AuthenticationController.getLoggedInUserId(req)
  194. await EditorController.promises.renameEntity(
  195. projectId,
  196. entityId,
  197. entityType,
  198. name,
  199. userId
  200. )
  201. res.sendStatus(204)
  202. }
  203. async function moveEntity(req, res, next) {
  204. const projectId = req.params.Project_id
  205. const entityId = req.params.entity_id
  206. const entityType = req.params.entity_type
  207. const folderId = req.body.folder_id
  208. const userId = AuthenticationController.getLoggedInUserId(req)
  209. await EditorController.promises.moveEntity(
  210. projectId,
  211. entityId,
  212. folderId,
  213. entityType,
  214. userId
  215. )
  216. res.sendStatus(204)
  217. }
  218. async function deleteDoc(req, res, next) {
  219. req.params.entity_type = 'doc'
  220. await deleteEntity(req, res, next)
  221. }
  222. async function deleteFile(req, res, next) {
  223. req.params.entity_type = 'file'
  224. await deleteEntity(req, res, next)
  225. }
  226. async function deleteFolder(req, res, next) {
  227. req.params.entity_type = 'folder'
  228. await deleteEntity(req, res, next)
  229. }
  230. async function deleteEntity(req, res, next) {
  231. const projectId = req.params.Project_id
  232. const entityId = req.params.entity_id
  233. const entityType = req.params.entity_type
  234. const userId = AuthenticationController.getLoggedInUserId(req)
  235. await EditorController.promises.deleteEntity(
  236. projectId,
  237. entityId,
  238. entityType,
  239. 'editor',
  240. userId
  241. )
  242. res.sendStatus(204)
  243. }
  244. async function convertDocToFile(req, res, next) {
  245. const projectId = req.params.Project_id
  246. const docId = req.params.entity_id
  247. const { userId } = req.body
  248. try {
  249. const fileRef = await ProjectEntityUpdateHandler.promises.convertDocToFile(
  250. projectId,
  251. docId,
  252. userId
  253. )
  254. res.json({ fileId: fileRef._id.toString() })
  255. } catch (err) {
  256. if (err instanceof Errors.NotFoundError) {
  257. throw new HttpErrors.NotFoundError({
  258. info: { public: { message: 'Document not found' } }
  259. })
  260. } else if (err instanceof Errors.DocHasRangesError) {
  261. return HttpErrorHandler.unprocessableEntity(
  262. req,
  263. res,
  264. 'Document has comments or tracked changes'
  265. )
  266. } else {
  267. throw err
  268. }
  269. }
  270. }