mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 06:33:55 +01:00
[web] Add deletedReason parameter to project deletion methods (#32221)
* [web] Add deletedReason parameter to project deletion methods * revert sinon.match.any in ProjectDuplicator negative assertion Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> GitOrigin-RevId: d1595eefe0e36150231ee9646fe5eba0786fd1f5
This commit is contained in:
committed by
Copybot
co-authored by
Copilot Autofix powered by AI
parent
c9ba2ac025
commit
6486ef3e1e
@@ -5,6 +5,7 @@ import ProjectEntityUpdateHandler from '../Project/ProjectEntityUpdateHandler.mj
|
|||||||
import ProjectOptionsHandler from '../Project/ProjectOptionsHandler.mjs'
|
import ProjectOptionsHandler from '../Project/ProjectOptionsHandler.mjs'
|
||||||
import ProjectDetailsHandler from '../Project/ProjectDetailsHandler.mjs'
|
import ProjectDetailsHandler from '../Project/ProjectDetailsHandler.mjs'
|
||||||
import ProjectDeleter from '../Project/ProjectDeleter.mjs'
|
import ProjectDeleter from '../Project/ProjectDeleter.mjs'
|
||||||
|
import { DeletedProjectReasons } from '../Project/DeletedProjectReasons.mjs'
|
||||||
import EditorRealTimeController from './EditorRealTimeController.mjs'
|
import EditorRealTimeController from './EditorRealTimeController.mjs'
|
||||||
import async from 'async'
|
import async from 'async'
|
||||||
import PublicAccessLevels from '../Authorization/PublicAccessLevels.mjs'
|
import PublicAccessLevels from '../Authorization/PublicAccessLevels.mjs'
|
||||||
@@ -445,7 +446,11 @@ const EditorController = {
|
|||||||
|
|
||||||
deleteProject(projectId, callback) {
|
deleteProject(projectId, callback) {
|
||||||
Metrics.inc('editor.delete-project')
|
Metrics.inc('editor.delete-project')
|
||||||
ProjectDeleter.deleteProject(projectId, callback)
|
ProjectDeleter.deleteProject(
|
||||||
|
projectId,
|
||||||
|
{ deletedReason: DeletedProjectReasons.USER },
|
||||||
|
callback
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
renameEntity(
|
renameEntity(
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export const DeletedProjectReasons = /** @type {const} */ ({
|
||||||
|
USER: 'user',
|
||||||
|
ACCOUNT_DELETION: 'account-deletion',
|
||||||
|
ZIP_IMPORT_FAILURE: 'zip-import-failure',
|
||||||
|
CLONE_FAILURE: 'clone-failure',
|
||||||
|
GITHUB_IMPORT_FAILURE: 'github-import-failure',
|
||||||
|
SCRIPT: 'script',
|
||||||
|
})
|
||||||
@@ -7,6 +7,7 @@ import logger from '@overleaf/logger'
|
|||||||
import { expressify } from '@overleaf/promise-utils'
|
import { expressify } from '@overleaf/promise-utils'
|
||||||
import mongodb from 'mongodb-legacy'
|
import mongodb from 'mongodb-legacy'
|
||||||
import ProjectDeleter from './ProjectDeleter.mjs'
|
import ProjectDeleter from './ProjectDeleter.mjs'
|
||||||
|
import { DeletedProjectReasons } from './DeletedProjectReasons.mjs'
|
||||||
import ProjectDuplicator from './ProjectDuplicator.mjs'
|
import ProjectDuplicator from './ProjectDuplicator.mjs'
|
||||||
import ProjectCreationHandler from './ProjectCreationHandler.mjs'
|
import ProjectCreationHandler from './ProjectCreationHandler.mjs'
|
||||||
import EditorController from '../Editor/EditorController.mjs'
|
import EditorController from '../Editor/EditorController.mjs'
|
||||||
@@ -171,6 +172,7 @@ const _ProjectController = {
|
|||||||
await ProjectDeleter.promises.deleteProject(projectId, {
|
await ProjectDeleter.promises.deleteProject(projectId, {
|
||||||
deleterUser: user,
|
deleterUser: user,
|
||||||
ipAddress: req.ip,
|
ipAddress: req.ip,
|
||||||
|
deletedReason: DeletedProjectReasons.USER,
|
||||||
})
|
})
|
||||||
ProjectAuditLogHandler.addEntryIfManagedInBackground(
|
ProjectAuditLogHandler.addEntryIfManagedInBackground(
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import EditorRealTimeController from '../Editor/EditorRealTimeController.mjs'
|
|||||||
import HistoryManager from '../History/HistoryManager.mjs'
|
import HistoryManager from '../History/HistoryManager.mjs'
|
||||||
import ChatApiHandler from '../Chat/ChatApiHandler.mjs'
|
import ChatApiHandler from '../Chat/ChatApiHandler.mjs'
|
||||||
import { promiseMapWithLimit } from '@overleaf/promise-utils'
|
import { promiseMapWithLimit } from '@overleaf/promise-utils'
|
||||||
|
import { DeletedProjectReasons } from './DeletedProjectReasons.mjs'
|
||||||
|
|
||||||
const PROJECT_EXPIRATION_BATCH_SIZE = 10000
|
const PROJECT_EXPIRATION_BATCH_SIZE = 10000
|
||||||
|
|
||||||
@@ -84,7 +85,11 @@ async function deleteUsersProjects(userId) {
|
|||||||
{ userId, projectCount: projects.length },
|
{ userId, projectCount: projects.length },
|
||||||
'found user projects to delete'
|
'found user projects to delete'
|
||||||
)
|
)
|
||||||
await promiseMapWithLimit(5, projects, project => deleteProject(project._id))
|
await promiseMapWithLimit(5, projects, project =>
|
||||||
|
deleteProject(project._id, {
|
||||||
|
deletedReason: DeletedProjectReasons.ACCOUNT_DELETION,
|
||||||
|
})
|
||||||
|
)
|
||||||
logger.info({ userId }, 'deleted all user projects')
|
logger.info({ userId }, 'deleted all user projects')
|
||||||
await CollaboratorsHandler.promises.removeUserFromAllProjects(userId)
|
await CollaboratorsHandler.promises.removeUserFromAllProjects(userId)
|
||||||
}
|
}
|
||||||
@@ -214,6 +219,7 @@ async function deleteProject(projectId, options = {}) {
|
|||||||
deleterId:
|
deleterId:
|
||||||
options.deleterUser != null ? options.deleterUser._id : undefined,
|
options.deleterUser != null ? options.deleterUser._id : undefined,
|
||||||
deleterIpAddress: options.ipAddress,
|
deleterIpAddress: options.ipAddress,
|
||||||
|
deletedReason: options.deletedReason,
|
||||||
deletedProjectId: project._id,
|
deletedProjectId: project._id,
|
||||||
deletedProjectOwnerId: project.owner_ref,
|
deletedProjectOwnerId: project.owner_ref,
|
||||||
deletedProjectCollaboratorIds: project.collaberator_refs,
|
deletedProjectCollaboratorIds: project.collaberator_refs,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import DocumentUpdaterHandler from '../DocumentUpdater/DocumentUpdaterHandler.mj
|
|||||||
import HistoryManager from '../History/HistoryManager.mjs'
|
import HistoryManager from '../History/HistoryManager.mjs'
|
||||||
import ProjectCreationHandler from './ProjectCreationHandler.mjs'
|
import ProjectCreationHandler from './ProjectCreationHandler.mjs'
|
||||||
import ProjectDeleter from './ProjectDeleter.mjs'
|
import ProjectDeleter from './ProjectDeleter.mjs'
|
||||||
|
import { DeletedProjectReasons } from './DeletedProjectReasons.mjs'
|
||||||
import ProjectEntityMongoUpdateHandler from './ProjectEntityMongoUpdateHandler.mjs'
|
import ProjectEntityMongoUpdateHandler from './ProjectEntityMongoUpdateHandler.mjs'
|
||||||
import ProjectEntityUpdateHandler from './ProjectEntityUpdateHandler.mjs'
|
import ProjectEntityUpdateHandler from './ProjectEntityUpdateHandler.mjs'
|
||||||
import ProjectGetter from './ProjectGetter.mjs'
|
import ProjectGetter from './ProjectGetter.mjs'
|
||||||
@@ -161,7 +162,9 @@ async function duplicate(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Clean up broken clone on error.
|
// Clean up broken clone on error.
|
||||||
// Make sure we delete the new failed project, not the original one!
|
// Make sure we delete the new failed project, not the original one!
|
||||||
await ProjectDeleter.promises.deleteProject(newProject._id)
|
await ProjectDeleter.promises.deleteProject(newProject._id, {
|
||||||
|
deletedReason: DeletedProjectReasons.CLONE_FAILURE,
|
||||||
|
})
|
||||||
throw OError.tag(err, 'error cloning project, broken clone deleted', {
|
throw OError.tag(err, 'error cloning project, broken clone deleted', {
|
||||||
originalProjectId,
|
originalProjectId,
|
||||||
newProjectName,
|
newProjectName,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import ProjectEntityMongoUpdateHandler from '../Project/ProjectEntityMongoUpdate
|
|||||||
import ProjectRootDocManager from '../Project/ProjectRootDocManager.mjs'
|
import ProjectRootDocManager from '../Project/ProjectRootDocManager.mjs'
|
||||||
import ProjectDetailsHandler from '../Project/ProjectDetailsHandler.mjs'
|
import ProjectDetailsHandler from '../Project/ProjectDetailsHandler.mjs'
|
||||||
import ProjectDeleter from '../Project/ProjectDeleter.mjs'
|
import ProjectDeleter from '../Project/ProjectDeleter.mjs'
|
||||||
|
import { DeletedProjectReasons } from '../Project/DeletedProjectReasons.mjs'
|
||||||
import TpdsProjectFlusher from '../ThirdPartyDataStore/TpdsProjectFlusher.mjs'
|
import TpdsProjectFlusher from '../ThirdPartyDataStore/TpdsProjectFlusher.mjs'
|
||||||
import logger from '@overleaf/logger'
|
import logger from '@overleaf/logger'
|
||||||
import OError from '@overleaf/o-error'
|
import OError from '@overleaf/o-error'
|
||||||
@@ -55,7 +56,9 @@ async function createProjectFromZipArchive(ownerId, defaultName, zipPath) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// no need to wait for the cleanup here
|
// no need to wait for the cleanup here
|
||||||
ProjectDeleter.promises
|
ProjectDeleter.promises
|
||||||
.deleteProject(project._id)
|
.deleteProject(project._id, {
|
||||||
|
deletedReason: DeletedProjectReasons.ZIP_IMPORT_FAILURE,
|
||||||
|
})
|
||||||
.catch(err =>
|
.catch(err =>
|
||||||
logger.error(
|
logger.error(
|
||||||
{ err, projectId: project._id },
|
{ err, projectId: project._id },
|
||||||
@@ -97,7 +100,9 @@ async function createProjectFromZipArchiveWithName(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// no need to wait for the cleanup here
|
// no need to wait for the cleanup here
|
||||||
ProjectDeleter.promises
|
ProjectDeleter.promises
|
||||||
.deleteProject(project._id)
|
.deleteProject(project._id, {
|
||||||
|
deletedReason: DeletedProjectReasons.ZIP_IMPORT_FAILURE,
|
||||||
|
})
|
||||||
.catch(err =>
|
.catch(err =>
|
||||||
logger.error(
|
logger.error(
|
||||||
{ err, projectId: project._id },
|
{ err, projectId: project._id },
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const DeleterDataSchema = new Schema({
|
|||||||
deletedProjectLastUpdatedAt: { type: Date },
|
deletedProjectLastUpdatedAt: { type: Date },
|
||||||
deletedProjectOverleafId: { type: Number },
|
deletedProjectOverleafId: { type: Number },
|
||||||
deletedProjectOverleafHistoryId: { type: Schema.Types.Mixed },
|
deletedProjectOverleafHistoryId: { type: Schema.Types.Mixed },
|
||||||
|
deletedReason: { type: String },
|
||||||
})
|
})
|
||||||
|
|
||||||
const DeletedProjectSchema = new Schema(
|
const DeletedProjectSchema = new Schema(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import minimist from 'minimist'
|
import minimist from 'minimist'
|
||||||
import ProjectDeleter from '../app/src/Features/Project/ProjectDeleter.mjs'
|
import ProjectDeleter from '../app/src/Features/Project/ProjectDeleter.mjs'
|
||||||
|
import { DeletedProjectReasons } from '../app/src/Features/Project/DeletedProjectReasons.mjs'
|
||||||
import { scriptRunner } from './lib/ScriptRunner.mjs'
|
import { scriptRunner } from './lib/ScriptRunner.mjs'
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -11,7 +12,9 @@ async function main() {
|
|||||||
}
|
}
|
||||||
console.log(`Soft deleting project ${projectId}`)
|
console.log(`Soft deleting project ${projectId}`)
|
||||||
// soft delete, project will be permanently deleted after 90 days
|
// soft delete, project will be permanently deleted after 90 days
|
||||||
await ProjectDeleter.promises.deleteProject(projectId)
|
await ProjectDeleter.promises.deleteProject(projectId, {
|
||||||
|
deletedReason: DeletedProjectReasons.SCRIPT,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -684,7 +684,7 @@ describe('EditorController', function () {
|
|||||||
describe('deleteProject', function () {
|
describe('deleteProject', function () {
|
||||||
beforeEach(function (ctx) {
|
beforeEach(function (ctx) {
|
||||||
ctx.err = 'errro'
|
ctx.err = 'errro'
|
||||||
ctx.ProjectDeleter.deleteProject.callsArgWith(1, ctx.err)
|
ctx.ProjectDeleter.deleteProject.callsArgWith(2, ctx.err)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call the project handler', async function (ctx) {
|
it('should call the project handler', async function (ctx) {
|
||||||
@@ -692,7 +692,9 @@ describe('EditorController', function () {
|
|||||||
ctx.EditorController.deleteProject(ctx.project_id, err => {
|
ctx.EditorController.deleteProject(ctx.project_id, err => {
|
||||||
err.should.equal(ctx.err)
|
err.should.equal(ctx.err)
|
||||||
ctx.ProjectDeleter.deleteProject
|
ctx.ProjectDeleter.deleteProject
|
||||||
.calledWith(ctx.project_id)
|
.calledWith(ctx.project_id, {
|
||||||
|
deletedReason: 'user',
|
||||||
|
})
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -710,6 +710,7 @@ describe('ProjectController', function () {
|
|||||||
.calledWith(ctx.project_id, {
|
.calledWith(ctx.project_id, {
|
||||||
deleterUser: ctx.user,
|
deleterUser: ctx.user,
|
||||||
ipAddress: ctx.req.ip,
|
ipAddress: ctx.req.ip,
|
||||||
|
deletedReason: 'user',
|
||||||
})
|
})
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
code.should.equal(200)
|
code.should.equal(200)
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ describe('ProjectDeleter', function () {
|
|||||||
{ 'deleterData.deletedProjectId': project._id },
|
{ 'deleterData.deletedProjectId': project._id },
|
||||||
{
|
{
|
||||||
project,
|
project,
|
||||||
deleterData: sinon.match.object,
|
deleterData: sinon.match.has('deletedReason', 'account-deletion'),
|
||||||
},
|
},
|
||||||
{ upsert: true }
|
{ upsert: true }
|
||||||
)
|
)
|
||||||
@@ -343,6 +343,7 @@ describe('ProjectDeleter', function () {
|
|||||||
it('should save a DeletedProject with additional deleterData', async function (ctx) {
|
it('should save a DeletedProject with additional deleterData', async function (ctx) {
|
||||||
ctx.deleterData.deleterIpAddress = ctx.ip
|
ctx.deleterData.deleterIpAddress = ctx.ip
|
||||||
ctx.deleterData.deleterId = ctx.user._id
|
ctx.deleterData.deleterId = ctx.user._id
|
||||||
|
ctx.deleterData.deletedReason = 'user'
|
||||||
|
|
||||||
ctx.ProjectMock.expects('deleteOne').chain('exec').resolves()
|
ctx.ProjectMock.expects('deleteOne').chain('exec').resolves()
|
||||||
ctx.DeletedProjectMock.expects('updateOne')
|
ctx.DeletedProjectMock.expects('updateOne')
|
||||||
@@ -359,6 +360,7 @@ describe('ProjectDeleter', function () {
|
|||||||
await ctx.ProjectDeleter.promises.deleteProject(ctx.project._id, {
|
await ctx.ProjectDeleter.promises.deleteProject(ctx.project._id, {
|
||||||
deleterUser: ctx.user,
|
deleterUser: ctx.user,
|
||||||
ipAddress: ctx.ip,
|
ipAddress: ctx.ip,
|
||||||
|
deletedReason: 'user',
|
||||||
})
|
})
|
||||||
ctx.DeletedProjectMock.verify()
|
ctx.DeletedProjectMock.verify()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -459,7 +459,8 @@ describe('ProjectDuplicator', function () {
|
|||||||
|
|
||||||
it('should delete the broken cloned project', function (ctx) {
|
it('should delete the broken cloned project', function (ctx) {
|
||||||
ctx.ProjectDeleter.promises.deleteProject.should.have.been.calledWith(
|
ctx.ProjectDeleter.promises.deleteProject.should.have.been.calledWith(
|
||||||
ctx.newBlankProject._id
|
ctx.newBlankProject._id,
|
||||||
|
{ deletedReason: 'clone-failure' }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,8 @@ describe('ProjectUploadManager', function () {
|
|||||||
|
|
||||||
it('should cleanup the blank project created', async function (ctx) {
|
it('should cleanup the blank project created', async function (ctx) {
|
||||||
ctx.ProjectDeleter.promises.deleteProject.should.have.been.calledWith(
|
ctx.ProjectDeleter.promises.deleteProject.should.have.been.calledWith(
|
||||||
ctx.project._id
|
ctx.project._id,
|
||||||
|
{ deletedReason: 'zip-import-failure' }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -471,7 +472,8 @@ describe('ProjectUploadManager', function () {
|
|||||||
|
|
||||||
it('should cleanup the blank project created', function (ctx) {
|
it('should cleanup the blank project created', function (ctx) {
|
||||||
ctx.ProjectDeleter.promises.deleteProject.should.have.been.calledWith(
|
ctx.ProjectDeleter.promises.deleteProject.should.have.been.calledWith(
|
||||||
ctx.project._id
|
ctx.project._id,
|
||||||
|
{ deletedReason: 'zip-import-failure' }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user