From 24c53a3d2633c5e750c5ee0dbe7d9607643aa1b2 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Tue, 19 May 2020 18:27:52 +0000 Subject: [PATCH] --- .../GlobalOpsSettingsController.cs | 47 ++++++----------- server/AyaNova/biz/AyaType.cs | 3 +- server/AyaNova/biz/BizRoles.cs | 9 ++++ server/AyaNova/biz/GlobalOpsSettingsBiz.cs | 32 ++++++------ server/AyaNova/generator/CoreJobBackup.cs | 52 +++++++++---------- server/AyaNova/resource/de.json | 1 + server/AyaNova/resource/en.json | 1 + server/AyaNova/resource/es.json | 1 + server/AyaNova/resource/fr.json | 1 + 9 files changed, 75 insertions(+), 72 deletions(-) diff --git a/server/AyaNova/Controllers/GlobalOpsSettingsController.cs b/server/AyaNova/Controllers/GlobalOpsSettingsController.cs index 144a1ab0..7d409bad 100644 --- a/server/AyaNova/Controllers/GlobalOpsSettingsController.cs +++ b/server/AyaNova/Controllers/GlobalOpsSettingsController.cs @@ -13,7 +13,7 @@ namespace AyaNova.Api.Controllers [ApiController] [ApiVersion("8.0")] - [Route("api/v{version:apiVersion}/global-biz-setting")] + [Route("api/v{version:apiVersion}/global-ops-setting")] [Produces("application/json")] [Authorize] public class GlobalOpsSettingsController : ControllerBase @@ -40,7 +40,7 @@ namespace AyaNova.Api.Controllers /// /// Get GlobalOpsSettings /// - /// Global settings object + /// Global ops settings object [HttpGet] public async Task GetGlobalOpsSettings() { @@ -74,16 +74,11 @@ namespace AyaNova.Api.Controllers { if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); - - //Instantiate the business object handler GlobalOpsSettingsBiz biz = GlobalOpsSettingsBiz.GetBiz(ct, HttpContext); - - if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) return StatusCode(403, new ApiNotAuthorizedResponse()); - try { if (!await biz.ReplaceAsync(global)) @@ -101,32 +96,24 @@ namespace AyaNova.Api.Controllers /// /// Global settings object [HttpGet("client")] - public ActionResult GetClientGlobalOpsSettings() + public async Task GetClientGlobalOpsSettings() { + //NOTE: currently this looks like a dupe of get above and it is + //but it's kept here in case want to return a subset of the settings only to client users + //where some internal server only stuff might not be desired to send to user + if (serverState.IsClosed) - return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - - //Instantiate the business object handler - // GlobalOpsSettingsBiz biz = GlobalOpsSettingsBiz.GetBiz(ct, HttpContext); - - //this route is available to any logged in user as it contains a subset of limited options relevant to any logged in user - // if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) - // return StatusCode(403, new ApiNotAuthorizedResponse()); - + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + GlobalOpsSettingsBiz biz = GlobalOpsSettingsBiz.GetBiz(ct, HttpContext); + //this route is available to Ops role user only + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); - - // var o = await biz.GetAsync(); - // if (o == null) - // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); - - //new object with only relevant items in it - var ret = new - { - SearchCaseSensitiveOnly = AyaNova.Util.ServerGlobalOpsSettings.SearchCaseSensitiveOnly - }; - - return Ok(ApiOkResponse.Response(ret)); + var o = await biz.GetAsync(); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); } }//eoc diff --git a/server/AyaNova/biz/AyaType.cs b/server/AyaNova/biz/AyaType.cs index 1b41148f..16a7aaa6 100644 --- a/server/AyaNova/biz/AyaType.cs +++ b/server/AyaNova/biz/AyaType.cs @@ -102,7 +102,8 @@ namespace AyaNova.Biz [CoreBizObject] WorkOrderTemplate = 45, [CoreBizObject] - WorkOrderTemplateItem = 46 + WorkOrderTemplateItem = 46, + GlobalOps=47 //NOTE: New objects added here need to also be added to the following classes: diff --git a/server/AyaNova/biz/BizRoles.cs b/server/AyaNova/biz/BizRoles.cs index 53475512..322d55f2 100644 --- a/server/AyaNova/biz/BizRoles.cs +++ b/server/AyaNova/biz/BizRoles.cs @@ -339,6 +339,15 @@ namespace AyaNova.Biz }); + //////////////////////////////////////////////////////////// + //GLOBAL OPS SETTINGS + // + roles.Add(AyaType.GlobalOps, new BizRoleSet() + { + Change = AuthorizationRoles.OpsAdminFull, + ReadFullRecord = AuthorizationRoles.OpsAdminLimited + }); + //////////////////////////////////////////////////////////// //USER diff --git a/server/AyaNova/biz/GlobalOpsSettingsBiz.cs b/server/AyaNova/biz/GlobalOpsSettingsBiz.cs index 3e696cfa..a5515b83 100644 --- a/server/AyaNova/biz/GlobalOpsSettingsBiz.cs +++ b/server/AyaNova/biz/GlobalOpsSettingsBiz.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using AyaNova.Util; @@ -16,7 +17,7 @@ namespace AyaNova.Biz UserId = currentUserId; UserTranslationId = userTranslationId; CurrentUserRoles = UserRoles; - BizType = AyaType.Global; + BizType = AyaType.GlobalOps; } internal static GlobalOpsSettingsBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) @@ -39,16 +40,14 @@ namespace AyaNova.Biz //first try to fetch from db var ret = await ct.GlobalOpsSettings.SingleOrDefaultAsync(m => m.Id == 1); if (logTheGetEvent && ret != null) - { - //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 1, BizType, AyaEvent.Retrieved), ct); - } + //not in db then get the default if (ret == null) - { throw new System.Exception("GlobalOpsSettingsBiz::GetAsync -> Global OPS settings object not found in database!!"); - } + return ret; } @@ -59,23 +58,26 @@ namespace AyaNova.Biz // //put - internal async Task ReplaceAsync(GlobalOpsSettings inObj) + internal async Task ReplaceAsync(GlobalOpsSettings putObject) { - var dbObj = await ct.GlobalOpsSettings.FirstOrDefaultAsync(m => m.Id == 1); - if (dbObj == null) + var dbObject = await ct.GlobalOpsSettings.FirstOrDefaultAsync(m => m.Id == 1); + if (dbObject == null) throw new System.Exception("GlobalOpsSettingsBiz::ReplaceAsync -> Global settings object not found in database!!"); - CopyObject.Copy(inObj, dbObj, "Id"); - ct.Entry(dbObj).OriginalValues["Concurrency"] = inObj.Concurrency; - - Validate(dbObj); + //If backup time has changed then reset last backup as well as it might block from taking effect + if (putObject.BackupTime.Hour != dbObject.BackupTime.Hour && putObject.BackupTime.Minute != dbObject.BackupTime.Minute) + { + putObject.LastBackup = DateTime.MinValue; + } + CopyObject.Copy(putObject, dbObject, "Id"); + ct.Entry(dbObject).OriginalValues["Concurrency"] = putObject.Concurrency; + Validate(dbObject); if (HasErrors) return false; await ct.SaveChangesAsync(); - //Log modification and save context await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 1, BizType, AyaEvent.Modified), ct); //Update the static copy for the server - ServerGlobalOpsSettings.Initialize(dbObj); + ServerGlobalOpsSettings.Initialize(dbObject); return true; } diff --git a/server/AyaNova/generator/CoreJobBackup.cs b/server/AyaNova/generator/CoreJobBackup.cs index 9c9e1cf6..786fb3fa 100644 --- a/server/AyaNova/generator/CoreJobBackup.cs +++ b/server/AyaNova/generator/CoreJobBackup.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using AyaNova.Models; +using AyaNova.Util; namespace AyaNova.Biz @@ -17,44 +18,43 @@ namespace AyaNova.Biz internal static class CoreJobBackup { private static ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger("CoreJobBackup"); - private static DateTime lastSweep = DateTime.MinValue; - private static TimeSpan SWEEP_EVERY_INTERVAL = new TimeSpan(0, 30, 0); - private static TimeSpan SUCCEEDED_JOBS_DELETE_AFTER_THIS_TIMESPAN = new TimeSpan(24, 0, 0);//24 hours - private static TimeSpan FAILED_JOBS_DELETE_AFTER_THIS_TIMESPAN = new TimeSpan(14, 0, 0, 0);//14 days (gives people time to notice and look into it) - private static TimeSpan RUNNING_JOBS_BECOME_FAILED_AFTER_THIS_TIMESPAN = new TimeSpan(24, 0, 0);//24 hours (time running jobs are allowed to sit in "running" state before considered failed) - + private static bool BackupIsRunning = false; //////////////////////////////////////////////////////////////////////////////////////////////// // // public static async Task DoWorkAsync(AyContext ct) { + if (BackupIsRunning) return; - //This will get triggered roughly every minute, but we don't want to sweep that frequently - if (DateTime.UtcNow - lastSweep < SWEEP_EVERY_INTERVAL) - return; + //get NOW in utc + DateTime utcNow = DateTime.UtcNow; + //what time should we backup today? + DateTime todayBackupTime = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, ServerGlobalOpsSettings.BackupTime.Hour, ServerGlobalOpsSettings.BackupTime.Minute, 0, DateTimeKind.Utc);//first start with NOW + + //Are we there yet? + if (utcNow < todayBackupTime) return;//nope + + //Yes, we've passed into the backup window time, but that's also true if we just ran the backup as well so + //need to check for that as well... + + //Has last backup run more than 24 hours ago? + if (ServerGlobalOpsSettings.LastBackup > utcNow.AddHours(-24)) + return;//nope, so we have already run today's backup + + //Ok, we're into backup time and it's been more than 24 hours since it last ran so let's do this... + BackupIsRunning = true; log.LogTrace("Backup starting"); - - //lock the server - //serverState.SetSystemLock(msg); - //SWEEP SUCCESSFUL JOBS - //calculate cutoff to delete - DateTime dtDeleteCutoff = DateTime.UtcNow - SUCCEEDED_JOBS_DELETE_AFTER_THIS_TIMESPAN; - await sweepAsync(ct, dtDeleteCutoff, JobStatus.Completed); - //SWEEP FAILED JOBS - //calculate cutoff to delete - dtDeleteCutoff = DateTime.UtcNow - FAILED_JOBS_DELETE_AFTER_THIS_TIMESPAN; - await sweepAsync(ct, dtDeleteCutoff, JobStatus.Failed); + //************* + //BACKUP TORA TORA TORA! + //*************** - //KILL STUCK JOBS - //calculate cutoff to delete - DateTime dtRunningDeadline = DateTime.UtcNow - RUNNING_JOBS_BECOME_FAILED_AFTER_THIS_TIMESPAN; - await killStuckJobsAsync(ct, dtRunningDeadline); - - lastSweep = DateTime.UtcNow; + //Update last backup + BackupIsRunning = false; + log.LogTrace("Backup completed"); } diff --git a/server/AyaNova/resource/de.json b/server/AyaNova/resource/de.json index 03a52454..091b57c7 100644 --- a/server/AyaNova/resource/de.json +++ b/server/AyaNova/resource/de.json @@ -445,6 +445,7 @@ "ContractRate": "Vertragssatz", "DispatchZone": "Zuweisungszone", "Global": "Global", + "GlobalOps": "Global ops", "GlobalWikiPage": "Global Wiki page", "GridFilter": "GridFilter", "HeadOffice": "Hauptsitz", diff --git a/server/AyaNova/resource/en.json b/server/AyaNova/resource/en.json index c5b73b1c..e5d99aa1 100644 --- a/server/AyaNova/resource/en.json +++ b/server/AyaNova/resource/en.json @@ -445,6 +445,7 @@ "ContractRate": "Contract rate", "DispatchZone": "Dispatch Zone", "Global": "Global", + "GlobalOps": "Global ops", "GlobalWikiPage": "Global Wiki page", "GridFilter": "GridFilter", "HeadOffice": "Head Office", diff --git a/server/AyaNova/resource/es.json b/server/AyaNova/resource/es.json index 06327cb5..eca7076c 100644 --- a/server/AyaNova/resource/es.json +++ b/server/AyaNova/resource/es.json @@ -445,6 +445,7 @@ "ContractRate": "Tarifa de contrato", "DispatchZone": "Zona de reparto", "Global": "Global", + "GlobalOps": "Global ops", "GlobalWikiPage": "Global Wiki page", "GridFilter": "GridFilter", "HeadOffice": "Sede", diff --git a/server/AyaNova/resource/fr.json b/server/AyaNova/resource/fr.json index 5a99f0c1..26604956 100644 --- a/server/AyaNova/resource/fr.json +++ b/server/AyaNova/resource/fr.json @@ -445,6 +445,7 @@ "ContractRate": "Tarif de contrat", "DispatchZone": "Zone d'expédition", "Global": "Général", + "GlobalOps": "Global ops", "GlobalWikiPage": "Global Wiki page", "GridFilter": "GridFilter", "HeadOffice": "Siège social",