mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #9493 from overleaf/jpa-dropbox-create-project-action
[misc] create new project (folder) when creating project in dropbox/web GitOrigin-RevId: 4235b6ed66d0957bf45cb6f6009201ee02e188ca
This commit is contained in:
parent
ac91f40c08
commit
37c69ec830
7 changed files with 155 additions and 5 deletions
|
@ -9,6 +9,7 @@ const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
|||
const TpdsProjectFlusher = require('../ThirdPartyDataStore/TpdsProjectFlusher')
|
||||
const CollaboratorsGetter = require('./CollaboratorsGetter')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const TpdsUpdateSender = require('../ThirdPartyDataStore/TpdsUpdateSender')
|
||||
|
||||
module.exports = {
|
||||
userIsTokenMember: callbackify(userIsTokenMember),
|
||||
|
@ -98,6 +99,8 @@ async function addUserIdToProject(
|
|||
privilegeLevel
|
||||
) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
owner_ref: 1,
|
||||
name: 1,
|
||||
collaberator_refs: 1,
|
||||
readOnly_refs: 1,
|
||||
})
|
||||
|
@ -127,6 +130,14 @@ async function addUserIdToProject(
|
|||
|
||||
await Project.updateOne({ _id: projectId }, { $addToSet: level }).exec()
|
||||
|
||||
// Ensure there is a dedicated folder for this "new" project.
|
||||
await TpdsUpdateSender.promises.createProject({
|
||||
projectId,
|
||||
projectName: project.name,
|
||||
ownerId: project.owner_ref,
|
||||
userId,
|
||||
})
|
||||
|
||||
// Flush to TPDS in background to add files to collaborator's Dropbox
|
||||
TpdsProjectFlusher.promises.flushProjectToTpds(projectId).catch(err => {
|
||||
logger.error(
|
||||
|
|
|
@ -15,6 +15,7 @@ const path = require('path')
|
|||
const { callbackify } = require('util')
|
||||
const _ = require('underscore')
|
||||
const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
||||
const TpdsUpdateSender = require('../ThirdPartyDataStore/TpdsUpdateSender')
|
||||
|
||||
const MONTH_NAMES = [
|
||||
'January',
|
||||
|
@ -35,9 +36,19 @@ const templateProjectDir = Features.hasFeature('saas')
|
|||
? 'example-project'
|
||||
: 'example-project-sp'
|
||||
|
||||
async function createBlankProject(ownerId, projectName, attributes = {}) {
|
||||
async function createBlankProject(
|
||||
ownerId,
|
||||
projectName,
|
||||
attributes = {},
|
||||
options
|
||||
) {
|
||||
const isImport = attributes && attributes.overleaf
|
||||
const project = await _createBlankProject(ownerId, projectName, attributes)
|
||||
const project = await _createBlankProject(
|
||||
ownerId,
|
||||
projectName,
|
||||
attributes,
|
||||
options
|
||||
)
|
||||
const segmentation = _.pick(attributes, [
|
||||
'fromV1TemplateId',
|
||||
'fromV1TemplateVersionId',
|
||||
|
@ -131,7 +142,12 @@ async function _addExampleProjectFiles(ownerId, projectName, project) {
|
|||
)
|
||||
}
|
||||
|
||||
async function _createBlankProject(ownerId, projectName, attributes = {}) {
|
||||
async function _createBlankProject(
|
||||
ownerId,
|
||||
projectName,
|
||||
attributes = {},
|
||||
{ skipCreatingInTPDS = false } = {}
|
||||
) {
|
||||
metrics.inc('project-creation')
|
||||
const timer = new metrics.Timer('project-creation')
|
||||
await ProjectDetailsHandler.promises.validateProjectName(projectName)
|
||||
|
@ -171,6 +187,14 @@ async function _createBlankProject(ownerId, projectName, attributes = {}) {
|
|||
const user = await User.findById(ownerId, 'ace.spellCheckLanguage')
|
||||
project.spellCheckLanguage = user.ace.spellCheckLanguage
|
||||
await project.save()
|
||||
if (!skipCreatingInTPDS) {
|
||||
await TpdsUpdateSender.promises.createProject({
|
||||
projectId: project._id,
|
||||
projectName,
|
||||
ownerId,
|
||||
userId: ownerId,
|
||||
})
|
||||
}
|
||||
timer.done()
|
||||
return project
|
||||
}
|
||||
|
|
|
@ -7,9 +7,29 @@ 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'
|
||||
|
@ -175,6 +195,7 @@ function splitPath(projectId, path) {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
createProject: expressify(createProject),
|
||||
mergeUpdate: expressify(mergeUpdate),
|
||||
deleteUpdate: expressify(deleteUpdate),
|
||||
updateFolder: expressify(updateFolder),
|
||||
|
|
|
@ -142,6 +142,33 @@ async function deleteEntity(params) {
|
|||
}
|
||||
}
|
||||
|
||||
async function createProject(params) {
|
||||
if (!tpdsUrl) return // Server CE/Pro
|
||||
|
||||
const { projectId, projectName, ownerId, userId } = params
|
||||
|
||||
const job = {
|
||||
method: 'post',
|
||||
headers: {
|
||||
sl_project_id: projectId.toString(),
|
||||
sl_all_user_ids: JSON.stringify([userId.toString()]),
|
||||
sl_project_owner_user_id: ownerId.toString(),
|
||||
},
|
||||
uri: Path.join(
|
||||
tpdsUrl,
|
||||
'user',
|
||||
userId.toString(),
|
||||
'project',
|
||||
'new',
|
||||
encodeURIComponent(projectName)
|
||||
),
|
||||
title: 'createProject',
|
||||
sl_all_user_ids: JSON.stringify([userId]),
|
||||
}
|
||||
|
||||
await enqueue(userId, 'standardHttpRequest', job)
|
||||
}
|
||||
|
||||
async function deleteProject(params) {
|
||||
const { projectId } = params
|
||||
// deletion only applies to project archiver
|
||||
|
@ -274,6 +301,7 @@ const TpdsUpdateSender = {
|
|||
addEntity: callbackify(addEntity),
|
||||
addFile: callbackify(addFile),
|
||||
deleteEntity: callbackify(deleteEntity),
|
||||
createProject: callbackify(createProject),
|
||||
deleteProject: callbackify(deleteProject),
|
||||
enqueue: callbackify(enqueue),
|
||||
moveEntity: callbackify(moveEntity),
|
||||
|
@ -283,6 +311,7 @@ const TpdsUpdateSender = {
|
|||
addEntity,
|
||||
addFile,
|
||||
deleteEntity,
|
||||
createProject,
|
||||
deleteProject,
|
||||
enqueue,
|
||||
moveEntity,
|
||||
|
|
|
@ -908,6 +908,11 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
|||
DocumentController.setDocument
|
||||
)
|
||||
|
||||
privateApiRouter.post(
|
||||
'/user/:user_id/project/new',
|
||||
AuthenticationController.requirePrivateApiAuth(),
|
||||
TpdsController.createProject
|
||||
)
|
||||
privateApiRouter.post(
|
||||
'/tpds/folder-update',
|
||||
AuthenticationController.requirePrivateApiAuth(),
|
||||
|
|
|
@ -20,6 +20,8 @@ describe('CollaboratorsHandler', function () {
|
|||
this.addingUserId = ObjectId()
|
||||
this.project = {
|
||||
_id: ObjectId(),
|
||||
owner_ref: this.addingUserId,
|
||||
name: 'Foo',
|
||||
}
|
||||
|
||||
this.archivedProject = {
|
||||
|
@ -46,6 +48,11 @@ describe('CollaboratorsHandler', function () {
|
|||
flushProjectToTpds: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.TpdsUpdateSender = {
|
||||
promises: {
|
||||
createProject: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.ProjectGetter = {
|
||||
promises: {
|
||||
getProject: sinon.stub().resolves(this.project),
|
||||
|
@ -66,6 +73,7 @@ describe('CollaboratorsHandler', function () {
|
|||
'../Contacts/ContactManager': this.ContactManager,
|
||||
'../../models/Project': { Project },
|
||||
'../ThirdPartyDataStore/TpdsProjectFlusher': this.TpdsProjectFlusher,
|
||||
'../ThirdPartyDataStore/TpdsUpdateSender': this.TpdsUpdateSender,
|
||||
'../Project/ProjectGetter': this.ProjectGetter,
|
||||
'../Project/ProjectHelper': this.ProjectHelper,
|
||||
'./CollaboratorsGetter': this.CollaboratorsGetter,
|
||||
|
@ -212,6 +220,17 @@ describe('CollaboratorsHandler', function () {
|
|||
)
|
||||
})
|
||||
|
||||
it('should create the project folder in dropbox', function () {
|
||||
expect(
|
||||
this.TpdsUpdateSender.promises.createProject
|
||||
).to.have.been.calledWith({
|
||||
projectId: this.project._id,
|
||||
projectName: this.project.name,
|
||||
ownerId: this.addingUserId,
|
||||
userId: this.userId,
|
||||
})
|
||||
})
|
||||
|
||||
it('should flush the project to the TPDS', function () {
|
||||
expect(
|
||||
this.TpdsProjectFlusher.promises.flushProjectToTpds
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
const { ObjectId } = require('mongodb')
|
||||
const { expect } = require('chai')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const { ObjectId } = require('mongodb')
|
||||
const Errors = require('../../../../app/src/Features/Errors/Errors')
|
||||
const MockResponse = require('../helpers/MockResponse')
|
||||
const MockRequest = require('../helpers/MockRequest')
|
||||
|
||||
const MODULE_PATH =
|
||||
'../../../../app/src/Features/ThirdPartyDataStore/TpdsController.js'
|
||||
|
@ -44,6 +46,15 @@ describe('TpdsController', function () {
|
|||
conflict: sinon.stub(),
|
||||
}
|
||||
|
||||
this.newProject = { _id: ObjectId() }
|
||||
this.ProjectCreationHandler = {
|
||||
promises: { createBlankProject: sinon.stub().resolves(this.newProject) },
|
||||
}
|
||||
this.ProjectDetailsHandler = {
|
||||
promises: {
|
||||
generateUniqueName: sinon.stub().resolves('unique'),
|
||||
},
|
||||
}
|
||||
this.TpdsController = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'./TpdsUpdateHandler': this.TpdsUpdateHandler,
|
||||
|
@ -52,12 +63,42 @@ describe('TpdsController', function () {
|
|||
'../Authentication/SessionManager': this.SessionManager,
|
||||
'../Errors/HttpErrorHandler': this.HttpErrorHandler,
|
||||
'./TpdsQueueManager': this.TpdsQueueManager,
|
||||
'../Project/ProjectCreationHandler': this.ProjectCreationHandler,
|
||||
'../Project/ProjectDetailsHandler': this.ProjectDetailsHandler,
|
||||
},
|
||||
})
|
||||
|
||||
this.user_id = 'dsad29jlkjas'
|
||||
})
|
||||
|
||||
describe('creating a project', function () {
|
||||
it('should yield the new projects id', function (done) {
|
||||
const res = new MockResponse()
|
||||
const req = new MockRequest()
|
||||
req.params.user_id = this.user_id
|
||||
req.body = { projectName: 'foo' }
|
||||
res.callback = err => {
|
||||
if (err) done(err)
|
||||
expect(res.body).to.equal(
|
||||
JSON.stringify({ projectId: this.newProject._id.toString() })
|
||||
)
|
||||
expect(
|
||||
this.ProjectDetailsHandler.promises.generateUniqueName
|
||||
).to.have.been.calledWith(this.user_id, 'foo')
|
||||
expect(
|
||||
this.ProjectCreationHandler.promises.createBlankProject
|
||||
).to.have.been.calledWith(
|
||||
this.user_id,
|
||||
'unique',
|
||||
{},
|
||||
{ skipCreatingInTPDS: true }
|
||||
)
|
||||
done()
|
||||
}
|
||||
this.TpdsController.createProject(req, res)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getting an update', function () {
|
||||
beforeEach(function () {
|
||||
this.projectName = 'projectName'
|
||||
|
|
Loading…
Reference in a new issue