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<UserOptions> UserOptions { get; set; }
|
||||||
public virtual DbSet<Widget> Widget { get; set; }
|
public virtual DbSet<Widget> Widget { get; set; }
|
||||||
public virtual DbSet<GlobalBizSettings> GlobalBizSettings { 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<Event> Event { get; set; }
|
||||||
public virtual DbSet<SearchDictionary> SearchDictionary { get; set; }
|
public virtual DbSet<SearchDictionary> SearchDictionary { get; set; }
|
||||||
public virtual DbSet<SearchKey> SearchKey { 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