ProjectHelper.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. const { ObjectId } = require('mongodb')
  2. const _ = require('lodash')
  3. const { promisify } = require('util')
  4. const Settings = require('@overleaf/settings')
  5. const ENGINE_TO_COMPILER_MAP = {
  6. latex_dvipdf: 'latex',
  7. pdflatex: 'pdflatex',
  8. xelatex: 'xelatex',
  9. lualatex: 'lualatex',
  10. }
  11. module.exports = {
  12. compilerFromV1Engine,
  13. isArchived,
  14. isTrashed,
  15. isArchivedOrTrashed,
  16. calculateArchivedArray,
  17. ensureNameIsUnique,
  18. getAllowedImagesForUser,
  19. promises: {
  20. ensureNameIsUnique: promisify(ensureNameIsUnique),
  21. },
  22. }
  23. function compilerFromV1Engine(engine) {
  24. return ENGINE_TO_COMPILER_MAP[engine]
  25. }
  26. function isArchived(project, userId) {
  27. userId = ObjectId(userId)
  28. if (Array.isArray(project.archived)) {
  29. return project.archived.some(id => id.equals(userId))
  30. } else {
  31. return !!project.archived
  32. }
  33. }
  34. function isTrashed(project, userId) {
  35. userId = ObjectId(userId)
  36. if (project.trashed) {
  37. return project.trashed.some(id => id.equals(userId))
  38. } else {
  39. return false
  40. }
  41. }
  42. function isArchivedOrTrashed(project, userId) {
  43. return isArchived(project, userId) || isTrashed(project, userId)
  44. }
  45. function _allCollaborators(project) {
  46. return _.unionWith(
  47. [project.owner_ref],
  48. project.collaberator_refs,
  49. project.readOnly_refs,
  50. project.tokenAccessReadAndWrite_refs,
  51. project.tokenAccessReadOnly_refs,
  52. _objectIdEquals
  53. )
  54. }
  55. function calculateArchivedArray(project, userId, action) {
  56. let archived = project.archived
  57. userId = ObjectId(userId)
  58. if (archived === true) {
  59. archived = _allCollaborators(project)
  60. } else if (!archived) {
  61. archived = []
  62. }
  63. if (action === 'ARCHIVE') {
  64. archived = _.unionWith(archived, [userId], _objectIdEquals)
  65. } else if (action === 'UNARCHIVE') {
  66. archived = archived.filter(id => !_objectIdEquals(id, userId))
  67. } else {
  68. throw new Error('Unrecognised action')
  69. }
  70. return archived
  71. }
  72. function ensureNameIsUnique(nameList, name, suffixes, maxLength, callback) {
  73. // create a set of all project names
  74. if (suffixes == null) {
  75. suffixes = []
  76. }
  77. const allNames = new Set(nameList)
  78. const isUnique = x => !allNames.has(x)
  79. // check if the supplied name is already unique
  80. if (isUnique(name)) {
  81. return callback(null, name)
  82. }
  83. // the name already exists, try adding the user-supplied suffixes to generate a unique name
  84. for (const suffix of suffixes) {
  85. const candidateName = _addSuffixToProjectName(name, suffix, maxLength)
  86. if (isUnique(candidateName)) {
  87. return callback(null, candidateName)
  88. }
  89. }
  90. // if there are no (more) suffixes, use a numeric one
  91. const uniqueName = _addNumericSuffixToProjectName(name, allNames, maxLength)
  92. if (uniqueName != null) {
  93. callback(null, uniqueName)
  94. } else {
  95. callback(new Error(`Failed to generate a unique name for: ${name}`))
  96. }
  97. }
  98. function _objectIdEquals(firstVal, secondVal) {
  99. // For use as a comparator for unionWith
  100. return firstVal.toString() === secondVal.toString()
  101. }
  102. function _addSuffixToProjectName(name, suffix, maxLength) {
  103. // append the suffix and truncate the project title if needed
  104. if (suffix == null) {
  105. suffix = ''
  106. }
  107. const truncatedLength = maxLength - suffix.length
  108. return name.substr(0, truncatedLength) + suffix
  109. }
  110. function _addNumericSuffixToProjectName(name, allProjectNames, maxLength) {
  111. const NUMERIC_SUFFIX_MATCH = / \((\d+)\)$/
  112. const suffixedName = function (basename, number) {
  113. const suffix = ` (${number})`
  114. return basename.substr(0, maxLength - suffix.length) + suffix
  115. }
  116. const match = name.match(NUMERIC_SUFFIX_MATCH)
  117. let basename = name
  118. let n = 1
  119. const last = allProjectNames.size + n
  120. if (match != null) {
  121. basename = name.replace(NUMERIC_SUFFIX_MATCH, '')
  122. n = parseInt(match[1])
  123. }
  124. const prefixMatcher = new RegExp(`^${basename} \\(\\d+\\)$`)
  125. const projectNamesWithSamePrefix = Array.from(allProjectNames).filter(name =>
  126. prefixMatcher.test(name)
  127. )
  128. const nIsLikelyAYear = n > 1000 && projectNamesWithSamePrefix.length < n / 2
  129. if (nIsLikelyAYear) {
  130. basename = name
  131. n = 1
  132. }
  133. while (n <= last) {
  134. const candidate = suffixedName(basename, n)
  135. if (!allProjectNames.has(candidate)) {
  136. return candidate
  137. }
  138. n += 1
  139. }
  140. return null
  141. }
  142. function getAllowedImagesForUser(sessionUser) {
  143. const images = Settings.allowedImageNames || []
  144. if (sessionUser && sessionUser.isAdmin) {
  145. return images
  146. } else {
  147. return images.filter(image => !image.adminOnly)
  148. }
  149. }