devcontainer_setup.mjs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. // @ts-check
  2. import Settings from '@overleaf/settings'
  3. import { waitForDb, db, ObjectId } from '../app/src/infrastructure/mongodb.mjs'
  4. import GracefulShutdown from '../app/src/infrastructure/GracefulShutdown.mjs'
  5. import UserRegistrationHandler from '../app/src/Features/User/UserRegistrationHandler.mjs'
  6. import { Subscription } from '../app/src/models/Subscription.mjs'
  7. import minimist from 'minimist'
  8. import {
  9. createProjectWithOldHistoryId,
  10. provisionSplitTests,
  11. } from './e2e_test_setup.mjs'
  12. import { Project } from '../app/src/models/Project.mjs'
  13. import OError from '@overleaf/o-error'
  14. import fs from 'node:fs'
  15. import Path from 'node:path'
  16. import { fileURLToPath } from 'node:url'
  17. const MONOREPO = Path.dirname(
  18. Path.dirname(Path.dirname(Path.dirname(fileURLToPath(import.meta.url))))
  19. )
  20. const { email: USER_EMAIL, password: PASSWORD } = minimist(
  21. process.argv.slice(2),
  22. { string: ['email', 'password'] }
  23. )
  24. /**
  25. * @param {string} email
  26. * @param {Object} opts
  27. * @param {boolean?} opts.isAdmin
  28. * @param {boolean?} opts.forceProfessional
  29. * @return {Promise<string>}
  30. */
  31. async function createUser(
  32. email,
  33. opts = { isAdmin: false, forceProfessional: false }
  34. ) {
  35. const { isAdmin = false, forceProfessional = false } = opts
  36. /** @type {import('mongodb-legacy').ObjectId} */
  37. let userId
  38. try {
  39. const user = await UserRegistrationHandler.promises.registerNewUser({
  40. email,
  41. password: PASSWORD,
  42. })
  43. userId = user._id
  44. } catch (err) {
  45. if (
  46. err instanceof OError &&
  47. err.message.includes('EmailAlreadyRegistered') &&
  48. err.info &&
  49. 'userId' in err.info &&
  50. err.info.userId instanceof ObjectId
  51. ) {
  52. userId = err.info.userId
  53. } else {
  54. throw err
  55. }
  56. }
  57. /** @type {string[]} */
  58. let adminRoles = []
  59. if (isAdmin) {
  60. adminRoles = ['engineering']
  61. }
  62. await db.users.updateOne(
  63. { _id: userId },
  64. {
  65. $set: {
  66. // Set admin flag.
  67. isAdmin,
  68. adminRoles,
  69. // disable AI features, does not work with custom GH Code Spaces domain.
  70. 'aiFeatures.enabled': false,
  71. // Override features.
  72. ...(forceProfessional
  73. ? {
  74. features: Settings.features.professional,
  75. featuresOverrides: [{ features: Settings.features.professional }],
  76. }
  77. : {}),
  78. },
  79. }
  80. )
  81. return userId.toString()
  82. }
  83. async function provisionUsers() {
  84. await Promise.all([
  85. createUser(USER_EMAIL, { isAdmin: true, forceProfessional: true }),
  86. createUser('admin@overleaf.com', {
  87. isAdmin: true,
  88. forceProfessional: true,
  89. }),
  90. createUser('free@overleaf.com'),
  91. createUser('premium@overleaf.com').then(async userId => {
  92. const subscription = new Subscription({
  93. admin_id: userId,
  94. member_ids: [userId],
  95. manager_ids: [userId],
  96. planCode: 'professional',
  97. customAccount: true,
  98. })
  99. try {
  100. await subscription.save()
  101. } catch (err) {
  102. if (!isAlreadyExistsErr(err)) throw err // ignore already exists error
  103. }
  104. }),
  105. createUser('group-owner@overleaf.com').then(async userId => {
  106. const memberId = await createUser('group-member@overleaf.com')
  107. const subscription = new Subscription({
  108. admin_id: userId,
  109. member_ids: [memberId],
  110. manager_ids: [userId],
  111. groupPlan: true,
  112. planCode: 'group_professional_10_enterprise',
  113. membersLimit: 10,
  114. teamName: 'Test Team',
  115. customAccount: true,
  116. })
  117. try {
  118. await subscription.save()
  119. } catch (err) {
  120. if (!isAlreadyExistsErr(err)) throw err // ignore already exists error
  121. }
  122. }),
  123. createUser('with-old-history@overleaf.com', {
  124. isAdmin: true,
  125. forceProfessional: true,
  126. }).then(async userId => {
  127. const projectName = 'old history id (Uses v1 postgres storage)'
  128. const ownedProjects = await Project.find(
  129. { owner_ref: userId },
  130. { name: true }
  131. ).exec()
  132. for (const project of ownedProjects) {
  133. if (project.name === projectName) return
  134. }
  135. await createProjectWithOldHistoryId(userId, projectName)
  136. }),
  137. ])
  138. }
  139. /**
  140. * @param {unknown} err
  141. * @return {boolean}
  142. */
  143. function isAlreadyExistsErr(err) {
  144. return err instanceof Error && 'code' in err && err.code === 11000
  145. }
  146. async function main() {
  147. if (process.env.NODE_ENV !== 'development') {
  148. throw new Error('only available in dev-env')
  149. }
  150. const extraSplitTests = JSON.parse(
  151. await fs.promises.readFile(
  152. Path.join(MONOREPO, '.devcontainer/extra-split-tests.json'),
  153. 'utf-8'
  154. )
  155. )
  156. await waitForDb()
  157. await Promise.all([
  158. provisionUsers(),
  159. provisionSplitTests(true, extraSplitTests),
  160. ])
  161. }
  162. if (import.meta.main) {
  163. await main()
  164. await GracefulShutdown.gracefulShutdown()
  165. }