2021-07-12 06:40:45 -04:00
|
|
|
// Script to create projects with sharelatex history for testing
|
|
|
|
// Example:
|
|
|
|
// node scripts/create_project.js --user-id=5dca84e11e71ae002ff73bd4 --name="My Test Project" --old-history
|
|
|
|
|
2024-11-04 04:09:11 -05:00
|
|
|
import fs from 'node:fs'
|
2024-10-18 07:04:57 -04:00
|
|
|
|
2024-11-04 04:09:11 -05:00
|
|
|
import path from 'node:path'
|
2024-10-18 07:04:57 -04:00
|
|
|
import _ from 'lodash'
|
|
|
|
import parseArgs from 'minimist'
|
|
|
|
import OError from '@overleaf/o-error'
|
|
|
|
import { User } from '../app/src/models/User.js'
|
|
|
|
import ProjectCreationHandler from '../app/src/Features/Project/ProjectCreationHandler.js'
|
|
|
|
import ProjectEntityUpdateHandler from '../app/src/Features/Project/ProjectEntityUpdateHandler.js'
|
|
|
|
import ProjectEntityHandler from '../app/src/Features/Project/ProjectEntityHandler.js'
|
|
|
|
import EditorController from '../app/src/Features/Editor/EditorController.js'
|
2024-11-04 04:09:11 -05:00
|
|
|
import { fileURLToPath } from 'node:url'
|
2024-10-18 07:04:57 -04:00
|
|
|
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
2021-07-12 06:40:45 -04:00
|
|
|
|
|
|
|
const argv = parseArgs(process.argv.slice(2), {
|
2024-10-10 06:20:04 -04:00
|
|
|
string: ['user-id', 'name', 'random-operations', 'extend-project-id'],
|
|
|
|
boolean: ['random-content'],
|
2021-07-12 06:40:45 -04:00
|
|
|
unknown: function (arg) {
|
|
|
|
console.error('unrecognised argument', arg)
|
|
|
|
process.exit(1)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
console.log('argv', argv)
|
|
|
|
|
|
|
|
const userId = argv['user-id']
|
|
|
|
const projectName = argv.name || `Test Project ${new Date().toISOString()}`
|
2024-10-10 06:20:04 -04:00
|
|
|
let randomOperations = 0
|
|
|
|
if (argv['random-content'] === true || argv['random-operations']) {
|
|
|
|
randomOperations = parseInt(argv['random-operations'] || '1000', 10)
|
|
|
|
}
|
|
|
|
const extendProjectId = argv['extend-project-id']
|
2021-07-12 06:40:45 -04:00
|
|
|
|
|
|
|
console.log('userId', userId)
|
|
|
|
|
|
|
|
async function _createRootDoc(project, ownerId, docLines) {
|
|
|
|
try {
|
|
|
|
const { doc } = await ProjectEntityUpdateHandler.promises.addDoc(
|
|
|
|
project._id,
|
|
|
|
project.rootFolder[0]._id,
|
|
|
|
'main.tex',
|
|
|
|
docLines,
|
2022-08-25 08:01:39 -04:00
|
|
|
ownerId,
|
2024-10-10 06:20:04 -04:00
|
|
|
'create-project-script'
|
2021-07-12 06:40:45 -04:00
|
|
|
)
|
|
|
|
await ProjectEntityUpdateHandler.promises.setRootDoc(project._id, doc._id)
|
|
|
|
} catch (error) {
|
|
|
|
throw OError.tag(error, 'error adding root doc when creating project')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function _addDefaultExampleProjectFiles(ownerId, projectName, project) {
|
2021-08-19 05:37:25 -04:00
|
|
|
const mainDocLines = await _buildTemplate(
|
|
|
|
'example-project/main.tex',
|
|
|
|
ownerId,
|
|
|
|
projectName
|
|
|
|
)
|
2021-07-12 06:40:45 -04:00
|
|
|
await _createRootDoc(project, ownerId, mainDocLines)
|
|
|
|
|
2021-08-19 05:37:25 -04:00
|
|
|
const bibDocLines = await _buildTemplate(
|
|
|
|
'example-project/sample.bib',
|
2021-07-12 06:40:45 -04:00
|
|
|
ownerId,
|
|
|
|
projectName
|
|
|
|
)
|
|
|
|
await ProjectEntityUpdateHandler.promises.addDoc(
|
|
|
|
project._id,
|
|
|
|
project.rootFolder[0]._id,
|
2021-08-19 05:37:25 -04:00
|
|
|
'sample.bib',
|
|
|
|
bibDocLines,
|
2022-08-25 08:01:39 -04:00
|
|
|
ownerId,
|
2024-10-10 06:20:04 -04:00
|
|
|
'create-project-script'
|
2021-07-12 06:40:45 -04:00
|
|
|
)
|
|
|
|
|
2021-09-14 06:43:57 -04:00
|
|
|
const frogPath = path.join(
|
|
|
|
__dirname,
|
|
|
|
'/../app/templates/project_files/example-project/frog.jpg'
|
2021-07-12 06:40:45 -04:00
|
|
|
)
|
|
|
|
await ProjectEntityUpdateHandler.promises.addFile(
|
|
|
|
project._id,
|
|
|
|
project.rootFolder[0]._id,
|
2021-08-19 05:37:25 -04:00
|
|
|
'frog.jpg',
|
|
|
|
frogPath,
|
2021-07-12 06:40:45 -04:00
|
|
|
null,
|
2022-08-25 08:01:39 -04:00
|
|
|
ownerId,
|
2024-10-10 06:20:04 -04:00
|
|
|
'create-project-script'
|
2021-07-12 06:40:45 -04:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
async function _buildTemplate(templateName, userId, projectName) {
|
|
|
|
const user = await User.findById(userId, 'first_name last_name')
|
|
|
|
|
2021-09-14 06:43:57 -04:00
|
|
|
const templatePath = path.join(
|
|
|
|
__dirname,
|
|
|
|
`/../app/templates/project_files/${templateName}`
|
2021-07-12 06:40:45 -04:00
|
|
|
)
|
|
|
|
const template = fs.readFileSync(templatePath)
|
|
|
|
const data = {
|
|
|
|
project_name: projectName,
|
|
|
|
user,
|
|
|
|
year: new Date().getUTCFullYear(),
|
|
|
|
month: new Date().getUTCMonth(),
|
|
|
|
}
|
|
|
|
const output = _.template(template.toString())(data)
|
|
|
|
return output.split('\n')
|
|
|
|
}
|
|
|
|
|
2023-02-02 04:55:25 -05:00
|
|
|
// Create a project with some random content and file operations for testing history migrations
|
|
|
|
// Unfortunately we cannot easily change the timestamps of the history entries, so everything
|
|
|
|
// will be created at the same time.
|
|
|
|
|
2024-10-10 06:20:04 -04:00
|
|
|
async function _pickRandomDoc(projectId) {
|
|
|
|
const result = await ProjectEntityHandler.promises.getAllDocs(projectId)
|
2023-02-02 04:55:25 -05:00
|
|
|
const keys = Object.keys(result)
|
|
|
|
if (keys.length === 0) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
const filepath = _.sample(keys)
|
|
|
|
result[filepath].path = filepath
|
|
|
|
return result[filepath]
|
|
|
|
}
|
|
|
|
|
|
|
|
let COUNTER = 0
|
|
|
|
// format counter as a 6 digit zero padded number
|
|
|
|
function nextId() {
|
|
|
|
return ('000000' + COUNTER++).slice(-6)
|
|
|
|
}
|
|
|
|
|
2024-10-10 06:20:04 -04:00
|
|
|
async function _applyRandomDocUpdate(ownerId, projectId) {
|
2023-02-02 04:55:25 -05:00
|
|
|
const action = _.sample(['create', 'edit', 'delete', 'rename'])
|
|
|
|
switch (action) {
|
|
|
|
case 'create': // create a new doc
|
|
|
|
await EditorController.promises.upsertDocWithPath(
|
2024-10-10 06:20:04 -04:00
|
|
|
projectId,
|
2023-02-02 04:55:25 -05:00
|
|
|
`subdir/new-doc-${nextId()}.tex`,
|
|
|
|
[`This is a new doc ${new Date().toISOString()}`],
|
|
|
|
'create-project-script',
|
|
|
|
ownerId
|
|
|
|
)
|
|
|
|
break
|
|
|
|
case 'edit': {
|
|
|
|
// edit an existing doc
|
2024-10-10 06:20:04 -04:00
|
|
|
const doc = await _pickRandomDoc(projectId)
|
2023-02-02 04:55:25 -05:00
|
|
|
if (!doc) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// pick a random line and either insert or delete a character
|
|
|
|
const lines = doc.lines
|
|
|
|
const index = _.random(0, lines.length - 1)
|
|
|
|
let thisLine = lines[index]
|
|
|
|
const pos = _.random(0, thisLine.length - 1)
|
|
|
|
if (Math.random() > 0.5) {
|
|
|
|
// insert a character
|
|
|
|
thisLine = thisLine.slice(0, pos) + 'x' + thisLine.slice(pos)
|
|
|
|
} else {
|
|
|
|
// delete a character
|
|
|
|
thisLine = thisLine.slice(0, pos) + thisLine.slice(pos + 1)
|
|
|
|
}
|
|
|
|
lines[index] = thisLine
|
|
|
|
await EditorController.promises.upsertDocWithPath(
|
2024-10-10 06:20:04 -04:00
|
|
|
projectId,
|
2023-02-02 04:55:25 -05:00
|
|
|
doc.path,
|
|
|
|
lines,
|
|
|
|
'create-project-script',
|
|
|
|
ownerId
|
|
|
|
)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case 'delete': {
|
|
|
|
// delete an existing doc (but not the root doc)
|
2024-10-10 06:20:04 -04:00
|
|
|
const doc = await _pickRandomDoc(projectId)
|
2023-02-02 04:55:25 -05:00
|
|
|
if (!doc || doc.path === '/main.tex') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
await EditorController.promises.deleteEntityWithPath(
|
2024-10-10 06:20:04 -04:00
|
|
|
projectId,
|
2023-02-02 04:55:25 -05:00
|
|
|
doc.path,
|
2024-10-10 06:20:04 -04:00
|
|
|
'create-project-script',
|
|
|
|
ownerId
|
2023-02-02 04:55:25 -05:00
|
|
|
)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case 'rename': {
|
|
|
|
// rename an existing doc (but not the root doc)
|
2024-10-10 06:20:04 -04:00
|
|
|
const doc = await _pickRandomDoc(projectId)
|
2023-02-02 04:55:25 -05:00
|
|
|
if (!doc || doc.path === '/main.tex') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const newName = `renamed-${nextId()}.tex`
|
|
|
|
await EditorController.promises.renameEntity(
|
2024-10-10 06:20:04 -04:00
|
|
|
projectId,
|
2023-02-02 04:55:25 -05:00
|
|
|
doc._id,
|
|
|
|
'doc',
|
|
|
|
newName,
|
|
|
|
ownerId,
|
|
|
|
'create-project-script'
|
|
|
|
)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-12 06:40:45 -04:00
|
|
|
async function createProject() {
|
|
|
|
const user = await User.findById(userId)
|
|
|
|
console.log('Will create project')
|
|
|
|
console.log('user_id:', userId, '=>', user.email)
|
2024-10-10 06:20:04 -04:00
|
|
|
let projectId
|
|
|
|
if (extendProjectId) {
|
|
|
|
console.log('extending existing project', extendProjectId)
|
|
|
|
projectId = extendProjectId
|
|
|
|
} else {
|
|
|
|
console.log('project name:', projectName)
|
|
|
|
const project = await ProjectCreationHandler.promises.createBlankProject(
|
|
|
|
userId,
|
|
|
|
projectName
|
|
|
|
)
|
|
|
|
await _addDefaultExampleProjectFiles(userId, projectName, project)
|
|
|
|
projectId = project._id
|
|
|
|
}
|
|
|
|
for (let i = 0; i < randomOperations; i++) {
|
|
|
|
await _applyRandomDocUpdate(userId, projectId)
|
2023-02-02 04:55:25 -05:00
|
|
|
}
|
2024-10-10 06:20:04 -04:00
|
|
|
return projectId
|
2021-07-12 06:40:45 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 07:04:57 -04:00
|
|
|
try {
|
|
|
|
const projectId = await createProject()
|
|
|
|
console.log('Created project', projectId)
|
|
|
|
process.exit()
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error)
|
|
|
|
process.exit(1)
|
|
|
|
}
|