ProjectHelper.mjs 4.1 KB

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