Merge pull request #2152 from overleaf/as-per-user-trash-backend

Add per-user trash endpoint

GitOrigin-RevId: 94a6e3416b047e1f8721159ac0d049e98785e5ce
This commit is contained in:
Eric Mc Sween 2019-09-26 08:42:15 -04:00 committed by sharelatex
parent 9a31361795
commit 9cd5af840a
4 changed files with 134 additions and 29 deletions

View file

@ -54,6 +54,7 @@ const Features = require('../../infrastructure/Features')
const BrandVariationsHandler = require('../BrandVariations/BrandVariationsHandler') const BrandVariationsHandler = require('../BrandVariations/BrandVariationsHandler')
const { getUserAffiliations } = require('../Institutions/InstitutionsAPI') const { getUserAffiliations } = require('../Institutions/InstitutionsAPI')
const V1Handler = require('../V1/V1Handler') const V1Handler = require('../V1/V1Handler')
const { Project } = require('../../models/Project')
module.exports = ProjectController = { module.exports = ProjectController = {
_isInPercentageRollout(rolloutName, objectId, percentage) { _isInPercentageRollout(rolloutName, objectId, percentage) {
@ -199,6 +200,38 @@ module.exports = ProjectController = {
}) })
}, },
trashProject(req, res, next) {
const projectId = req.params.project_id
const userId = AuthenticationController.getLoggedInUserId(req)
Project.update(
{ _id: projectId },
{ $addToSet: { trashed: userId } },
error => {
if (error) {
return next(error)
}
res.sendStatus(200)
}
)
},
untrashProject(req, res, next) {
const projectId = req.params.project_id
const userId = AuthenticationController.getLoggedInUserId(req)
Project.update(
{ _id: projectId },
{ $pull: { trashed: userId } },
error => {
if (error) {
return next(error)
}
res.sendStatus(200)
}
)
},
cloneProject(req, res, next) { cloneProject(req, res, next) {
res.setTimeout(5 * 60 * 1000) // allow extra time for the copy to complete res.setTimeout(5 * 60 * 1000) // allow extra time for the copy to complete
metrics.inc('cloned-project') metrics.inc('cloned-project')

View file

@ -443,6 +443,18 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
CompileController.wordCount CompileController.wordCount
) )
webRouter.post(
'/project/:project_id/trash',
AuthorizationMiddleware.ensureUserCanReadProject,
ProjectController.trashProject
)
webRouter.delete(
'/project/:project_id/trash',
AuthorizationMiddleware.ensureUserCanReadProject,
ProjectController.untrashProject
)
webRouter.delete( webRouter.delete(
'/Project/:Project_id', '/Project/:Project_id',
AuthorizationMiddleware.ensureUserCanAdminProject, AuthorizationMiddleware.ensureUserCanAdminProject,

View file

@ -1,43 +1,102 @@
/* eslint-disable
handle-callback-err,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { expect } = require('chai') const { expect } = require('chai')
const async = require('async') const User = require('./helpers/User').promises
const User = require('./helpers/User') const { Project } = require('../../../app/src/models/Project')
describe('Project CRUD', function() { describe('Project CRUD', function() {
beforeEach(function(done) { beforeEach(async function() {
this.user = new User() this.user = new User()
return this.user.login(done) await this.user.login()
this.projectId = await this.user.createProject('example-project')
})
afterEach(async function() {
// TODO: This can be removed after migrations are merged
await Project.deleteMany({}).exec()
}) })
describe("when project doesn't exist", function() { describe("when project doesn't exist", function() {
it('should return 404', function(done) { it('should return 404', async function() {
return this.user.request.get( const { response } = await this.user.doRequest(
'/project/aaaaaaaaaaaaaaaaaaaaaaaa', 'GET',
(err, res, body) => { '/project/aaaaaaaaaaaaaaaaaaaaaaaa'
expect(res.statusCode).to.equal(404)
return done()
}
) )
expect(response.statusCode).to.equal(404)
}) })
}) })
describe('when project has malformed id', function() { describe('when project has malformed id', function() {
it('should return 404', function(done) { it('should return 404', async function() {
return this.user.request.get('/project/blah', (err, res, body) => { const { response } = await this.user.doRequest('GET', '/project/blah')
expect(res.statusCode).to.equal(404) expect(response.statusCode).to.equal(404)
return done() })
}) })
describe('when trashing a project', function() {
it('should mark the project as trashed for the user', async function() {
const { response } = await this.user.doRequest(
'POST',
`/project/${this.projectId}/trash`
)
expect(response.statusCode).to.equal(200)
const trashedProject = await Project.findById(this.projectId).exec()
expectObjectIdArrayEqual(trashedProject.trashed, [this.user._id])
})
it('does nothing if the user has already trashed the project', async function() {
// Mark as trashed the first time
await this.user.doRequest('POST', `/project/${this.projectId}/trash`)
// And then a second time
await this.user.doRequest('POST', `/project/${this.projectId}/trash`)
const trashedProject = await Project.findById(this.projectId).exec()
expectObjectIdArrayEqual(trashedProject.trashed, [this.user._id])
})
})
describe('when untrashing a project', function() {
it('should mark the project as untrashed for the user', async function() {
await Project.update(
{ _id: this.projectId },
{ trashed: [this.user._id] }
)
const { response } = await this.user.doRequest(
'DELETE',
`/project/${this.projectId}/trash`
)
expect(response.statusCode).to.equal(200)
const trashedProject = await Project.findById(this.projectId).exec()
expectObjectIdArrayEqual(trashedProject.trashed, [])
})
it('does nothing if the user has already untrashed the project', async function() {
await Project.update(
{ _id: this.projectId },
{ trashed: [this.user._id] }
)
// Mark as untrashed the first time
await this.user.doRequest('DELETE', `/project/${this.projectId}/trash`)
// And then a second time
await this.user.doRequest('DELETE', `/project/${this.projectId}/trash`)
const trashedProject = await Project.findById(this.projectId).exec()
expectObjectIdArrayEqual(trashedProject.trashed, [])
})
it('sets trashed to an empty array if not set', async function() {
await this.user.doRequest('DELETE', `/project/${this.projectId}/trash`)
const trashedProject = await Project.findById(this.projectId).exec()
expectObjectIdArrayEqual(trashedProject.trashed, [])
}) })
}) })
}) })
function expectObjectIdArrayEqual(objectIdArray, stringArray) {
const stringifiedArray = objectIdArray.map(id => id.toString())
expect(stringifiedArray).to.deep.equal(stringArray)
}

View file

@ -171,7 +171,8 @@ describe('ProjectController', function() {
'../Institutions/InstitutionsAPI': { '../Institutions/InstitutionsAPI': {
getUserAffiliations: this.getUserAffiliations getUserAffiliations: this.getUserAffiliations
}, },
'../V1/V1Handler': {} '../V1/V1Handler': {},
'../../models/Project': {}
} }
}) })