From dd61b94a1446e28c7094970a37998c7f7576e7be Mon Sep 17 00:00:00 2001 From: david-loe <56305409+david-loe@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:47:10 +0200 Subject: [PATCH] introduce reference checks --- backend/controller/controller.ts | 17 ++++++++++++++++- backend/controller/error.ts | 5 +++++ backend/controller/organisationController.ts | 3 ++- backend/controller/projectController.ts | 12 +++++++++++- backend/controller/userController.ts | 12 +++++++++++- common/locales/de.json | 1 + common/locales/en.json | 1 + 7 files changed, 47 insertions(+), 4 deletions(-) diff --git a/backend/controller/controller.ts b/backend/controller/controller.ts index 8ae0af70..6c65ac25 100644 --- a/backend/controller/controller.ts +++ b/backend/controller/controller.ts @@ -3,7 +3,7 @@ import { FilterQuery, HydratedDocument, Model, ProjectionType, Types } from 'mon import { Controller as TsoaController } from 'tsoa' import { Base64 } from '../../common/scripts.js' import { GETResponse, Meta, User, _id } from '../../common/types.js' -import { NotAllowedError, NotFoundError } from './error.js' +import { ConflictError, NotAllowedError, NotFoundError } from './error.js' import { IdDocument } from './types.js' export interface GetterQuery { @@ -144,6 +144,7 @@ export interface DeleterQuery { } export interface DeleterOptions extends DeleterQuery { + referenceChecks?: { paths: string[]; model: Model; conditions?: { [key: string]: any } }[] cb?: (data: DeleteResult) => any checkOldObject?: (oldObject: HydratedDocument & ModelMethods) => Promise } @@ -299,6 +300,20 @@ export class Controller extends TsoaController { if (options.checkOldObject && !(await options.checkOldObject(doc))) { throw new NotAllowedError(`Not allowed to delete this ${model.modelName}`) } + if (options.referenceChecks) { + for (const referenceCheck of options.referenceChecks) { + const filter = { $or: [] as { [key: string]: any }[] } + for (const path of referenceCheck.paths) { + const conditions: { [key: string]: any } = structuredClone(referenceCheck.conditions) || {} + conditions[path] = options._id + filter.$or.push(conditions) + } + const count = await referenceCheck.model.countDocuments(filter) + if (count > 0) { + throw new ConflictError(`${count} ${referenceCheck.model.modelName}${count > 1 ? 's' : ''} linked.`) + } + } + } const result = await doc.deleteOne() if (options.cb) { options.cb(result) diff --git a/backend/controller/error.ts b/backend/controller/error.ts index ffac0339..9ced623d 100644 --- a/backend/controller/error.ts +++ b/backend/controller/error.ts @@ -22,6 +22,11 @@ export class NotFoundError extends ClientError { name = 'alerts.notFound' } +export class ConflictError extends ClientError { + status = 409 + name = 'alerts.conflict' +} + export class NotImplementedError extends ClientError { status = 501 name = 'alerts.notImplemented' diff --git a/backend/controller/organisationController.ts b/backend/controller/organisationController.ts index a23e15bf..74329aba 100644 --- a/backend/controller/organisationController.ts +++ b/backend/controller/organisationController.ts @@ -4,6 +4,7 @@ import { Body, Consumes, Delete, Get, Middlewares, Post, Queries, Query, Request import { Organisation as IOrganisation, _id } from '../../common/types.js' import { documentFileHandler } from '../helper.js' import Organisation from '../models/organisation.js' +import Project from '../models/project.js' import { Controller, GetterQuery, SetterBody } from './controller.js' const fileHandler = multer({ limits: { fileSize: 16000000 } }) @@ -42,6 +43,6 @@ export class OrganisationAdminController extends Controller { @Delete() public async deleteOrganisation(@Query() _id: _id) { - return await this.deleter(Organisation, { _id: _id }) + return await this.deleter(Organisation, { _id: _id, referenceChecks: [{ model: Project, paths: ['organisation'] }] }) } } diff --git a/backend/controller/projectController.ts b/backend/controller/projectController.ts index 4ed35727..fcb5b7ff 100644 --- a/backend/controller/projectController.ts +++ b/backend/controller/projectController.ts @@ -2,7 +2,10 @@ import { Request as ExRequest } from 'express' import { Body, Delete, Get, Post, Queries, Query, Request, Route, Security, Tags } from 'tsoa' import { Project as IProject, ProjectSimple, _id } from '../../common/types.js' import { getSettings } from '../helper.js' +import ExpenseReport from '../models/expenseReport.js' +import HealthCareCost from '../models/healthCareCost.js' import Project from '../models/project.js' +import Travel from '../models/travel.js' import { Controller, GetterQuery, SetterBody } from './controller.js' import { AuthorizationError } from './error.js' @@ -46,6 +49,13 @@ export class ProjectAdminController extends Controller { } @Delete() public async deleteProject(@Query() _id: _id) { - return await this.deleter(Project, { _id: _id }) + return await this.deleter(Project, { + _id: _id, + referenceChecks: [ + { model: ExpenseReport, paths: ['project'] }, + { model: Travel, paths: ['project'] }, + { model: HealthCareCost, paths: ['project'] } + ] + }) } } diff --git a/backend/controller/userController.ts b/backend/controller/userController.ts index c1974173..5733edb2 100644 --- a/backend/controller/userController.ts +++ b/backend/controller/userController.ts @@ -7,7 +7,10 @@ import { User as IUser, _id } from '../../common/types.js' import { documentFileHandler } from '../helper.js' import i18n from '../i18n.js' import { sendMail } from '../mail/mail.js' +import ExpenseReport from '../models/expenseReport.js' +import HealthCareCost from '../models/healthCareCost.js' import Token from '../models/token.js' +import Travel from '../models/travel.js' import User from '../models/user.js' import { Controller, GetterQuery, SetterBody } from './controller.js' import { NotAllowedError, NotFoundError } from './error.js' @@ -131,7 +134,14 @@ export class UserAdminController extends Controller { @Delete() public async deleteUser(@Query() _id: _id) { - return await this.deleter(User, { _id }) + return await this.deleter(User, { + _id, + referenceChecks: [ + { model: Travel, paths: ['owner', 'editor', 'comments.author'], conditions: { historic: false } }, + { model: ExpenseReport, paths: ['owner', 'editor', 'comments.author'], conditions: { historic: false } }, + { model: HealthCareCost, paths: ['owner', 'editor', 'comments.author'], conditions: { historic: false } } + ] + }) } @Post('merge') diff --git a/common/locales/de.json b/common/locales/de.json index ace15321..baa76204 100755 --- a/common/locales/de.json +++ b/common/locales/de.json @@ -15,6 +15,7 @@ "alerts": { "areYouSureDelete": "Bist du dir sicher, dass du das löschen willst?", "areYouSureMerge": "Bist du dir sicher, dass du diese Benutzer zusammenführen willst? Das kann nicht rückgängig gemacht werden.", + "conflict": "Konflikt!", "countryChangeBetweenStages": "Länderwechsel zwischen aufeinanderfolgenden Etappen", "db": { "createdSettings": "Einstellungen wurden vom Standard erstellt", diff --git a/common/locales/en.json b/common/locales/en.json index 0a709193..fa583357 100755 --- a/common/locales/en.json +++ b/common/locales/en.json @@ -15,6 +15,7 @@ "alerts": { "areYouSureDelete": "Are you sure that you want to delete this?", "areYouSureMerge": "Are you sure you want to merge these users? This cannot be undone.", + "conflict": "Conflict!", "countryChangeBetweenStages": "Country change between consecutive stages", "db": { "createdSettings": "Created Settings from Default",