v1.2.11.2

This commit is contained in:
2024-02-27 15:45:47 +00:00
parent 52232331f8
commit 9643447b85
90 changed files with 2010 additions and 688 deletions
+10 -1
View File
@@ -15,7 +15,16 @@ namespace Saxonite.Net.Core.Web.Admin.App
//-------------------------------------------------------------------
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
try
{
CreateHostBuilder(args).Build().Run();
}
finally
{
// Cleanup - globals, async processes etc.
if (SaxApplication_Admin.AppInstance != null)
SaxApplication_Admin.AppInstance.Dispose();
}
}
//-------------------------------------------------------------------
@@ -49,30 +49,45 @@ namespace Saxonite.Net.Core.Web.Admin.App
try
{
AppInstance = this;
SaxGlobals.App = this;
SaxGlobals.System = (System_Web = new SaxSystem_Web());
Configuration = configuration;
ConsoleHelper_Web = new SaxConsoleHelper_WebAdmin(this);
ConsoleHelper_Admin = new SaxConsoleHelper_Admin(this);
}
catch (Exception exc)
{
SaLogger.Error(GetType().Name + ".CTR " + ExceptionUtil.GetErrorStateMessage(exc));
throw exc;
throw;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Clear globals.
AppInstance = null;
// Console helper.
if (ConsoleHelper_Admin != null)
ConsoleHelper_Admin.Dispose();
ConsoleHelper_Admin = null;
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Properties.
public static SaxApplication_Admin AppInstance { get; private set; }
public List<String> Messages { get; } = new List<String>();
public override SaxConsoleHelper ConsoleHelper { get { return ConsoleHelper_Admin; } }
public SaxConsoleHelper_Admin ConsoleHelper_Admin { get; private set; }
// Database.
public override ISacsDatabase GetDatabase(SaxSessionContext sessionContext, Sax.Database databaseType)
{
return sessionContexts.GetValue(sessionContext.Index, null)?.Databases.GetValue(databaseType, null);
}
public override String SessionCookieName { get { return Sax.Config.Admin.Session.Cookie.Name; } }
// Configuration.
public override SaxAppConfig AppConfig { get { return appConfigs.Values.ToList().getConfigForApplication(); } }
public override SaxEmailConfig EmailConfig { get { return emailConfigs.Values.ToList().getConfigForSession(AppConfig); } }
public override String SessionCookieName { get { return Sax.Config.Admin.Session.Cookie.Name; } }
// Startup messages - for debugging. See Admin.Views._Layout.
public List<String> Messages { get; } = new List<String>();
//-------------------------------------------------------------------
// Initialisation.
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Saxonite.Net.Core.Web.Admin.Consoles;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Standard.Base.App;
using System;
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Consoles
{
//=======================================================================
public class SaxConsoleHelper_WebAdmin : SaxConsoleHelper_Web
public class SaxConsoleHelper_Admin : SaxConsoleHelper_Web
{
//-------------------------------------------------------------------
// Data members:
@@ -18,10 +18,23 @@ namespace Saxonite.Net.Core.Web.Admin.Consoles
//-------------------------------------------------------------------
// Construction.
public SaxConsoleHelper_WebAdmin(SaxApplication_Admin app)
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.
@@ -40,6 +53,20 @@ namespace Saxonite.Net.Core.Web.Admin.Consoles
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(GetType().Name + ".GetProcessConsole 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)
@@ -0,0 +1,120 @@
using MimeKit;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Procs;
using Saxonite.Net.Core.Web.Admin.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
}
//-------------------------------------------------------------------
// Process cache - to sustain processes outside of request/response timescales.
internal class ProcessData
{
public ProcessData(SaxProcess_Admin process)
{
this.Process = process;
}
public SaxProcess_Admin 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_Admin GetProcess(ProcessType processType)
{
return processes.GetValueOrDefault(processType, null)?.Process;
}
public bool IsProcessing(ProcessType processType)
{
SaxProcess_Admin 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_Admin 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_Admin process)
{
ClearProcess(processType);
processes.Add(processType, new ProcessData(process));
}
//-------------------------------------------------------------------
// Specific processes.
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Organisation Upload.
public SaxOrganisationUploader GetSaxOrganisationUploader()
{
return GetProcess(ProcessType.OrganisationUpload) as SaxOrganisationUploader;
}
public void SetProcess(SaxOrganisationUploader process)
{
SetProcess(ProcessType.OrganisationUpload, process);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -26,6 +26,7 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
{
public String Caption { get; set; }
public AjaxResponse_Data.ItemLabelValue Awaiting { get; set; }
public String UsersLink { get; set; }
public String AwaitingLink { get; set; }
public List<AjaxResponse_Data.ItemLabelValue> OnlineStats { get; set; }
public String OnlineLink { get; set; }
@@ -35,6 +36,7 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
{
public String Caption { get; set; }
public AjaxResponse_Data.ItemLabelValue Awaiting { get; set; }
public String OrganisationsLink { get; set; }
public String AwaitingLink { get; set; }
}
public OrganisationStatsData Organisation { get; } = new OrganisationStatsData();
@@ -98,10 +100,12 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
{
case Sax.DataItemType.UserAccount:
responseData.Stats.User.Awaiting = new AjaxResponse_Data.ItemLabelValue() { Label = Localiser["label.awaiting.activation"], Value = itemCount.ToString() };
responseData.Stats.User.UsersLink = Url.Action("", "User").TrimStart('/');
responseData.Stats.User.AwaitingLink = Url.Action("", "User", new RouteValueDictionary() { { "sta", "Await" } }).TrimStart('/');
break;
case Sax.DataItemType.Organisation:
responseData.Stats.Organisation.Awaiting = new AjaxResponse_Data.ItemLabelValue() { Label = Localiser["label.awaiting.activation"], Value = itemCount.ToString() };
responseData.Stats.Organisation.OrganisationsLink = Url.Action("", "Organisation").TrimStart('/');
responseData.Stats.Organisation.AwaitingLink = Url.Action("", "Organisation", new RouteValueDictionary() { { "sta", "Await" } }).TrimStart('/');
break;
}
@@ -36,30 +36,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
{
String fn = GetType().Name + ".Index";
// Check for 'Keep Logged In' cookie.
// NB Only allowed if there isn't a current user session.
if (!ValidUserSession && AppConfig.Security.SignIn.KeepLoggedIn.Allowed)
{
String encryptedParams = GetRequestCookie(KeepLoggedIn.CookieName);
KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams);
String logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", ".");
if (SysId.IsValid(keepLoggedInParams.UserAccountId))
{
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(keepLoggedInParams.UserAccountId);
if (userAccount != null)
{
if (userAccount.IsStatusActive)
{
String errorMessage = "";
if (SignUserIn(userAccount, AccountMgr.GetRolesForUser(userAccount.Id), true, ref errorMessage))
SaLogger.Info(fn + " Keep Logged In cookie found." + logSuffix);
else SaLogger.Warn(fn + " Keep Logged In cookie found but failed to sign user in: " + errorMessage + "." + logSuffix);
}
else SaLogger.Warn(fn + " Keep Logged In cookie found but UserAccount is not active." + logSuffix);
}
else SaLogger.Warn(fn + " Keep Logged In cookie found but UserAccount not found." + logSuffix);
}
}
if (!ValidUserSession)
AutoLoginUser();
// Redirect to sign-in if not signed-in.
if (!ValidUserSession)
@@ -0,0 +1,145 @@
using Microsoft.AspNetCore.Mvc;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Email;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Admin.Consoles;
using Saxonite.Net.Core.Web.Admin.Models;
using Saxonite.Net.Core.Web.Admin.Procs;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Controllers
{
//=======================================================================
// Controller: Organisation - Ajax - Upload
//=======================================================================
partial class OrganisationController
{
//-------------------------------------------------------------------
// Definitions.
//-------------------------------------------------------------------
// Organisation Uploader process info.
//-------------------------------------------------------------------
private class AjaxResponse_OrganisationUploadInfo : AjaxResponse_Data
{
// Construction.
public AjaxResponse_OrganisationUploadInfo(SaxModel_Admin model) : base(model) { }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/UploadInfo")]
public IActionResult Ajax_OrganisationUploadInfo(SaxModel_Admin model)
{
String fn = GetType().Name + ".Ajax_OrganisationUploadInfo";
String messageTitle = Localiser["caption.upload.organisations"];
AjaxResponse_OrganisationUploadInfo responseData = new AjaxResponse_OrganisationUploadInfo(model);
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
// Process console.
SaxProcessConsole_Admin processConsole = GetProcessConsole_Admin(SessionContext);
if (processConsole == null)
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
try
{
// Check whether upload is in progress.
if (processConsole.IsProcessing(SaxProcessConsole_Admin.ProcessType.OrganisationUpload))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.process.in.progress"]));
}
catch (Exception exc)
{
SaLogger.Warn(fn + " " + exc.GetType().Name + " initialising organisation uploader.");
return Json(responseData.SetMessage(messageTitle, exc.GetType().Name + " initialising organisation uploader."));
}
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Upload Organisations.
//-------------------------------------------------------------------
private class AjaxResponse_OrganisationUpload : AjaxResponse_Data
{
// Construction.
public AjaxResponse_OrganisationUpload(SaxModel_Admin model) : base(model) { }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/Upload")]
public IActionResult Ajax_UploadOrganisations(Model_OrganisationUpload model)
{
String fn = GetType().Name + ".Ajax_UploadOrganisations";
String messageTitle = Localiser["caption.upload.organisations"];
AjaxResponse_OrganisationUpload responseData = new AjaxResponse_OrganisationUpload(model);
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
// Validate supplied data properties.
if (!model.ValidateProperties(responseData))
return Json(responseData);
// Process console.
SaxProcessConsole_Admin processConsole = GetProcessConsole_Admin(SessionContext);
if (processConsole == null)
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
try
{
// Check whether upload is in progress.
if (processConsole.IsProcessing(SaxProcessConsole_Admin.ProcessType.OrganisationUpload))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.process.in.progress"]));
// Create the upload process.
SaxOrganisationUploader.Config processConfig = new SaxOrganisationUploader.Config(SessionContext, model.OrganisationDataFile[0]);
processConfig.SheetNameOrNumber = model.SheetNameOrNumber;
SaxOrganisationUploader process = new SaxOrganisationUploader(processConfig);
// Store the process and run.
processConsole.SetProcess(process);
process.RunAsync();
}
catch (Exception exc)
{
SaLogger.Warn(fn + " " + exc.GetType().Name + " initialising organisation uploader.");
return Json(responseData.SetMessage(messageTitle, exc.GetType().Name + " initialising organisation uploader."));
}
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
}
@@ -5,8 +5,10 @@ using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Admin.Models;
using Saxonite.Net.Core.Web.Admin.Procs;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Mgr;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
@@ -126,6 +128,7 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public List<AjaxResponse_Data.ItemCodeDesc> ContactMethods { get; } = new List<ItemCodeDesc>();
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
public String DefaultStatusCode { get; set; }
public List<String> UploadContentTypes { get; set; }
}
//-------------------------------------------------------------------
[HttpPost]
@@ -154,6 +157,9 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
responseData.DefaultStatusCode = SacsSystem.GetStatusCode(SaxOrganisation.DefaultStatus);
// Reference data - Image content types.
responseData.UploadContentTypes = SaxOrganisationUploader.ValidContentTypes;
// Store filter values.
SysId filterSportId = model.Filter.OrganisationSportId.ToSysId();
SessionData_Admin.Filter.SportId = SysId.IsValid(filterSportId) ? filterSportId : null;
@@ -161,14 +167,7 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
SessionData_Admin.Filter.RegionId = SysId.IsValid(filterRegionId) ? filterRegionId : null;
// Item filter.
SaxOrganisation.Params prmOrganisation = new SaxOrganisation.Params();
prmOrganisation.OrganisationFilter = model.Filter.Organisation;
prmOrganisation.OrganisationType = model.FilterOrganisationType;
prmOrganisation.OrganisationCategory = model.FilterOrganisationCategory;
prmOrganisation.SportIdsIncluded.AddId(filterSportId);
prmOrganisation.RegionIdsIncluded.AddId(filterRegionId);
prmOrganisation.StatusCat = model.Filter.OrganisationStatusCategoryCode != null ? model.FilterOrganisationStatusCategory : SaxOrganisation.StatusCat.NotDeleted;
prmOrganisation.SortColumns.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Name));
SaxOrganisation.Params prmOrganisation = GetOrganisationListFilter(model, AccountMgr);
model.Paging.ItemTotal = (ulong)AccountMgr.GetOrganisationCount(prmOrganisation);
model.PopulatePagingTo(prmOrganisation.ResultsPaging);
@@ -237,6 +236,27 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
return Json(responseData);
}
//-------------------------------------------------------------------
/// <summary>
/// Construct Organisation filter params.
/// <para>- Shared with Organisation Exporter process.</para>
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public static SaxOrganisation.Params GetOrganisationListFilter(Model_Organisation model, SaxAccountMgr accountMgr)
{
// Item filter.
SaxOrganisation.Params prmOrganisation = new SaxOrganisation.Params();
prmOrganisation.OrganisationFilter = model.Filter.Organisation;
prmOrganisation.OrganisationType = model.FilterOrganisationType;
prmOrganisation.OrganisationCategory = model.FilterOrganisationCategory;
prmOrganisation.SportIdsIncluded.AddId(model.Filter.OrganisationSportId.ToSysId());
prmOrganisation.RegionIdsIncluded.AddId(model.Filter.OrganisationRegionId.ToSysId());
prmOrganisation.StatusCat = model.Filter.OrganisationStatusCategoryCode != null ? model.FilterOrganisationStatusCategory : SaxOrganisation.StatusCat.NotDeleted;
prmOrganisation.SortColumns.Add(accountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Name));
return prmOrganisation;
}
//-------------------------------------------------------------------
// Get Organisation ajax request.
//-------------------------------------------------------------------
@@ -0,0 +1,79 @@
using Microsoft.AspNetCore.Mvc;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Email;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Proc.Excel;
using Saxonite.Net.Core.Web.Admin.Consoles;
using Saxonite.Net.Core.Web.Admin.Models;
using Saxonite.Net.Core.Web.Admin.Procs;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Controllers
{
//=======================================================================
// Controller: Organisation - Download
//=======================================================================
partial class OrganisationController
{
//-------------------------------------------------------------------
// Definitions.
//-------------------------------------------------------------------
// Download Organisations.
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Download")]
public IActionResult Ajax_DownloadOrganisations(Model_OrganisationDownload model)
{
String fn = GetType().Name + ".Ajax_DownloadOrganisations";
// Validate session.
if (!ValidUserSession)
return Unauthorized();
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
// Validate supplied data properties.
if (!model.ValidateProperties())
return BadRequest();
// Export data.
SaxOrganisationExporter.Config exporterConfig = new SaxOrganisationExporter.Config(SessionContext, GetOrganisationListFilter(model, AccountMgr));
exporterConfig.Format = SaxOrganisationExporter.ExportFormat.Excel_Open;
exporterConfig.IncludeId = StringUtil.ToBool(model.IncludeId);
SaxOrganisationExporter exporter = new SaxOrganisationExporter(exporterConfig);
if (exporter.RunProcess())
{
// Organisations exported successfully.
byte[] data = exporter.GetDocumentData();
using (MemoryStream content = new MemoryStream(data))
{
String contentType = exporter.GetContentType();
var fileName = "Sportierre-Organisations-" + DateTime.Now.ToString("yyyyMMdd") + ".xlsx";
return File(content.ToArray(), contentType, fileName);
}
}
else
{
// Problems occurred.
}
}
return NoContent();
}
//-------------------------------------------------------------------
}
}
@@ -29,6 +29,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public IActionResult Index(String sta)
{
// Validate session.
if (!ValidUserSession)
AutoLoginUser();
if (!ValidUserSession)
return RedirectToAction("", "");
@@ -64,6 +66,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public IActionResult Members(Model_OrganisationMembers model)
{
// Validate session.
if (!ValidUserSession)
AutoLoginUser();
if (!ValidUserSession)
return RedirectToAction("", "");
@@ -90,6 +94,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public IActionResult Associations(Model_OrganisationAssociations model)
{
// Validate session.
if (!ValidUserSession)
AutoLoginUser();
if (!ValidUserSession)
return RedirectToAction("", "");
@@ -26,6 +26,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
AutoLoginUser();
if (!ValidUserSession)
return RedirectToAction("", "");
@@ -6,6 +6,7 @@ using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Admin.App;
using Saxonite.Net.Core.Web.Admin.Consoles;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Standard.Base.App;
@@ -41,6 +42,15 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
return JsonConvert.DeserializeObject<SaxSessionData_Admin>(sessionDataJson, SaxSessionDataJsonSerializerSettings);
}
//-------------------------------------------------------------------
// Globals.
//-------------------------------------------------------------------
protected SaxConsoleHelper_Admin ConsoleHelper_Admin { get { return ConsoleHelper as SaxConsoleHelper_Admin; } }
protected SaxProcessConsole_Admin GetProcessConsole_Admin(SaxSessionContext sessionContext)
{
return ConsoleHelper_Admin?.GetProcessConsole(sessionContext) as SaxProcessConsole_Admin;
}
//-------------------------------------------------------------------
// User Session.
//-------------------------------------------------------------------
@@ -50,54 +60,60 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
/// <para>- Open user session - store details against Sesion Data.</para>
/// <para>- Set 'Keep logged in' cookie if required.</para>
/// </summary>
protected bool SignUserIn(SaxUserAccount userAccount, List<SaxUserRole> userRoles, bool keepLoggedIn, ref String errorMessage)
protected bool SignUserIn(SaxUserAccount userAccount, bool keepLoggedIn, ref String errorMessage)
{
String fn = GetType().Name + ".SignUserIn";
// Validate User Account, just in case - should have been done before arriving here!
if (userAccount == null)
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
SaLogger.Error(fn + " Unable to sign account in - null user account.");
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
List<SaxUserRole> userRoles = AccountMgr.GetRolesForUser(userAccount.Id);
disposableItems.AddRange(userRoles);
// Validate User Account, just in case - should have been done before arriving here!
if (userAccount == null)
{
SaLogger.Error(fn + " Unable to sign account in - null user account.");
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
if (!userAccount.IsStatusActive)
{
SaLogger.Error(fn + " Unable to sign account in - user account is not active. Email: " + userAccount.EmailAddress);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
// 'Keep logged in' can be switched off by the application configuration.
keepLoggedIn = App.AppConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn;
// Log user in - Admin details stored against System Admin role.
SaxUserRole userRole = userRoles.Find(item => item.UserRole == Sax.UserRole.SystemAdmin);
if (userRole == null)
{
SaLogger.Warn(fn + " System Admin role not found for " + userAccount.EmailAddress);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
userRole.KeepLoggedIn = keepLoggedIn;
userRole.LastLoginAt = DateTime.Now;
userRole.InvalidLoginCount = 0;
userRole.LastInvalidLoginAt = DateTime.MinValue;
if (!AccountMgr.UpdateUserRole(userRole, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update Admin user role for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
SaLogger.Info(fn + " User session opened for " + userAccount.EmailAddress, true);
// Create session.
OpenUserSession(SessionContext, userAccount, userRoles);
// Send the 'Keep Logged In' cookie if requested.
if (userRole.KeepLoggedIn)
SetKeepLoggedInCookie();
}
if (!userAccount.IsStatusActive)
{
SaLogger.Error(fn + " Unable to sign account in - user account is not active. Email: " + userAccount.EmailAddress);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
// 'Keep logged in' can be switched off by the application configuration.
keepLoggedIn = App.AppConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn;
// Log user in - Admin details stored against System Admin role.
SaxUserRole userRole = userRoles.Find(item => item.UserRole == Sax.UserRole.SystemAdmin);
if (userRole == null)
{
SaLogger.Warn(fn + " System Admin role not found for " + userAccount.EmailAddress);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
userRole.KeepLoggedIn = keepLoggedIn;
userRole.LastLoginAt = DateTime.Now;
userRole.InvalidLoginCount = 0;
userRole.LastInvalidLoginAt = DateTime.MinValue;
if (!AccountMgr.UpdateUserRole(userRole, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update Admin user role for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
SaLogger.Info(fn + " User session opened for " + userAccount.EmailAddress, true);
// Create session.
OpenUserSession(SessionContext, userAccount, userRoles);
// Send the 'Keep Logged In' cookie if requested.
if (userRole.KeepLoggedIn)
SetKeepLoggedInCookie();
return true;
}
@@ -121,22 +137,50 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
}
//-------------------------------------------------------------------
// IMage utilities.
// Keep Logged In.
//-------------------------------------------------------------------
/// <summary>
/// Construct URL to display Organisation logo.
/// <para>- Option to allow for 'Upload' image if active.</para>
/// Check for 'Keep Logged In' cookie and auto-login user if details are valid.
/// </summary>
protected String GetOrganisationLogoLink(SysId organisationId, bool uploadImage)
/// <returns></returns>
protected bool AutoLoginUser()
{
String imageLink = "Image/Organisation/Logo" + (uploadImage ? "/Upload" : "") + "?";
NameValueCollection parameters = new NameValueCollection();
parameters.Add(ImageController.ParamName_OrganisationId, SysId.ToString(organisationId));
return imageLink + ImageController.ParamName_OrganisationLogoEncrypted + "=" + EncryptSessionParams(parameters);
String fn = "AutoLoginUser";
// NB Only allowed if there isn't a current user session.
if (ValidUserSession) return true;
if (!AppConfig.Security.SignIn.KeepLoggedIn.Allowed) return false;
// Extract cookie details.
String encryptedParams = GetRequestCookie(KeepLoggedIn.CookieName);
KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams);
String logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", ".");
if (SysId.IsValid(keepLoggedInParams.UserAccountId))
{
using (SaxUserAccount userAccount = AccountMgr.GetUserAccountById(keepLoggedInParams.UserAccountId))
{
if (userAccount != null)
{
if (userAccount.IsStatusActive)
{
String errorMessage = "";
if (SignUserIn(userAccount, true, ref errorMessage))
{
// Successful login.
SaLogger.Info(fn + " Keep Logged In cookie found." + logSuffix);
return true;
}
else SaLogger.Warn(fn + " Keep Logged In cookie found but failed to sign user in: " + errorMessage + "." + logSuffix);
}
else SaLogger.Warn(fn + " Keep Logged In cookie found but UserAccount is not active." + logSuffix);
}
else SaLogger.Warn(fn + " Keep Logged In cookie found but UserAccount not found." + logSuffix);
}
}
ClearKeepLoggedInCookie();
return false;
}
//-------------------------------------------------------------------
// Keep Logged In.
//-------------------------------------------------------------------
/// <summary>Keep-Logged-In cookie.</summary>
protected void SetKeepLoggedInCookie()
@@ -164,8 +208,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
// - IV Must be regenerated if key is changed: uncomment 'Generated' variable and copy resulting value to IV variable.
public static class KeepLoggedIn
{
public static String CookieName = "SXKLI";
public static String Base64Key = SacsEncryptionHelper.EncodePlainKey("<=Saxonite^{kEEp+1Ogg3d-IN}~32*>", SacsEncryptionHelper.KeyLength);
public static String CookieName = "SXKLI";
public static String Base64Key = SacsEncryptionHelper.EncodePlainKey("<=Saxonite^{kEEp+1Ogg3d-IN}~32*>", SacsEncryptionHelper.KeyLength);
public static String Base64IV = "YOX10FIPp1zM1pg5U6fW7A==";
private static String Base64IV_Generated = SacsEncryptionHelper.InitSymmetricEncryptionIV(KeepLoggedIn.Base64Key);
}
@@ -205,12 +249,27 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
return keepLoggedInParams;
}
//-------------------------------------------------------------------
// Image utilities.
//-------------------------------------------------------------------
/// <summary>
/// Construct URL to display Organisation logo.
/// <para>- Option to allow for 'Upload' image if active.</para>
/// </summary>
protected String GetOrganisationLogoLink(SysId organisationId, bool uploadImage)
{
String imageLink = "Image/Organisation/Logo" + (uploadImage ? "/Upload" : "") + "?";
NameValueCollection parameters = new NameValueCollection();
parameters.Add(ImageController.ParamName_OrganisationId, SysId.ToString(organisationId));
return imageLink + ImageController.ParamName_OrganisationLogoEncrypted + "=" + EncryptSessionParams(parameters);
}
//-------------------------------------------------------------------
// Parameter encryption, eg. for images.
//-------------------------------------------------------------------
// Encryption key is the same for all users each time the application is started,
// but a different IV value is generated for each session so that the links/parameters cannot be shared.
public static String Base64Key_AdminApp { get; } = SacsEncryptionHelper.EncodePlainKey("Saxonite&Admin#Encryption-"+DateTime.Today.Format("ddMMyy"), SacsEncryptionHelper.KeyLength);
public static String Base64Key_AdminApp { get; } = SacsEncryptionHelper.EncodePlainKey("Saxonite&Admin#Encryption-" + DateTime.Today.Format("ddMMyy"), SacsEncryptionHelper.KeyLength);
protected String EncryptSessionParams(NameValueCollection parameters)
{
return HttpUtility.UrlEncode(EncryptParameters(parameters, SessionData_Admin.Base64IV_AdminApp, Base64Key_AdminApp));
@@ -56,7 +56,7 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
return Json(responseData);
String errorMessage = "";
if (!SignUserIn(userAccount, AccountMgr.GetRolesForUser(userAccount.Id), model.KeepMeSignedIn, ref errorMessage))
if (!SignUserIn(userAccount, model.KeepMeSignedIn, ref errorMessage))
{
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
}
@@ -26,6 +26,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
AutoLoginUser();
if (!ValidUserSession)
return RedirectToAction("", "");
@@ -23,7 +23,7 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
//=======================================================================
// System - Ajax - Database controller
//=======================================================================
partial class SystemController : SaDbProcess_CompareDatabaseSchemas.IProcessNotifier
partial class SystemController
{
//-------------------------------------------------------------------
// Database data.
@@ -169,14 +169,14 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
SaDbProcess_ConvertDbSchema.Config cfgSchemaConvert = new SaDbProcess_ConvertDbSchema.Config();
SaDbProcess_ConvertDbSchema procSchemaConvert = new SaDbProcess_ConvertDbSchema(cfgSchemaConvert);
List<DbDfnTable> tables = procSchemaConvert.ConvertXmlToTables(xmlSchema);
if (procSchemaConvert.HasProcessError())
return Json(responseData.SetMessage(messageTitle, "Failed to convert XML schema: " + procSchemaConvert.GetProcessError()));
if (procSchemaConvert.HasErrorMessage())
return Json(responseData.SetMessage(messageTitle, "Failed to convert XML schema: " + procSchemaConvert.GetErrorMessage()));
SaDbProcess_CompareDatabaseSchemas.Config cfgSchemaCompare = new SaDbProcess_CompareDatabaseSchemas.Config(tables, db);
cfgSchemaCompare.HideIdenticalObjects = true;
cfgSchemaCompare.IgnorePosition = true;
SaDbProcess_CompareDatabaseSchemas procSchemaCompare = new SaDbProcess_CompareDatabaseSchemas(cfgSchemaCompare);
procSchemaCompare.ProcessNotifier = this;
procSchemaCompare.OnSchemaComparisonItemAdded += OnSchemaComparisonItemAdded;
procSchemaCompare.RunProcess();
List<SaDbProcess_CompareDatabaseSchemas.SchemaComparisonItem> schemaComparisonItems = procSchemaCompare.GetSchemaComparisonItems();
@@ -200,17 +200,10 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
return Json(responseData);
}
public void OnStartProcess()
{
}
public void SetProgressInfo(List<string> progressInfo)
{
}
public void OnEndProcess()
{
}
public void OnSchemaComparisonItemAdded(SaDbProcess_CompareDatabaseSchemas proc)
// Processing events.
public void OnSchemaComparisonItemAdded(object sender, SaDbProcess_CompareDatabaseSchemas.SchemaComparisonItemAddedEventArgs e)
{
SaDbProcess_CompareDatabaseSchemas proc = sender as SaDbProcess_CompareDatabaseSchemas;
}
//-------------------------------------------------------------------
@@ -45,6 +45,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public IActionResult Database(Model_System model)
{
// Validate session.
if (!ValidUserSession)
AutoLoginUser();
if (!ValidUserSession)
return RedirectToAction("", "");
@@ -60,6 +62,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public IActionResult Logs(Model_System model)
{
// Validate session.
if (!ValidUserSession)
AutoLoginUser();
if (!ValidUserSession)
return RedirectToAction("", "");
@@ -28,6 +28,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public IActionResult Index(String sta)
{
// Validate session.
if (!ValidUserSession)
AutoLoginUser();
if (!ValidUserSession)
return RedirectToAction("", "");
@@ -28,6 +28,8 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public IActionResult Index(String sta)
{
// Validate session.
if (!ValidUserSession)
AutoLoginUser();
if (!ValidUserSession)
return RedirectToAction("", "");
@@ -1,4 +1,5 @@
using Sarand.Net.Standard.Base.App;
using Sacs.Web.Components;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
@@ -63,7 +64,7 @@ namespace Saxonite.Net.Core.Web.Admin.Models
// Validation.
//-------------------------------------------------------------------
// Address.
public bool ValidateAddressData(SaxModel_Admin model, SaxController_Base.AjaxResponse_Data responseData)
public bool ValidateAddressData(SaxModel_Admin model, SaController.AjaxResponse_Data responseData)
{
// Are any properties populated.
bool populated = IsPopulated();
@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Proc.Excel;
using Saxonite.Base.App;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Models
{
//=======================================================================
/// <summary>
/// Model for Organisation Download.
/// </summary>
public class Model_OrganisationDownload : Model_Organisation
{
//-------------------------------------------------------------------
// Construction.
public Model_OrganisationDownload(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Properties.
public String IncludeId { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
public bool ValidateProperties()
{
return true;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Proc.Excel;
using Saxonite.Base.App;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Models
{
//=======================================================================
/// <summary>
/// Model for Organisation Upload.
/// </summary>
public class Model_OrganisationUpload : SaxModel_Admin
{
//-------------------------------------------------------------------
// Construction.
public Model_OrganisationUpload(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Organisation data files - only one allowed per request.
public List<IFormFile> OrganisationDataFile { get; set; }
// Data file properties.
public String SheetNameOrNumber { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
public bool ValidateProperties(SaxController_Base.AjaxResponse_Data responseData)
{
// Data file.
{
String controlName = nameof(OrganisationDataFile);
// A single data file must be supplied.
if (OrganisationDataFile == null || !OrganisationDataFile.Any())
responseData.SetControlError(controlName, Localiser["msg.error.data.file.missing"]);
else if (OrganisationDataFile != null && OrganisationDataFile.Count() > 1)
responseData.SetControlError(controlName, Localiser["msg.error.data.file.multiple"]);
}
// Sheet name or number.
{
String controlName = nameof(SheetNameOrNumber);
if (StringUtil.IsBlank(SheetNameOrNumber))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!ExcelHelper.IsValidSheetName(SheetNameOrNumber))
responseData.SetControlError(controlName, Localiser["msg.error.sheet.name.invalid"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,198 @@
using Microsoft.AspNetCore.Http;
using Saxonite.Net.Core.Web.Base.Procs.Data;
using System.Collections.Generic;
using System;
using Saxonite.Net.Core.Web.Admin.Models;
using System.Collections;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Proc.Excel;
using Sarand.Net.Standard.Base.App;
using System.IO;
using DocumentFormat.OpenXml.EMMA;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Core.Web.Admin.Controllers;
using Saxonite.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Dbas;
namespace Saxonite.Net.Core.Web.Admin.Procs
{
//=======================================================================
public class SaxOrganisationExporter : SaxProcess_Admin
{
//-------------------------------------------------------------------
// Definitions.
public enum ExportFormat
{
Unknown,
Excel_Open, /* xlxs */
}
// Process configuration.
public new class Config : SaxProcess_Admin.Config
{
public Config(SaxSessionContext sessionContext, SaxOrganisation.Params prmOrganisation) : base(sessionContext)
{
PrmOrganisation = prmOrganisation;
}
// Properties.
public SaxOrganisation.Params PrmOrganisation { get; }
public ExportFormat Format { get; set; }
public bool IncludeId { get; set; }
public ushort MaxRows { get; set; } = 2000;
}
// Processing control data.
private class ProcessControlData
{
public Config Config { get; set; }
public SaMsSpreadsheet Spreadsheet { get; set; }
}
//-------------------------------------------------------------------
// Data members.
private ProcessControlData control { get; } = new ProcessControlData();
//-------------------------------------------------------------------
// Construction.
public SaxOrganisationExporter(Config config) : base(config)
{
control.Config = config;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Spreadsheet.
DisposeSpreadsheet();
}
base.Dispose(disposing);
}
private void DisposeSpreadsheet()
{
if (control.Spreadsheet != null)
control.Spreadsheet.Dispose();
control.Spreadsheet = null;
}
//-------------------------------------------------------------------
// Properties.
public override string ProcessDescription => "Organisation Export";
public byte[] GetDocumentData()
{
switch (control.Config.Format)
{
case ExportFormat.Excel_Open:
return control.Spreadsheet.GetDocumentData();
}
return null;
}
public String GetContentType()
{
switch (control.Config.Format)
{
case ExportFormat.Excel_Open: return SacsContentTypeHelper.ContentType_Excel_Open;
}
return "";
}
//-------------------------------------------------------------------
// Run the process.
protected override bool Run()
{
String fn = GetType().Name + ".Run";
// Verify configuration.
if (control.Config == null)
{
AddErrorMessage("Configuration must be supplied.");
SaLogger.Warn(fn + " " + GetErrorMessage());
return OK;
}
if (control.Config.PrmOrganisation == null)
{
AddErrorMessage("Organisation params must be supplied.");
SaLogger.Warn(fn + " " + GetErrorMessage());
return OK;
}
else if (control.Config.Format != ExportFormat.Excel_Open)
{
AddErrorMessage("Unsupported export format: " + control.Config.Format.ToString() + ".");
SaLogger.Warn(fn + " " + GetErrorMessage());
}
AddInfoMessage("Export format: " + control.Config.Format.ToString());
if (OK) ExportOrganisations();
return OK;
}
//-------------------------------------------------------------------
private bool ExportOrganisations()
{
if (!OK) return false;
DisposeSpreadsheet();
control.Spreadsheet = new SaMsSpreadsheet_POI();
control.Spreadsheet.CreateWorkbook();
control.Spreadsheet.WorksheetName = "Organisations";
control.Spreadsheet.ApplyDefaultStyle(GetExceldefaultWorkbookStyle());
// Columns.
ColumnDfn colId = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Id);
colId.Alignment = ColumnAlignment.Center;
// Columns to output.
List<ColumnDfn> columnsExport = new List<ColumnDfn>();
if (control.Config.IncludeId)
columnsExport.Add(colId);
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Name));
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.OrganisationCategory_Desc));
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.OrganisationType_Desc));
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Sport_Name));
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Region_Name));
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.User_Count));
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Member_Count));
// Initialise spreadsheet column headings and formats etc
SaMsExcelColumnHeading_Config configHeading = GetExcelColumnHeadingConfig();
control.Spreadsheet.SetColumnHeadings(columnsExport, configHeading);
// Columns to query - ensure Id is in the list.
List<ColumnDfn> columnsQuery = new List<ColumnDfn>(columnsExport);
if (!columnsQuery.Exists(item => item.Id == (int)SaxOrganisation.Column.Id))
columnsQuery.Add(colId);
// Organisation params.
control.Config.PrmOrganisation.ResultsPaging.PageNo = 1;
control.Config.PrmOrganisation.ResultsPaging.PageLength = (ushort)Math.Min(ushort.MaxValue, Math.Max(ushort.MinValue, control.Config.MaxRows));
// Query Organisations.
uint dataRowPos = configHeading.FirstDataRowPos - 1;
using (DbResultSet resultSet = AccountMgr.ExecuteOrganisationQuery(columnsQuery, control.Config.PrmOrganisation))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
dataRowPos++;
NamedValue.Map values = resultSet.GetRecordValues();
SysId organisationId = new SysId(values.GetLong(colId.DataName));
int colIdx = -1;
foreach (ColumnDfn col in columnsExport)
{
colIdx++;
uint dataColPos = (uint)(configHeading.FirstHeadingColPos + colIdx);
DataValue value = AccountMgr.GetOrganisationDataValue(col, values);
if (col.ShowZeroValues || !col.IsNumeric || value.ToNumeric() != 0.0)
control.Spreadsheet.SetCellValue(dataColPos, dataRowPos, value);
}
}
}
return OK;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,259 @@
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Proc.Excel;
using Sarand.Net.Standard.Proc.Procs;
using Saxonite.Net.Core.Web.Base.Procs.Data;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Procs;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Procs
{
//=======================================================================
public class SaxOrganisationUploader : SaxProcess_Admin
{
//-------------------------------------------------------------------
// Definitions.
// Process configuration.
public new class Config : SaxProcess_Admin.Config
{
public Config(SaxSessionContext sessionContext, IFormFile dataFile) : base(sessionContext)
{
this.DataFile = new UploadFileData(dataFile);
}
// Properties.
public UploadFileData DataFile { get; }
public String SheetNameOrNumber { get; set; }
public uint HeaderRowPos { get; set; }
public bool ValidateOnly { get; set; }
public bool AllowCreateOrganisation { get; set; }
public bool RequireOrganisationIdForUpdate { get; set; }
}
// Supported content types.
private static HashSet<String> validContentTypes { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
SacsContentTypeHelper.ContentType_Excel_Legacy, /* xls */
SacsContentTypeHelper.ContentType_Excel_Open, /* xlsx */
SacsContentTypeHelper.ContentType_Excel_MacroEnabled, /* xlsm */
SacsContentTypeHelper.ContentType_Excel_BinaryMacroEnabled /* xlsb */
};
/// <summary>Allowed content types.</summary>
public static List<String> ValidContentTypes { get { return new List<String>(validContentTypes); } }
//-------------------------------------------------------------------
// Organisation data profile.
private enum OrganisationProperty
{
// Property columns.
OrganisationId, OrganisationName, Sport, Region,
// Address.
AddressLine1, AddressLine2, AddressTown, AddressPostcode,
// Contact personal.
ContactName, ContactRole, ContactEmail,
// Contact.
ContactPhone, ContactWeb, ContactMap, ContactWiki, ContactFacebook, ContactInstagram, ContactTwitter,
}
private class OrganisationPropertyData
{
public OrganisationPropertyData(OrganisationProperty property, String columnHeaders)
{
this.Property = property;
foreach (String value in Regex.Split(columnHeaders, "[\\s,]+", RegexOptions.None))
if (!StringUtil.IsBlank(value)) this.ColumnHeaders.Add(value);
}
public OrganisationProperty Property { get; }
public HashSet<String> ColumnHeaders { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public uint ColumnPos { get; set; }
public bool IsValid { get { return ColumnPos > 0; } }
}
private class OrganisationDataProfile
{
public OrganisationDataProfile()
{
// Property columns.
AddProperty(OrganisationProperty.OrganisationId, "Id, Organisation Id");
AddProperty(OrganisationProperty.OrganisationName, "Name, Organisation Name");
AddProperty(OrganisationProperty.Sport, "Sport");
AddProperty(OrganisationProperty.Region, "Region");
// Address.
AddProperty(OrganisationProperty.AddressLine1, "Address Line 1, Line 1, Street");
AddProperty(OrganisationProperty.AddressLine2, "Address Line 2, Line 2");
AddProperty(OrganisationProperty.AddressTown, "Town,City,Address Town,Address City");
AddProperty(OrganisationProperty.AddressPostcode, "Postcode");
// Contact personal.
AddProperty(OrganisationProperty.ContactName, "Contact Name");
AddProperty(OrganisationProperty.ContactRole, "Contact Role");
AddProperty(OrganisationProperty.ContactEmail, "Contatc Email");
// Contact.
AddProperty(OrganisationProperty.ContactPhone, "Phone");
AddProperty(OrganisationProperty.ContactWeb, "Web, Web Link, Website");
AddProperty(OrganisationProperty.ContactMap, "Web Map, Map Link");
AddProperty(OrganisationProperty.ContactWiki, "Wiki, Wikipedia, Wiki Link");
AddProperty(OrganisationProperty.ContactFacebook, "Facebook");
AddProperty(OrganisationProperty.ContactInstagram, "Instagram");
AddProperty(OrganisationProperty.ContactTwitter, "X, Twitter");
}
public Dictionary<OrganisationProperty, OrganisationPropertyData> Properties { get; } = new Dictionary<OrganisationProperty, OrganisationPropertyData>();
private void AddProperty(OrganisationProperty property, String columnHeaders)
{
Properties.Add(property, new OrganisationPropertyData(property, columnHeaders));
}
public bool IsValidProperty(OrganisationProperty property)
{
OrganisationPropertyData propertyData = Properties[property];
return propertyData != null && propertyData.IsValid;
}
}
// Processing control data.
private class ProcessControlData
{
public Config config { get; set; }
public SaMsSpreadsheet spreadsheet { get; set; }
public OrganisationDataProfile dataProfile { get; } = new OrganisationDataProfile();
}
//-------------------------------------------------------------------
// Data members.
private ProcessControlData control { get; } = new ProcessControlData();
//-------------------------------------------------------------------
// Construction.
public SaxOrganisationUploader(Config config) : base(config)
{
control.config = config;
}
//-------------------------------------------------------------------
// Properties.
public override string ProcessDescription => "Organisation Upload";
//-------------------------------------------------------------------
// Run the process.
protected override bool Run()
{
String fn = GetType().Name + ".Run";
// Verify file.
if (control.config.DataFile == null)
{
AddErrorMessage("Data file must be supplied.");
SaLogger.Warn(fn + " " + GetErrorMessage());
return OK;
}
else if (!validContentTypes.Contains(control.config.DataFile.ContentType))
{
AddErrorMessage("Unsupported content type: " + control.config.DataFile.ContentType + ".");
SaLogger.Warn(fn + " " + GetErrorMessage());
}
AddInfoMessage("Data file: " + control.config.DataFile.FileName);
AddInfoMessage("Sheet: " + control.config.SheetNameOrNumber);
try
{
if (OK)
{
// Open spreadsheet.
using (SaMsSpreadsheet_POI spreadsheet = new SaMsSpreadsheet_POI())
using (Stream stream = new MemoryStream())
{
stream.Write(control.config.DataFile.FileData, 0, (int)control.config.DataFile.Length);
if (!spreadsheet.OpenWorkbook(stream))
{
// Failed to open workbook.
String msg = "Failed to open workbook - " + spreadsheet.ErrorStateMessage;
AddErrorMessage(msg);
SaLogger.Warn(fn + " " + msg);
}
if (OK && !spreadsheet.OpenWorksheet(1))
{
// Failed to open workbook.
String msg = "Failed to open worksheet - " + spreadsheet.ErrorStateMessage;
AddErrorMessage(msg);
SaLogger.Warn(fn + " " + msg);
}
if (OK)
{
control.spreadsheet = spreadsheet;
PopulateDataProfile();
}
if (OK)
{
uint firstRowPos = spreadsheet.FirstRowPos;
for (uint rowPos = firstRowPos; rowPos <= firstRowPos; rowPos++)
{
for (uint colPos = 1; colPos <= 5; colPos++)
{
SaLogger.Info("Row: " + rowPos + ", colPos: " + colPos + ": '" + spreadsheet.GetCellText(colPos, rowPos) + "'");
}
}
}
// Clean-up.
control.spreadsheet = null;
}
}
}
catch (Exception exc)
{
String msg = exc.GetType().Name + " uploading organisation data.";
AddErrorMessage(msg);
SaLogger.Warn(fn + " " + msg + " " + exc.Message + ExceptionUtil.GetStackEntries(exc, 6).Concat("", "\n\t"));
}
return OK;
}
//-------------------------------------------------------------------
private bool PopulateDataProfile()
{
String fn = GetType().Name + ".PopulateDataProfile";
// Identify header row.
uint headerRowPos = control.config.HeaderRowPos;
if (headerRowPos < 1)
headerRowPos = control.spreadsheet.FirstRowPos;
// Column headings.
List<String> columnHeadings = new List<string>();
for (uint colPos = 1; colPos <= 100; colPos++)
{
columnHeadings.Add(control.spreadsheet.GetCellText(colPos, headerRowPos));
}
SaLogger.Info(fn + " Column headings: " + columnHeadings.Concat(", "));
// Populate header columns in profile.
foreach (OrganisationPropertyData propertyData in control.dataProfile.Properties.Values)
{
for (uint colPos = 1; colPos <= columnHeadings.Count; colPos++)
{
String columnHeading = columnHeadings[(int)(colPos - 1)];
if (propertyData.ColumnHeaders.ContainsCI(columnHeading))
{
if (control.dataProfile.Properties.Values.ToList().Exists(item => item.ColumnPos == colPos))
AddWarningMessage("Multiple properties associated with column '" + columnHeading + "'.");
propertyData.ColumnPos = colPos;
break;
}
}
}
if (!control.dataProfile.IsValidProperty(OrganisationProperty.OrganisationId) && !control.dataProfile.IsValidProperty(OrganisationProperty.OrganisationName))
return AddErrorMessage("Organisation Id or Name column must be present");
return OK;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,64 @@
using Sarand.Net.Standard.Proc.Excel;
using Sarand.Net.Standard.Proc.Procs;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Procs;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Admin.Procs
{
//=======================================================================
public abstract class SaxProcess_Admin : SaxProcess_Base
{
//-------------------------------------------------------------------
// Definitions.
public new class Config : SaxProcess_Base.Config
{
protected Config(SaxSessionContext sessionContext) : base(sessionContext)
{ }
}
//-------------------------------------------------------------------
// Data members.
private Config config;
//-------------------------------------------------------------------
// Construction.
protected SaxProcess_Admin(Config config) : base(config)
{
this.config = config;
}
//-------------------------------------------------------------------
// Excel Spreadsheet utilities.
//-------------------------------------------------------------------
protected SaMsExcelColumnHeading_Config GetExcelColumnHeadingConfig()
{
SaMsExcelColumnHeading_Config configHeading = new SaMsExcelColumnHeading_Config();
configHeading.FontAttr.Name = "Arial";
configHeading.FontAttr.HeightInPoints = 10;
configHeading.FontAttr.ColourIndex = SaXlColourIndex.DarkBlue;
configHeading.FontAttr.Bold = true;
configHeading.HeadingTextWrap = true;
configHeading.ForegroundColourIndex = SaXlColourIndex.LightYellow;
configHeading.BorderWeight = SaXlBorderWeight.Medium;
return configHeading;
}
protected SaMsExcelStyleAttr GetExceldefaultWorkbookStyle()
{
SaMsExcelStyleAttr styleAttr = new SaMsExcelStyleAttr();
styleAttr.FontAttr.Name = "Arial";
styleAttr.FontAttr.HeightInPoints = 9;
styleAttr.FontAttr.ColourIndex = SaXlColourIndex.Black;
styleAttr.HorizontalAlignment = (short)XlHAlign.xlHAlignLeft;
styleAttr.VerticalAlignment = (short)XlVAlign.xlVAlignTop;
styleAttr.WrapText = true;
return styleAttr;
}
//-------------------------------------------------------------------
}
//=======================================================================
}