ProjectController.coffee 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. async = require("async")
  2. logger = require("logger-sharelatex")
  3. projectDeleter = require("./ProjectDeleter")
  4. projectDuplicator = require("./ProjectDuplicator")
  5. projectCreationHandler = require("./ProjectCreationHandler")
  6. editorController = require("../Editor/EditorController")
  7. metrics = require('../../infrastructure/Metrics')
  8. Project = require('../../models/Project').Project
  9. User = require('../../models/User').User
  10. TagsHandler = require("../Tags/TagsHandler")
  11. SubscriptionLocator = require("../Subscription/SubscriptionLocator")
  12. NotificationsHandler = require("../Notifications/NotificationsHandler")
  13. LimitationsManager = require("../Subscription/LimitationsManager")
  14. _ = require("underscore")
  15. Settings = require("settings-sharelatex")
  16. SecurityManager = require("../../managers/SecurityManager")
  17. fs = require "fs"
  18. InactiveProjectManager = require("../InactiveData/InactiveProjectManager")
  19. ProjectUpdateHandler = require("./ProjectUpdateHandler")
  20. ProjectGetter = require("./ProjectGetter")
  21. module.exports = ProjectController =
  22. updateProjectSettings: (req, res, next) ->
  23. project_id = req.params.Project_id
  24. jobs = []
  25. if req.body.compiler?
  26. jobs.push (callback) ->
  27. editorController.setCompiler project_id, req.body.compiler, callback
  28. if req.body.name?
  29. jobs.push (callback) ->
  30. editorController.renameProject project_id, req.body.name, callback
  31. if req.body.spellCheckLanguage?
  32. jobs.push (callback) ->
  33. editorController.setSpellCheckLanguage project_id, req.body.spellCheckLanguage, callback
  34. if req.body.rootDocId?
  35. jobs.push (callback) ->
  36. editorController.setRootDoc project_id, req.body.rootDocId, callback
  37. if req.body.publicAccessLevel?
  38. jobs.push (callback) ->
  39. editorController.setPublicAccessLevel project_id, req.body.publicAccessLevel, callback
  40. async.series jobs, (error) ->
  41. return next(error) if error?
  42. res.sendStatus(204)
  43. deleteProject: (req, res) ->
  44. project_id = req.params.Project_id
  45. forever = req.query?.forever?
  46. logger.log project_id: project_id, forever: forever, "received request to delete project"
  47. if forever
  48. doDelete = projectDeleter.deleteProject
  49. else
  50. doDelete = projectDeleter.archiveProject
  51. doDelete project_id, (err)->
  52. if err?
  53. res.sendStatus 500
  54. else
  55. res.sendStatus 200
  56. restoreProject: (req, res) ->
  57. project_id = req.params.Project_id
  58. logger.log project_id:project_id, "received request to restore project"
  59. projectDeleter.restoreProject project_id, (err)->
  60. if err?
  61. res.sendStatus 500
  62. else
  63. res.sendStatus 200
  64. cloneProject: (req, res, next)->
  65. metrics.inc "cloned-project"
  66. project_id = req.params.Project_id
  67. projectName = req.body.projectName
  68. logger.log project_id:project_id, projectName:projectName, "cloning project"
  69. if !req.session.user?
  70. return res.send redir:"/register"
  71. projectDuplicator.duplicate req.session.user, project_id, projectName, (err, project)->
  72. if err?
  73. logger.error err:err, project_id: project_id, user_id: req.session.user._id, "error cloning project"
  74. return next(err)
  75. res.send(project_id:project._id)
  76. newProject: (req, res)->
  77. user = req.session.user
  78. projectName = req.body.projectName?.trim()
  79. template = req.body.template
  80. logger.log user: user, projectType: template, name: projectName, "creating project"
  81. async.waterfall [
  82. (cb)->
  83. if template == 'example'
  84. projectCreationHandler.createExampleProject user._id, projectName, cb
  85. else
  86. projectCreationHandler.createBasicProject user._id, projectName, cb
  87. ], (err, project)->
  88. if err?
  89. logger.error err: err, project: project, user: user, name: projectName, templateType: template, "error creating project"
  90. res.sendStatus 500
  91. else
  92. logger.log project: project, user: user, name: projectName, templateType: template, "created project"
  93. res.send {project_id:project._id}
  94. renameProject: (req, res)->
  95. project_id = req.params.Project_id
  96. newName = req.body.newProjectName
  97. if newName.length > 150
  98. return res.sendStatus 400
  99. editorController.renameProject project_id, newName, (err)->
  100. if err?
  101. logger.err err:err, project_id:project_id, newName:newName, "problem renaming project"
  102. res.sendStatus 500
  103. else
  104. res.sendStatus 200
  105. projectListPage: (req, res, next)->
  106. timer = new metrics.Timer("project-list")
  107. user_id = req.session.user._id
  108. async.parallel {
  109. tags: (cb)->
  110. TagsHandler.getAllTags user_id, cb
  111. notifications: (cb)->
  112. NotificationsHandler.getUserNotifications user_id, cb
  113. projects: (cb)->
  114. ProjectGetter.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref', cb
  115. hasSubscription: (cb)->
  116. LimitationsManager.userHasSubscriptionOrIsGroupMember req.session.user, cb
  117. user: (cb) ->
  118. User.findById user_id, "featureSwitches", cb
  119. }, (err, results)->
  120. if err?
  121. logger.err err:err, "error getting data for project list page"
  122. return next(err)
  123. logger.log results:results, user_id:user_id, "rendering project list"
  124. tags = results.tags[0]
  125. notifications = require("underscore").map results.notifications, (notification)->
  126. notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts)
  127. return notification
  128. projects = ProjectController._buildProjectList results.projects[0], results.projects[1], results.projects[2]
  129. user = results.user
  130. ProjectController._injectProjectOwners projects, (error, projects) ->
  131. return next(error) if error?
  132. viewModel = {
  133. title:'your_projects'
  134. priority_title: true
  135. projects: projects
  136. tags: tags
  137. notifications: notifications or []
  138. user: user
  139. hasSubscription: results.hasSubscription[0]
  140. }
  141. if Settings?.algolia?.app_id? and Settings?.algolia?.read_only_api_key?
  142. viewModel.showUserDetailsArea = true
  143. viewModel.algolia_api_key = Settings.algolia.read_only_api_key
  144. viewModel.algolia_app_id = Settings.algolia.app_id
  145. else
  146. viewModel.showUserDetailsArea = false
  147. res.render 'project/list', viewModel
  148. timer.done()
  149. loadEditor: (req, res, next)->
  150. timer = new metrics.Timer("load-editor")
  151. if !Settings.editorIsOpen
  152. return res.render("general/closed", {title:"updating_site"})
  153. if req.session.user?
  154. user_id = req.session.user._id
  155. anonymous = false
  156. else
  157. anonymous = true
  158. user_id = 'openUser'
  159. project_id = req.params.Project_id
  160. logger.log project_id:project_id, "loading editor"
  161. async.parallel {
  162. project: (cb)->
  163. Project.findPopulatedById project_id, cb
  164. user: (cb)->
  165. if user_id == 'openUser'
  166. cb null, defaultSettingsForAnonymousUser(user_id)
  167. else
  168. User.findById user_id, (err, user)->
  169. logger.log project_id:project_id, user_id:user_id, "got user"
  170. cb err, user
  171. subscription: (cb)->
  172. if user_id == 'openUser'
  173. return cb()
  174. SubscriptionLocator.getUsersSubscription user_id, cb
  175. activate: (cb)->
  176. InactiveProjectManager.reactivateProjectIfRequired project_id, cb
  177. markAsOpened: (cb)->
  178. #don't need to wait for this to complete
  179. ProjectUpdateHandler.markAsOpened project_id, ->
  180. cb()
  181. }, (err, results)->
  182. if err?
  183. logger.err err:err, "error getting details for project page"
  184. return next err
  185. project = results.project
  186. user = results.user
  187. subscription = results.subscription
  188. daysSinceLastUpdated = (new Date() - project.lastUpdated) /86400000
  189. logger.log project_id:project_id, daysSinceLastUpdated:daysSinceLastUpdated, "got db results for loading editor"
  190. SecurityManager.userCanAccessProject user, project, (canAccess, privilegeLevel)->
  191. if !canAccess
  192. return res.sendStatus 401
  193. if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt?
  194. allowedFreeTrial = !!subscription.freeTrial.allowed || true
  195. logger.log project_id:project_id, "rendering editor page"
  196. res.render 'project/editor',
  197. title: project.name
  198. priority_title: true
  199. bodyClasses: ["editor"]
  200. project : project
  201. project_id : project._id
  202. user : {
  203. id : user.id
  204. email : user.email
  205. first_name : user.first_name
  206. last_name : user.last_name
  207. referal_id : user.referal_id
  208. subscription :
  209. freeTrial: {allowed: allowedFreeTrial}
  210. featureSwitches: user.featureSwitches
  211. }
  212. userSettings: {
  213. mode : user.ace.mode
  214. theme : user.ace.theme
  215. fontSize : user.ace.fontSize
  216. autoComplete: user.ace.autoComplete
  217. pdfViewer : user.ace.pdfViewer
  218. }
  219. privilegeLevel: privilegeLevel
  220. chatUrl: Settings.apis.chat.url
  221. anonymous: anonymous
  222. languages: Settings.languages
  223. themes: THEME_LIST
  224. maxDocLength: Settings.max_doc_length
  225. timer.done()
  226. _buildProjectList: (ownedProjects, sharedProjects, readOnlyProjects)->
  227. projects = []
  228. for project in ownedProjects
  229. projects.push ProjectController._buildProjectViewModel(project, "owner")
  230. for project in sharedProjects
  231. projects.push ProjectController._buildProjectViewModel(project, "readWrite")
  232. for project in readOnlyProjects
  233. projects.push ProjectController._buildProjectViewModel(project, "readOnly")
  234. return projects
  235. _buildProjectViewModel: (project, accessLevel) ->
  236. {
  237. id: project._id
  238. name: project.name
  239. lastUpdated: project.lastUpdated
  240. publicAccessLevel: project.publicAccesLevel
  241. accessLevel: accessLevel
  242. archived: !!project.archived
  243. owner_ref: project.owner_ref
  244. }
  245. _injectProjectOwners: (projects, callback = (error, projects) ->) ->
  246. users = {}
  247. for project in projects
  248. if project.owner_ref?
  249. users[project.owner_ref.toString()] = true
  250. jobs = []
  251. for user_id, _ of users
  252. do (user_id) ->
  253. jobs.push (callback) ->
  254. User.findById user_id, "first_name last_name", (error, user) ->
  255. return callback(error) if error?
  256. users[user_id] = user
  257. callback()
  258. async.series jobs, (error) ->
  259. for project in projects
  260. if project.owner_ref?
  261. project.owner = users[project.owner_ref.toString()]
  262. callback null, projects
  263. defaultSettingsForAnonymousUser = (user_id)->
  264. id : user_id
  265. ace:
  266. mode:'none'
  267. theme:'textmate'
  268. fontSize: '12'
  269. autoComplete: true
  270. spellCheckLanguage: ""
  271. pdfViewer: ""
  272. subscription:
  273. freeTrial:
  274. allowed: true
  275. featureSwitches:
  276. github: false
  277. THEME_LIST = []
  278. do generateThemeList = () ->
  279. files = fs.readdirSync __dirname + '/../../../../public/js/ace'
  280. for file in files
  281. if file.slice(-2) == "js" and file.match(/^theme-/)
  282. cleanName = file.slice(0,-3).slice(6)
  283. THEME_LIST.push cleanName