create_project.mjs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. // Script to create projects with sharelatex history for testing
  2. // Example:
  3. // node scripts/create_project.js --user-id=5dca84e11e71ae002ff73bd4 --name="My Test Project" --old-history
  4. import fs from 'fs'
  5. import path from 'path'
  6. import _ from 'lodash'
  7. import parseArgs from 'minimist'
  8. import OError from '@overleaf/o-error'
  9. import { waitForDb } from '../app/src/infrastructure/mongodb.js'
  10. import { User } from '../app/src/models/User.js'
  11. import ProjectCreationHandler from '../app/src/Features/Project/ProjectCreationHandler.js'
  12. import ProjectEntityUpdateHandler from '../app/src/Features/Project/ProjectEntityUpdateHandler.js'
  13. import ProjectEntityHandler from '../app/src/Features/Project/ProjectEntityHandler.js'
  14. import EditorController from '../app/src/Features/Editor/EditorController.js'
  15. import { fileURLToPath } from 'url'
  16. const __dirname = path.dirname(fileURLToPath(import.meta.url))
  17. const argv = parseArgs(process.argv.slice(2), {
  18. string: ['user-id', 'name', 'random-operations', 'extend-project-id'],
  19. boolean: ['random-content'],
  20. unknown: function (arg) {
  21. console.error('unrecognised argument', arg)
  22. process.exit(1)
  23. },
  24. })
  25. console.log('argv', argv)
  26. const userId = argv['user-id']
  27. const projectName = argv.name || `Test Project ${new Date().toISOString()}`
  28. let randomOperations = 0
  29. if (argv['random-content'] === true || argv['random-operations']) {
  30. randomOperations = parseInt(argv['random-operations'] || '1000', 10)
  31. }
  32. const extendProjectId = argv['extend-project-id']
  33. console.log('userId', userId)
  34. async function _createRootDoc(project, ownerId, docLines) {
  35. try {
  36. const { doc } = await ProjectEntityUpdateHandler.promises.addDoc(
  37. project._id,
  38. project.rootFolder[0]._id,
  39. 'main.tex',
  40. docLines,
  41. ownerId,
  42. 'create-project-script'
  43. )
  44. await ProjectEntityUpdateHandler.promises.setRootDoc(project._id, doc._id)
  45. } catch (error) {
  46. throw OError.tag(error, 'error adding root doc when creating project')
  47. }
  48. }
  49. async function _addDefaultExampleProjectFiles(ownerId, projectName, project) {
  50. const mainDocLines = await _buildTemplate(
  51. 'example-project/main.tex',
  52. ownerId,
  53. projectName
  54. )
  55. await _createRootDoc(project, ownerId, mainDocLines)
  56. const bibDocLines = await _buildTemplate(
  57. 'example-project/sample.bib',
  58. ownerId,
  59. projectName
  60. )
  61. await ProjectEntityUpdateHandler.promises.addDoc(
  62. project._id,
  63. project.rootFolder[0]._id,
  64. 'sample.bib',
  65. bibDocLines,
  66. ownerId,
  67. 'create-project-script'
  68. )
  69. const frogPath = path.join(
  70. __dirname,
  71. '/../app/templates/project_files/example-project/frog.jpg'
  72. )
  73. await ProjectEntityUpdateHandler.promises.addFile(
  74. project._id,
  75. project.rootFolder[0]._id,
  76. 'frog.jpg',
  77. frogPath,
  78. null,
  79. ownerId,
  80. 'create-project-script'
  81. )
  82. }
  83. async function _buildTemplate(templateName, userId, projectName) {
  84. const user = await User.findById(userId, 'first_name last_name')
  85. const templatePath = path.join(
  86. __dirname,
  87. `/../app/templates/project_files/${templateName}`
  88. )
  89. const template = fs.readFileSync(templatePath)
  90. const data = {
  91. project_name: projectName,
  92. user,
  93. year: new Date().getUTCFullYear(),
  94. month: new Date().getUTCMonth(),
  95. }
  96. const output = _.template(template.toString())(data)
  97. return output.split('\n')
  98. }
  99. // Create a project with some random content and file operations for testing history migrations
  100. // Unfortunately we cannot easily change the timestamps of the history entries, so everything
  101. // will be created at the same time.
  102. async function _pickRandomDoc(projectId) {
  103. const result = await ProjectEntityHandler.promises.getAllDocs(projectId)
  104. const keys = Object.keys(result)
  105. if (keys.length === 0) {
  106. return null
  107. }
  108. const filepath = _.sample(keys)
  109. result[filepath].path = filepath
  110. return result[filepath]
  111. }
  112. let COUNTER = 0
  113. // format counter as a 6 digit zero padded number
  114. function nextId() {
  115. return ('000000' + COUNTER++).slice(-6)
  116. }
  117. async function _applyRandomDocUpdate(ownerId, projectId) {
  118. const action = _.sample(['create', 'edit', 'delete', 'rename'])
  119. switch (action) {
  120. case 'create': // create a new doc
  121. await EditorController.promises.upsertDocWithPath(
  122. projectId,
  123. `subdir/new-doc-${nextId()}.tex`,
  124. [`This is a new doc ${new Date().toISOString()}`],
  125. 'create-project-script',
  126. ownerId
  127. )
  128. break
  129. case 'edit': {
  130. // edit an existing doc
  131. const doc = await _pickRandomDoc(projectId)
  132. if (!doc) {
  133. return
  134. }
  135. // pick a random line and either insert or delete a character
  136. const lines = doc.lines
  137. const index = _.random(0, lines.length - 1)
  138. let thisLine = lines[index]
  139. const pos = _.random(0, thisLine.length - 1)
  140. if (Math.random() > 0.5) {
  141. // insert a character
  142. thisLine = thisLine.slice(0, pos) + 'x' + thisLine.slice(pos)
  143. } else {
  144. // delete a character
  145. thisLine = thisLine.slice(0, pos) + thisLine.slice(pos + 1)
  146. }
  147. lines[index] = thisLine
  148. await EditorController.promises.upsertDocWithPath(
  149. projectId,
  150. doc.path,
  151. lines,
  152. 'create-project-script',
  153. ownerId
  154. )
  155. break
  156. }
  157. case 'delete': {
  158. // delete an existing doc (but not the root doc)
  159. const doc = await _pickRandomDoc(projectId)
  160. if (!doc || doc.path === '/main.tex') {
  161. return
  162. }
  163. await EditorController.promises.deleteEntityWithPath(
  164. projectId,
  165. doc.path,
  166. 'create-project-script',
  167. ownerId
  168. )
  169. break
  170. }
  171. case 'rename': {
  172. // rename an existing doc (but not the root doc)
  173. const doc = await _pickRandomDoc(projectId)
  174. if (!doc || doc.path === '/main.tex') {
  175. return
  176. }
  177. const newName = `renamed-${nextId()}.tex`
  178. await EditorController.promises.renameEntity(
  179. projectId,
  180. doc._id,
  181. 'doc',
  182. newName,
  183. ownerId,
  184. 'create-project-script'
  185. )
  186. break
  187. }
  188. }
  189. }
  190. async function createProject() {
  191. await waitForDb()
  192. const user = await User.findById(userId)
  193. console.log('Will create project')
  194. console.log('user_id:', userId, '=>', user.email)
  195. let projectId
  196. if (extendProjectId) {
  197. console.log('extending existing project', extendProjectId)
  198. projectId = extendProjectId
  199. } else {
  200. console.log('project name:', projectName)
  201. const project = await ProjectCreationHandler.promises.createBlankProject(
  202. userId,
  203. projectName
  204. )
  205. await _addDefaultExampleProjectFiles(userId, projectName, project)
  206. projectId = project._id
  207. }
  208. for (let i = 0; i < randomOperations; i++) {
  209. await _applyRandomDocUpdate(userId, projectId)
  210. }
  211. return projectId
  212. }
  213. try {
  214. const projectId = await createProject()
  215. console.log('Created project', projectId)
  216. process.exit()
  217. } catch (error) {
  218. console.error(error)
  219. process.exit(1)
  220. }