v1.3.0 Restructure Saxonite workspace and upgrade to .NET 10 (#1)

Move Saxonite’s projects into a dedicated `Workspace` folder and align the solution with .NET 10 and the current Common workspace. Update encryption integration, test project structure and coverage, and repository guidance and upgrade notes.

- Relocate application and library projects while retaining Git rename history.
- Split tests into Admin, Online, and Shared projects; add representative behavioral and serialization tests.
- Update Common integration, application version, and repository documentation.

Application version: 1.3.0

Reviewed-on: #1
Co-authored-by: andy <andy@sarand.com>
This commit was merged in pull request #1.
This commit is contained in:
2026-10-16 18:24:16 +01:00
committed by andy
parent 2c3fb146ab
commit 9f22b522df
1996 changed files with 1992 additions and 1179 deletions
@@ -0,0 +1,31 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.Consoles;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Admin.Consoles
{
//=======================================================================
public class SaxActivityConsole_Admin : SaxActivityConsole_Web
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
public SaxActivityConsole_Admin(SaxSessionContext sessionContext) :
base(sessionContext)
{
SaLogger.Info(SysUtil.Fn + "");
}
//-------------------------------------------------------------------
// Utilities.
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,88 @@
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Admin.App;
using Saxonite.Net.Core.Web.Base.Consoles;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Consoles
{
//=======================================================================
public class SaxConsoleHelper_Admin : SaxConsoleHelper_Web
{
//-------------------------------------------------------------------
// Data members:
private readonly SaxApplication_Admin app;
//-------------------------------------------------------------------
// Construction.
public SaxConsoleHelper_Admin(SaxApplication_Admin app)
{
this.app = app;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Clear process consoles.
foreach (SaxProcessConsole_Admin processConsole in processConsoles.Values)
{
processConsole.Dispose();
}
processConsoles.Clear();
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Data cache console.
public override SaxDataCacheConsole GetDataCacheConsole(SaxSessionContext sessionContext)
{
//throw new NotSupportedException(fn + " Data Cache Console not supported in Admin app.");
return null;
}
//-------------------------------------------------------------------
// Session data console.
public override SaxSessionDataConsole GetSessionDataConsole(SaxSessionContext sessionContext)
{
// Method is called by Base Controller on ValidUserSession and must return null for Admin app.
return null;
}
//-------------------------------------------------------------------
// Process console.
private Dictionary<ushort, SaxProcessConsole_Admin> processConsoles { get; } = new Dictionary<ushort, SaxProcessConsole_Admin>();
public SaxProcessConsole GetProcessConsole(SaxSessionContext sessionContext)
{
if (sessionContext == null) throw new NotSupportedException($"{SysUtil.Fn} Session Context must be specified.");
SaxProcessConsole_Admin processConsole = processConsoles.GetValueOrDefault(sessionContext.Index, null);
if (processConsole == null)
{
processConsole = processConsoles[sessionContext.Index] = new SaxProcessConsole_Admin(sessionContext);
}
return processConsole;
}
//-------------------------------------------------------------------
// Activity console.
public override SaxActivityConsole CreateActivityConsole(SaxSessionContext sessionContext)
{
return new SaxActivityConsole_Admin(sessionContext);
}
//-------------------------------------------------------------------
// Email console.
public override SaxEmailConsole CreateEmailConsole(SaxSessionContext sessionContext)
{
SaxEmailConsole emailConsole = new SaxEmailConsole_Web(app.EmailConfig);
return emailConsole;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,124 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Tasks;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Admin.App;
using Saxonite.Net.Core.Web.Base.Consoles;
using Saxonite.Net.Core.Web.Base.Procs;
using Saxonite.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Consoles
{
//=======================================================================
public class SaxHostedServiceConsole_Admin : SaxHostedServiceConsole_Web
{
//-------------------------------------------------------------------
// Definitions.
private enum ProcessType { Unknown = 0, GolfUsgaReferenceLookup }
/// <summary>
/// Process tracking data to control when process is run.
/// </summary>
private class ProcessControlData
{
// Construction.
public ProcessControlData(ProcessType processType, TimeSpan minTimeBetweenRuns)
{
this.ProcessType = processType;
this.MinTimeBetweenRuns = minTimeBetweenRuns;
ResetRunTime();
}
// Properties.
public ProcessType ProcessType { get; }
public TimeSpan MinTimeBetweenRuns { get; }
public DateTime LastRunAt { get; private set; }
// Utilities.
public bool ReadyToRun()
{
//SaLogger.Info($"{SysUtil.Fn} Process: {ProcessType}. Secs since last run: {(DateTime.Now - LastRunAt).TotalSeconds}. Seconds between runs: {MinTimeBetweenRuns.TotalSeconds}.");
return (DateTime.Now - LastRunAt) > MinTimeBetweenRuns;
}
public void ResetRunTime()
{
LastRunAt = DateTime.Now;
}
}
//-------------------------------------------------------------------
// Data members.
private Dictionary<ProcessType, ProcessControlData> processes { get; } = new Dictionary<ProcessType, ProcessControlData>();
//-------------------------------------------------------------------
// Construction.
public SaxHostedServiceConsole_Admin()
{
InitialiseProcesses();
}
//-------------------------------------------------------------------
// Clean-up.
protected override void Dispose(bool disposing)
{
}
//-------------------------------------------------------------------
/// <summary>
/// Periodic processing to execute application processes as required.
/// <para>- See parent class for timer logic.</para>
/// </summary>
protected override void DoWork(object state)
{
//SaLogger.Info($"{SysUtil.Fn} Timed Background Service is working. State: {state}");
SaxSessionContext sessionContext = SaxApplication_Admin.AppInstance.SessionContext;
DbUpdateInfo dbUpdateInfo = new DbUpdateInfo(new SysId(), "AdminScheduled", SacsSystem.ComputerName);
// Check for cancellation.
var token = ProcessTaskData.CancellationToken;
if (token.IsCancellationRequested) return;
// Identify processes that are ready to run.
foreach (ProcessControlData process in processes.Where(item => item.Value.ReadyToRun()).Select(item => item.Value))
{
//SaLogger.Info($"{SysUtil.Fn} Executing process: {process.ProcessType}.");
process.ResetRunTime();
switch (process.ProcessType)
{
case ProcessType.GolfUsgaReferenceLookup:
// Run on separate thread.
Task task = new Task(() =>
{
SaxGolfUsgaDataLookup.ExecuteScheduledUsgaDataLookup(sessionContext, dbUpdateInfo, ProcessTaskData);
}, ProcessTaskData.CancellationToken);
task.Start();
break;
}
}
}
//-------------------------------------------------------------------
// Initialisation.
private void InitialiseProcesses()
{
if (Sax.Config.Admin.Processes.Scheduled.UsgaGolfDataLookup.Enabled)
InitialiseProcess(ProcessType.GolfUsgaReferenceLookup, TimeSpan.FromMinutes(MathUtil.Constrain(Sax.Config.Admin.Processes.Scheduled.UsgaGolfDataLookup.MinsBetweenExecution, 2, 120)));
}
private void InitialiseProcess(ProcessType processType, TimeSpan minTimeBetweenRuns)
{
if (!processes.ContainsKey(processType))
processes[processType] = new ProcessControlData(processType, minTimeBetweenRuns);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,133 @@
using MimeKit;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Procs;
using Saxonite.Net.Core.Web.Admin.Procs;
using Saxonite.Net.Core.Web.Base.Procs;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using Saxonite.Net.Standard.Base.Procs;
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Saxonite.Net.Core.Web.Admin.Consoles
{
//=======================================================================
public class SaxProcessConsole_Admin : SaxProcessConsole
{
//-------------------------------------------------------------------
// Definitions.
public enum ProcessType
{
OrganisationUpload,
GolfUsgaDataLookup
}
//-------------------------------------------------------------------
// Process cache - to sustain processes outside of request/response timescales.
internal class ProcessData
{
public ProcessData(SaxProcess_Web process)
{
this.Process = process;
}
public SaxProcess_Web Process { get; }
}
// Cache.
private Dictionary<ProcessType, ProcessData> processes { get; } = new Dictionary<ProcessType, ProcessData>();
//-------------------------------------------------------------------
// Data members.
//private SaxInternals internals = null;
//-------------------------------------------------------------------
// Construction.
public SaxProcessConsole_Admin(SaxSessionContext sessionContext) : base(sessionContext)
{
}
protected async override void Dispose(bool disposing)
{
if (disposing)
{
// Clear processes.
foreach (ProcessData processData in processes.Values)
{
await processData.Process.StopProcessAsync();
processData.Process.Dispose();
}
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Processes.
//-------------------------------------------------------------------
public SaxProcess_Web GetProcess(ProcessType processType)
{
return processes.GetValueOrDefault(processType, null)?.Process;
}
public bool IsProcessing(ProcessType processType)
{
SaxProcess_Web process = GetProcess(processType);
if (process != null)
if (process.Processing) return true;
return false;
}
/// <summary>
/// Remove the process from the cache.
/// </summary>
/// <param name="processType"></param>
/// <returns></returns>
/// <exception cref="InProgressException"></exception>
public bool ClearProcess(ProcessType processType)
{
SaxProcess_Web process = GetProcess(processType);
if (process != null)
{
if (process.Processing)
throw new SaProcess_Base.InProgressException();
process.Dispose();
}
return processes.Remove(processType);
}
public void SetProcess(ProcessType processType, SaxProcess_Web process)
{
ClearProcess(processType);
processes.Add(processType, new ProcessData(process));
}
//-------------------------------------------------------------------
// Specific processes.
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Organisation Upload.
public SaxOrganisationUploader GetOrganisationUploader()
{
return GetProcess(ProcessType.OrganisationUpload) as SaxOrganisationUploader;
}
public void SetProcess(SaxOrganisationUploader process)
{
SetProcess(ProcessType.OrganisationUpload, process);
}
//-------------------------------------------------------------------
// Organisation Upload.
public SaxGolfUsgaDataLookup GetGolfUsgaDataLookup()
{
return GetProcess(ProcessType.GolfUsgaDataLookup) as SaxGolfUsgaDataLookup;
}
public void SetProcess(SaxGolfUsgaDataLookup process)
{
SetProcess(ProcessType.OrganisationUpload, process);
}
//-------------------------------------------------------------------
}
//=======================================================================
}