devcontainer_setup.mjs 4.8 KB

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