ProjectOptionsHandler.mjs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import { Project } from '../../models/Project.mjs'
  2. import settings from '@overleaf/settings'
  3. import { callbackify } from 'node:util'
  4. import { db, ObjectId } from '../../infrastructure/mongodb.mjs'
  5. import Errors from '../Errors/Errors.js'
  6. import mongodb from 'mongodb-legacy'
  7. import OError from '@overleaf/o-error'
  8. const { ReturnDocument } = mongodb
  9. const ProjectOptionsHandler = {
  10. /**
  11. * @param {string} compiler
  12. * @return {string}
  13. */
  14. normalizeCompiler(compiler) {
  15. compiler = compiler.toLowerCase()
  16. if (!settings.safeCompilers.includes(compiler)) {
  17. throw new OError('invalid compiler', { compiler })
  18. }
  19. return compiler
  20. },
  21. async setCompiler(projectId, compiler) {
  22. if (!compiler) {
  23. return
  24. }
  25. compiler = ProjectOptionsHandler.normalizeCompiler(compiler)
  26. const conditions = { _id: projectId }
  27. const update = { compiler }
  28. return Project.updateOne(conditions, update, {})
  29. },
  30. /**
  31. * @param {string} imageName
  32. * @return {string | undefined}
  33. */
  34. normalizeImageName(imageName) {
  35. if (!imageName || !Array.isArray(settings.allowedImageNames)) {
  36. return undefined
  37. }
  38. imageName = imageName.toLowerCase()
  39. const isAllowed = settings.allowedImageNames.find(
  40. allowed => imageName === allowed.imageName
  41. )
  42. if (!isAllowed) {
  43. throw new OError('invalid imageName', { imageName })
  44. }
  45. return settings.imageRoot + '/' + imageName
  46. },
  47. async setImageName(projectId, imageName) {
  48. imageName = ProjectOptionsHandler.normalizeImageName(imageName)
  49. if (!imageName) {
  50. return
  51. }
  52. const conditions = { _id: projectId }
  53. const update = { imageName }
  54. return Project.updateOne(conditions, update, {})
  55. },
  56. async setSpellCheckLanguage(projectId, languageCode) {
  57. if (!Array.isArray(settings.languages)) {
  58. return
  59. }
  60. const language = settings.languages.find(
  61. language => language.code === languageCode
  62. )
  63. if (languageCode && !language) {
  64. throw new OError('invalid languageCode', { languageCode })
  65. }
  66. const conditions = { _id: projectId }
  67. const update = { spellCheckLanguage: languageCode }
  68. return Project.updateOne(conditions, update, {})
  69. },
  70. async setBrandVariationId(projectId, brandVariationId) {
  71. if (!brandVariationId) {
  72. return
  73. }
  74. const conditions = { _id: projectId }
  75. const update = { brandVariationId }
  76. return Project.updateOne(conditions, update, {})
  77. },
  78. async unsetBrandVariationId(projectId) {
  79. const conditions = { _id: projectId }
  80. const update = { $unset: { brandVariationId: 1 } }
  81. return Project.updateOne(conditions, update, {})
  82. },
  83. async setHistoryRangesSupport(projectId, enabled) {
  84. const conditions = { _id: new ObjectId(projectId) }
  85. const update = {
  86. $set: { 'overleaf.history.rangesSupportEnabled': enabled },
  87. }
  88. // NOTE: Updating the Mongoose model with the same query doesn't work. Maybe
  89. // because rangesSupportEnabled is not part of the schema?
  90. return db.projects.updateOne(conditions, update)
  91. },
  92. async setOTMigrationStage(projectId, nextStage) {
  93. const project = await db.projects.findOneAndUpdate(
  94. { _id: new ObjectId(projectId) },
  95. // Use $max to ensure that we never downgrade the migration stage.
  96. { $max: { 'overleaf.history.otMigrationStage': nextStage } },
  97. {
  98. returnDocument: ReturnDocument.AFTER,
  99. projection: { 'overleaf.history.otMigrationStage': 1 },
  100. }
  101. )
  102. if (!project) throw new Errors.NotFoundError('project does not exist')
  103. const { otMigrationStage } = project.overleaf.history
  104. return { otMigrationStage }
  105. },
  106. }
  107. export default {
  108. setCompiler: callbackify(ProjectOptionsHandler.setCompiler),
  109. setImageName: callbackify(ProjectOptionsHandler.setImageName),
  110. setSpellCheckLanguage: callbackify(
  111. ProjectOptionsHandler.setSpellCheckLanguage
  112. ),
  113. setBrandVariationId: callbackify(ProjectOptionsHandler.setBrandVariationId),
  114. unsetBrandVariationId: callbackify(
  115. ProjectOptionsHandler.unsetBrandVariationId
  116. ),
  117. setHistoryRangesSupport: callbackify(
  118. ProjectOptionsHandler.setHistoryRangesSupport
  119. ),
  120. promises: ProjectOptionsHandler,
  121. }