Backup
This commit is contained in:
133
server/AyaNova/Controllers/GlobalOpsSettingsController.cs
Normal file
133
server/AyaNova/Controllers/GlobalOpsSettingsController.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/global-biz-setting")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class GlobalOpsSettingsController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<GlobalOpsSettingsController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public GlobalOpsSettingsController(AyContext dbcontext, ILogger<GlobalOpsSettingsController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get GlobalOpsSettings
|
||||
/// </summary>
|
||||
/// <returns>Global settings object</returns>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetGlobalOpsSettings()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
GlobalOpsSettingsBiz biz = GlobalOpsSettingsBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
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));
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// POST (replace) Global biz settings
|
||||
/// </summary>
|
||||
/// <param name="global"></param>
|
||||
/// <returns>nothing</returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ReplaceGlobalOpsSettings([FromBody] GlobalOpsSettings global)
|
||||
{
|
||||
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))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
try
|
||||
{
|
||||
if (!await biz.ReplaceAsync(global))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Client app relevant GlobalOpsSettings
|
||||
/// </summary>
|
||||
/// <returns>Global settings object</returns>
|
||||
[HttpGet("client")]
|
||||
public ActionResult GetClientGlobalOpsSettings()
|
||||
{
|
||||
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());
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
104
server/AyaNova/biz/GlobalOpsSettingsBiz.cs
Normal file
104
server/AyaNova/biz/GlobalOpsSettingsBiz.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using AyaNova.Util;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Models;
|
||||
|
||||
namespace AyaNova.Biz
|
||||
{
|
||||
|
||||
internal class GlobalOpsSettingsBiz : BizObject
|
||||
{
|
||||
|
||||
internal GlobalOpsSettingsBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles)
|
||||
{
|
||||
ct = dbcontext;
|
||||
UserId = currentUserId;
|
||||
UserTranslationId = userTranslationId;
|
||||
CurrentUserRoles = UserRoles;
|
||||
BizType = AyaType.Global;
|
||||
}
|
||||
|
||||
internal static GlobalOpsSettingsBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null)
|
||||
{
|
||||
if (httpContext != null)
|
||||
return new GlobalOpsSettingsBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items));
|
||||
else
|
||||
return new GlobalOpsSettingsBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// GET
|
||||
|
||||
//Get one
|
||||
internal async Task<GlobalOpsSettings> GetAsync(bool logTheGetEvent = true)
|
||||
{
|
||||
//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 settings object not found in database!!");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//UPDATE
|
||||
//
|
||||
|
||||
//put
|
||||
internal async Task<bool> ReplaceAsync(GlobalOpsSettings inObj)
|
||||
{
|
||||
var dbObj = await ct.GlobalOpsSettings.FirstOrDefaultAsync(m => m.Id == 1);
|
||||
if (dbObj == 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 (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);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//VALIDATION
|
||||
//
|
||||
|
||||
//Can save or update?
|
||||
private void Validate(GlobalOpsSettings inObj)
|
||||
{
|
||||
|
||||
//currently nothing to validate
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
|
||||
122
server/AyaNova/generator/CoreJobBackup.cs
Normal file
122
server/AyaNova/generator/CoreJobBackup.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
|
||||
|
||||
namespace AyaNova.Biz
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Backup
|
||||
///
|
||||
/// </summary>
|
||||
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)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
//
|
||||
public static async Task DoWorkAsync(AyContext ct)
|
||||
{
|
||||
|
||||
|
||||
//This will get triggered roughly every minute, but we don't want to sweep that frequently
|
||||
if (DateTime.UtcNow - lastSweep < SWEEP_EVERY_INTERVAL)
|
||||
return;
|
||||
|
||||
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);
|
||||
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
|
||||
private static async Task sweepAsync(AyContext ct, DateTime dtDeleteCutoff, JobStatus jobStatus)
|
||||
{
|
||||
|
||||
//Get the deleteable succeeded jobs list
|
||||
var jobs = await ct.OpsJob
|
||||
.AsNoTracking()
|
||||
.Where(z => z.Created < dtDeleteCutoff && z.JobStatus == jobStatus)
|
||||
.OrderBy(z => z.Created)
|
||||
.ToListAsync();
|
||||
|
||||
log.LogTrace($"SweepAsync processing: cutoff={dtDeleteCutoff.ToString()}, for {jobs.Count.ToString()} jobs of status {jobStatus.ToString()}");
|
||||
|
||||
foreach (OpsJob j in jobs)
|
||||
{
|
||||
try
|
||||
{
|
||||
await JobsBiz.DeleteJobAndLogAsync(j.GId, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogError(ex, "sweepAsync exception calling JobsBiz.DeleteJobAndLogAsync");
|
||||
//for now just throw it but this needs to be removed when logging added and better handling
|
||||
throw (ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Kill jobs that have been stuck in "running" state for too long
|
||||
/// </summary>
|
||||
/// <param name="ct"></param>
|
||||
/// <param name="dtRunningDeadline"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task killStuckJobsAsync(AyContext ct, DateTime dtRunningDeadline)
|
||||
{
|
||||
//Get the deleteable succeeded jobs list
|
||||
var jobs = await ct.OpsJob
|
||||
.AsNoTracking()
|
||||
.Where(z => z.Created < dtRunningDeadline && z.JobStatus == JobStatus.Running)
|
||||
.OrderBy(z => z.Created)
|
||||
.ToListAsync();
|
||||
|
||||
log.LogTrace($"killStuckJobsAsync processing: cutoff={dtRunningDeadline.ToString()}, for {jobs.Count.ToString()} jobs of status {JobStatus.Running.ToString()}");
|
||||
|
||||
foreach (OpsJob j in jobs)
|
||||
{
|
||||
//OPSMETRIC
|
||||
await JobsBiz.LogJobAsync(j.GId, "Job took too long to run - setting to failed", ct);
|
||||
log.LogError($"Job found job stuck in running status and set to failed: deadline={dtRunningDeadline.ToString()}, jobId={j.GId.ToString()}, jobname={j.Name}, jobtype={j.JobType.ToString()}, jobObjectType={j.ObjectType.ToString()}, jobObjectId={j.ObjectId.ToString()}");
|
||||
await JobsBiz.UpdateJobStatusAsync(j.GId, JobStatus.Failed, ct);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace AyaNova.Models
|
||||
public virtual DbSet<UserOptions> UserOptions { get; set; }
|
||||
public virtual DbSet<Widget> Widget { get; set; }
|
||||
public virtual DbSet<GlobalBizSettings> GlobalBizSettings { get; set; }
|
||||
public virtual DbSet<GlobalOpsSettings> GlobalOpsSettings { get; set; }
|
||||
public virtual DbSet<Event> Event { get; set; }
|
||||
public virtual DbSet<SearchDictionary> SearchDictionary { get; set; }
|
||||
public virtual DbSet<SearchKey> SearchKey { get; set; }
|
||||
|
||||
22
server/AyaNova/models/GlobalOpsSettings.cs
Normal file
22
server/AyaNova/models/GlobalOpsSettings.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json;
|
||||
namespace AyaNova.Models
|
||||
{
|
||||
|
||||
public class GlobalOpsSettings
|
||||
{
|
||||
public long Id { get; set; }//this is always 1 as there is only ever one single global Ops object
|
||||
public uint Concurrency { get; set; }
|
||||
|
||||
//Global settings
|
||||
//Picklist and other searches override the normal case insensitive value
|
||||
//this is precautionarily added for non latinate languages where it could be an issue
|
||||
public bool SearchCaseSensitiveOnly {get;set;}
|
||||
|
||||
public GlobalOpsSettings()
|
||||
{
|
||||
Id=1;//always 1
|
||||
SearchCaseSensitiveOnly = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
server/AyaNova/util/ServerGlobalOpsSettings.cs
Normal file
47
server/AyaNova/util/ServerGlobalOpsSettings.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using AyaNova.Models;
|
||||
|
||||
namespace AyaNova.Util
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Contains static mirror copy in memory of global settings values that are set from DB during boot
|
||||
/// and accessible to Biz admin user (equivalent of v7's global object)
|
||||
/// used by many areas of the biz logic and processing too often to fetch on every request
|
||||
/// set at boot and on any update to the db global biz settings record
|
||||
/// </summary>
|
||||
internal static class ServerGlobalOpsSettings
|
||||
{
|
||||
|
||||
internal static bool SearchCaseSensitiveOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Populate and / or create the settings
|
||||
/// </summary>
|
||||
internal static void Initialize(GlobalBizSettings global, AyContext ct = null)
|
||||
{
|
||||
|
||||
if (global == null)
|
||||
{
|
||||
//fetch or create as not provided (meaning this was called from Startup.cs)
|
||||
global = ct.GlobalBizSettings.FirstOrDefault(z => z.Id == 1);
|
||||
if (global == null)
|
||||
{
|
||||
global = new GlobalBizSettings();
|
||||
ct.GlobalBizSettings.Add(global);
|
||||
ct.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
//We have the object, now copy the static values here
|
||||
SearchCaseSensitiveOnly = global.SearchCaseSensitiveOnly;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
Reference in New Issue
Block a user