mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-27 11:01:56 +02:00
[misc] create new project (folder) when creating project in dropbox/web GitOrigin-RevId: 4235b6ed66d0957bf45cb6f6009201ee02e188ca
209 lines
6.2 KiB
JavaScript
209 lines
6.2 KiB
JavaScript
const { expressify } = require('../../util/promises')
|
|
const TpdsUpdateHandler = require('./TpdsUpdateHandler')
|
|
const UpdateMerger = require('./UpdateMerger')
|
|
const Errors = require('../Errors/Errors')
|
|
const logger = require('@overleaf/logger')
|
|
const Path = require('path')
|
|
const metrics = require('@overleaf/metrics')
|
|
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
|
|
const SessionManager = require('../Authentication/SessionManager')
|
|
const ProjectCreationHandler = require('../Project/ProjectCreationHandler')
|
|
const ProjectDetailsHandler = require('../Project/ProjectDetailsHandler')
|
|
const HttpErrorHandler = require('../Errors/HttpErrorHandler')
|
|
const TpdsQueueManager = require('./TpdsQueueManager')
|
|
|
|
async function createProject(req, res) {
|
|
const { user_id: userId } = req.params
|
|
let { projectName } = req.body
|
|
projectName = await ProjectDetailsHandler.promises.generateUniqueName(
|
|
userId,
|
|
projectName
|
|
)
|
|
const project = await ProjectCreationHandler.promises.createBlankProject(
|
|
userId,
|
|
projectName,
|
|
{},
|
|
{ skipCreatingInTPDS: true }
|
|
)
|
|
res.json({
|
|
projectId: project._id.toString(),
|
|
})
|
|
}
|
|
|
|
// mergeUpdate and deleteUpdate are used by Dropbox, where the project is only
|
|
// passed as the name, as the first part of the file path. They have to check
|
|
// the project exists, find it, and create it if not. They also ignore 'noisy'
|
|
// files like .DS_Store, .gitignore, etc.
|
|
|
|
async function mergeUpdate(req, res) {
|
|
metrics.inc('tpds.merge-update')
|
|
const { filePath, userId, projectId, projectName } = parseParams(req)
|
|
const source = req.headers['x-sl-update-source'] || 'unknown'
|
|
|
|
let metadata
|
|
try {
|
|
metadata = await TpdsUpdateHandler.promises.newUpdate(
|
|
userId,
|
|
projectId,
|
|
projectName,
|
|
filePath,
|
|
req,
|
|
source
|
|
)
|
|
} catch (err) {
|
|
if (err.name === 'TooManyRequestsError') {
|
|
logger.warn(
|
|
{ err, userId, filePath },
|
|
'tpds update failed to be processed, too many requests'
|
|
)
|
|
return res.sendStatus(429)
|
|
} else if (err.message === 'project_has_too_many_files') {
|
|
logger.warn(
|
|
{ err, userId, filePath },
|
|
'tpds trying to append to project over file limit'
|
|
)
|
|
NotificationsBuilder.tpdsFileLimit(userId).create(projectName)
|
|
return res.sendStatus(400)
|
|
} else {
|
|
throw err
|
|
}
|
|
}
|
|
|
|
if (metadata == null) {
|
|
return res.json({ status: 'rejected' })
|
|
}
|
|
|
|
const payload = {
|
|
status: 'applied',
|
|
projectId: metadata.projectId.toString(),
|
|
entityId: metadata.entityId.toString(),
|
|
entityType: metadata.entityType,
|
|
folderId: metadata.folderId.toString(),
|
|
}
|
|
|
|
// When the update is a doc edit, the update is merged in docupdater and
|
|
// doesn't generate a new rev.
|
|
if (metadata.rev != null) {
|
|
payload.rev = metadata.rev.toString()
|
|
}
|
|
res.json(payload)
|
|
}
|
|
|
|
async function deleteUpdate(req, res) {
|
|
metrics.inc('tpds.delete-update')
|
|
const { filePath, userId, projectId, projectName } = parseParams(req)
|
|
const source = req.headers['x-sl-update-source'] || 'unknown'
|
|
|
|
await TpdsUpdateHandler.promises.deleteUpdate(
|
|
userId,
|
|
projectId,
|
|
projectName,
|
|
filePath,
|
|
source
|
|
)
|
|
res.sendStatus(200)
|
|
}
|
|
|
|
/**
|
|
* Update endpoint that accepts update details as JSON
|
|
*/
|
|
async function updateFolder(req, res) {
|
|
const userId = req.body.userId
|
|
const projectId = req.body.projectId
|
|
const { projectName, filePath } = splitPath(projectId, req.body.path)
|
|
const metadata = await TpdsUpdateHandler.promises.createFolder(
|
|
userId,
|
|
projectId,
|
|
projectName,
|
|
filePath
|
|
)
|
|
if (metadata == null) {
|
|
return HttpErrorHandler.conflict(req, res, 'Could not create folder', {
|
|
userId,
|
|
projectName,
|
|
filePath,
|
|
})
|
|
}
|
|
res.json({
|
|
entityId: metadata.folderId.toString(),
|
|
projectId: metadata.projectId.toString(),
|
|
path: metadata.path,
|
|
folderId: metadata.parentFolderId?.toString() || null,
|
|
})
|
|
}
|
|
|
|
// updateProjectContents and deleteProjectContents are used by GitHub. The
|
|
// project_id is known so we can skip right ahead to creating/updating/deleting
|
|
// the file. These methods will not ignore noisy files like .DS_Store,
|
|
// .gitignore, etc because people are generally more explicit with the files
|
|
// they want in git.
|
|
|
|
async function updateProjectContents(req, res, next) {
|
|
const projectId = req.params.project_id
|
|
const path = `/${req.params[0]}` // UpdateMerger expects leading slash
|
|
const source = req.headers['x-sl-update-source'] || 'unknown'
|
|
|
|
try {
|
|
await UpdateMerger.promises.mergeUpdate(null, projectId, path, req, source)
|
|
} catch (error) {
|
|
if (error.constructor === Errors.InvalidNameError) {
|
|
return res.sendStatus(422)
|
|
} else {
|
|
throw error
|
|
}
|
|
}
|
|
res.sendStatus(200)
|
|
}
|
|
|
|
async function deleteProjectContents(req, res, next) {
|
|
const projectId = req.params.project_id
|
|
const path = `/${req.params[0]}` // UpdateMerger expects leading slash
|
|
const source = req.headers['x-sl-update-source'] || 'unknown'
|
|
|
|
await UpdateMerger.promises.deleteUpdate(null, projectId, path, source)
|
|
res.sendStatus(200)
|
|
}
|
|
|
|
async function getQueues(req, res, next) {
|
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
|
res.json(await TpdsQueueManager.promises.getQueues(userId))
|
|
}
|
|
|
|
function parseParams(req) {
|
|
const userId = req.params.user_id
|
|
const projectId = req.params.project_id
|
|
const { projectName, filePath } = splitPath(projectId, req.params[0])
|
|
return { filePath, userId, projectName, projectId }
|
|
}
|
|
|
|
function splitPath(projectId, path) {
|
|
let filePath, projectName
|
|
path = Path.join('/', path)
|
|
if (projectId) {
|
|
filePath = path
|
|
projectName = ''
|
|
} else if (path.substring(1).indexOf('/') === -1) {
|
|
filePath = '/'
|
|
projectName = path.substring(1)
|
|
} else {
|
|
filePath = path.substring(path.indexOf('/', 1))
|
|
projectName = path.substring(0, path.indexOf('/', 1))
|
|
projectName = projectName.replace('/', '')
|
|
}
|
|
|
|
return { filePath, projectName }
|
|
}
|
|
|
|
module.exports = {
|
|
createProject: expressify(createProject),
|
|
mergeUpdate: expressify(mergeUpdate),
|
|
deleteUpdate: expressify(deleteUpdate),
|
|
updateFolder: expressify(updateFolder),
|
|
updateProjectContents: expressify(updateProjectContents),
|
|
deleteProjectContents: expressify(deleteProjectContents),
|
|
getQueues: expressify(getQueues),
|
|
|
|
// for tests only
|
|
parseParams,
|
|
}
|