diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index f8d63bb272..ea0959a63d 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -54,6 +54,7 @@ const Features = require('../../infrastructure/Features') const BrandVariationsHandler = require('../BrandVariations/BrandVariationsHandler') const { getUserAffiliations } = require('../Institutions/InstitutionsAPI') const V1Handler = require('../V1/V1Handler') +const { Project } = require('../../models/Project') module.exports = ProjectController = { _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) { res.setTimeout(5 * 60 * 1000) // allow extra time for the copy to complete metrics.inc('cloned-project') diff --git a/services/web/app/src/router.js b/services/web/app/src/router.js index 7633b66290..c489dd96ca 100644 --- a/services/web/app/src/router.js +++ b/services/web/app/src/router.js @@ -443,6 +443,18 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) { CompileController.wordCount ) + webRouter.post( + '/project/:project_id/trash', + AuthorizationMiddleware.ensureUserCanReadProject, + ProjectController.trashProject + ) + + webRouter.delete( + '/project/:project_id/trash', + AuthorizationMiddleware.ensureUserCanReadProject, + ProjectController.untrashProject + ) + webRouter.delete( '/Project/:Project_id', AuthorizationMiddleware.ensureUserCanAdminProject, diff --git a/services/web/test/acceptance/src/ProjectCRUDTests.js b/services/web/test/acceptance/src/ProjectCRUDTests.js index 853017b2ae..93768482df 100644 --- a/services/web/test/acceptance/src/ProjectCRUDTests.js +++ b/services/web/test/acceptance/src/ProjectCRUDTests.js @@ -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 async = require('async') -const User = require('./helpers/User') +const User = require('./helpers/User').promises +const { Project } = require('../../../app/src/models/Project') describe('Project CRUD', function() { - beforeEach(function(done) { + beforeEach(async function() { 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() { - it('should return 404', function(done) { - return this.user.request.get( - '/project/aaaaaaaaaaaaaaaaaaaaaaaa', - (err, res, body) => { - expect(res.statusCode).to.equal(404) - return done() - } + it('should return 404', async function() { + const { response } = await this.user.doRequest( + 'GET', + '/project/aaaaaaaaaaaaaaaaaaaaaaaa' ) + expect(response.statusCode).to.equal(404) }) }) describe('when project has malformed id', function() { - it('should return 404', function(done) { - return this.user.request.get('/project/blah', (err, res, body) => { - expect(res.statusCode).to.equal(404) - return done() - }) + it('should return 404', async function() { + const { response } = await this.user.doRequest('GET', '/project/blah') + expect(response.statusCode).to.equal(404) + }) + }) + + 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) +} diff --git a/services/web/test/unit/src/Project/ProjectControllerTests.js b/services/web/test/unit/src/Project/ProjectControllerTests.js index a5c6d05a7f..1268eb9ba7 100644 --- a/services/web/test/unit/src/Project/ProjectControllerTests.js +++ b/services/web/test/unit/src/Project/ProjectControllerTests.js @@ -171,7 +171,8 @@ describe('ProjectController', function() { '../Institutions/InstitutionsAPI': { getUserAffiliations: this.getUserAffiliations }, - '../V1/V1Handler': {} + '../V1/V1Handler': {}, + '../../models/Project': {} } })