From 8d9e430f912805bf21991a156ffa108ddaa509d2 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Wed, 10 Nov 2021 23:50:36 +0000 Subject: [PATCH] User ValidateCanDelete switched to checking all references from only event log --- server/AyaNova/biz/UserBiz.cs | 89 +++++++++++++++++++++++++++------ server/AyaNova/util/AySchema.cs | 8 +-- 2 files changed, 78 insertions(+), 19 deletions(-) diff --git a/server/AyaNova/biz/UserBiz.cs b/server/AyaNova/biz/UserBiz.cs index d1fbdeb4..1736e53c 100644 --- a/server/AyaNova/biz/UserBiz.cs +++ b/server/AyaNova/biz/UserBiz.cs @@ -842,24 +842,83 @@ namespace AyaNova.Biz //Can delete? private async Task ValidateCanDelete(User inObj) { + // //To make this simple and avoid a whole host of issues and work + // //I've decided that a user can't be deleted if they have *any* activity in the event log + // //this way a newly created user can be deleted before they do any real work still to cover a scenario where a user + // //makes a user but then doesn't need it or did it wrong + // //This avoids the whole issues related to having to check every table everywhere for their work and + // //the associated fuckery with trying to back them out of those tables without knock-on effects + // //They can always make any user inactive to get rid of them and it will mean referential integrity issues are not there + // //There's only one rule - have they done anything eventlog worthy yet? + // //if (await ct.Event.Select(z => z).Where(z => z.UserId == inObj.Id).Count() > 0) + // if (await ct.Event.AnyAsync(z => z.UserId == inObj.Id)) + // { + // //Theres no more specific error to show for this + // AddError(ApiErrorCode.INVALID_OPERATION, "generalerror", await Translate("ErrorDBForeignKeyViolation")); + // return; + // } - //To make this simple and avoid a whole host of issues and work - //I've decided that a user can't be deleted if they have *any* activity in the event log - //this way a newly created user can be deleted before they do any real work still to cover a scenario where a user - //makes a user but then doesn't need it or did it wrong - //This avoids the whole issues related to having to check every table everywhere for their work and - //the associated fuckery with trying to back them out of those tables without knock-on effects - //They can always make any user inactive to get rid of them and it will mean referential integrity issues are not there + //NOPE, the above is not going to fly for many reasons, going to have to do it the hard way + //Users need to know *what* is holding up deleting a user if they should choose to do so and + //the event log will eventually have a purge feature so it's not a reliable source + //the db would prevent it anyway, but it's best to do it right - //There's only one rule - have they done anything eventlog worthy yet? - //if (await ct.Event.Select(z => z).Where(z => z.UserId == inObj.Id).Count() > 0) - if (await ct.Event.AnyAsync(z => z.UserId == inObj.Id)) - { - //Theres no more specific error to show for this - AddError(ApiErrorCode.INVALID_OPERATION, "generalerror", await Translate("ErrorDBForeignKeyViolation")); - return; - } + //FOREIGN KEY CHECKS + if (await ct.Memo.AnyAsync(m => m.ToId == inObj.Id || m.FromId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Memo")); + // if (await ct.Reminder.AnyAsync(m => m.UserId == inObj.Id)) + // AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Reminder"));//CASCADE DELETES + if (await ct.Review.AnyAsync(m => m.AssignedByUserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Review")); + if (await ct.CustomerNote.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("CustomerNote")); + if (await ct.Project.AnyAsync(m => m.ProjectOverseerId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Project")); + if (await ct.PurchaseOrderItem.AnyAsync(m => m.PartRequestedById == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("PurchaseOrderItem")); + if (await ct.WorkOrderState.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("WorkOrderState")); + if (await ct.WorkOrderItemExpense.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("WorkOrderItemExpense")); + if (await ct.WorkOrderItemLabor.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("WorkOrderItemLabor")); + if (await ct.WorkOrderItemPartRequest.AnyAsync(m => m.RequestedByUserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("WorkOrderItemPartRequest")); + if (await ct.WorkOrderItemScheduledUser.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("WorkOrderItemScheduledUser")); + if (await ct.WorkOrderItemTask.AnyAsync(m => m.CompletedByUserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("WorkOrderItemTask")); + if (await ct.WorkOrderItemTravel.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("WorkOrderItemTravel")); + if (await ct.Quote.AnyAsync(m => m.PreparedById == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Quote")); + var quotetext = await Translate("Quote"); + var pmtext = await Translate("PM"); + if (await ct.QuoteState.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("QuoteState")); + if (await ct.QuoteItemExpense.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", quotetext + " - " + await Translate("WorkOrderItemExpense")); + if (await ct.QuoteItemLabor.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", quotetext + " - " + await Translate("WorkOrderItemLabor")); + if (await ct.QuoteItemScheduledUser.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", quotetext + " - " + await Translate("WorkOrderItemScheduledUser")); + if (await ct.QuoteItemTask.AnyAsync(m => m.CompletedByUserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", quotetext + " - " + await Translate("WorkOrderItemTask")); + if (await ct.QuoteItemTravel.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", quotetext + " - " + await Translate("WorkOrderItemTravel")); + if (await ct.PMItemExpense.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", pmtext + " - " + await Translate("WorkOrderItemExpense")); + if (await ct.PMItemLabor.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", pmtext + " - " + await Translate("WorkOrderItemLabor")); + if (await ct.PMItemScheduledUser.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", pmtext + " - " + await Translate("WorkOrderItemScheduledUser")); + if (await ct.PMItemTask.AnyAsync(m => m.CompletedByUserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", pmtext + " - " + await Translate("WorkOrderItemTask")); + if (await ct.PMItemTravel.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", pmtext + " - " + await Translate("WorkOrderItemTravel")); + if (await ct.CustomerServiceRequest.AnyAsync(m => m.RequestedByUserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("CustomerServiceRequest")); } diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 8b86dab1..b92f42af 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -611,7 +611,7 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); //Add user options table await ExecQueryAsync("CREATE TABLE auseroptions (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, " - + "userid BIGINT NOT NULL UNIQUE REFERENCES auser (id), translationid BIGINT NOT NULL REFERENCES atranslation (id), languageoverride TEXT, timezoneoverride TEXT, " + + "userid BIGINT NOT NULL UNIQUE REFERENCES auser (id) ON DELETE CASCADE, translationid BIGINT NOT NULL REFERENCES atranslation (id), languageoverride TEXT, timezoneoverride TEXT, " + "currencyname TEXT, hour12 BOOL NOT NULL, emailaddress TEXT, phone1 TEXT, phone2 TEXT, phone3 TEXT, mapurltemplate TEXT)"); @@ -1302,16 +1302,16 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); //NOTIFICATION await ExecQueryAsync("CREATE TABLE anotifysubscription (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, " - + "userid BIGINT NOT NULL REFERENCES auser (id), ayatype INTEGER NOT NULL, eventtype INTEGER NOT NULL, advancenotice INTERVAL NOT NULL, " + + "userid BIGINT NOT NULL REFERENCES auser (id) ON DELETE CASCADE, ayatype INTEGER NOT NULL, eventtype INTEGER NOT NULL, advancenotice INTERVAL NOT NULL, " + "idvalue BIGINT NOT NULL, decvalue DECIMAL(38,18) NOT NULL, agevalue INTERVAL NOT NULL, deliverymethod INTEGER NOT NULL, " + "deliveryaddress TEXT, linkreportid BIGINT NOT NULL, tags VARCHAR(255) ARRAY)"); await ExecQueryAsync("CREATE TABLE anotifyevent (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, created TIMESTAMPTZ NOT NULL, " + "ayatype INTEGER NOT NULL, objectid BIGINT NOT NULL, name TEXT NOT NULL, eventtype INTEGER NOT NULL, notifysubscriptionid BIGINT NOT NULL REFERENCES anotifysubscription(id) ON DELETE CASCADE, " - + "userid BIGINT NOT NULL REFERENCES auser (id), eventdate TIMESTAMPTZ NOT NULL, decvalue DECIMAL(38,18) NULL, message TEXT)"); + + "userid BIGINT NOT NULL REFERENCES auser (id) ON DELETE CASCADE, eventdate TIMESTAMPTZ NOT NULL, decvalue DECIMAL(38,18) NULL, message TEXT)"); - await ExecQueryAsync("CREATE TABLE ainappnotification (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, userid BIGINT NOT NULL REFERENCES auser (id), " + await ExecQueryAsync("CREATE TABLE ainappnotification (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, userid BIGINT NOT NULL REFERENCES auser (id) ON DELETE CASCADE, " + "created TIMESTAMPTZ NOT NULL, ayatype INTEGER NOT NULL, objectid BIGINT NOT NULL, name TEXT NOT NULL, agevalue INTERVAL, eventtype INTEGER NOT NULL, " + "decvalue DECIMAL(38,18) NULL, notifysubscriptionid BIGINT NOT NULL REFERENCES anotifysubscription(id) ON DELETE CASCADE, message TEXT, fetched BOOL NOT NULL)");