EditorHttpController.js 7.7 KB

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