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:
@@ -0,0 +1,259 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.App
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaxAppConfig
|
||||
{
|
||||
//---------------------------------------------------------------
|
||||
// Data members.
|
||||
private readonly List<String> environments = new List<string>();
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Construction.
|
||||
public SaxAppConfig(String context, ConfigurationSection configSection, SaxAppConfig defaultConfig)
|
||||
{
|
||||
Context = context;
|
||||
PopulateDefaults(defaultConfig);
|
||||
PopulateConfig(configSection);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Properties.
|
||||
public String Context { get; }
|
||||
public List<String> Environments { get { return new List<String>(environments); } }
|
||||
public String Instance { get; set; } = "";
|
||||
// URLs.
|
||||
public class URLConfig
|
||||
{
|
||||
public class OnlineConfig
|
||||
{
|
||||
public String Root { get; set; }
|
||||
}
|
||||
public OnlineConfig Online { get; } = new OnlineConfig();
|
||||
}
|
||||
public URLConfig URL { get; } = new URLConfig();
|
||||
// Security.
|
||||
public class SecurityConfig
|
||||
{
|
||||
public class PasswordConfig
|
||||
{
|
||||
public int MinLength { get; set; }
|
||||
}
|
||||
public PasswordConfig Password { get; } = new PasswordConfig();
|
||||
public class SignUpConfig
|
||||
{
|
||||
public class ActivationConfig
|
||||
{
|
||||
public int EmailLinkExpiresMins = Sax.Config.Security.Activation.EmailLinkExpiresMins_Default;
|
||||
}
|
||||
public ActivationConfig Activation { get; } = new ActivationConfig();
|
||||
}
|
||||
public SignUpConfig SignUp { get; } = new SignUpConfig();
|
||||
public class SignInConfig
|
||||
{
|
||||
public class PasswordRequestConfig
|
||||
{
|
||||
public int EmailLinkExpiresMins = Sax.Config.Security.Activation.EmailLinkExpiresMins_Default;
|
||||
}
|
||||
public PasswordRequestConfig PasswordRequest { get; } = new PasswordRequestConfig();
|
||||
public class KeepLoggedInConfig
|
||||
{
|
||||
public bool Allowed { get; set; } = Sax.Config.Security.SignIn.KeepLoggedIn.Allowed_Default;
|
||||
public int CookieExpiresDays { get; set; } = Sax.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Default;
|
||||
}
|
||||
public KeepLoggedInConfig KeepLoggedIn { get; } = new KeepLoggedInConfig();
|
||||
}
|
||||
public SignInConfig SignIn { get; } = new SignInConfig();
|
||||
}
|
||||
public SecurityConfig Security { get; } = new SecurityConfig();
|
||||
// Email.
|
||||
public class EmailConfig
|
||||
{
|
||||
public class SpecificEmailConfig
|
||||
{
|
||||
public List<String> AddressesTo { get; } = new List<string>();
|
||||
public List<String> AddressesCc { get; } = new List<string>();
|
||||
public List<String> AddressesBcc { get; } = new List<string>();
|
||||
}
|
||||
public SpecificEmailConfig ContactUs { get; } = new SpecificEmailConfig();
|
||||
public SpecificEmailConfig Signup_Activate { get; } = new SpecificEmailConfig();
|
||||
public SpecificEmailConfig Signup_Welcome { get; } = new SpecificEmailConfig();
|
||||
public String Context { get; internal set; }
|
||||
}
|
||||
public EmailConfig Email { get; } = new EmailConfig();
|
||||
// Databases.
|
||||
public class DatabaseConfig
|
||||
{
|
||||
public String Location { get; internal set; }
|
||||
public String User { get; internal set; }
|
||||
public String Password { get; internal set; }
|
||||
}
|
||||
public class DatabasesConfig
|
||||
{
|
||||
public Dictionary<Sax.Database, DatabaseConfig> Databases { get; } = new Dictionary<Sax.Database, DatabaseConfig>();
|
||||
public DatabaseConfig Core { get { return Databases.GetValue(Sax.Database.Core, new DatabaseConfig()); } }
|
||||
public DatabaseConfig Reference { get { return Databases.GetValue(Sax.Database.Reference, new DatabaseConfig()); } }
|
||||
public DatabaseConfig Support { get { return Databases.GetValue(Sax.Database.Support, new DatabaseConfig()); } }
|
||||
}
|
||||
public DatabasesConfig Databases { get; } = new DatabasesConfig();
|
||||
// Documents.
|
||||
public class DocumentsConfig
|
||||
{
|
||||
public String RootFolder { get; internal set; }
|
||||
}
|
||||
public DocumentsConfig Documents { get; } = new DocumentsConfig();
|
||||
// Logging.
|
||||
public class LoggingConfig
|
||||
{
|
||||
public String RootFolder { get; internal set; }
|
||||
}
|
||||
public LoggingConfig Logging { get; } = new LoggingConfig();
|
||||
|
||||
//---------------------------------------------------------------
|
||||
private void PopulateDefaults(SaxAppConfig defaultConfig)
|
||||
{
|
||||
if (defaultConfig == null) return;
|
||||
Security.Password.MinLength = defaultConfig.Security.Password.MinLength;
|
||||
Security.SignIn.KeepLoggedIn.Allowed = defaultConfig.Security.SignIn.KeepLoggedIn.Allowed;
|
||||
Security.SignIn.KeepLoggedIn.CookieExpiresDays = defaultConfig.Security.SignIn.KeepLoggedIn.CookieExpiresDays;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
private void PopulateConfig(ConfigurationSection configSection)
|
||||
{
|
||||
if (configSection != null)
|
||||
{
|
||||
// Environments.
|
||||
foreach (ConfigurationSection environment in configSection.GetSection("Environments").GetChildren())
|
||||
environments.Add(StringUtil.TrimAll(environment.Value));
|
||||
Instance = StringUtil.OnEmpty(configSection["Instance"], Instance);
|
||||
|
||||
// URLs.
|
||||
IConfigurationSection urls = configSection.GetSection("URLs");
|
||||
if (urls.Exists())
|
||||
{
|
||||
IConfigurationSection onlineUrls = urls.GetSection("Online");
|
||||
if (onlineUrls.Exists())
|
||||
{
|
||||
URL.Online.Root = onlineUrls["Root"];
|
||||
}
|
||||
}
|
||||
|
||||
// Security.
|
||||
IConfigurationSection security = configSection.GetSection("Security");
|
||||
if (security.Exists())
|
||||
{
|
||||
// Security - Password.
|
||||
IConfigurationSection securityPassword = security.GetSection("Password");
|
||||
if (securityPassword.Exists())
|
||||
{
|
||||
Security.Password.MinLength = StringUtil.ToInt(securityPassword["MinLength"], Security.Password.MinLength);
|
||||
}
|
||||
// Security - Sign-Up.
|
||||
IConfigurationSection securitySignUp = security.GetSection("SignUp");
|
||||
if (securitySignUp.Exists())
|
||||
{
|
||||
}
|
||||
// Security - Sign-In.
|
||||
IConfigurationSection securitySignIn = security.GetSection("SignIn");
|
||||
if (securitySignIn.Exists())
|
||||
{
|
||||
IConfigurationSection keepLoggedIn = securitySignIn.GetSection("SignIn");
|
||||
if (keepLoggedIn.Exists())
|
||||
{
|
||||
Security.SignIn.KeepLoggedIn.Allowed = StringUtil.ToBool(StringUtil.OnEmpty(keepLoggedIn["Allowed"], Security.SignIn.KeepLoggedIn.Allowed.ToString()));
|
||||
Security.SignIn.KeepLoggedIn.CookieExpiresDays = StringUtil.ToInt(keepLoggedIn["CookieExpiresDays"], Security.SignIn.KeepLoggedIn.CookieExpiresDays);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Email.
|
||||
IConfigurationSection email = configSection.GetSection("Email");
|
||||
if (email.Exists())
|
||||
{
|
||||
Email.Context = email["Context"];
|
||||
PopulateEmailConfig(Email.ContactUs, email.GetSection("ContactUs"));
|
||||
PopulateEmailConfig(Email.Signup_Activate, email.GetSection("Signup").GetSection("Activate"));
|
||||
PopulateEmailConfig(Email.Signup_Welcome, email.GetSection("Signup").GetSection("Welcome"));
|
||||
}
|
||||
|
||||
// Documents.
|
||||
IConfigurationSection documents = configSection.GetSection("Documents");
|
||||
if (documents.Exists())
|
||||
{
|
||||
Documents.RootFolder = StringUtil.OnEmpty(documents["RootFolder"], Documents.RootFolder);
|
||||
}
|
||||
|
||||
// Logging.
|
||||
IConfigurationSection logging = configSection.GetSection("Logging");
|
||||
if (logging.Exists())
|
||||
{
|
||||
Logging.RootFolder = StringUtil.OnEmpty(logging["RootFolder"], Logging.RootFolder);
|
||||
}
|
||||
|
||||
// Database.
|
||||
ExtractDatabasesConfig(Databases, configSection.GetSection("Databases"));
|
||||
|
||||
// Variants.
|
||||
foreach (ConfigurationSection variantSection in configSection.GetSection("Variants").GetChildren())
|
||||
{
|
||||
// Variants override selected values but must match the application root folder to identify a specific instance.
|
||||
String applicationRootPath = variantSection["ApplicationRootFolder"];
|
||||
if (StringUtil.IsEmpty(applicationRootPath)) continue;
|
||||
if (!SaxSystem.ApplicationFolder.EqualsCI(applicationRootPath)) continue;
|
||||
PopulateConfig(variantSection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
private void PopulateEmailConfig(EmailConfig.SpecificEmailConfig config, IConfigurationSection emailSection)
|
||||
{
|
||||
if (!emailSection.Exists()) return;
|
||||
foreach (var addressTo in emailSection.GetSection("AddressesTo").GetChildren())
|
||||
config.AddressesTo.Add(StringUtil.TrimAll(addressTo.Value));
|
||||
foreach (var addressCc in emailSection.GetSection("AddressesCc").GetChildren())
|
||||
config.AddressesCc.Add(StringUtil.TrimAll(addressCc.Value));
|
||||
foreach (var addressBcc in emailSection.GetSection("AddressesBcc").GetChildren())
|
||||
config.AddressesBcc.Add(StringUtil.TrimAll(addressBcc.Value));
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
private void ExtractDatabasesConfig(DatabasesConfig databases, IConfigurationSection databasesSection)
|
||||
{
|
||||
if (!databasesSection.Exists()) return;
|
||||
// 'Database' section contains a separate named configuration for each database: 'Core', 'Support' etc.
|
||||
foreach (var databaseSection in databasesSection.GetChildren())
|
||||
ExtractDatabaseConfig(databases, databaseSection);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
private void ExtractDatabaseConfig(DatabasesConfig databases, IConfigurationSection databaseSection)
|
||||
{
|
||||
if (!databaseSection.Exists()) return;
|
||||
Sax.Database databaseType = EnumUtil.ParseEnum<Sax.Database>(databaseSection.Key);
|
||||
if (databaseType <= 0)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Unrecognised database: {databaseSection.Key}");
|
||||
return;
|
||||
}
|
||||
// Extract database configuration.
|
||||
DatabaseConfig database = new DatabaseConfig();
|
||||
database.Location = StringUtil.OnEmpty(databaseSection["Location"], database.Location);
|
||||
database.User = StringUtil.OnEmpty(databaseSection["User"], database.User);
|
||||
database.Password = StringUtil.OnEmpty(databaseSection["Password"], database.Password);
|
||||
databases.Databases.Add(databaseType, database);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Base.Email;
|
||||
using Sarand.Net.Standard.Base.IO;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Sarand.Net.Standard.Dbas.Base;
|
||||
using Sarand.Net.Standard.Dbas.Data;
|
||||
using Saxonite.Net.Core.Web.Base.Consoles;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using Saxonite.Net.Standard.Base.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.App
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxApplication_Web : SaxApplication_Base
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
protected Sax.InstanceType instanceType;
|
||||
protected String environmentName = "";
|
||||
// Session context.
|
||||
protected class SessionContextData_Web
|
||||
{
|
||||
public SessionContextData_Web(SaxSessionContext_Web sessionContext)
|
||||
{
|
||||
SessionContext = sessionContext;
|
||||
}
|
||||
public SaxSessionContext_Web SessionContext { get; }
|
||||
public Dictionary<Sax.Database, ISacsDatabase> Databases { get; } = new Dictionary<Sax.Database, ISacsDatabase>();
|
||||
public ISacsDatabase CoreDatabase { get { return Databases.GetValue(Sax.Database.Core, null); } }
|
||||
public ISacsDatabase ReferenceDatabase { get { return Databases.GetValue(Sax.Database.Reference, null); } }
|
||||
public ISacsDatabase SupportDatabase { get { return Databases.GetValue(Sax.Database.Support, null); } }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SaxApplication_Web(Sax.Application applicationType)
|
||||
{
|
||||
WebApp = this;
|
||||
System_Web = new SaxSystem_Web();
|
||||
ApplicationType = applicationType;
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
WebApp = null;
|
||||
System_Web = null;
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Globals.
|
||||
public static SaxApplication_Web WebApp { get; private set; }
|
||||
public Sax.Application ApplicationType { get; }
|
||||
public override SaxSystem System { get { return System_Web; } }
|
||||
public SaxSystem_Web System_Web { get; private set; }
|
||||
// Application instance.
|
||||
public override Sax.InstanceType InstanceType { get { return instanceType; } }
|
||||
public override String EnvironmentName { get { return environmentName; } }
|
||||
public bool Development { get; protected set; }
|
||||
public String LoggingRootFolder { get; protected set; } = "";
|
||||
// Application components.
|
||||
public abstract String SessionCookieName { get; }
|
||||
public virtual SaxSessionContext SessionContext { get; protected set; }
|
||||
public abstract SaxAppConfig AppConfig { get; }
|
||||
public SaxConsoleHelper_Web ConsoleHelper_Web { get; protected set; }
|
||||
public override IDatabaseInfo CreateDbInfo(SaxSessionContext sessionContext, Sax.Database databaseType) { return new DbDataItem.DatabaseInfo(GetDatabase(sessionContext, databaseType)); }
|
||||
// Configuration.
|
||||
public IConfiguration Configuration { get; protected set; }
|
||||
public IConfiguration SaxConfiguration { get; private set; }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
protected void InitEnvironment(IWebHostEnvironment env, String logFilenamePrefix)
|
||||
{
|
||||
Development = env.IsDevelopment();
|
||||
environmentName = env.EnvironmentName;
|
||||
SaxConfiguration = LoadApplicationConfiguration();
|
||||
|
||||
// Configure logging.
|
||||
SaLogger.LogFilenamePrefix = logFilenamePrefix;
|
||||
SaLogger.IncludeHostInFileMessage = false;
|
||||
SaLogger.IncludeUserInFileMessage = false;
|
||||
|
||||
//Messages.Add($"Host: {SaxSystem.ComputerName}");
|
||||
//Messages.Add($"Config file: appsettings.machine.{SaxSystem.ComputerName}.json");
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Application configuration.
|
||||
private IConfiguration LoadApplicationConfiguration()
|
||||
{
|
||||
// Load configuration resource files.
|
||||
// Later files override values from previous.
|
||||
var builder =
|
||||
new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddEnvironmentVariables();
|
||||
|
||||
// Saxonite Web configuration - shared by all applications.
|
||||
Stream configStream = SaxResourceHelper.GetResourceStream("appsettings.json", "Config");
|
||||
if (configStream != null) builder.AddJsonStream(configStream);
|
||||
configStream = SaxResourceHelper.GetResourceStream($"appsettings.{environmentName}.json", "Config");
|
||||
if (configStream != null) builder.AddJsonStream(configStream);
|
||||
configStream = SaxResourceHelper.GetResourceStream($"appsettings.machine.{SaxSystem.ComputerName}.json", "Config");
|
||||
if (configStream != null) builder.AddJsonStream(configStream);
|
||||
|
||||
// Application specific configuration.
|
||||
builder
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
|
||||
.AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: false)
|
||||
.AddJsonFile($"appsettings.machine.{SaxSystem.ComputerName}.json", optional: true, reloadOnChange: false);
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Database configuration.
|
||||
protected void ConfigureDatabaseConnection(SessionContextData_Web sessionContextData, Sax.Database databaseInstance)
|
||||
{
|
||||
// Configure database connection.
|
||||
SaxAppConfig.DatabaseConfig databaseConfig = AppConfig.Databases.Databases.GetValue(databaseInstance, new SaxAppConfig.DatabaseConfig());
|
||||
SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig();
|
||||
config.Location = databaseConfig.Location;
|
||||
config.User = databaseConfig.User;
|
||||
config.Password = databaseConfig.Password;
|
||||
SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config);
|
||||
if (database.HasErrorState())
|
||||
SaLogger.Warn(SysUtil.Fn + " " + databaseInstance.ToString() + " Database initialisation error: " + database.ErrorStateMessage);
|
||||
sessionContextData.Databases.Add(databaseInstance, database);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Logging.
|
||||
protected void LogApplicationStart(String fn)
|
||||
{
|
||||
// Log application start.
|
||||
List<StringPair> applicationParams = new List<StringPair>();
|
||||
applicationParams.Add("Host", SaxSystem.ComputerName);
|
||||
applicationParams.Add("Version", Sax.Version.GetFullVersion());
|
||||
applicationParams.Add("Instance", InstanceType.ToString() + (!StringUtil.IsEmpty(AppConfig.Instance) ? " [" + AppConfig.Instance + "]" : ""));
|
||||
applicationParams.Add("Configuration", SaxSystem.DebugMode ? "Debug" : "Release");
|
||||
applicationParams.Add("Environment", EnvironmentName);
|
||||
if (!StringUtil.IsBlank(AppConfig.Databases.Core.Location))
|
||||
applicationParams.Add("Database - Core", AppConfig.Databases.Core.Location);
|
||||
if (!StringUtil.IsBlank(AppConfig.Databases.Reference.Location))
|
||||
applicationParams.Add("Database - Reference", AppConfig.Databases.Reference.Location);
|
||||
if (!StringUtil.IsBlank(AppConfig.Databases.Support.Location))
|
||||
applicationParams.Add("Database - Support", AppConfig.Databases.Support.Location);
|
||||
applicationParams.Add("Application root", SaxSystem.ApplicationFolder);
|
||||
if (!StringUtil.IsEmpty(AppConfig.Documents.RootFolder))
|
||||
applicationParams.Add("Documents root", AppConfig.Documents.RootFolder);
|
||||
applicationParams.Add("Email context", AppConfig.Email.Context + (!StringUtil.IsEmpty(EmailConfig.SMTP.Server) ? " [" + EmailConfig.SMTP.Server + "]" : ""));
|
||||
SaLogger.Info(fn + " Application start." + applicationParams.Select(item => StringUtil.PadRight(item.Item1 + ":", 23, ' ') + " " + item.Item2).Concat("", "\n\t"), true);
|
||||
if (StringUtil.IsEmpty(EmailConfig.SMTP.Server) || StringUtil.IsEmpty(EmailConfig.Message.AddressFrom))
|
||||
SaLogger.Warn(fn + " Incomplete email configuration.", true);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
protected void InitialiseTemporaryLoggingFolder()
|
||||
{
|
||||
// Default logging folder - in case of startup failure.
|
||||
try
|
||||
{
|
||||
foreach (String folder in new String[] { @"D:\Sarand\Projects\User\AHoward\VS2017\Saxonite\Logs" })
|
||||
{
|
||||
if (DirectoryUtil.DirectoryExists(folder))
|
||||
{
|
||||
SaLogger.LogFolder = folder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.App
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SaxGlobals_Web
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// System.
|
||||
public static SaxSystem_Web System { get { return SaxGlobals_Web.System as SaxSystem_Web; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Application.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.Localization;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
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 Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Resources;
|
||||
using System.Web;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.App
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxLocaliser
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Localisers are supplied by derived object.
|
||||
/// </summary>
|
||||
protected SaxLocaliser()
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public abstract IStringLocalizer MainProperties { get; }
|
||||
public virtual List<IStringLocalizer> AlternativeProperties { get; } = new List<IStringLocalizer>();
|
||||
private HashSet<String> MissingKeysLogged { get; } = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Generic phrase translation.
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Retrieve localised text.</summary>
|
||||
public LocalizedString GetText(string key)
|
||||
{
|
||||
return GetText(key, null);
|
||||
}
|
||||
/// <summary>Retrieve localised text.</summary>
|
||||
public LocalizedString GetText(string key, bool logMissingKey = true)
|
||||
{
|
||||
return GetText(key, null, logMissingKey);
|
||||
}
|
||||
/// <summary>Retrieve localised text.</summary>
|
||||
public LocalizedString GetText(string key, object param)
|
||||
{
|
||||
return GetText(key, new object[] { param });
|
||||
}
|
||||
/// <summary>Retrieve localised text.</summary>
|
||||
public LocalizedString GetText(string key, object param1, object param2)
|
||||
{
|
||||
return GetText(key, new object[] { param1, param2 });
|
||||
}
|
||||
/// <summary>Retrieve localised text.</summary>
|
||||
public LocalizedString GetText(string key, object[] parameters, bool logMissingKey = true)
|
||||
{
|
||||
LocalizedString text = parameters == null || parameters.IsEmpty() ? MainProperties.GetString(key) : MainProperties.GetString(key, parameters);
|
||||
if (text.ResourceNotFound)
|
||||
{
|
||||
// Look up key in alternative property files.
|
||||
foreach (IStringLocalizer properties in AlternativeProperties ?? new List<IStringLocalizer>())
|
||||
{
|
||||
text = parameters == null || parameters.IsEmpty() ? properties.GetString(key) : properties.GetString(key, parameters);
|
||||
if (!text.ResourceNotFound)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (text.ResourceNotFound && logMissingKey && !MissingKeysLogged.Contains(key))
|
||||
{
|
||||
MissingKeysLogged.Add(key);
|
||||
SaLogger.Debug($"{SysUtil.Fn} Key not found: {key}");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// Square bracket indexing.
|
||||
public LocalizedString this[String key]
|
||||
{
|
||||
get { return GetText(key); }
|
||||
}
|
||||
public LocalizedString this[String key, object param]
|
||||
{
|
||||
get { return GetText(key, param); }
|
||||
}
|
||||
public LocalizedString this[String key, object param1, object param2]
|
||||
{
|
||||
get { return GetText(key, param1, param2); }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Localised HTML text.
|
||||
/// <para>Line delimiters changed to '<br/>'.</para>
|
||||
/// </summary>
|
||||
public HtmlString GetHtmlText(string key, String param1 = "", String param2 = "")
|
||||
{
|
||||
return GetHtmlText(key, new Object[] { param1, param2 });
|
||||
}
|
||||
/// <summary>
|
||||
/// Localised HTML text.
|
||||
/// <para>Line delimiters changed to '<br/>'.</para>
|
||||
/// </summary>
|
||||
public HtmlString GetHtmlText(string key, object[] parameters)
|
||||
{
|
||||
LocalizedString localisedText = GetText(key, parameters);
|
||||
String value = localisedText.Value;
|
||||
// Replace all line delimiters.
|
||||
value = value.Replace("\r\n", "\n").Replace("\n", "<br/>").Replace("\r", "<br/>");
|
||||
return new HtmlString(value);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Localised javascript string.
|
||||
/// <para>All quotes are escaped.</para>
|
||||
/// </summary>
|
||||
public LocalizedString GetJavascriptText(string key, String param1 = "", String param2 = "")
|
||||
{
|
||||
LocalizedString localisedText = GetText(key, param1, param2);
|
||||
return new LocalizedString(localisedText, EscapeJavascriptText(localisedText.Value), localisedText.ResourceNotFound);
|
||||
}
|
||||
public String EscapeJavascriptText(string value)
|
||||
{
|
||||
// Escape all quotes.
|
||||
value = value.Replace("'", "\\'").Replace("\"", "\\\"");
|
||||
return value;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Numeric and datetime formatting
|
||||
//-------------------------------------------------------------------
|
||||
public static String DateFormat_Default { get; } = "d-MMM-yyyy";
|
||||
public static String DateWithWeekdayFormat_Default { get; } = "ddd d-MMM-yyyy";
|
||||
public String FormatDate(DateTime? value, Boolean includeWeekday = true)
|
||||
{
|
||||
String dateFormat = includeWeekday ? DateWithWeekdayFormat_Default : DateFormat_Default;
|
||||
return DateUtil.Format(value, dateFormat);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Specific phrase translation.
|
||||
//-------------------------------------------------------------------
|
||||
private class CodesCache
|
||||
{
|
||||
private Dictionary<Sax.CodeType, List<TypeCodeDesc>> codeTypeCache { get; } = new Dictionary<Sax.CodeType, List<TypeCodeDesc>>();
|
||||
public List<TypeCodeDesc> GetCodesOfType(Sax.CodeType codeType)
|
||||
{
|
||||
List<TypeCodeDesc> codes = null;
|
||||
lock (codeTypeCache)
|
||||
{
|
||||
codes = codeTypeCache.GetValue(codeType, null);
|
||||
if (codes == null)
|
||||
{
|
||||
codes = SaxSystem.GetCodesOfType(codeType);
|
||||
codeTypeCache.Add(codeType, codes);
|
||||
}
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
}
|
||||
private CodesCache codesCache = new CodesCache();
|
||||
|
||||
// Status.
|
||||
public String GetStatusCaption(SaStatus status)
|
||||
{
|
||||
// ToDo Status description localisation.
|
||||
return SacsSystem.GetStatusDesc(status);
|
||||
}
|
||||
|
||||
// Generic code type.
|
||||
private String GetCodeTypeCaption<T>(Sax.CodeType codeType, T type) where T : struct, IConvertible
|
||||
{
|
||||
if (!(type is Enum))
|
||||
throw new ArgumentException($"{SysUtil.Fn} Type parameter must be an enumerated type. Type supplied: {type.GetType().Name}");
|
||||
return GetCodeTypeCaption(codeType, (int)(type as IConvertible), type.ToString());
|
||||
}
|
||||
private String GetCodeTypeCaption(Sax.CodeType codeType, int type, String typeName)
|
||||
{
|
||||
if (type < 1) return "";
|
||||
List<TypeCodeDesc> codes = this.codesCache.GetCodesOfType(codeType);
|
||||
TypeCodeDesc code = codes.GetEntryForType(type);
|
||||
if (code != null)
|
||||
{
|
||||
// Code Type localisation.
|
||||
String codeTypeKey = "codeType." + codeType.ToString() + "." + typeName;
|
||||
String typeDesc = GetText(codeTypeKey, false);
|
||||
if (typeDesc.EqualsCI(codeTypeKey))
|
||||
typeDesc = "";
|
||||
return StringUtil.OnEmpty(typeDesc, code.Desc);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Yes/No.
|
||||
public String GetYesNoCaption(Sax.YesNo yesNo)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.YesNo, yesNo);
|
||||
}
|
||||
|
||||
// Data item type.
|
||||
public String GetItemDataTypeCaption(Sax.DataItemType itemType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.DataItemType, itemType);
|
||||
}
|
||||
|
||||
// Gender.
|
||||
public String GetGenderCaption(Sax.Gender gender)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.Gender, gender);
|
||||
}
|
||||
|
||||
// Sport Grouping.
|
||||
public String GetSportGroupingCaption(Sax.SportGrouping sportGrouping)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.SportGrouping, sportGrouping);
|
||||
}
|
||||
|
||||
// Sport Speciality label.
|
||||
public String GetSportSpecialityLabelCaption(Sax.SportSpecialityLabel sportSpecialityLabel)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.SportSpecialityLabel, sportSpecialityLabel);
|
||||
}
|
||||
|
||||
// Sport Contact Type.
|
||||
public String GetSportContactTypeCaption(Sax.SportContactType sportContactType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.SportContactType, sportContactType);
|
||||
}
|
||||
|
||||
// Sport Participating.
|
||||
public String GetSportParticipatingCaption(Sax.SportParticipating participating)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.SportParticipating, participating);
|
||||
}
|
||||
|
||||
// Sport Preparing.
|
||||
public String GetSportPreparingCaption(Sax.SportPreparing preparing)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.SportPreparing, preparing);
|
||||
}
|
||||
|
||||
// Sport Officiating.
|
||||
public String GetSportOfficiatingCaption(Sax.SportOfficiating officiating)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.SportOfficiating, officiating);
|
||||
}
|
||||
|
||||
// Region type.
|
||||
public String GetRegionTypeCaption(Sax.RegionType regionType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.RegionType, regionType);
|
||||
}
|
||||
|
||||
// Eligibility type.
|
||||
public String GetEligibilityTypeCaption(Sax.EligibilityType regionType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.EligibilityType, regionType);
|
||||
}
|
||||
|
||||
// User role.
|
||||
public String GetUserRoleCaption(Sax.UserRole userRole)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.UserRole, userRole);
|
||||
}
|
||||
|
||||
// Organisation type.
|
||||
public String GetOrganisationTypeCaption(Sax.OrganisationType organisationType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.OrganisationType, organisationType);
|
||||
}
|
||||
|
||||
// Organisation category.
|
||||
public String GetOrganisationCategoryCaption(Sax.OrganisationCategory organisationCategory)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.OrganisationCategory, organisationCategory);
|
||||
}
|
||||
|
||||
// Organisation role.
|
||||
public String GetOrganisationRoleCaption(Sax.OrganisationRole organisationRole)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.OrganisationRole, organisationRole);
|
||||
}
|
||||
|
||||
// Association types.
|
||||
public String GetAssociationTypeCaption(Sax.AssociationType addressType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.AssociationType, addressType);
|
||||
}
|
||||
|
||||
// Address types.
|
||||
public String GetAddressTypeCaption(Sax.AddressType addressType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.AddressType, addressType);
|
||||
}
|
||||
|
||||
// Contact Method.
|
||||
public String GetContactMethodCaption(Sax.ContactMethod contactMethod)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.ContactMethod, contactMethod);
|
||||
}
|
||||
|
||||
// Event type.
|
||||
public String GetEventTypeCaption(Sax.EventType eventType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.EventType, eventType);
|
||||
}
|
||||
|
||||
// Opportunity type.
|
||||
public String GetOpportunityTypeCaption(Sax.OpportunityType opportunityType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.OpportunityType, opportunityType);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Support.
|
||||
|
||||
// User Report Type.
|
||||
public String GetUserReportTypeCaption(Sax.UserReportType reportType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.UserReportType, reportType);
|
||||
}
|
||||
|
||||
// Support Item Process Type.
|
||||
public String GetSupportItemProcessTypeCaption(Sax.SupportItemProcessType processType)
|
||||
{
|
||||
return GetCodeTypeCaption(Sax.CodeType.SupportItemProcessType, processType);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Region.
|
||||
public String GetRegionName(SaxRegion.Data region)
|
||||
{
|
||||
if (region == null) return "";
|
||||
// ToDo Region name localisation.
|
||||
return region.Name;
|
||||
}
|
||||
public String GetRegionName(SysId regionId, String regionName)
|
||||
{
|
||||
// ToDo Region name localisation.
|
||||
return regionName;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Sport.
|
||||
public String GetSportName(SaxSport.Data sport)
|
||||
{
|
||||
if (sport == null) return "";
|
||||
// ToDo Sport name localisation.
|
||||
return sport.Name;
|
||||
}
|
||||
public String GetSportName(SysId sportId, String sportName)
|
||||
{
|
||||
// ToDo Sport name localisation.
|
||||
return sportName;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.App
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaxSessionContext_Web : SaxSessionContext
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Blank construction - used for deserilisation of session data.</summary>
|
||||
protected SaxSessionContext_Web () : base() { }
|
||||
public SaxSessionContext_Web (ushort sesionContextIndex, Sax.Application application) :
|
||||
base(sesionContextIndex, application)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.App
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxSessionData_Web : SaxSessionData
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Private construction - for JSON deserialisation.
|
||||
protected SaxSessionData_Web(long sessionIdx, int itemIdx, SaxSessionContext sessionContext) :
|
||||
base(sessionIdx, itemIdx, sessionContext)
|
||||
{
|
||||
}
|
||||
// Protected construction.
|
||||
protected SaxSessionData_Web(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles) :
|
||||
base(sessionContext, userAccount, userRoles)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Saxonite.Net.Core.Web.Base.Middleware;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.App
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaxStartup
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
// Localisation.
|
||||
private static RequestCulture defaultRequestCulture = new RequestCulture("en-GB", "en-GB");
|
||||
private static List<CultureInfo> supportedCultures = new List<CultureInfo>
|
||||
{
|
||||
new CultureInfo("en-GB"),
|
||||
new CultureInfo("en-US"),
|
||||
new CultureInfo("en"),
|
||||
new CultureInfo("fr-FR"),
|
||||
new CultureInfo("fr"),
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SaxStartup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public IConfiguration Configuration { get; }
|
||||
public static RequestCulture DefaultRequestCulture { get { return defaultRequestCulture; } }
|
||||
public static List<CultureInfo> SupportedCultures { get { return new List<CultureInfo>(supportedCultures); } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
protected class ConfigureServicesConfig
|
||||
{
|
||||
public IServiceCollection services;
|
||||
public String sessionCookieName;
|
||||
public bool sessionCookieHttpOnly;
|
||||
public TimeSpan sessionIdleTimeout;
|
||||
}
|
||||
protected void ConfigureServices(ConfigureServicesConfig config)
|
||||
{
|
||||
// Authentication service.
|
||||
// Required to handle 'Forbidden' responses etc.
|
||||
config.services
|
||||
.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = "forbidScheme";
|
||||
options.DefaultForbidScheme = "forbidScheme";
|
||||
options.AddScheme<SaxAuthenticationHandler>("forbidScheme", "Handle Forbidden");
|
||||
});
|
||||
|
||||
// Added to support memory caching of sessions.
|
||||
config.services.AddMemoryCache();
|
||||
//config.services.AddDistributedMemoryCache(); - for multiple web servers.
|
||||
// Online web application cookie definition.
|
||||
config.services.AddSession
|
||||
(options =>
|
||||
{
|
||||
options.Cookie.Name = config.sessionCookieName;
|
||||
options.Cookie.HttpOnly = config.sessionCookieHttpOnly;
|
||||
options.IdleTimeout = config.sessionIdleTimeout;
|
||||
// Override cookie policy for session cookie. Ref. config.app.UseCookiePolicy()
|
||||
options.Cookie.IsEssential = true;
|
||||
}
|
||||
);
|
||||
|
||||
// Localisation properties folder.
|
||||
String resourcesPath = "Localisation";
|
||||
config.services.AddLocalization(opts => { opts.ResourcesPath = resourcesPath; });
|
||||
|
||||
// Modify pages during debug.
|
||||
#if DEBUG
|
||||
if (SaxSystem.DebugMode)
|
||||
{
|
||||
// If the following line throws an error such as:
|
||||
// 'imvcbuilder' does not contain a definition for 'addrazorruntimecompilation'
|
||||
// Revert the NuGet Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation package
|
||||
// to a previous version and then reinstal the latest version.
|
||||
config.services.AddRazorPages().AddRazorRuntimeCompilation();
|
||||
//config.services.AddControllersWithViews().AddRazorRuntimeCompilation();
|
||||
}
|
||||
#endif
|
||||
|
||||
config.services.AddMvc
|
||||
(options =>
|
||||
{
|
||||
// Model binding - intercepting form values being bound to models.
|
||||
options.ModelBinderProviders.Insert(0, new SaxModelBinderProvider());
|
||||
// Blank form values to be populated to model as empty strings rather than null values.
|
||||
options.ModelMetadataDetailsProviders.Add(new SaxMetadataProvider());
|
||||
}
|
||||
)
|
||||
// View and data annotation localisations.
|
||||
.AddViewLocalization(
|
||||
LanguageViewLocationExpanderFormat.Suffix,
|
||||
opts => { opts.ResourcesPath = resourcesPath; })
|
||||
.AddDataAnnotationsLocalization()
|
||||
// Ignore null values during object serialiation.
|
||||
.AddJsonOptions(options => { options.JsonSerializerOptions.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull; });
|
||||
|
||||
// Configure localisation middleware.
|
||||
config.services.Configure<RequestLocalizationOptions>
|
||||
(opts =>
|
||||
{
|
||||
// Default culture to use for requests.
|
||||
opts.DefaultRequestCulture = DefaultRequestCulture;
|
||||
// Cultures supported by the application. Formatting numbers, dates, etc.
|
||||
opts.SupportedCultures = SupportedCultures;
|
||||
// UI cultures supported by the application. UI strings that have been localized.
|
||||
opts.SupportedUICultures = SupportedCultures;
|
||||
}
|
||||
);
|
||||
|
||||
// Allows access to the HttpContext inside a service.
|
||||
config.services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
protected class ConfigureConfig
|
||||
{
|
||||
public IApplicationBuilder app;
|
||||
public IHostApplicationLifetime hostApplicationLifetime;
|
||||
public IWebHostEnvironment env;
|
||||
}
|
||||
protected void Configure(ConfigureConfig config)
|
||||
{
|
||||
if (config.env.IsDevelopment())
|
||||
{
|
||||
config.app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
config.app.UseExceptionHandler("/Home/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
config.app.UseHsts();
|
||||
}
|
||||
|
||||
config.app.UseHttpsRedirection();
|
||||
config.app.UseStaticFiles();
|
||||
config.app.UseCookiePolicy();
|
||||
config.app.UseSession();
|
||||
config.app.UseRouting();
|
||||
//config.app.UseAuthorization();
|
||||
config.app.UseAntiforgeryTokens();
|
||||
|
||||
// Localisation.
|
||||
IOptions<RequestLocalizationOptions> localizationOptions = config.app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
|
||||
config.app.UseRequestLocalization(localizationOptions.Value);
|
||||
|
||||
config.app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
});
|
||||
|
||||
// Application events.
|
||||
// NB The shutdown events only fire if he application stops 'gracefully'.
|
||||
config.hostApplicationLifetime.ApplicationStarted.Register(OnApplicationStarted);
|
||||
config.hostApplicationLifetime.ApplicationStopping.Register(OnApplicationStopping);
|
||||
config.hostApplicationLifetime.ApplicationStopped.Register(OnApplicationStopped);
|
||||
|
||||
config.app.Use(async (context, next) =>
|
||||
{
|
||||
if (context.Request.Method.Equals(HttpMethod.Get.Method))
|
||||
{
|
||||
context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true,
|
||||
MaxAge = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
//context.Response.Headers[HeaderNames.Vary] = new string[] { "Accept-Encoding" };
|
||||
}
|
||||
await next();
|
||||
});
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Application events.
|
||||
private readonly static String ApplicationEventLogPrefix = "### ";
|
||||
private void OnApplicationStarted()
|
||||
{
|
||||
SaLogger.Info(ApplicationEventLogPrefix + "Application started");
|
||||
}
|
||||
private void OnApplicationStopping()
|
||||
{
|
||||
SaLogger.Info(ApplicationEventLogPrefix + "Application stopping");
|
||||
}
|
||||
private void OnApplicationStopped()
|
||||
{
|
||||
SaLogger.Info(ApplicationEventLogPrefix + "Application stopped");
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Saxonite.Net.Standard.Dbas.App;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Mgr;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.App
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaxSystem_Web : SaxSystem_Dbas
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Static initialisation.</summary>
|
||||
static SaxSystem_Web()
|
||||
{
|
||||
InitComputerName();
|
||||
InitUserName();
|
||||
InitApplicationPath();
|
||||
InitApplicationFolder();
|
||||
InitAssemblyBuildAt();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SaxSystem_Web() : base()
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Computer name
|
||||
/// <para>Windows: System.Environment.MachineName</para>
|
||||
/// <para>Web: HttpContext.Current.Server.MachineName</para>
|
||||
/// <para>FQDN: System.Net.Dns.GetHostName()</para>
|
||||
/// </summary>
|
||||
public static void InitComputerName()
|
||||
{
|
||||
try
|
||||
{
|
||||
ComputerName = System.Environment.MachineName;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn("SaxSystem.InitComputerName Failed to initialise computer name from environment. " + ExceptionUtil.GetErrorStateMessage(exc));
|
||||
}
|
||||
try
|
||||
{
|
||||
if (StringUtil.IsEmpty(ComputerName))
|
||||
ComputerName = Environment.GetEnvironmentVariable("COMPUTERNAME") ?? Environment.GetEnvironmentVariable("HOSTNAME");
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn("SaxSystem.InitComputerName Failed to initialise computer name from environment variable. " + ExceptionUtil.GetErrorStateMessage(exc));
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>To be populated by application<para>Windows: Environment.UserName</para></summary>
|
||||
private static void InitUserName()
|
||||
{
|
||||
try
|
||||
{
|
||||
UserName = Environment.UserName;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn("SaxSystem.InitUserName Failed to initialise user name. " + ExceptionUtil.GetErrorStateMessage(exc));
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Application Path.
|
||||
/// <para>Windows: System.Reflection.Assembly.GetEntryAssembly().Location;</para>
|
||||
/// </summary>
|
||||
private static void InitApplicationPath()
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplicationPath = Assembly.GetEntryAssembly().Location;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn("SaxSystem.InitApplicationPath Failed to initialise application path. " + ExceptionUtil.GetErrorStateMessage(exc));
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Application folder.
|
||||
/// <para>Windows: System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);</para>
|
||||
/// </summary>
|
||||
private static void InitApplicationFolder()
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplicationFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn("SaxSystem.InitApplicationFolder Failed to initialise application folder. " + ExceptionUtil.GetErrorStateMessage(exc));
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// DateTime when the executing assembly was built.
|
||||
/// </summary>
|
||||
private static void InitAssemblyBuildAt()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Assembly build date.
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
AssemblyBuildAt = File.GetLastWriteTime(assembly.Location);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn("SaxSystem.InitAssemblyBuildAt Failed to initialise assembly build date. " + ExceptionUtil.GetErrorStateMessage(exc));
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,607 @@
|
||||
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.Utils;
|
||||
using Sarand.Net.Standard.Dbas.Data;
|
||||
using Saxonite.Base.App;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using Saxonite.Net.Standard.Dbas.Mgr;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Consoles
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxActivityConsole_Web : SaxActivityConsole
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private readonly String alias_ActivityIndex = SaxActivityMgr_Dbas.aliasActivityIndex;
|
||||
private readonly String alias_ActivityIndex_UserActivity = SaxActivityMgr_Dbas.aliasActivityIndex_UserActivity;
|
||||
protected class IndexStatsData
|
||||
{
|
||||
public int EntriesInserted { get; set; }
|
||||
public int EntriesUpdated { get; set; }
|
||||
public int EntriesDeleted { get; set; }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SaxActivityConsole_Web(SaxSessionContext sessionContext) :
|
||||
base(sessionContext)
|
||||
{
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
// Query Activity Index Entries for User Context.
|
||||
//-------------------------------------------------------------------
|
||||
private class ActivityData_Web : IndexResults.ActivityData
|
||||
{
|
||||
public ActivityData_Web(ItemId itemId, DateTime effectiveOn) : base(itemId, effectiveOn) { }
|
||||
public void SetOrganisation(SysId organisationId, String organisation, bool hasLogo) { OrganisationId = organisationId; Organisation = organisation; OrganisationHasLogo = hasLogo; }
|
||||
public void SetAuthor(SysId userAccountId, String author) { UserAccountIdAuthor = userAccountId; Author = author; }
|
||||
public void SetDetails(String caption, String blurb) { Caption = caption; Blurb = blurb; }
|
||||
public void SetSport(SysId sportId, String sportName) { SportId = sportId; SportName = sportName; }
|
||||
public void SetRegion(SysId regionId, String regionName) { RegionId = regionId; RegionName = regionName; }
|
||||
// User Activity.
|
||||
public void SetLastListedAt(DateTime lastListedAt) { LastListedAt = lastListedAt; }
|
||||
public void SetLastViewedAt(DateTime lastViewedAt) { LastViewedAt = lastViewedAt; }
|
||||
public void SetReaction(Sax.ActivityReaction reaction) { Reaction = reaction; }
|
||||
public void SetLastReactedAt(DateTime lastReactedAt) { LastReactedAt = lastReactedAt; }
|
||||
public static int CompareActivityIndices(ActivityData_Web item1, ActivityData_Web item2)
|
||||
{
|
||||
int effectiveCompare = item1.EffectiveOn.CompareTo(item2.EffectiveOn);
|
||||
if (effectiveCompare != 0) return effectiveCompare * -1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
public override IndexResults QueryIndex(IndexContext context)
|
||||
{
|
||||
String fn = GetType().Name + ".QueryIndex";
|
||||
ClearErrorState();
|
||||
IndexResults results = new IndexResults();
|
||||
if (!context.IsValid()) return results;
|
||||
|
||||
// Columns to query.
|
||||
ColumnDfn colItemType = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.ItemType);
|
||||
ColumnDfn colItemId = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.ItemId);
|
||||
// NB Would like to include the Organisation namd and logo count but this involves joing to Organisation and item image
|
||||
// which will make the following query significantly more complex.
|
||||
ColumnDfn colOrganisationId = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.OrganisationIdOwner);
|
||||
ColumnDfn colOrganisationName = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.OrganisationOwner_Name);
|
||||
ColumnDfn colOrganisationLogoCount = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.OrganisationOwner_LogoCount);
|
||||
String colDataNameEffectiveOn = "EffectiveOn";
|
||||
|
||||
// Query the Activity Index.
|
||||
// NB An item may have multiple index entries but these wil only differe by Sport and/or Region.
|
||||
// User, Organisation Effective & Expiry dates wil be the same for all entries for an item.
|
||||
ISacsDatabase db = SaxGlobals.App.GetDatabase(SessionContext, Sax.Database.Core);
|
||||
DbQuery query = new DbQuery(db.DbmsType);
|
||||
query.Tables.Add(SaxDbDfn_Core.GetTableName(SaxDbDfn_Core.TableIdx.ActivityIndex, false), alias_ActivityIndex);
|
||||
query.Columns.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemType, alias_ActivityIndex), colItemType.DataName);
|
||||
query.Columns.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemId, alias_ActivityIndex), colItemId.DataName);
|
||||
query.Columns.Add("Max(" + SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_EffectiveOn, alias_ActivityIndex) + ")", colDataNameEffectiveOn);
|
||||
query.GroupBy.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemType, alias_ActivityIndex));
|
||||
query.GroupBy.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemId, alias_ActivityIndex));
|
||||
query.OrderBy.Add("Max(" + SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_EffectiveOn, alias_ActivityIndex) + ") DESC");
|
||||
|
||||
// Filter.
|
||||
SaxActivityIndex.Params prmActivityIndex = new SaxActivityIndex.Params();
|
||||
prmActivityIndex.UserAccountIdsOwnerIncluded.AddRange(context.UserAccountIdsOwner);
|
||||
prmActivityIndex.OrganisationIdsOwnerIncluded.AddRange(context.OrganisationIdsOwner);
|
||||
prmActivityIndex.SportRegionIdsIncluded.AddRange(context.SportRegionIdsIncluded);
|
||||
prmActivityIndex.EffectiveFrom = context.EffectiveFrom;
|
||||
prmActivityIndex.EffectiveTo = context.EffectiveTo;
|
||||
prmActivityIndex.NotExpiredOn = context.NotExpiredOn;
|
||||
prmActivityIndex.Alias_ActivityIndex = alias_ActivityIndex;
|
||||
prmActivityIndex.ResultsPaging.PageNo = 1;
|
||||
prmActivityIndex.ResultsPaging.PageLength = 100;
|
||||
query.Filters.AddRange(Internals.ActivityMgr.GetActivityIndexQueryFilter(prmActivityIndex));
|
||||
|
||||
// First pass, note the activity Item and effective date.
|
||||
List<ActivityData_Web> activityDataList = new List<ActivityData_Web>();
|
||||
using (DbResultSet dbResultSet = DbResultSet_DataReader.ExecuteQuery(db, query.BuildQuery(context.Paging)))
|
||||
{
|
||||
while (dbResultSet != null && dbResultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = dbResultSet.GetRecordValues();
|
||||
Sax.DataItemType itemType = (Sax.DataItemType)values.GetLong(colItemType.DataName);
|
||||
ItemId itemId = new ItemId((short)itemType, new SysId(values.GetLong(colItemId.DataName)));
|
||||
DateTime effectiveOn = values.GetDate(colDataNameEffectiveOn);
|
||||
ActivityData_Web activityData = new ActivityData_Web(itemId, effectiveOn);
|
||||
activityDataList.Add(activityData);
|
||||
SaLogger.Info(fn + " Activity found - " + itemType.ToString() + ":" + SysId.ToString(itemId.Id) + ". Effective: " + DateUtil.Format(effectiveOn, "d-MMM-yyyy"));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the activity list.
|
||||
activityDataList.Sort(ActivityData_Web.CompareActivityIndices);
|
||||
|
||||
// Populate activity details.
|
||||
PopulateActivityDetails(context, activityDataList);
|
||||
|
||||
// ToDo Remove activities with blank caption.
|
||||
|
||||
results.Items.AddRange(activityDataList);
|
||||
return results;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Populate Activity Details.
|
||||
//-------------------------------------------------------------------
|
||||
private void PopulateActivityDetails(IndexContext context, List<ActivityData_Web> activities)
|
||||
{
|
||||
PopulateUserActivityDetails(context, activities);
|
||||
PopulateArticleDetails(context, activities);
|
||||
PopulateOrganisationEventDetails(context, activities);
|
||||
PopulateOrganisationOpportunityDetails(context, activities);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private void PopulateUserActivityDetails(IndexContext context, List<ActivityData_Web> activities)
|
||||
{
|
||||
// Validate parameters.
|
||||
if (!SysId.IsValid(context.UserAccountId)) return;
|
||||
|
||||
// Extract article Ids.
|
||||
List<ItemId> activityItemIds = activities.Select(item => new ItemId(item.ItemId.Type, item.ItemId.Id)).ToList();
|
||||
if (activityItemIds.IsEmpty()) return;
|
||||
|
||||
// User Activity columns - limit to essential information.
|
||||
ColumnDfn colItemType = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.ItemType);
|
||||
ColumnDfn colItemId = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.ItemId);
|
||||
ColumnDfn colUserAccountId = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.UserAccountId);
|
||||
ColumnDfn colLastListedAt = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.LastListedAt);
|
||||
ColumnDfn colLastViewedAt = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.LastViewedAt);
|
||||
ColumnDfn colReaction = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.Reaction);
|
||||
ColumnDfn colLastReactedAt = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.LastReactedAt);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>() { colItemType, colItemId, colUserAccountId, colLastListedAt, colLastViewedAt, colReaction, colLastReactedAt };
|
||||
|
||||
// User Activity filter.
|
||||
SaxUserActivity.Params prmUserActivity = new SaxUserActivity.Params();
|
||||
prmUserActivity.UserAccountIdsIncluded.Add(context.UserAccountId);
|
||||
prmUserActivity.ItemIdsIncluded.AddRange(activityItemIds);
|
||||
prmUserActivity.StatusCat = SaxUserActivity.StatusCat.ActiveOnly;
|
||||
|
||||
using (DbResultSet dbResultSet = Internals.ActivityMgr.ExecuteUserActivityQuery(columns, prmUserActivity))
|
||||
{
|
||||
while (dbResultSet != null && dbResultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = dbResultSet.GetRecordValues();
|
||||
ItemId activityItemId = new ItemId((short)values.GetLong(colItemType.DataName), new SysId(values.GetLong(colItemId.DataName)));
|
||||
DateTime lastListedAt = values.GetDateTime(colLastListedAt.DataName);
|
||||
DateTime lastViewedAt = values.GetDateTime(colLastViewedAt.DataName);
|
||||
Sax.ActivityReaction reaction = (Sax.ActivityReaction)values.GetLong(colReaction.DataName);
|
||||
DateTime lastReactedAt = values.GetDateTime(colLastReactedAt.DataName);
|
||||
|
||||
// Populate to corresponding activities.
|
||||
foreach (ActivityData_Web activity in activities.FindAll(item => item.ItemId.Equals(activityItemId)))
|
||||
{
|
||||
activity.SetLastListedAt(lastListedAt);
|
||||
activity.SetLastViewedAt(lastViewedAt);
|
||||
activity.SetReaction(reaction);
|
||||
activity.SetLastReactedAt(lastReactedAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Article details.
|
||||
private void PopulateArticleDetails(IndexContext context, List<ActivityData_Web> activities)
|
||||
{
|
||||
// Extract article Ids.
|
||||
List<SysId> articleIds = activities.FindAll(item => item.ItemId.Type == (int)Sax.DataItemType.Article).Select(ItemId => ItemId.ItemId.Id).ToList();
|
||||
if (articleIds.IsEmpty()) return;
|
||||
|
||||
// Article columns - limit to essential information.
|
||||
ColumnDfn colId = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Id);
|
||||
ColumnDfn colOrganisationId = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.OrganisationId);
|
||||
ColumnDfn colOrganisation = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Organisation_Name);
|
||||
ColumnDfn colOrganisationLogoCount = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Organisation_LogoCount);
|
||||
ColumnDfn colAuthorId = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.UserAccountIdAuthor);
|
||||
ColumnDfn colAuthor = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Author_DisplayName);
|
||||
ColumnDfn colHeadline = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Headline);
|
||||
ColumnDfn colSummary = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Summary);
|
||||
ColumnDfn colItemImageIdThumbnail = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.ItemImageIdThumbnail);
|
||||
ColumnDfn colSportId = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.SportIdPrimary);
|
||||
ColumnDfn colSportName = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Sport_Name);
|
||||
ColumnDfn colRegionId = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.RegionIdPrimary);
|
||||
ColumnDfn colRegionName = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Region_Name);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>()
|
||||
{
|
||||
colId, colOrganisationId, colOrganisation, colOrganisationLogoCount, colAuthor, colAuthorId,
|
||||
colHeadline, colSummary, colItemImageIdThumbnail, colSportId, colSportName, colRegionId, colRegionName
|
||||
};
|
||||
|
||||
// Article filter.
|
||||
SaxArticle.Params prmArticle = new SaxArticle.Params();
|
||||
prmArticle.IdsIncluded.AddRange(articleIds);
|
||||
prmArticle.StatusCat = SaxArticle.StatusCat.ActiveOnly;
|
||||
|
||||
using (DbResultSet dbResultSet = Internals.ActivityMgr.ExecuteArticleQuery(columns, prmArticle))
|
||||
{
|
||||
while (dbResultSet != null && dbResultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = dbResultSet.GetRecordValues();
|
||||
ItemId itemId = new ItemId((short)Sax.DataItemType.Article, new SysId(values.GetLong(colId.DataName)));
|
||||
SysId organisationId = new SysId(values.GetLong(colOrganisationId.DataName));
|
||||
String organisation = values.GetString(colOrganisation.DataName);
|
||||
long organisationLogoCount = values.GetLong(colOrganisationLogoCount.DataName);
|
||||
SysId authorId = new SysId(values.GetLong(colAuthorId.DataName));
|
||||
String author = Internals.ActivityMgr.GetArticleDataValue(colAuthor, values).ToString();
|
||||
String headline = values.GetString(colHeadline.DataName);
|
||||
String summary = values.GetString(colSummary.DataName);
|
||||
SysId itemImageIdThumbnail = new SysId(values.GetLong(colItemImageIdThumbnail.DataName));
|
||||
SysId sportId = new SysId(values.GetLong(colSportId.DataName));
|
||||
String sportName = values.GetString(colSportName.DataName);
|
||||
SysId regionId = new SysId(values.GetLong(colRegionId.DataName));
|
||||
String regionName = values.GetString(colRegionName.DataName);
|
||||
|
||||
// Populate to corresponding activities.
|
||||
foreach (ActivityData_Web activity in activities.FindAll(item => item.ItemId.Equals(itemId)))
|
||||
{
|
||||
activity.SetOrganisation(organisationId, organisation, organisationLogoCount > 0);
|
||||
activity.SetAuthor(authorId, author);
|
||||
activity.SetDetails(headline, summary);
|
||||
activity.ItemImageIdThumbnail = itemImageIdThumbnail;
|
||||
activity.SetSport(sportId, sportName);
|
||||
activity.SetRegion(regionId, regionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Organisation Event details.
|
||||
private void PopulateOrganisationEventDetails(IndexContext context, List<ActivityData_Web> activities)
|
||||
{
|
||||
// Extract organisationEvent Ids.
|
||||
List<SysId> organisationEventIds = activities.FindAll(item => item.ItemId.Type == (int)Sax.DataItemType.OrganisationEvent).Select(ItemId => ItemId.ItemId.Id).ToList();
|
||||
if (organisationEventIds.IsEmpty()) return;
|
||||
|
||||
// Event columns - limit to essential information.
|
||||
ColumnDfn colId = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Id);
|
||||
ColumnDfn colOrganisationId = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.OrganisationId);
|
||||
ColumnDfn colOrganisation = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Organisation_Name);
|
||||
ColumnDfn colOrganisationLogoCount = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Organisation_LogoCount);
|
||||
ColumnDfn colEventAt = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.EventAt_DateTime);
|
||||
ColumnDfn colSummary = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Summary);
|
||||
ColumnDfn colDetails = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Details);
|
||||
ColumnDfn colSportId = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.SportIdPrimary);
|
||||
ColumnDfn colSportName = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Sport_Name);
|
||||
ColumnDfn colRegionId = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.RegionIdCountry);
|
||||
ColumnDfn colRegionName = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Region_Name);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>()
|
||||
{
|
||||
colId, colOrganisationId, colOrganisation, colOrganisationLogoCount,
|
||||
colEventAt, colSummary, colDetails, colSportId, colSportName, colRegionId, colRegionName
|
||||
};
|
||||
|
||||
// Event filter.
|
||||
SaxOrganisationEvent.Params prmEvent = new SaxOrganisationEvent.Params();
|
||||
prmEvent.IdsIncluded.AddRange(organisationEventIds);
|
||||
prmEvent.StatusCat = SaxOrganisationEvent.StatusCat.ActiveOnly;
|
||||
|
||||
using (DbResultSet dbResultSet = Internals.ActivityMgr.ExecuteOrganisationEventQuery(columns, prmEvent))
|
||||
{
|
||||
while (dbResultSet != null && dbResultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = dbResultSet.GetRecordValues();
|
||||
ItemId itemId = new ItemId((short)Sax.DataItemType.OrganisationEvent, new SysId(values.GetLong(colId.DataName)));
|
||||
SysId organisationId = new SysId(values.GetLong(colOrganisationId.DataName));
|
||||
String organisation = values.GetString(colOrganisation.DataName);
|
||||
long organisationLogoCount = values.GetLong(colOrganisationLogoCount.DataName);
|
||||
DateTime eventAt = values.GetDateTime(colEventAt.DataName);
|
||||
String eventAtDisplay = DateUtil.Format(eventAt, "d-MMM-yy");
|
||||
if (eventAt.Hour > 0 || eventAt.Minute > 0)
|
||||
eventAtDisplay += DateUtil.Format(values.GetDateTime(colEventAt.DataName), " HH:mm");
|
||||
String eventSummary = values.GetString(colSummary.DataName);
|
||||
String eventDetails = values.GetString(colDetails.DataName);
|
||||
SysId sportId = new SysId(values.GetLong(colSportId.DataName));
|
||||
String sportName = values.GetString(colSportName.DataName);
|
||||
SysId regionId = new SysId(values.GetLong(colRegionId.DataName));
|
||||
String regionName = values.GetString(colRegionName.DataName);
|
||||
|
||||
// Populate to corresponding activities.
|
||||
foreach (ActivityData_Web activity in activities.FindAll(item => item.ItemId.Equals(itemId)))
|
||||
{
|
||||
activity.SetOrganisation(organisationId, organisation, organisationLogoCount > 0);
|
||||
activity.SetDetails(eventAtDisplay, eventSummary);
|
||||
activity.SetSport(sportId, sportName);
|
||||
activity.SetRegion(regionId, regionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Organisation Opportunity details.
|
||||
private void PopulateOrganisationOpportunityDetails(IndexContext context, List<ActivityData_Web> activities)
|
||||
{
|
||||
// Extract opportunity Ids.
|
||||
List<SysId> opportunityIds = activities.FindAll(item => item.ItemId.Type == (int)Sax.DataItemType.OrganisationOpportunity).Select(ItemId => ItemId.ItemId.Id).ToList();
|
||||
if (opportunityIds.IsEmpty()) return;
|
||||
|
||||
// Opportunity columns - limit to essential information.
|
||||
ColumnDfn colId = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Id);
|
||||
ColumnDfn colOrganisationId = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.OrganisationId);
|
||||
ColumnDfn colOrganisation = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Organisation_Name);
|
||||
ColumnDfn colOrganisationLogoCount = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Organisation_LogoCount);
|
||||
ColumnDfn colPosition = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Position);
|
||||
ColumnDfn colOutline = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Outline);
|
||||
ColumnDfn colSportId = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.SportIdPrimary);
|
||||
ColumnDfn colSportName = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Sport_Name);
|
||||
ColumnDfn colRegionId = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.RegionIdPrimary);
|
||||
ColumnDfn colRegionName = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Region_Name);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>()
|
||||
{
|
||||
colId, colOrganisationId, colOrganisation, colOrganisationLogoCount,
|
||||
colPosition, colOutline, colSportId, colSportName, colRegionId, colRegionName
|
||||
};
|
||||
|
||||
// Opportunity filter.
|
||||
SaxOrganisationOpportunity.Params prmOpportunity = new SaxOrganisationOpportunity.Params();
|
||||
prmOpportunity.IdsIncluded.AddRange(opportunityIds);
|
||||
prmOpportunity.StatusCat = SaxOrganisationOpportunity.StatusCat.ActiveOnly;
|
||||
|
||||
using (DbResultSet dbResultSet = Internals.ActivityMgr.ExecuteOrganisationOpportunityQuery(columns, prmOpportunity))
|
||||
{
|
||||
while (dbResultSet != null && dbResultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = dbResultSet.GetRecordValues();
|
||||
ItemId itemId = new ItemId((short)Sax.DataItemType.OrganisationOpportunity, new SysId(values.GetLong(colId.DataName)));
|
||||
SysId organisationId = new SysId(values.GetLong(colOrganisationId.DataName));
|
||||
String organisation = values.GetString(colOrganisation.DataName);
|
||||
long organisationLogoCount = values.GetLong(colOrganisationLogoCount.DataName);
|
||||
String position = values.GetString(colPosition.DataName);
|
||||
String outline = values.GetString(colOutline.DataName);
|
||||
SysId sportId = new SysId(values.GetLong(colSportId.DataName));
|
||||
String sportName = values.GetString(colSportName.DataName);
|
||||
SysId regionId = new SysId(values.GetLong(colRegionId.DataName));
|
||||
String regionName = values.GetString(colRegionName.DataName);
|
||||
|
||||
// Populate to corresponding activities.
|
||||
foreach (ActivityData_Web activity in activities.FindAll(item => item.ItemId.Equals(itemId)))
|
||||
{
|
||||
activity.SetOrganisation(organisationId, organisation, organisationLogoCount > 0);
|
||||
activity.SetDetails(position, outline);
|
||||
activity.SetSport(sportId, sportName);
|
||||
activity.SetRegion(regionId, regionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Store Activity Index.
|
||||
//-------------------------------------------------------------------
|
||||
protected override bool StoreActivityIndexData(IndexData indexData, DbUpdateInfo dbUpdateInfo)
|
||||
{
|
||||
// Store the Activity Index data for the specified item - Article, Event, Opportunity etc.
|
||||
// An item may have multiple index entries to describe the different
|
||||
// combination of sport and region that they apply to.
|
||||
// NB All entries for the same item must have the same values for:
|
||||
// - Owner User Account Id.
|
||||
// - Owner Organisation Id.
|
||||
// - Effective on date.
|
||||
// - Expires on date.
|
||||
String fn = GetType().Name + ".StoreActivityIndexData";
|
||||
ClearErrorState();
|
||||
|
||||
// Validate paramaters.
|
||||
if (!ItemId.IsValid(indexData.ItemId)) return false;
|
||||
if (!ValidActivityItemTypes.Contains((Sax.DataItemType)indexData.ItemId.Type))
|
||||
{
|
||||
SaLogger.Warn(fn + " Invalid item type specified for Activity Index: " + ((Sax.DataItemType)indexData.ItemId.Type).ToString());
|
||||
return SetErrorState("Invalid item type specified for Activity Index");
|
||||
}
|
||||
IndexStatsData stats = new IndexStatsData();
|
||||
|
||||
// Index entries required for activity.
|
||||
List<IndexEntryData> indexEntryDataList = GetEntriesForIndex(indexData);
|
||||
|
||||
// Retrieve existing index entries for the item.
|
||||
List<SaxActivityIndex> indexEntries = Internals.ActivityMgr.GetActivityIndexListForItem(indexData.ItemId);
|
||||
foreach (SaxActivityIndex indexEntry in indexEntries)
|
||||
{
|
||||
// Is the index entry still required.
|
||||
// Logic only compares the Ids (item, sport, region) and then updates the effective date if a match is found.
|
||||
// Based on the fact that and change in the effective date will impact all entries in the index for the item
|
||||
// and this approach minimises the number of updates required as Ids can't be changed in code (protected setters).
|
||||
IndexEntryData indexEntrydata = indexEntryDataList.Find(item => item.MatchesIds(indexEntry));
|
||||
if (indexEntrydata != null)
|
||||
{
|
||||
// Match found - update properties.
|
||||
indexEntry.UserAccountIdOwner = indexEntrydata.UserAccountIdOwner;
|
||||
indexEntry.OrganisationIdOwner = indexEntrydata.OrganisationIdOwner;
|
||||
indexEntry.EffectiveOn = indexEntrydata.EffectiveOn;
|
||||
indexEntry.ExpiresOn = indexEntrydata.ExpiresOn;
|
||||
if (indexEntry.Changed)
|
||||
{
|
||||
if (Internals.ActivityMgr.UpdateActivityIndex(indexEntry, dbUpdateInfo))
|
||||
stats.EntriesUpdated++;
|
||||
else SaLogger.Warn(fn + " Failed to update Activity Index - " + Internals.ActivityMgr.ErrorStateMessage);
|
||||
}
|
||||
|
||||
// Remove the entry from the 'required' list.
|
||||
// Entries remaining in this list will be created.
|
||||
indexEntryDataList.Remove(indexEntrydata);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Index entry no longer required.
|
||||
if (Internals.ActivityMgr.DeleteActivityIndex(indexEntry, dbUpdateInfo))
|
||||
stats.EntriesDeleted++;
|
||||
else SaLogger.Warn(fn + " Failed to delete Activity Index - " + Internals.ActivityMgr.ErrorStateMessage);
|
||||
}
|
||||
}
|
||||
// The remaining required Index entries will be created.
|
||||
foreach (IndexEntryData indexEntry in indexEntryDataList)
|
||||
{
|
||||
SaxActivityIndex itemIndexEntry = Internals.ActivityMgr.CreateActivityIndex(indexEntry.ItemId, indexEntry.SportId, indexEntry.RegionId);
|
||||
itemIndexEntry.UserAccountIdOwner = indexEntry.UserAccountIdOwner;
|
||||
itemIndexEntry.OrganisationIdOwner = indexEntry.OrganisationIdOwner;
|
||||
itemIndexEntry.EffectiveOn = indexEntry.EffectiveOn;
|
||||
itemIndexEntry.ExpiresOn = indexEntry.ExpiresOn;
|
||||
if (Internals.ActivityMgr.InsertActivityIndex(itemIndexEntry, dbUpdateInfo))
|
||||
stats.EntriesInserted++;
|
||||
else SaLogger.Warn(fn + " Failed to insert Activity Index - " + Internals.ActivityMgr.ErrorStateMessage);
|
||||
}
|
||||
|
||||
// Log statistics.
|
||||
List<String> statsLog = new List<string>();
|
||||
if (stats.EntriesInserted > 0) statsLog.Add("Inserted: " + stats.EntriesInserted);
|
||||
if (stats.EntriesUpdated > 0) statsLog.Add("Updated: " + stats.EntriesUpdated);
|
||||
if (stats.EntriesDeleted > 0) statsLog.Add("Deleted: " + stats.EntriesDeleted);
|
||||
SaLogger.Info(fn + " Activity Index stored for " + ((Sax.DataItemType)indexData.ItemId.Type).ToString() + ":" + indexData.ItemId.Id.ToLong() + "." + statsLog.Concat("", "", ". "));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
protected override bool ClearActivityIndex(ItemId itemId, DbUpdateInfo dbUpdateInfo)
|
||||
{
|
||||
String fn = GetType().Name + ".ClearActivityIndex";
|
||||
ClearErrorState();
|
||||
String tableName = SaxDbDfn_Core.GetTableName(SaxDbDfn_Core.TableIdx.ActivityIndex);
|
||||
String filter =
|
||||
SaxDbDfn_Core.GetColumnName(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemType) + "=" + itemId.Type +
|
||||
" And " + SaxDbDfn_Core.GetColumnName(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemId) + "=" + itemId.Id.ToLong();
|
||||
ISacsDatabase db = CoreDatabase;
|
||||
int recordsDeleted = db.ExecuteDelete(tableName, filter);
|
||||
if (recordsDeleted > 0)
|
||||
SaLogger.Info(fn + " Records deleted: " + recordsDeleted + ". Filter: " + filter, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// User Activities
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Record listing of activities by user - those populated to the application page.
|
||||
/// <para>- Activities not previously listed are added to the user's list.</para>
|
||||
/// <para>- Activities previously listed are updated in the user's list.</para>
|
||||
/// <para>- User Activities no longer listed are flagged as 'Not currently listed'.</para>
|
||||
/// </summary>
|
||||
public override bool RecordUserActivitiesListed(SysId userAccountId, List<ItemId> activityItemIds, DbUpdateInfo dbUpdateInfo)
|
||||
{
|
||||
String fn = GetType().Name + ".RecordUserActivitiesListed";
|
||||
if (!SysId.IsValid(userAccountId)) return SetErrorState("User Account must be specified");
|
||||
if (activityItemIds.IsEmpty()) return SetErrorState("Activity item ids must be specified");
|
||||
String sqlNow = "'" + DateUtil.FormatSqlDateTime(DateTime.Now) + "'";
|
||||
String sqlStatus = "'" + SacsSystem.GetStatusCode(SaxUserActivity.DefaultStatus) + "'";
|
||||
|
||||
// Merge query - insert missing user activities and update existing.
|
||||
List<String> query = new List<String>()
|
||||
{
|
||||
"Merge",
|
||||
"\tInto " + SaxDbDfn_Core.GetTableName(SaxDbDfn_Core.TableIdx.UserActivity) + " As UsrAct",
|
||||
// Virtual table defining Activity Item Ids currently listed.
|
||||
"\tUsing",
|
||||
"\t(",
|
||||
"\t\tSelect ItemType, ItemId From (Select 0 ItemType, 0 ItemId" + activityItemIds.Select(item => " Union Select " + item.Type.ToString() + ", " + item.Id.ToString()).ToList().Concat("", " ") + ") Dat Where Dat.ItemType>0 And Dat.ItemId>0",
|
||||
"\t) As Act",
|
||||
"\tOn UsrAct.UserAccountId="+userAccountId.ToString() + " And UsrAct.ItemType=Act.ItemType And UsrAct.ItemId=Act.ItemId",
|
||||
// Matched - Update the User Activity to indicate 'Currently listed' and update the last listed date/time.
|
||||
"\tWhen Matched Then",
|
||||
"\t\tUpdate Set CurrentlyListed=1, ListedAtLast=" + sqlNow + ", Status=" + sqlStatus + ", UpdateOn=" + sqlNow + ", UpdateBy=" + dbUpdateInfo.UserId.ToString() + ", UpdateHost='" + dbUpdateInfo.Host + "'",
|
||||
// Not Matched By Target - Insert the User Activity and indicate 'Currently listed' with first and last listed date/times.
|
||||
"\tWhen Not Matched Then",
|
||||
"\t\tInsert",
|
||||
"\t\t\t(UserAccountId, ItemType, ItemId, CurrentlyListed, ListedAtFirst, ListedAtLast, Reaction, UserActivityData, Notes, Flags, Status, CreateOn, CreateBy, CreateHost, UpdateOn, UpdateBy, UpdateHost)",
|
||||
"\t\t\tValues ("+userAccountId.ToString()+", Act.ItemType, Act.ItemId, 1, " + sqlNow + ", " + sqlNow + ", 0, '', '', 0, " + sqlStatus + ", " + sqlNow + ", " + dbUpdateInfo.UserId.ToString() + ", '" + dbUpdateInfo.Host + "', " + sqlNow + ", " + dbUpdateInfo.UserId.ToString() + ", '" + dbUpdateInfo.Host + "')",
|
||||
// Not Matched By Source - Update the User Activity to indicate 'Not currently listed'.
|
||||
"\tWhen Not Matched By Source And UsrAct.UserAccountId=" + userAccountId.ToString() + " And CurrentlyListed=1 Then",
|
||||
"\t\tUpdate Set CurrentlyListed=0, UpdateOn=" + sqlNow + ", UpdateBy=" + dbUpdateInfo.UserId.ToString() + ", UpdateHost='" + dbUpdateInfo.Host + "'",
|
||||
";"
|
||||
};
|
||||
String sql = query.Concat("\n");
|
||||
//SaLogger.Debug(fn + " Query:\n" + sql);
|
||||
ISacsDatabase db = CoreDatabase;
|
||||
int rows = db.ExecuteNonQuery(sql);
|
||||
if (db.HasErrorState())
|
||||
SaLogger.Debug(fn + " Database error: " + db.ErrorStateMessage);
|
||||
SaLogger.Debug(fn + " Rows modified: " + rows);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public override bool RecordUserActivityViewed(SysId userAccountId, ItemId activityItemId, DbUpdateInfo dbUpdateInfo)
|
||||
{
|
||||
String fn = GetType().Name + ".RecordUserActivityViewed";
|
||||
if (!SysId.IsValid(userAccountId)) return SetErrorState("User Account must be specified");
|
||||
if (!ItemId.IsValid(activityItemId)) return SetErrorState("Activity item id must be specified");
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
SaxUserActivity userActivity = Internals.ActivityMgr.GetUserActivityForUserItem(userAccountId, activityItemId);
|
||||
if (userActivity != null)
|
||||
{
|
||||
// Update activity.
|
||||
userActivity.CurrentlyListed = true;
|
||||
if (!DateUtil.IsValid(userActivity.ViewedAtFirst))
|
||||
userActivity.ViewedAtFirst = now;
|
||||
userActivity.ViewedAtLast = now;
|
||||
userActivity.Status = SaxUserActivity.DefaultStatus;
|
||||
if (!Internals.ActivityMgr.UpdateUserActivity(userActivity, dbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to update User Activity for viewing: " + Internals.ActivityMgr.ErrorStateMessage);
|
||||
return SetErrorState(Internals.ActivityMgr.ErrorStateMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SaLogger.Warn(fn + " User Activity not found. UserAccountId: " + userAccountId.ToString() + ". Item Type: " + (Sax.DataItemType)activityItemId.Type + ". ItemId: " + activityItemId.Id.ToString() + ".");
|
||||
return SetErrorState("User Activity not found");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public override bool RecordUserActivityReaction(SysId userAccountId, ItemId activityItemId, Sax.ActivityReaction activityReaction, DbUpdateInfo dbUpdateInfo)
|
||||
{
|
||||
String fn = GetType().Name + ".RecordUserActivityReaction";
|
||||
if (!SysId.IsValid(userAccountId)) return SetErrorState("User Account must be specified");
|
||||
if (!ItemId.IsValid(activityItemId)) return SetErrorState("Activity item id must be specified");
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
SaxUserActivity userActivity = Internals.ActivityMgr.GetUserActivityForUserItem(userAccountId, activityItemId);
|
||||
if (userActivity != null)
|
||||
{
|
||||
if (userActivity.Reaction != activityReaction)
|
||||
{
|
||||
// Update activity.
|
||||
userActivity.CurrentlyListed = true;
|
||||
if (!DateUtil.IsValid(userActivity.ViewedAtFirst))
|
||||
userActivity.ViewedAtFirst = now;
|
||||
userActivity.ViewedAtLast = now;
|
||||
userActivity.Reaction = activityReaction;
|
||||
if (!DateUtil.IsValid(userActivity.ReactedAtFirst))
|
||||
userActivity.ReactedAtFirst = now;
|
||||
userActivity.ReactedAtLast = now;
|
||||
userActivity.Status = SaxUserActivity.DefaultStatus;
|
||||
if (!Internals.ActivityMgr.UpdateUserActivity(userActivity, dbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to update User Activity with reaction: " + Internals.ActivityMgr.ErrorStateMessage);
|
||||
return SetErrorState(Internals.ActivityMgr.ErrorStateMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SaLogger.Warn(fn + " User Activity not found. UserAccountId: " + userAccountId.ToString() + ". Item Type: " + (Sax.DataItemType)activityItemId.Type + ". ItemId: " + activityItemId.Id.ToString() + ".");
|
||||
return SetErrorState("User Activity not found");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Consoles
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxConsoleHelper_Web : SaxConsoleHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SaxConsoleHelper_Web()
|
||||
{
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Consoles
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxDataCacheConsole_Web : SaxDataCacheConsole
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private class DataMembers
|
||||
{
|
||||
public SaxInternals internals;
|
||||
public Dictionary<Sax.DataItemType, Dictionary<SysId, String>> encryptedIdCache = new Dictionary<Sax.DataItemType, Dictionary<SysId, String>>();
|
||||
}
|
||||
private DataMembers data = new DataMembers();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SaxDataCacheConsole_Web(SaxSessionContext sessionContext) :
|
||||
base(sessionContext)
|
||||
{
|
||||
data.internals = new SaxInternals(sessionContext, false);
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
data.internals.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Utilities.
|
||||
protected abstract void ForceRefresh();
|
||||
|
||||
public override void PopulateCachedDataToInternals(SaxInternals internals)
|
||||
{
|
||||
if (regionsData.regionDataMap == null) ForceRefresh();
|
||||
|
||||
// Regions.
|
||||
List<SaxRegion.Data> activeRegionDataList = new List<SaxRegion.Data>();
|
||||
lock (regionsData)
|
||||
activeRegionDataList.AddRange(regionsData.activeRegionDataList);
|
||||
internals.PopulateRegionCache(activeRegionDataList);
|
||||
|
||||
// Sports.
|
||||
List<SaxSport.Data> activeSportDataList = new List<SaxSport.Data>();
|
||||
lock (sportsData)
|
||||
activeSportDataList.AddRange(sportsData.activeSportDataList);
|
||||
internals.PopulateSportCache(activeSportDataList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypted Ids are stored in cache to ensure that they are consistent throughout a user's session
|
||||
/// by persisting them through the life of the cache (application restart).
|
||||
/// <para>Required as the encrypted values are compared in javascript.</para>
|
||||
/// <para>Candidate encrypted value supplied by calling routine as cache doesn't know how to encrypt - used if Id not previously encrypted.</para>
|
||||
/// </summary>
|
||||
public override String GetEncryptedId(Sax.DataItemType itemType, SysId id, String candidateValue)
|
||||
{
|
||||
// Invalid Id always has a blank encrypted value.
|
||||
if (!SysId.IsValid(id)) return "";
|
||||
|
||||
// Only Regions and Sports are supported.
|
||||
if (!(new List<Sax.DataItemType>() { Sax.DataItemType.Sport, Sax.DataItemType.Region }).Contains(itemType))
|
||||
return candidateValue;
|
||||
|
||||
lock (data.encryptedIdCache)
|
||||
{
|
||||
Dictionary<SysId, String> itemIds = data.encryptedIdCache.GetValue(itemType, null);
|
||||
if (itemIds == null)
|
||||
{
|
||||
// Add cache entry for this item type.
|
||||
itemIds = new Dictionary<SysId, string>();
|
||||
data.encryptedIdCache.Add(itemType, itemIds);
|
||||
}
|
||||
String encryptedId = itemIds.GetValue(id, null);
|
||||
if (encryptedId == null && !StringUtil.IsEmpty(candidateValue))
|
||||
{
|
||||
// Use the candidate value and store it.
|
||||
encryptedId = candidateValue;
|
||||
itemIds.Add(id, encryptedId);
|
||||
}
|
||||
return encryptedId;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Regions.
|
||||
public bool RegionsSupported { get; protected set; } = false;
|
||||
private class RegionsData
|
||||
{
|
||||
public Dictionary<SysId, SaxRegion.Data> regionDataMap = null;
|
||||
public List<SaxRegion.Data> activeRegionDataList = null;
|
||||
}
|
||||
private readonly RegionsData regionsData = new RegionsData();
|
||||
public override List<SaxRegion.Data> GetAllRegions()
|
||||
{
|
||||
String fn = GetType().Name + ".ActiveRegions";
|
||||
if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported.");
|
||||
List<SaxRegion.Data> activeRegions = new List<SaxRegion.Data>();
|
||||
if (regionsData.regionDataMap == null) ForceRefresh();
|
||||
lock (regionsData)
|
||||
{
|
||||
if (regionsData.regionDataMap == null) throw new NotSupportedException(fn + " Regions not populated.");
|
||||
activeRegions.AddRange(regionsData.regionDataMap.Values);
|
||||
}
|
||||
return activeRegions;
|
||||
}
|
||||
public override List<SaxRegion.Data> GetActiveRegions()
|
||||
{
|
||||
String fn = GetType().Name + ".ActiveRegions";
|
||||
if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported.");
|
||||
List<SaxRegion.Data> activeRegions = new List<SaxRegion.Data>();
|
||||
if (regionsData.regionDataMap == null) ForceRefresh();
|
||||
lock (regionsData)
|
||||
{
|
||||
if (regionsData.regionDataMap == null) throw new NotSupportedException(fn + " Regions not populated.");
|
||||
activeRegions.AddRange(regionsData.activeRegionDataList);
|
||||
}
|
||||
return activeRegions;
|
||||
}
|
||||
protected void RefreshRegions()
|
||||
{
|
||||
String fn = GetType().Name + ".RefreshRegions";
|
||||
if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported.");
|
||||
|
||||
try
|
||||
{
|
||||
DateTime startedAt = DateTime.Now;
|
||||
|
||||
// Refreshed lists will be populated in isolation and then used to instantaneously replace the cache lists.
|
||||
Dictionary<SysId, SaxRegion.Data> newRegionDataMap = new Dictionary<SysId, SaxRegion.Data>();
|
||||
List<SaxRegion.Data> newActiveRegionDataList = new List<SaxRegion.Data>();
|
||||
|
||||
// Query Regions to replace cache.
|
||||
SaxRegion.Params prmRegion = new SaxRegion.Params();
|
||||
prmRegion.StatusCat = SaxRegion.StatusCat.NotDeleted;
|
||||
foreach (SaxRegion region in data.internals.ReferenceMgr.GetRegionList(prmRegion))
|
||||
{
|
||||
// Only interested in retaining the Data objects.
|
||||
newRegionDataMap[region.Id] = region.GetData();
|
||||
region.Dispose();
|
||||
}
|
||||
|
||||
// Populate replacement list of active regions.
|
||||
foreach (SaxRegion.Data regionData in newRegionDataMap.GetValues())
|
||||
{
|
||||
if (IsRegionActive(newRegionDataMap, regionData))
|
||||
newActiveRegionDataList.Add(regionData.Clone());
|
||||
}
|
||||
|
||||
// Replace the list of Regions.
|
||||
lock (regionsData)
|
||||
{
|
||||
// Assign the refreshed regions and active regions.
|
||||
// NB The old regions don't need to be disposed as they are SaxRegion.Data objects.
|
||||
regionsData.regionDataMap = newRegionDataMap;
|
||||
regionsData.activeRegionDataList = newActiveRegionDataList;
|
||||
}
|
||||
SaLogger.Info(fn + " Regions found: " + regionsData.regionDataMap.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds));
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Error(fn + ExceptionUtil.GetLogMessage(exc, "refreshing Regions cache"));
|
||||
}
|
||||
}
|
||||
private static bool IsRegionActive(Dictionary<SysId, SaxRegion.Data> regionDataMap, SaxRegion.Data regionData)
|
||||
{
|
||||
if (regionData == null) return false;
|
||||
if (!regionData.IsStatusActive) return false;
|
||||
if (SysId.IsValid(regionData.RegionIdParent))
|
||||
return IsRegionActive(regionDataMap, regionDataMap.GetValue(regionData.RegionIdParent, null));
|
||||
return true;
|
||||
}
|
||||
private SaxRegion GetRegion(SaxRegion.Data regionData)
|
||||
{
|
||||
if (regionData == null) return null;
|
||||
SaxRegion region = data.internals.ReferenceMgr.CreateRegion();
|
||||
region.SetData(regionData);
|
||||
return region;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Sports.
|
||||
public bool SportsSupported { get; protected set; } = false;
|
||||
private class SportsData
|
||||
{
|
||||
public Dictionary<SysId, SaxSport.Data> sportDataMap = null;
|
||||
public List<SaxSport.Data> activeSportDataList = null;
|
||||
}
|
||||
private readonly SportsData sportsData = new SportsData();
|
||||
public override List<SaxSport.Data> GetAllSports()
|
||||
{
|
||||
String fn = GetType().Name + ".GetAllSports";
|
||||
if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported.");
|
||||
List<SaxSport.Data> sports = new List<SaxSport.Data>();
|
||||
if (sportsData.sportDataMap == null) ForceRefresh();
|
||||
lock (sportsData)
|
||||
{
|
||||
if (sportsData.sportDataMap == null) throw new NotSupportedException(fn + " Sports not populated.");
|
||||
sports.AddRange(sportsData.sportDataMap.Values);
|
||||
}
|
||||
return sports;
|
||||
}
|
||||
public override List<SaxSport.Data> GetActiveSports()
|
||||
{
|
||||
String fn = GetType().Name + ".GetActiveSports";
|
||||
if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported.");
|
||||
List<SaxSport.Data> activeSports = new List<SaxSport.Data>();
|
||||
if (sportsData.sportDataMap == null) ForceRefresh();
|
||||
lock (sportsData)
|
||||
{
|
||||
if (sportsData.sportDataMap == null) throw new NotSupportedException(fn + " Sports not populated.");
|
||||
activeSports.AddRange(sportsData.activeSportDataList);
|
||||
}
|
||||
return activeSports;
|
||||
}
|
||||
protected void RefreshSports()
|
||||
{
|
||||
String fn = GetType().Name + ".RefreshSports";
|
||||
if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported.");
|
||||
|
||||
try
|
||||
{
|
||||
DateTime startedAt = DateTime.Now;
|
||||
|
||||
// Refreshed lists will be populated in isolation and then used to instantaneously replace the cache lists.
|
||||
Dictionary<SysId, SaxSport.Data> newSportDataMap = new Dictionary<SysId, SaxSport.Data>();
|
||||
List<SaxSport.Data> newActiveSportDataList = new List<SaxSport.Data>();
|
||||
|
||||
// Query Sports to replace cache.
|
||||
SaxSport.Params prmSport = new SaxSport.Params();
|
||||
prmSport.StatusCat = SaxSport.StatusCat.NotDeleted;
|
||||
foreach (SaxSport sport in data.internals.ReferenceMgr.GetSportList(prmSport))
|
||||
{
|
||||
// Only interested in retaining the Data objects.
|
||||
newSportDataMap[sport.Id] = sport.GetData();
|
||||
sport.Dispose();
|
||||
}
|
||||
|
||||
// Populate replacement list of active sports.
|
||||
foreach (SaxSport.Data sportData in newSportDataMap.GetValues())
|
||||
{
|
||||
if (IsSportActive(newSportDataMap, sportData))
|
||||
newActiveSportDataList.Add(sportData.Clone());
|
||||
}
|
||||
|
||||
// Replace the list of Sports.
|
||||
lock (sportsData)
|
||||
{
|
||||
// Assign the refreshed sports and active sports.
|
||||
// NB The old sports don't need to be disposed as they are SaxSport.Data objects.
|
||||
sportsData.sportDataMap = newSportDataMap;
|
||||
sportsData.activeSportDataList = newActiveSportDataList;
|
||||
}
|
||||
SaLogger.Info(fn + " Sports found: " + sportsData.sportDataMap.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds));
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Error(fn + ExceptionUtil.GetLogMessage(exc, "refreshing Sports cache"));
|
||||
}
|
||||
}
|
||||
private static bool IsSportActive(Dictionary<SysId, SaxSport.Data> sportDataMap, SaxSport.Data sportData)
|
||||
{
|
||||
if (sportData == null) return false;
|
||||
if (!sportData.IsStatusActive) return false;
|
||||
return true;
|
||||
}
|
||||
private SaxSport GetSport(SaxSport.Data sportData)
|
||||
{
|
||||
if (sportData == null) return null;
|
||||
SaxSport sport = data.internals.ReferenceMgr.CreateSport();
|
||||
sport.SetData(sportData);
|
||||
return sport;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Consoles
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaxEmailConsole_Web : SaxEmailConsole
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
public SaxEmailConsole_Web(SaxEmailConfig emailConfig) :
|
||||
base(emailConfig)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Tasks;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Consoles
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxHostedServiceConsole_Web : SacsDisposable, IHostedService
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Timer processTimer;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
protected SaxHostedServiceConsole_Web ()
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Clean-up.
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
DisposeTimer();
|
||||
if (ProcessTaskData != null) ProcessTaskData.Dispose();
|
||||
ProcessTaskData = null;
|
||||
}
|
||||
|
||||
private void DisposeTimer()
|
||||
{
|
||||
if (processTimer != null) processTimer.Dispose();
|
||||
processTimer = null;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
|
||||
// Task data - allows cancellation to be triggered for executing processes, eg. when application is stopping.
|
||||
protected SacsTaskData ProcessTaskData { get; private set; }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Periodic processing to execute application processes as required.
|
||||
/// </summary>
|
||||
protected abstract void DoWork(object state);
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host is ready to start the service.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
|
||||
/// <returns></returns>
|
||||
public virtual Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
SaLogger.Info($"{SysUtil.Fn} Timed Background Service started.");
|
||||
|
||||
// Initialise the background task cancellation token.
|
||||
ProcessTaskData = new SacsTaskData();
|
||||
|
||||
// Initialise the scheduled task timer.
|
||||
processTimer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host is performing a graceful shutdown.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
|
||||
/// <returns></returns>
|
||||
public virtual Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
SaLogger.Info($"{SysUtil.Fn} Timed Background Service stopped.");
|
||||
|
||||
// Signal cancellation to the background tasks.
|
||||
ProcessTaskData.TriggerCancellation();
|
||||
|
||||
// Stop the timer.
|
||||
processTimer?.Change(Timeout.Infinite, 0);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Consoles
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxSessionDataConsole_Web : SaxSessionDataConsole
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
protected SaxSessionDataConsole_Web(SaxSessionContext sessionContext) :
|
||||
base(sessionContext)
|
||||
{
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
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.Email;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Core.Web.Base.App;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using Saxonite.Net.Standard.Base.Mgr;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Reference;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Support;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Controllers
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxController_Base : SaController
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private SaxInternals internals;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SaxController_Base(SaxLocaliser localiser)
|
||||
{
|
||||
Localiser = localiser;
|
||||
|
||||
// Initialise console (debug) logging listener.
|
||||
//if (Directory.Exists(App.LoggingRootFolder) && SaxSystem.DebugMode)
|
||||
// SaxConsoleListener.ConfigureDebugListener(App.LoggingRootFolder);
|
||||
if (DebugMode) SaLogger.Trace($"{GetType().Name}.CTR");
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
protected SaxApplication_Web App { get { return SaxApplication_Web.WebApp; } }
|
||||
public SaxAppConfig AppConfig { get { return App.AppConfig; } }
|
||||
public SaxSessionContext SessionContext { get { return App.SessionContext; } }
|
||||
public SaxLocaliser Localiser { get; }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Globals.
|
||||
protected SaxConsoleHelper ConsoleHelper { get { return SaxGlobals.ConsoleHelper; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Business components.
|
||||
public SaxInternals Internals { get { if (internals == null) internals = new SaxInternals(SessionContext); return internals; } }
|
||||
// Consoles.
|
||||
public SaxDataCacheConsole DataCacheConsole { get { return Internals.DataCacheConsole; } }
|
||||
public SaxSessionDataConsole SessionDataConsole { get { return Internals.SessionDataConsole; } }
|
||||
public SaxActivityConsole ActivityConsole { get { return Internals.ActivityConsole; } }
|
||||
// Managers.
|
||||
public SaxReferenceMgr ReferenceMgr { get { return Internals.ReferenceMgr; } }
|
||||
public SaxAccountMgr AccountMgr { get { return Internals.AccountMgr; } }
|
||||
public SaxActivityMgr ActivityMgr { get { return Internals.ActivityMgr; } }
|
||||
public SaxItemDataMgr ItemDataMgr { get { return Internals.ItemDataMgr; } }
|
||||
public SaxReferenceGolfMgr ReferenceGolfMgr { get { return Internals.ReferenceGolfMgr; } }
|
||||
public SaxSupportMgr SupportMgr { get { return Internals.SupportMgr; } }
|
||||
// System codes.
|
||||
public List<TypeCodeDesc> DataItemTypeCodes { get { return Internals.DataItemTypeCodes; } }
|
||||
public List<TypeCodeDesc> SportGroupingCodes { get { return Internals.SportGroupingCodes; } }
|
||||
public List<TypeCodeDesc> SportSpecialityLabelCodes { get { return Internals.SportSpecialityLabelCodes; } }
|
||||
public List<TypeCodeDesc> SportContactTypeCodes { get { return Internals.SportContactTypeCodes; } }
|
||||
public List<TypeCodeDesc> SportParticipatingCodes { get { return Internals.SportParticipatingCodes; } }
|
||||
public List<TypeCodeDesc> SportPreparingCodes { get { return Internals.SportPreparingCodes; } }
|
||||
public List<TypeCodeDesc> SportOfficiatingCodes { get { return Internals.SportOfficiatingCodes; } }
|
||||
public List<TypeCodeDesc> RegionTypeCodes { get { return Internals.RegionTypeCodes; } }
|
||||
public List<TypeCodeDesc> EligibilityTypeCodes { get { return Internals.EligibilityTypeCodes; } }
|
||||
public List<TypeCodeDesc> UserRoleCodes { get { return Internals.UserRoleCodes; } }
|
||||
public List<TypeCodeDesc> OrganisationTypeCodes { get { return Internals.OrganisationTypeCodes; } }
|
||||
public List<TypeCodeDesc> OrganisationCategoryCodes { get { return Internals.OrganisationCategoryCodes; } }
|
||||
public List<TypeCodeDesc> OrganisationRoleCodes { get { return Internals.OrganisationRoleCodes; } }
|
||||
public List<TypeCodeDesc> AssociationTypeCodes { get { return Internals.AssociationTypeCodes; } }
|
||||
public List<TypeCodeDesc> AddressTypeCodes { get { return Internals.AddressTypeCodes; } }
|
||||
public List<TypeCodeDesc> ContactMethodCodes { get { return Internals.ContactMethodCodes; } }
|
||||
public List<TypeCodeDesc> EventTypeCodes { get { return Internals.EventTypeCodes; } }
|
||||
public List<TypeCodeDesc> OpportunityTypeCodes { get { return Internals.OpportunityTypeCodes; } }
|
||||
public List<TypeCodeDesc> ActivityReactionCodes { get { return Internals.ActivityReactionCodes; } }
|
||||
// Support.
|
||||
public List<TypeCodeDesc> UserReportTypeCodes { get { return Internals.UserReportTypeCodes; } }
|
||||
public List<TypeCodeDesc> SupportItemProcessTypeCodes { get { return Internals.SupportItemProcessTypeCodes; } }
|
||||
// Email.
|
||||
public SaEmailConfig EmailConfig { get { return App.EmailConfig; } }
|
||||
public SaxEmailConsole EmailConsole { get { return Internals.EmailConsole; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
[NonAction]
|
||||
/// <summary>
|
||||
/// Retrieve sport by matching the name and include groups as required.
|
||||
/// </summary>
|
||||
/// <param name="sportName"></param>
|
||||
/// <param name="includeGroups"></param>
|
||||
/// <returns>List of matching sports.</returns>
|
||||
public List<SaxSport.Data> GetSportsByName(String sportName, bool includeGroups = false)
|
||||
{
|
||||
List<SaxSport.Data> sportsData = new List<SaxSport.Data>();
|
||||
if (DataCacheConsole != null)
|
||||
{
|
||||
List<SaxSport.Data> allSports = DataCacheConsole.GetActiveSports();
|
||||
foreach (SaxSport.Data sport in allSports.Where(item => item.Name.EqualsCI(sportName)))
|
||||
{
|
||||
sportsData.Add(sport);
|
||||
if (!includeGroups) continue;
|
||||
foreach (SaxSport.Data sportParent in allSports.Where(item => item.Id.Equals(sport.SportIdParent)))
|
||||
sportsData.Add(sportParent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Data cache is not available.
|
||||
// Query direct from the database (Admin).
|
||||
Dictionary<SysId, SaxSport> sports = new Dictionary<SysId, SaxSport>();
|
||||
|
||||
// Sport by name.
|
||||
SaxSport.Params prmSport = new SaxSport.Params();
|
||||
prmSport.NameFull = sportName;
|
||||
sports.AddRange(ReferenceMgr.GetSportList(prmSport).ToDictionary(item => item.Id));
|
||||
|
||||
if (includeGroups)
|
||||
{
|
||||
// Parent sports.
|
||||
prmSport = new SaxSport.Params();
|
||||
prmSport.IdsIncluded.AddIds(sports.Values.Where(item => SysId.IsValid(item.SportIdParent)).Select(item => item.SportIdParent));
|
||||
if (prmSport.IdsIncluded.Any())
|
||||
sports.AddRange(ReferenceMgr.GetSportList(prmSport).ToDictionary(item => item.Id));
|
||||
}
|
||||
sportsData = sports.Values.Select(item => item.GetData()).ToList();
|
||||
}
|
||||
return sportsData;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Core.Web.Base.App;
|
||||
using Saxonite.Net.Core.Web.Base.Models;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Controllers
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxController_Web : SaxController_Base
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SaxController_Web(SaxLocaliser localiser) : base(localiser)
|
||||
{
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Action execution events.
|
||||
//-------------------------------------------------------------------
|
||||
public override void OnActionExecuting(ActionExecutingContext filterContext)
|
||||
{
|
||||
//if (DebugMode) SaLogger.Debug($"{SysUtil.Fn}" + (ViewData.Model != null ? " Model: " + ViewData.Model : ""));
|
||||
|
||||
// Refreh model properties.
|
||||
SaxModel_Base baseModel = ViewData.Model as SaxModel_Base;
|
||||
RefreshModel(baseModel);
|
||||
|
||||
base.OnActionExecuting(filterContext);
|
||||
}
|
||||
public override void OnActionExecuted(ActionExecutedContext filterContext)
|
||||
{
|
||||
//if (DebugMode) SaLogger.Debug($"{SysUtil.Fn}" + (ViewData.Model != null ? " Model: " + ViewData.Model : ""));
|
||||
|
||||
// Refreh model properties.
|
||||
SaxModel_Base baseModel = ViewData.Model as SaxModel_Base;
|
||||
RefreshModel(baseModel);
|
||||
|
||||
// Store session data if present - as potentially modfied.
|
||||
if (SessionData != null)
|
||||
{
|
||||
// Most actions will store the session data in case it's been modified.
|
||||
// Some actions (eg. score password) don't need to.
|
||||
if (StoreSessionData)
|
||||
{
|
||||
String sessionDataJson = NewtonsoftJsonUtil.SerializeObject(SessionData);
|
||||
HttpContext.Session.Set(SessionStorage.SessionKey_SessionData, SessionStorage.sessionDataEncoding.GetBytes(sessionDataJson));
|
||||
}
|
||||
}
|
||||
else HttpContext.Session.Remove(SessionStorage.SessionKey_SessionData);
|
||||
|
||||
// Response headers.
|
||||
if (baseModel != null && baseModel.ProhibitCache)
|
||||
{
|
||||
if (Response.Headers.ContainsKey(HeaderNames.CacheControl))
|
||||
Response.Headers.Remove(HeaderNames.CacheControl);
|
||||
Response.Headers.Add(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate");
|
||||
Response.Headers.Add(HeaderNames.Pragma, "no-cache");
|
||||
Response.Headers.Add(HeaderNames.Expires, "0");
|
||||
}
|
||||
|
||||
base.OnActionExecuted(filterContext);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Model.
|
||||
//-------------------------------------------------------------------
|
||||
protected void RefreshModel(SaxModel_Base model)
|
||||
{
|
||||
if (model == null) return;
|
||||
|
||||
// Session data.
|
||||
model.Controller = this;
|
||||
model.SessionData = SessionData;
|
||||
|
||||
// Redirect locations.
|
||||
model.RedirectLocation.Base = GetRedirectLocation(RedirectLocation.WebAppBase);
|
||||
model.RedirectLocation.Home = GetRedirectLocation(RedirectLocation.WebAppHome);
|
||||
model.RedirectLocation.Home_SessionExpired = GetRedirectLocation(RedirectLocation.WebAppHome_SessionExpired);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Online encryption parameters.
|
||||
//-------------------------------------------------------------------
|
||||
// Online Request Password encryption parameters are defined in
|
||||
// the base Controller so that they can be shared with the Admin application.
|
||||
protected static String Base64Key_RequestPassword_Online { get; } = SacsEncryptionHelper.EncodePlainKey("{~Saxonite@@Request^Password=}", SacsEncryptionHelper.KeyLength);
|
||||
protected const String Purpose_RequestPassword_Online = "Saxonite.Online.RequestPassword";
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Session.
|
||||
//-------------------------------------------------------------------
|
||||
private bool UserActiveRecorded { get; set; } = false;
|
||||
public bool ValidUserSession
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SessionData == null || !SessionData.ValidUserSession) return false;
|
||||
SaxSessionDataConsole sessionDataConsole = SessionDataConsole;
|
||||
if (sessionDataConsole != null && !UserActiveRecorded)
|
||||
{
|
||||
// Record the user as active once per request.
|
||||
sessionDataConsole.RecordUserActive(SessionUserAccountId, CreatedAt);
|
||||
UserActiveRecorded = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public SysId SessionUserAccountId { get { return SessionData?.User?.UserAccountId ?? null; } }
|
||||
public DbUpdateInfo DbUpdateInfo { get { return SessionData?.DbUpdateInfo ?? new DbUpdateInfo(null, "", SaxSystem.ComputerName); } }
|
||||
// Referrer.
|
||||
public String Referer { get { return Request.Headers["Referer"].ToString(); } }
|
||||
public bool ValidReferer { get { return !StringUtil.IsEmpty(Referer); } }
|
||||
|
||||
/// <summary>Store session data, eg. to start a new user session on sign-in.</summary>
|
||||
protected bool SetSessionData(SaxSessionData sessionData)
|
||||
{
|
||||
if (sessionStorage.SessionData != null) return false;
|
||||
sessionStorage.SessionData = sessionData;
|
||||
return true;
|
||||
}
|
||||
protected void ClearSessionData()
|
||||
{
|
||||
if (HttpContext.Session.IsAvailable)
|
||||
HttpContext.Session.Clear();
|
||||
sessionStorage.SessionData = null;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Session storage.
|
||||
public class SessionStorage
|
||||
{
|
||||
public SaxSessionData SessionData { get; set; }
|
||||
public static String SessionKey_SessionData { get; } = "SaxSessionData";
|
||||
public static Encoding sessionDataEncoding { get; } = Encoding.UTF8;
|
||||
//JSON Serialisation.
|
||||
public class SaxSessionDataContractResolver : DefaultContractResolver
|
||||
{
|
||||
// To allow private properties to be deserialised.
|
||||
// NB Only applies to top level properties in the serialised object.
|
||||
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
|
||||
{
|
||||
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
MemberInfo[] fields = objectType.GetFields(flags);
|
||||
return fields
|
||||
.Concat(objectType.GetProperties(flags).Where(propInfo => propInfo.CanWrite))
|
||||
.ToList();
|
||||
}
|
||||
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
|
||||
{
|
||||
return base.CreateProperties(type, MemberSerialization.Fields);
|
||||
}
|
||||
};
|
||||
public static JsonSerializerSettings JsonDeserialisationSettings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new SaxSessionDataContractResolver(),
|
||||
// Force all deserialised objects to be replaced - to avoid proxy lists not populating.
|
||||
ObjectCreationHandling = ObjectCreationHandling.Replace
|
||||
};
|
||||
}
|
||||
private readonly SessionStorage sessionStorage = new SessionStorage();
|
||||
protected JsonSerializerSettings SaxSessionDataJsonSerializerSettings { get { return SessionStorage.JsonDeserialisationSettings; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public SaxSessionData SessionData
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
// Retrieve session data if not already in controller.
|
||||
if (sessionStorage.SessionData == null)
|
||||
{
|
||||
byte[] sessionDataBytes = null;
|
||||
HttpContext.Session.TryGetValue(SessionStorage.SessionKey_SessionData, out sessionDataBytes);
|
||||
if (sessionDataBytes != null)
|
||||
{
|
||||
String sessionDataJson = SessionStorage.sessionDataEncoding.GetString(sessionDataBytes);
|
||||
sessionStorage.SessionData = DeserializeSessionData(sessionDataJson);
|
||||
}
|
||||
}
|
||||
return sessionStorage.SessionData;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} {exc.GetType().Name} - {exc.Message}{exc.GetStackEntries().Concat("", "\n\t")}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
protected abstract SaxSessionData DeserializeSessionData(String sessionDataJson);
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Core.Web.Base.App;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SaxAppConfigHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
public static Dictionary<String, SaxAppConfig> ExtractAppConfigs(IConfigurationSection appConfigsSection)
|
||||
{
|
||||
// NB The set of configurations will only allow one per context.
|
||||
// Only the last configuration found for each context will be stored (NB usually only one of each apart from 'Defaults').
|
||||
Dictionary<String, SaxAppConfig> appConfigs = new Dictionary<String, SaxAppConfig>();
|
||||
|
||||
// Start the list with a default 'Defaults' configuration - values set to application defaults (defined in Sax).
|
||||
// NB This can be overridden in each level of configuration.
|
||||
// Settings from the 'Defaults' configuration provide values where they are missing from other configurations.
|
||||
// NB Coverage may not be 100% - just where it's proven needed.
|
||||
SaxAppConfig defaultsAppConfig = new SaxAppConfig(Sax.Config.Context_Defaults, null, null);
|
||||
appConfigs.Add(defaultsAppConfig.Context, defaultsAppConfig);
|
||||
|
||||
// Load session configurations.
|
||||
foreach (ConfigurationSection appConfigSection in appConfigsSection.GetChildren())
|
||||
{
|
||||
if (StringUtil.IsEmpty(appConfigSection.Key)) continue;
|
||||
|
||||
// Extract the configuration, supplying current 'Defaults' to supply missing values, and add to the list.
|
||||
SaxAppConfig appConfig = new SaxAppConfig(appConfigSection.Key, appConfigSection, defaultsAppConfig);
|
||||
appConfigs.Remove(appConfigSection.Key);
|
||||
appConfigs.Add(appConfigSection.Key, appConfig);
|
||||
|
||||
// Replace the 'Defaults' configuration if this one matches the context.
|
||||
if (appConfig.Context.EqualsCI(Sax.Config.Context_Defaults))
|
||||
defaultsAppConfig = appConfig;
|
||||
}
|
||||
return appConfigs;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
public static class AppConfigList
|
||||
{
|
||||
public static SaxAppConfig getConfigForApplication(this List<SaxAppConfig> appConfigs)
|
||||
{
|
||||
SaxAppConfig defaultsAppConfig = new SaxAppConfig("", null, null);
|
||||
SaxApplication_Base app = SaxGlobals.App;
|
||||
if (app != null)
|
||||
{
|
||||
foreach (SaxAppConfig appConfig in appConfigs)
|
||||
{
|
||||
if (appConfig.Environments.ContainsCI(app.EnvironmentName))
|
||||
return appConfig;
|
||||
if (appConfig.Context.EqualsCI(Sax.Config.Context_Defaults))
|
||||
defaultsAppConfig = appConfig;
|
||||
}
|
||||
}
|
||||
return defaultsAppConfig;
|
||||
}
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Email;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using Saxonite.Net.Core.Web.Base.App;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SaxEmailConfigHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Extract the email configurations from the application settings.</summary>
|
||||
public static Dictionary<String, SaxEmailConfig> ExtractEmailConfigs(IConfigurationSection emailConfigsSection)
|
||||
{
|
||||
Dictionary<String, SaxEmailConfig> emailConfigs = new Dictionary<String, SaxEmailConfig>();
|
||||
foreach (ConfigurationSection emailConfigSection in emailConfigsSection.GetChildren())
|
||||
{
|
||||
if (!StringUtil.IsEmpty(emailConfigSection.Key))
|
||||
emailConfigs.Add(emailConfigSection.Key, CreateEmailConfig(emailConfigSection.Key, emailConfigSection));
|
||||
}
|
||||
return emailConfigs;
|
||||
}
|
||||
private static SaxEmailConfig CreateEmailConfig(String context, ConfigurationSection emailConfigSection)
|
||||
{
|
||||
SaxEmailConfig emailConfig = new SaxEmailConfig(context);
|
||||
if (emailConfigSection != null)
|
||||
{
|
||||
IConfigurationSection message = emailConfigSection.GetSection("Message");
|
||||
emailConfig.Message.AddressFrom = message["AddressFrom"];
|
||||
foreach (var addressBcc in message.GetSection("AddressesBcc").GetChildren())
|
||||
emailConfig.Message.AddressesBcc.Add(StringUtil.TrimAll(addressBcc.Value));
|
||||
IConfigurationSection smtp = emailConfigSection.GetSection("SMTP");
|
||||
emailConfig.SMTP.Server = smtp["Server"];
|
||||
emailConfig.SMTP.ServerPort = StringUtil.ToInt(smtp["ServerPort"]);
|
||||
emailConfig.SMTP.UseSSL = StringUtil.ToBool(smtp["UseSSL"]);
|
||||
IConfigurationSection send = emailConfigSection.GetSection("Send");
|
||||
emailConfig.Send.Using = send["Using"];
|
||||
emailConfig.Send.UserName = send["UserName"];
|
||||
emailConfig.Send.Password = send["Password"];
|
||||
}
|
||||
return emailConfig;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Send email.
|
||||
public static bool SendEmail(SaEmailData emailData)
|
||||
{
|
||||
// ToDo return EmailConsole.SendEmail(emailData);
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Replace dynamic tags in email message body.
|
||||
/// <para></para>
|
||||
/// </summary>
|
||||
public static void ReplaceDynamicEmailBodyTags(SaEmailData emailData, SaxSessionData sessionData, SaxUserAccount userAccount)
|
||||
{
|
||||
// Replace core tags, eg. main stylesheet.
|
||||
// ToDo. EmailConsole.ReplaceDynamicEmailTags(emailData);
|
||||
|
||||
// Replace tags known to the web application.
|
||||
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
public static class EmailConfigList
|
||||
{
|
||||
public static SaxEmailConfig getConfigForSession(this List<SaxEmailConfig> emailConfigs, SaxAppConfig appConfig)
|
||||
{
|
||||
if (appConfig != null && !StringUtil.IsEmpty(appConfig.Email.Context))
|
||||
{
|
||||
foreach (SaxEmailConfig emailConfig in emailConfigs)
|
||||
{
|
||||
if (emailConfig.Context.EqualsCI(appConfig.Email.Context))
|
||||
return emailConfig;
|
||||
}
|
||||
}
|
||||
return new SaxEmailConfig("");
|
||||
}
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SaxResourceHelper_Web
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public static Assembly MyAssembly { get; } = typeof(SaxResourceHelper_Web).GetTypeInfo().Assembly;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Text resources.
|
||||
|
||||
/// <summary> Retrieve contents of text resource in this assembly by name.</summary>
|
||||
public static String GetResourceText(String resourceFilename, String partialPath = "")
|
||||
{
|
||||
if (!StringUtil.IsBlank(resourceFilename))
|
||||
{
|
||||
String resourceText = SacsResourceHelper.GetManifestResourceAsText(MyAssembly, partialPath, resourceFilename);
|
||||
if (!StringUtil.IsBlank(resourceText))
|
||||
return resourceText;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Resource stream.
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve stream to resource in this assembly.
|
||||
/// </summary>
|
||||
public static Stream GetResourceStream(String resourceFilename, String partialPath = "")
|
||||
{
|
||||
if (!StringUtil.IsBlank(resourceFilename))
|
||||
{
|
||||
return SacsResourceHelper.GetManifestResourceStream(MyAssembly, partialPath, resourceFilename);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Middleware
|
||||
{
|
||||
//=======================================================================
|
||||
// Authentication handler required to allow actions to issue Forbid() responses.
|
||||
// Ref. https://stackoverflow.com/questions/54578426/why-do-i-need-to-set-a-defaultforbidscheme/54587054#54587054
|
||||
//=======================================================================
|
||||
public class SaxAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public SaxAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) :
|
||||
base(options, logger, encoder, clock)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
|
||||
{
|
||||
Context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return base.HandleForbiddenAsync(properties);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
// NoOp task to avoid compiler warning.
|
||||
await Task.CompletedTask;
|
||||
Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Middleware
|
||||
{
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Force blank form values to be populated to the model as empty strings rather than null values.
|
||||
/// </summary>
|
||||
public class SaxMetadataProvider : IMetadataDetailsProvider, IDisplayMetadataProvider
|
||||
{
|
||||
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
|
||||
{
|
||||
if (context.Key.MetadataKind == ModelMetadataKind.Property)
|
||||
{
|
||||
context.DisplayMetadata.ConvertEmptyStringToNull = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Core.Web.Base.App;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using static Saxonite.Net.Standard.Base.App.Sax.Config.Security;
|
||||
|
||||
// Ignore obsolete warning on ComplexTypeModelBinder.
|
||||
#pragma warning disable 0618
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Middleware
|
||||
{
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Maps a browser request to a data object. Default class provides a base implementation of a model binder.
|
||||
/// <para>Overridden to trim whitespace from input values.</para>
|
||||
/// </summary>
|
||||
public class SaxModelBinder : ComplexTypeModelBinder
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SaxModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) :
|
||||
base(propertyBinders, loggerFactory)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Creates the model with one (or more) injected service parameters.
|
||||
/// </summary>
|
||||
protected override object CreateModel(ModelBindingContext bindingContext)
|
||||
{
|
||||
var services = bindingContext.HttpContext.RequestServices;
|
||||
var modelType = bindingContext.ModelType;
|
||||
var ctors = modelType.GetConstructors();
|
||||
foreach (var ctor in ctors)
|
||||
{
|
||||
var paramTypes = ctor.GetParameters().Select(p => p.ParameterType).ToList();
|
||||
var parameters = paramTypes.Select(p => services.GetService(p)).ToArray();
|
||||
if (parameters.All(p => p != null))
|
||||
{
|
||||
var model = ctor.Invoke(parameters);
|
||||
return model;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Intercept String properties and trim whitespace.
|
||||
/// </summary>
|
||||
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
|
||||
{
|
||||
//if (SaxSystem.DebugMode) Logger.Debug(fn + " Form property " + modelName + ": '" + result.Model.ToString() + ".");
|
||||
object originalValue = result.Model;
|
||||
// Anything that looks like a password will be left as is.
|
||||
bool password = modelName.ContainsCI("Password");
|
||||
// Exclude properties that retain leading/trailing whitespace.
|
||||
if (result.Model is string)
|
||||
{
|
||||
if (!password)
|
||||
{
|
||||
string value = result.Model as string;
|
||||
|
||||
// Trim leading and trailing whitespace.
|
||||
value = StringUtil.TrimAll(value);
|
||||
|
||||
// Check for multi-line.
|
||||
if (value.Contains('\n'))
|
||||
{
|
||||
// Simplify line delimiters.
|
||||
value = Regex.Replace(value, "\\r\\n", "\n");
|
||||
// Remove white space in front of newlines.
|
||||
value = Regex.Replace(value, "\\s+\\n", "\n");
|
||||
// Remove multiple newlines.
|
||||
value = Regex.Replace(value, "\\n+", "\n");
|
||||
}
|
||||
|
||||
// Email addresses must be lowercase to ensure match to encrypted data.
|
||||
if (modelName.EqualsCI("EmailAddress") || modelName.EqualsCI("AccountEmailAddress"))
|
||||
value = value.ToLower();
|
||||
|
||||
// Uppercase selected values.
|
||||
if (modelName.EqualsCI("Postcode") || modelName.EqualsCI("AccountPostcode") || modelName.EqualsCI("RegisteredAddressPostcode"))
|
||||
value = value.ToUpper();
|
||||
|
||||
// Assign resulting value.
|
||||
result = ModelBindingResult.Success(value);
|
||||
}
|
||||
}
|
||||
// Log property details.
|
||||
if (SaxSystem.DebugMode &&
|
||||
// Ignore blank values.
|
||||
result.Model != null && !StringUtil.IsBlank(result.Model.ToString()) &&
|
||||
// Ignore passwords.
|
||||
!password &&
|
||||
// Ignore items that correspond with objects in the model rather than values.
|
||||
!result.Model.ToString().Equals(result.Model.GetType().FullName) &&
|
||||
// Ignore form files.
|
||||
!result.Model.ToString().ContainsCI("Microsoft.AspNetCore.Http.IFormFile"))
|
||||
SaLogger.Debug(SysUtil.Fn + " Form property " + modelName + ": '" + result.Model.ToString() + "'." + (originalValue != null && !originalValue.Equals(result.Model) ? " Original value: '" + originalValue.ToString() + "'" : ""));
|
||||
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Middleware
|
||||
{
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Maps a browser request to a data object. Default class provides a base implementation of a model binder.
|
||||
/// <para>Overridden to trim whitespace from input values.</para>
|
||||
/// </summary>
|
||||
public class SaxModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SaxModelBinderProvider()
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
|
||||
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
|
||||
{
|
||||
var propertyBinders = context.Metadata.Properties.ToDictionary(property => property, context.CreateBinder);
|
||||
ILoggerFactory loggerFactory = (ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory));
|
||||
return new SaxModelBinder(propertyBinders, loggerFactory);
|
||||
}
|
||||
|
||||
// Default - return null so that default binding provider is used.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Middleware
|
||||
{
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Extend IApplicationBuilder class to use the anit-forgery token validation middleware.
|
||||
/// </summary>
|
||||
public static class ApplicationBuilderExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseAntiforgeryTokens(this IApplicationBuilder app)
|
||||
{
|
||||
return app.UseMiddleware<ValidateAntiForgeryTokenMiddleware>();
|
||||
}
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Ensure relevant actions validate the anti-forgery token, eg. post requests.
|
||||
/// </summary>
|
||||
public class ValidateAntiForgeryTokenMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IAntiforgery _antiforgery;
|
||||
|
||||
public ValidateAntiForgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery)
|
||||
{
|
||||
_next = next;
|
||||
_antiforgery = antiforgery;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
String fn = GetType().Name + ".Invoke";
|
||||
|
||||
// Log action.
|
||||
//String rawTarget = context.Features.Get<IHttpRequestFeature>().RawTarget;
|
||||
if (SaxSystem.DebugMode) SaLogger.Trace(fn + " Action: '" + Regex.Replace(context.Request.Path.ToString(), "^/", "") + "'. Method: " + context.Request.Method);
|
||||
|
||||
// Intercept POST requests and apply anti-forgery test.
|
||||
if (HttpMethods.IsPost(context.Request.Method))
|
||||
{
|
||||
await _antiforgery.ValidateRequestAsync(context);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn(fn + " " + ExceptionUtil.GetLogMessage(exc));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
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.Dbas;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Base.App;
|
||||
using Saxonite.Net.Core.Web.Base.App;
|
||||
using Saxonite.Net.Core.Web.Base.Controllers;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using Saxonite.Net.Standard.Base.Mgr;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Reference;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Support;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using static Saxonite.Net.Standard.Base.App.Sax;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Models
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaxModel_Base : SaModel
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private SaxInternals internals;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SaxModel_Base(SaxLocaliser localiser)
|
||||
{
|
||||
Localiser = localiser;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Application properties.
|
||||
private SaxApplication_Web App { get { return SaxApplication_Web.WebApp; } }
|
||||
protected SaxAppConfig AppConfig { get { return App.AppConfig; } }
|
||||
protected SaxSystem_Web System { get { return App.System_Web; } }
|
||||
public String SessionCookieName { get { return App.SessionCookieName; } }
|
||||
public SaxLocaliser Localiser { get; }
|
||||
public override String GetText(String textKey, String defaultText)
|
||||
{
|
||||
String text = Localiser[textKey];
|
||||
if (StringUtil.IsBlank(text) || text.EqualsCI(textKey))
|
||||
return defaultText;
|
||||
return text;
|
||||
}
|
||||
// Session properties.
|
||||
public SaxSessionData SessionData { get; set; }
|
||||
public SaxSessionContext SessionContext { get { return SessionData?.SessionContext ?? App.SessionContext; } }
|
||||
public DbUpdateInfo DbUpdateInfo { get { return SessionData?.DbUpdateInfo ?? new DbUpdateInfo(null, "", SaxSystem.ComputerName); } }
|
||||
// Request properties.
|
||||
public SaxController_Base Controller { get; set; }
|
||||
public CultureInfo SessionCulture { get { return Controller?.SessionCulture ?? CultureInfo.InvariantCulture; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Page configuration.
|
||||
public String Version { get { return SaxSystem.DebugMode ? $"v{Sax.Version.GetFullVersion()}" : $"v{Sax.Version.GetAppVersion()}"; } }
|
||||
public String InstanceVersion { get { return (!StringUtil.IsEmpty(App.AppConfig.Instance) ? App.AppConfig.Instance + " " : "") + $"{Version}"; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Business components.
|
||||
public SaxInternals Internals { get { if (internals == null) internals = new SaxInternals(SessionContext); return internals; } }
|
||||
// Consoles.
|
||||
protected SaxDataCacheConsole DataCacheConsole { get { return Internals.DataCacheConsole; } }
|
||||
protected SaxSessionDataConsole SessionDataConsole { get { return Internals.SessionDataConsole; } }
|
||||
protected SaxActivityConsole ActivityConsole { get { return Internals.ActivityConsole; } }
|
||||
// Managers.
|
||||
public SaxReferenceMgr ReferenceMgr { get { return Internals.ReferenceMgr; } }
|
||||
public SaxAccountMgr AccountMgr { get { return Internals.AccountMgr; } }
|
||||
public SaxActivityMgr ActivityMgr { get { return Internals.ActivityMgr; } }
|
||||
public SaxItemDataMgr ItemDataMgr { get { return Internals.ItemDataMgr; } }
|
||||
public SaxReferenceGolfMgr ReferenceGolfMgr { get { return Internals.ReferenceGolfMgr; } }
|
||||
public SaxSupportMgr SupportMgr { get { return Internals.SupportMgr; } }
|
||||
// System codes.
|
||||
public List<TypeCodeDesc> DataItemTypeCodes { get { return Internals.DataItemTypeCodes; } }
|
||||
public List<TypeCodeDesc> YesNoCodes { get { return Internals.YesNoCodes; } }
|
||||
public List<TypeCodeDesc> SportGroupingCodes { get { return Internals.SportGroupingCodes; } }
|
||||
public List<TypeCodeDesc> SportSpecialityLabelCodes { get { return Internals.SportSpecialityLabelCodes; } }
|
||||
public List<TypeCodeDesc> SportContactTypeCodes { get { return Internals.SportContactTypeCodes; } }
|
||||
public List<TypeCodeDesc> SportParticipatingCodes { get { return Internals.SportParticipatingCodes; } }
|
||||
public List<TypeCodeDesc> SportPreparingCodes { get { return Internals.SportPreparingCodes; } }
|
||||
public List<TypeCodeDesc> SportOfficiatingCodes { get { return Internals.SportOfficiatingCodes; } }
|
||||
public List<TypeCodeDesc> RegionTypeCodes { get { return Internals.RegionTypeCodes; } }
|
||||
public List<TypeCodeDesc> EligibilityTypeCodes { get { return Internals.EligibilityTypeCodes; } }
|
||||
public List<TypeCodeDesc> UserRoleCodes { get { return Internals.UserRoleCodes; } }
|
||||
public List<TypeCodeDesc> OrganisationTypeCodes { get { return Internals.OrganisationTypeCodes; } }
|
||||
public List<TypeCodeDesc> OrganisationCategoryCodes { get { return Internals.OrganisationCategoryCodes; } }
|
||||
public List<TypeCodeDesc> OrganisationRoleCodes { get { return Internals.OrganisationRoleCodes; } }
|
||||
public List<TypeCodeDesc> AssociationTypeCodes { get { return Internals.AssociationTypeCodes; } }
|
||||
public List<TypeCodeDesc> AddressTypeCodes { get { return Internals.AddressTypeCodes; } }
|
||||
public List<TypeCodeDesc> ContactMethodCodes { get { return Internals.ContactMethodCodes; } }
|
||||
public List<TypeCodeDesc> EventTypeCodes { get { return Internals.EventTypeCodes; } }
|
||||
public List<TypeCodeDesc> OpportunityTypeCodes { get { return Internals.OpportunityTypeCodes; } }
|
||||
public List<TypeCodeDesc> ActivityReactionCodes { get { return Internals.ActivityReactionCodes; } }
|
||||
// Support.
|
||||
public List<TypeCodeDesc> UserReportTypeCodes { get { return Internals.UserReportTypeCodes; } }
|
||||
public List<TypeCodeDesc> SupportItemProcessTypeCodes { get { return Internals.SupportItemProcessTypeCodes; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Common data components.
|
||||
// User Accounts.
|
||||
public SaxUserAccount GetUserAccount(SysId userAccountId)
|
||||
{
|
||||
return Internals.GetUserAccount(userAccountId);
|
||||
}
|
||||
public String GetUserAccountDisplayName(SysId userAccountId)
|
||||
{
|
||||
return Internals.GetUserAccountDisplayName(userAccountId);
|
||||
}
|
||||
// Organisations.
|
||||
public SaxOrganisation GetOrganisation(SysId organisationId)
|
||||
{
|
||||
return Internals.GetOrganisation(organisationId);
|
||||
}
|
||||
public String GetOrganisationName(SysId organisationId)
|
||||
{
|
||||
return Internals.GetOrganisationName(organisationId);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Security.
|
||||
public bool ValidUserSession { get { return SessionData?.ValidUserSession ?? false; } }
|
||||
public bool AwaitingLogin { get { return !ValidUserSession; } }
|
||||
public bool KeepLoggedIn { get { return !AwaitingLogin && SessionData.User.KeepLoggedIn; } }
|
||||
/// <summary>Maximum length allowed for password entry: sign-up, change password etc.</summary>
|
||||
public int PasswordMaxLength { get; } = 32;
|
||||
|
||||
// Account actions.
|
||||
// NB The form proerty will never be used - account actions processed within page.
|
||||
public String AccountAction { get; set; }
|
||||
public IEnumerable<SelectListItem> GetAccountActions()
|
||||
{
|
||||
List<CodeDesc> items = new List<CodeDesc>();
|
||||
if (SessionData?.ValidUserSession ?? false)
|
||||
{
|
||||
items.Add("Profile", Localiser["label.view.profile"]);
|
||||
items.Add("ChangePassword", Localiser["label.change.password"]);
|
||||
items.Add("SignOut", Localiser["label.sign.out"]);
|
||||
}
|
||||
return new SelectList(items, "Code", "Desc");
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Link URLs.
|
||||
public class LinksData
|
||||
{
|
||||
public String Facebook { get; } = "https://www.facebook.com/sportierre/";
|
||||
public String LinkedIn { get; } = "https://www.linkedin.com/company/sportierre/about/";
|
||||
}
|
||||
public LinksData Links { get; } = new LinksData();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Copyright notice.
|
||||
public HtmlString CopyrightNotice
|
||||
{
|
||||
get
|
||||
{
|
||||
int initialYear = 2020;
|
||||
int currentYear = DateTime.Today.Year;
|
||||
return Localiser.GetHtmlText("copyright.symbol.year.html", (initialYear != currentYear ? initialYear + "-" : "") + currentYear.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Form control value max length.
|
||||
/// <para>In many cases the database columns allow plenty of extra space for the value, in case they need to be encrypted.</para>
|
||||
/// <para>Hence, several cases where the value entered in the control isn't allowed the full database length.</para>
|
||||
/// </summary>
|
||||
public int GetControlMaxLength(SaxDbDfn_Core.ColumnIdx column)
|
||||
{
|
||||
int maxLength = (int)SaxDbDfn_Core.GetColTextLen(column);
|
||||
switch (column)
|
||||
{
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress:
|
||||
return Math.Min(maxLength, 60);
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName:
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_LastName:
|
||||
return Math.Min(maxLength, 40);
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_About:
|
||||
return Math.Min(maxLength, 2000);
|
||||
case SaxDbDfn_Core.ColumnIdx.Organisation_Name:
|
||||
return Math.Min(maxLength, 80);
|
||||
case SaxDbDfn_Core.ColumnIdx.Organisation_About:
|
||||
case SaxDbDfn_Core.ColumnIdx.Organisation_Facilities:
|
||||
case SaxDbDfn_Core.ColumnIdx.Organisation_Notes:
|
||||
return Math.Min(maxLength, 2000);
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_PhoneMain:
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_PhoneMobile:
|
||||
return Math.Min(maxLength, 20);
|
||||
//case SaxDbDfn.ColumnIdx.UserAccount_Postcode:
|
||||
//case SaxDbDfn.ColumnIdx.ItemAddress_Postcode:
|
||||
// return Math.Min(maxLength, 16);
|
||||
case SaxDbDfn_Core.ColumnIdx.Article_Headline:
|
||||
return Math.Min(maxLength, 2000);
|
||||
case SaxDbDfn_Core.ColumnIdx.Article_Summary:
|
||||
return Math.Min(maxLength, 1000);
|
||||
}
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Form control value max length - support database.
|
||||
/// <para>In many cases the database columns allow plenty of extra space for the value, in case they need to be encrypted.</para>
|
||||
/// <para>Hence, several cases where the value entered in the control isn't allowed the full database length.</para>
|
||||
/// </summary>
|
||||
public int GetControlMaxLength(SaxDbDfn_Support.ColumnIdx column)
|
||||
{
|
||||
int maxLength = (int)SaxDbDfn_Support.GetColTextLen(column);
|
||||
switch (column)
|
||||
{
|
||||
case SaxDbDfn_Support.ColumnIdx.UserReport_Title:
|
||||
break;
|
||||
}
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Display data - Dates.
|
||||
public String FormatDateForDisplay(DateTime value)
|
||||
{
|
||||
return FormatDateForDisplay((DateTime?)value);
|
||||
}
|
||||
public String FormatDateForDisplay(DateTime? value)
|
||||
{
|
||||
return DateUtil.Format(value, "d-MMM-yy", SessionCulture);
|
||||
}
|
||||
public String FormatDateTimeForDisplay(DateTime value)
|
||||
{
|
||||
return FormatDateTimeForDisplay((DateTime?)value);
|
||||
}
|
||||
public String FormatDateTimeForDisplay(DateTime? value)
|
||||
{
|
||||
return StringUtil.TrimAll(DateUtil.Format(value, "d-MMM-yy HH:mm", SessionCulture));
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Form data - Dates.
|
||||
// Format may depend on locale, but as long as these methods are used consistently,
|
||||
// there should be consistent formatting, display and parsing of date values.
|
||||
public String CalendarDateFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
// JQuery-UI DatePicker date format.
|
||||
// Format:
|
||||
// d Single digit day of month
|
||||
// dd Double digit day of month
|
||||
// D 3 letter day of week abbreviation.
|
||||
// DD Day of week.
|
||||
// m Single digit month.
|
||||
// mm Double digit month.
|
||||
// M 3 letter month abbreviation.
|
||||
// MM Month name.
|
||||
// yy 4 digit year.
|
||||
// Examples:
|
||||
// dd/mm/yy 07/04/2012
|
||||
// dd-M-yy 07-Apr-2012
|
||||
// d-M-yy 7-Apr-2012
|
||||
return "dd-M-yy";
|
||||
}
|
||||
}
|
||||
public String FormatDateForForm(DateTime? value)
|
||||
{
|
||||
return DateUtil.Format(value, "dd-MMM-yyyy", SessionCulture);
|
||||
}
|
||||
public DateTime ParseDateFromForm(String value)
|
||||
{
|
||||
DateTime date = DateUtil.ParseExact(value, "dd-MMM-yyyy", SessionCulture);
|
||||
if (!date.IsValid())
|
||||
date = DateUtil.ParseExact(value, "dd-MMM-yyyy", CultureInfo.CurrentUICulture);
|
||||
if (!date.IsValid())
|
||||
date = DateUtil.ParseExact(value, "dd-MMM-yyyy", CultureInfo.CurrentCulture);
|
||||
if (!date.IsValid())
|
||||
date = DateUtil.ParseExact(value, "dd-MMM-yyyy", CultureInfo.InvariantCulture);
|
||||
return date;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Form data.
|
||||
//-------------------------------------------------------------------
|
||||
// Common form values.
|
||||
public String FormAction { get; set; }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>List of Yes/No options for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> YesNoOptionsForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc yesNo in YesNoCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetYesNoCaption((Sax.YesNo)yesNo.Type), yesNo.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Sport Grouping for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> SportGroupingCodesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc sportGrouping in SportGroupingCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetSportGroupingCaption((Sax.SportGrouping)sportGrouping.Type), sportGrouping.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Sport Speciality Labels for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> SportSpecialityLabelCodesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
values.Add(new SelectListItem("Default", ""));
|
||||
foreach (TypeCodeDesc sportSpecialityLabel in SportSpecialityLabelCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetSportSpecialityLabelCaption((Sax.SportSpecialityLabel)sportSpecialityLabel.Type), sportSpecialityLabel.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Sport Participating options for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> SportParticipatingCodesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc sportParticipating in SportParticipatingCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetSportParticipatingCaption((Sax.SportParticipating)sportParticipating.Type), sportParticipating.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Sport Preparing options for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> SportPreparingCodesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc sportPreparing in SportPreparingCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetSportPreparingCaption((Sax.SportPreparing)sportPreparing.Type), sportPreparing.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Sport Officiating options for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> SportOfficiatingCodesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc sportOfficiating in SportOfficiatingCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetSportOfficiatingCaption((Sax.SportOfficiating)sportOfficiating.Type), sportOfficiating.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Region Types for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> RegionTypesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc organisationType in RegionTypeCodes.FindAll(item => item.Type != (int)Sax.RegionType.Universe && item.Type != (int)Sax.RegionType.Galaxy))
|
||||
values.Add(new SelectListItem(Localiser.GetRegionTypeCaption((Sax.RegionType)organisationType.Type), organisationType.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of titles for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> TitlesForSelect
|
||||
{
|
||||
get { return new SelectList(SaxSystem.GetCodesOfType(Sax.CodeType.Title), "Code", "Desc"); }
|
||||
}
|
||||
|
||||
/// <summary>List of eligibility types for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> EligibilityTypesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc eligibilityType in Internals.EligibilityTypeCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetEligibilityTypeCaption((Sax.EligibilityType)eligibilityType.Type), eligibilityType.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Organisation Types for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> OrganisationTypesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc organisationType in OrganisationTypeCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetOrganisationTypeCaption((Sax.OrganisationType)organisationType.Type), organisationType.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of Organisation Categories for dropdown.
|
||||
/// <para>NB Does not include 'Partner' or 'Other'.</para>
|
||||
/// </summary>
|
||||
public IEnumerable<SelectListItem> OrganisationCategoriesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc organisationCategory in OrganisationCategoryCodes.FindAll(item => item.Type != (int)Sax.OrganisationCategory.Partner && item.Type != (int)Sax.OrganisationCategory.Other))
|
||||
values.Add(new SelectListItem(Localiser.GetOrganisationCategoryCaption((Sax.OrganisationCategory)organisationCategory.Type), organisationCategory.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of Organisation Categories for Admin dropdown.
|
||||
/// <para>Includes complete set of categories.</para>
|
||||
/// </summary>
|
||||
public IEnumerable<SelectListItem> AdminOrganisationCategoriesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc organisationCategory in OrganisationCategoryCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetOrganisationCategoryCaption((Sax.OrganisationCategory)organisationCategory.Type), organisationCategory.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Procs.Data
|
||||
{
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Object to persist upload file after request has terminated, and the Form File has been disposed.
|
||||
/// <para>- Too allow async processes to access the file after request completed.</para>
|
||||
/// </summary>
|
||||
public class UploadFileData
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
public UploadFileData(IFormFile dataFile)
|
||||
{
|
||||
ContentDisposition = dataFile.ContentDisposition;
|
||||
ContentType = dataFile.ContentType;
|
||||
FileName = dataFile.FileName;
|
||||
Length = dataFile.Length;
|
||||
Name = dataFile.Name;
|
||||
foreach (var header in dataFile.Headers)
|
||||
Headers.Add(header.Key, header.Value.ToString());
|
||||
// Load file data.
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
dataFile.CopyTo(stream);
|
||||
FileData = stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
|
||||
/// <summary>Gets the raw Content-Disposition header of the uploaded file.</summary>
|
||||
public String ContentDisposition { get; }
|
||||
|
||||
/// <summary>Gets the raw Content-Type header of the uploaded file.</summary>
|
||||
public String ContentType { get; }
|
||||
|
||||
/// <summary>Gets the file name from the Content-Disposition header.</summary>
|
||||
public String FileName { get; }
|
||||
|
||||
/// <summary>Gets the header dictionary of the uploaded file.</summary>
|
||||
public Dictionary<String, String> Headers { get; } = new Dictionary<String, String>();
|
||||
|
||||
/// <summary>Gets the file length in bytes.</summary>
|
||||
public long Length { get; }
|
||||
|
||||
/// <summary>Gets the form field name from the Content-Disposition header.</summary>
|
||||
public String Name { get; }
|
||||
|
||||
/// <summary>File data.</summary>
|
||||
public byte[] FileData { get; }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
using DocumentFormat.OpenXml.Drawing.Diagrams;
|
||||
using DocumentFormat.OpenXml.Office2016.Excel;
|
||||
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.Helpers;
|
||||
using Sarand.Net.Standard.Base.Tasks;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus.Reference;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Procs
|
||||
{
|
||||
//=======================================================================
|
||||
partial class SaxGolfUsgaDataLookup
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
private class ScheduledUsgaDataLookupControlData
|
||||
{
|
||||
public DateTime CountriesLastLookedUp { get; set; } = DateTime.Now;
|
||||
}
|
||||
private static ScheduledUsgaDataLookupControlData usgaDataLookupControl { get; } = new ScheduledUsgaDataLookupControlData();
|
||||
|
||||
/// <summary>
|
||||
/// Determine which USGA data population process to run.
|
||||
/// <para>- Method is invoked periodically (every 20 mins) by the Admin Hosted Service Console.</para>
|
||||
/// <para>- 1. Populate countries if not already done or not done for a while (eg. 24 hours).</para>
|
||||
/// <para>- 2. Populate provinces for a Country that has none or occasionally refresh one that has.</para>
|
||||
/// <para>- 3. Populate Courses for a Province that has none or occasionally refresh one that has.</para>
|
||||
/// <para>- 4. Populate Markers for a Course that has none or occasionally refresh one that has.</para>
|
||||
/// </summary>
|
||||
public static void ExecuteScheduledUsgaDataLookup(SaxSessionContext sessionContext, DbUpdateInfo dbUpdateInfo, SacsTaskData taskData)
|
||||
{
|
||||
SaLogger.Debug($"{SysUtil.Fn}");
|
||||
|
||||
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
|
||||
{
|
||||
SaxInternals internals = new SaxInternals(sessionContext);
|
||||
disposableItems.Add(internals);
|
||||
|
||||
// Configure process.
|
||||
SaxGolfUsgaDataLookup.Config courseLookupConfig = new SaxGolfUsgaDataLookup.Config(sessionContext, dbUpdateInfo);
|
||||
SaxGolfUsgaDataLookup usgaDataLookup = new SaxGolfUsgaDataLookup(courseLookupConfig);
|
||||
|
||||
if (taskData.IsCancellationRequested) return;
|
||||
|
||||
// Determine which process to run.
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// 1. Countries.
|
||||
{
|
||||
// Check USGA country population.
|
||||
List<SaxGolfUsgaCountry> usgaCountries = internals.ReferenceGolfMgr.GetUsgaCountryList(new SaxGolfUsgaCountry.Params());
|
||||
if (!usgaCountries.Any() || (DateTime.Now - usgaDataLookupControl.CountriesLastLookedUp).TotalHours > MathUtil.Constrain(Sax.Config.Admin.Processes.Scheduled.UsgaGolfDataLookup.HoursBetweenCountryLookup, 1, 24 * 7))
|
||||
{
|
||||
// No countries currently found or long enough since last lookup.
|
||||
// Lookup USGA countries.
|
||||
List<SaxGolfUsgaDataLookup.CountryData> countriesData = usgaDataLookup.LookupUsgaCountries();
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Countries populated: {countriesData.Count}.", true);
|
||||
usgaDataLookupControl.CountriesLastLookedUp = DateTime.Now;
|
||||
return;
|
||||
}
|
||||
if (taskData.IsCancellationRequested) return;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// 2. Provinces.
|
||||
|
||||
// 2a. Population of active priority Country with no Provinces.
|
||||
{
|
||||
bool populatePriorityCountryNoProvinces = SacsRandomHelper.GetInt32(1, 5) == 1;
|
||||
if (populatePriorityCountryNoProvinces)
|
||||
{
|
||||
SaxGolfUsgaCountry.Params prmCountry = new SaxGolfUsgaCountry.Params();
|
||||
prmCountry.PriorityFlagState = SacsHierarchyItem.Params.FlagState.True;
|
||||
prmCountry.HasProvinces = SacsHierarchyItem.Params.BoolState.False;
|
||||
prmCountry.StatusCat = SaxGolfUsgaCountry.StatusCat.ActiveOnly;
|
||||
prmCountry.ResultsPaging.Set(1, 4);
|
||||
prmCountry.RandomOrder = true;
|
||||
List<SaxGolfUsgaCountry> countriesNoProvinces = internals.ReferenceGolfMgr.GetUsgaCountryList(prmCountry).Randomise();
|
||||
disposableItems.AddRange(countriesNoProvinces);
|
||||
if (countriesNoProvinces.Any())
|
||||
{
|
||||
SaxGolfUsgaCountry usgaCountry = countriesNoProvinces[0];
|
||||
List<SaxGolfUsgaDataLookup.ProvinceData> provincesData = usgaDataLookup.LookupUsgaProvincesForCountry(usgaCountry.CountryCode);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Provinces populated for {usgaCountry.CountryCode} {usgaCountry.CountryName}" + (usgaCountry.Priority ? " [Priority]" : "") + $": {provincesData.Count}.", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2b. Refresh active priority Country with Provinces.
|
||||
{
|
||||
bool refreshPriorityCountryWithProvinces = SacsRandomHelper.GetInt32(1, 25) == 1;
|
||||
if (refreshPriorityCountryWithProvinces)
|
||||
{
|
||||
SaxGolfUsgaCountry.Params prmCountry = new SaxGolfUsgaCountry.Params();
|
||||
prmCountry.PriorityFlagState = SacsHierarchyItem.Params.FlagState.True;
|
||||
prmCountry.HasProvinces = SacsHierarchyItem.Params.BoolState.True;
|
||||
prmCountry.StatusCat = SaxGolfUsgaCountry.StatusCat.ActiveOnly;
|
||||
prmCountry.ResultsPaging.Set(1, 4);
|
||||
prmCountry.RandomOrder = true;
|
||||
List<SaxGolfUsgaCountry> countriesWithProvinces = internals.ReferenceGolfMgr.GetUsgaCountryList(prmCountry).Randomise();
|
||||
disposableItems.AddRange(countriesWithProvinces);
|
||||
if (countriesWithProvinces.Any())
|
||||
{
|
||||
SaxGolfUsgaCountry usgaCountry = countriesWithProvinces[0];
|
||||
List<SaxGolfUsgaDataLookup.ProvinceData> provincesData = usgaDataLookup.LookupUsgaProvincesForCountry(usgaCountry.CountryCode);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Provinces refreshed for {usgaCountry.CountryCode} {usgaCountry.CountryName}" + (usgaCountry.Priority ? " [Priority]" : "") + $": {provincesData.Count}.", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// 3. Courses.
|
||||
|
||||
// 3a. Populate active priority Province with no Facilities (Courses).
|
||||
{
|
||||
bool populatePriorityProvinceNoFacilities = SacsRandomHelper.GetInt32(1, 4) == 1;
|
||||
if (populatePriorityProvinceNoFacilities)
|
||||
{
|
||||
// Active priority Provinces with no Facilities.
|
||||
SaxGolfUsgaProvince.Params prmProvince = new SaxGolfUsgaProvince.Params();
|
||||
prmProvince.PriorityFlagState = SacsHierarchyItem.Params.FlagState.True;
|
||||
prmProvince.HasFacilities = SacsHierarchyItem.Params.BoolState.False;
|
||||
prmProvince.StatusCat = SaxGolfUsgaProvince.StatusCat.ActiveOnly;
|
||||
prmProvince.ResultsPaging.Set(1, 4);
|
||||
prmProvince.RandomOrder = true;
|
||||
List<SaxGolfUsgaProvince> provincesNoFacilities = internals.ReferenceGolfMgr.GetUsgaProvinceList(prmProvince).Randomise();
|
||||
disposableItems.AddRange(provincesNoFacilities);
|
||||
if (provincesNoFacilities.Any())
|
||||
{
|
||||
// Province has no Facilities - populate courses.
|
||||
SaxGolfUsgaProvince usgaProvince = provincesNoFacilities[0];
|
||||
List<SaxGolfUsgaDataLookup.CourseData> coursesData = LookupUsgaCourses(internals, usgaDataLookup, usgaProvince, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3b. Refresh active priority Province with Facilities (courses).
|
||||
{
|
||||
bool refreshProvinceWithFacilities = SacsRandomHelper.GetInt32(1, 21) == 1;
|
||||
if (refreshProvinceWithFacilities)
|
||||
{
|
||||
SaxGolfUsgaProvince.Params prmProvince = new SaxGolfUsgaProvince.Params();
|
||||
prmProvince.PriorityFlagState = SacsHierarchyItem.Params.FlagState.True;
|
||||
prmProvince.HasFacilities = SacsHierarchyItem.Params.BoolState.True;
|
||||
prmProvince.StatusCat = SaxGolfUsgaProvince.StatusCat.ActiveOnly;
|
||||
prmProvince.ResultsPaging.Set(1, 4);
|
||||
prmProvince.RandomOrder = true;
|
||||
List<SaxGolfUsgaProvince> provincesWithFacilities = internals.ReferenceGolfMgr.GetUsgaProvinceList(prmProvince).Randomise();
|
||||
disposableItems.AddRange(provincesWithFacilities);
|
||||
if (provincesWithFacilities.Any())
|
||||
{
|
||||
SaxGolfUsgaProvince usgaProvince = provincesWithFacilities[0];
|
||||
List<SaxGolfUsgaDataLookup.CourseData> coursesData = LookupUsgaCourses(internals, usgaDataLookup, usgaProvince, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// 4. Markers.
|
||||
|
||||
// 4a. Populate active priority Course with no Markers.
|
||||
{
|
||||
bool populatePriorityCourseNoMarkers = SacsRandomHelper.GetInt32(1, 2) == 1;
|
||||
if (populatePriorityCourseNoMarkers)
|
||||
{
|
||||
SaxGolfUsgaCourse.Params prmCourse = new SaxGolfUsgaCourse.Params();
|
||||
prmCourse.PriorityFlagState = SacsHierarchyItem.Params.FlagState.True;
|
||||
prmCourse.HasMarkers = SacsHierarchyItem.Params.BoolState.False;
|
||||
prmCourse.StatusCat = SaxGolfUsgaCourse.StatusCat.ActiveOnly;
|
||||
prmCourse.ResultsPaging.Set(1, 4);
|
||||
prmCourse.RandomOrder = true;
|
||||
List<SaxGolfUsgaCourse> coursesNoMarkers = internals.ReferenceGolfMgr.GetUsgaCourseList(prmCourse).Randomise();
|
||||
disposableItems.AddRange(coursesNoMarkers);
|
||||
if (coursesNoMarkers.Any())
|
||||
{
|
||||
SaxGolfUsgaCourse usgaCourse = coursesNoMarkers[0];
|
||||
List<SaxGolfUsgaDataLookup.MarkerData> markersData = LookupUsgaMarkers(internals, usgaDataLookup, usgaCourse, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4b. Refresh active priority Course with Markers.
|
||||
{
|
||||
bool refreshPriorityCourseWithMarkers = SacsRandomHelper.GetInt32(1, 17) == 1;
|
||||
if (refreshPriorityCourseWithMarkers)
|
||||
{
|
||||
// Active priority Course with no Markers.
|
||||
SaxGolfUsgaCourse.Params prmCourse = new SaxGolfUsgaCourse.Params();
|
||||
prmCourse.PriorityFlagState = SacsHierarchyItem.Params.FlagState.True;
|
||||
prmCourse.HasMarkers = SacsHierarchyItem.Params.BoolState.True;
|
||||
prmCourse.StatusCat = SaxGolfUsgaCourse.StatusCat.ActiveOnly;
|
||||
prmCourse.ResultsPaging.Set(1, 4);
|
||||
prmCourse.RandomOrder = true;
|
||||
List<SaxGolfUsgaCourse> coursesWithMarkers = internals.ReferenceGolfMgr.GetUsgaCourseList(prmCourse).Randomise();
|
||||
disposableItems.AddRange(coursesWithMarkers);
|
||||
if (coursesWithMarkers.Any())
|
||||
{
|
||||
// Refresh first Course with Markers.
|
||||
SaxGolfUsgaCourse usgaCourse = coursesWithMarkers[0];
|
||||
List<SaxGolfUsgaDataLookup.MarkerData> markersData = LookupUsgaMarkers(internals, usgaDataLookup, usgaCourse, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// 5. Fallback population.
|
||||
|
||||
// 5a. Active Country with no Provinces.
|
||||
{
|
||||
bool populateCountryNoProvinces = SacsRandomHelper.GetInt32(1, 17) == 1;
|
||||
if (populateCountryNoProvinces)
|
||||
{
|
||||
SaxGolfUsgaCountry.Params prmCountry = new SaxGolfUsgaCountry.Params();
|
||||
prmCountry.HasProvinces = SacsHierarchyItem.Params.BoolState.False;
|
||||
prmCountry.StatusCat = SaxGolfUsgaCountry.StatusCat.ActiveOnly;
|
||||
prmCountry.ResultsPaging.Set(1,4);
|
||||
prmCountry.RandomOrder = true;
|
||||
List<SaxGolfUsgaCountry> countriesNoProvinces = internals.ReferenceGolfMgr.GetUsgaCountryList(prmCountry).Randomise();
|
||||
disposableItems.AddRange(countriesNoProvinces);
|
||||
if (countriesNoProvinces.Any())
|
||||
{
|
||||
SaxGolfUsgaCountry usgaCountry = countriesNoProvinces[0];
|
||||
List<SaxGolfUsgaDataLookup.ProvinceData> provincesData = usgaDataLookup.LookupUsgaProvincesForCountry(usgaCountry.CountryCode);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Provinces populated for {usgaCountry.CountryCode} {usgaCountry.CountryName} [Fallback]: {provincesData.Count}.", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5b. Active Province with no Facilities (courses).
|
||||
{
|
||||
bool populateProvinceNoFacilities = SacsRandomHelper.GetInt32(1, 3) == 1;
|
||||
if (populateProvinceNoFacilities)
|
||||
{
|
||||
// Active Provinces with no Facilities.
|
||||
SaxGolfUsgaProvince.Params prmProvince = new SaxGolfUsgaProvince.Params();
|
||||
prmProvince.HasFacilities = SacsHierarchyItem.Params.BoolState.False;
|
||||
prmProvince.StatusCat = SaxGolfUsgaProvince.StatusCat.ActiveOnly;
|
||||
prmProvince.ResultsPaging.Set(1, 4);
|
||||
prmProvince.RandomOrder = true;
|
||||
List<SaxGolfUsgaProvince> provincesNoFacilities = internals.ReferenceGolfMgr.GetUsgaProvinceList(prmProvince).Randomise();
|
||||
disposableItems.AddRange(provincesNoFacilities);
|
||||
if (provincesNoFacilities.Any())
|
||||
{
|
||||
// Province has no Facilities - populate courses.
|
||||
SaxGolfUsgaProvince usgaProvince = provincesNoFacilities[0];
|
||||
List<SaxGolfUsgaDataLookup.CourseData> coursesData = LookupUsgaCourses(internals, usgaDataLookup, usgaProvince, false, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5c. Populate active Course with no Markers.
|
||||
{
|
||||
bool populateCourseNoMarkers = SacsRandomHelper.GetInt32(1, 2) == 1;
|
||||
if (populateCourseNoMarkers)
|
||||
{
|
||||
SaxGolfUsgaCourse.Params prmCourse = new SaxGolfUsgaCourse.Params();
|
||||
prmCourse.HasMarkers = SacsHierarchyItem.Params.BoolState.False;
|
||||
prmCourse.StatusCat = SaxGolfUsgaCourse.StatusCat.ActiveOnly;
|
||||
prmCourse.ResultsPaging.Set(1, 4);
|
||||
prmCourse.RandomOrder = true;
|
||||
List<SaxGolfUsgaCourse> coursesNoMarkers = internals.ReferenceGolfMgr.GetUsgaCourseList(prmCourse).Randomise();
|
||||
disposableItems.AddRange(coursesNoMarkers);
|
||||
if (coursesNoMarkers.Any())
|
||||
{
|
||||
SaxGolfUsgaCourse usgaCourse = coursesNoMarkers[0];
|
||||
List<SaxGolfUsgaDataLookup.MarkerData> markersData = LookupUsgaMarkers(internals, usgaDataLookup, usgaCourse, false, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
|
||||
SaLogger.Warn($"{SysUtil.Fn} No data population task found to execute.");
|
||||
|
||||
//-----------------------------------------------------------
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private static List<SaxGolfUsgaDataLookup.CourseData> LookupUsgaCourses(SaxInternals internals, SaxGolfUsgaDataLookup usgaDataLookup, SaxGolfUsgaProvince usgaProvince, bool refresh, bool fallback = false)
|
||||
{
|
||||
List<SaxGolfUsgaDataLookup.CourseData> coursesData = new List<CourseData>();
|
||||
SaxGolfUsgaCountry usgaCountry = internals.ReferenceGolfMgr.GetUsgaCountryById(usgaProvince.CountryId);
|
||||
if (usgaCountry != null)
|
||||
{
|
||||
coursesData = usgaDataLookup.LookupUsgaCoursesForCountryAndProvince(usgaCountry.CountryCode, usgaProvince.ProvinceCode);
|
||||
List<String> annotations = new List<string>();
|
||||
if (usgaProvince.Priority) annotations.Add("Priority");
|
||||
if (fallback) annotations.Add("Fallback");
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Courses refreshed for {usgaCountry.CountryCode} {usgaCountry.CountryName} - {usgaProvince.ProvinceCode} {usgaProvince.ProvinceName}" + (annotations.Any() ? $" [{annotations.Concat(", ")}]" : "") + $": {coursesData.Count}.", true);
|
||||
}
|
||||
else SaLogger.Error($"{SysUtil.Fn} USGA Country not found for Id {usgaProvince.CountryId} for Province {usgaProvince.ProvinceCode} {usgaProvince.ProvinceName}.");
|
||||
return coursesData;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private static List<SaxGolfUsgaDataLookup.MarkerData> LookupUsgaMarkers(SaxInternals internals, SaxGolfUsgaDataLookup usgaDataLookup, SaxGolfUsgaCourse usgaCourse, bool refresh, bool fallback = false)
|
||||
{
|
||||
SaxGolfUsgaFacility usgaFacility = internals.ReferenceGolfMgr.GetUsgaFacilityById(usgaCourse.FacilityId);
|
||||
SaxGolfUsgaProvince usgaProvince = internals.ReferenceGolfMgr.GetUsgaProvinceById(usgaFacility?.ProvinceId);
|
||||
SaxGolfUsgaCountry usgaCountry = internals.ReferenceGolfMgr.GetUsgaCountryById(usgaProvince?.CountryId);
|
||||
List<SaxGolfUsgaDataLookup.MarkerData> markersData = new List<MarkerData>();
|
||||
if (usgaFacility != null)
|
||||
{
|
||||
if (usgaProvince != null)
|
||||
{
|
||||
if (usgaCountry != null)
|
||||
{
|
||||
markersData = usgaDataLookup.LookupUsgaMarkersForCourse(usgaCourse.UsgaCourseId);
|
||||
List<String> annotations = new List<string>();
|
||||
if (usgaCourse.Priority) annotations.Add("Priority");
|
||||
if (fallback) annotations.Add("Fallback");
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Markers " + (refresh ? "refreshed" : "populated") + $" for {usgaCountry.CountryCode} {usgaCountry.CountryName} - {usgaProvince.ProvinceCode} {usgaProvince.ProvinceName} - {usgaCourse.Caption} [Id: {usgaCourse.UsgaCourseId}" + (annotations.Any() ? $"{annotations.Concat("", ", ")}" : "") + $"]: {markersData.Count}.", true);
|
||||
}
|
||||
else SaLogger.Error($"{SysUtil.Fn} Missing Country for Province {usgaProvince.ProvinceName}. Country Id: {usgaProvince.CountryId}.");
|
||||
}
|
||||
else SaLogger.Error($"{SysUtil.Fn} Missing Province for Facility {usgaFacility.FacilityName}. Province Id: {usgaFacility.ProvinceId}.");
|
||||
}
|
||||
else SaLogger.Error($"{SysUtil.Fn} Missing Facility for Course {usgaCourse.FullName}. Facility Id: {usgaCourse.FacilityId}.");
|
||||
return markersData;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,178 @@
|
||||
using DocumentFormat.OpenXml.InkML;
|
||||
using HtmlAgilityPack;
|
||||
using MailKit;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
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.Helpers;
|
||||
using Sarand.Net.Standard.Base.Net;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Core.Web.Base.Controllers;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using Saxonite.Net.Standard.Base.Bus.Reference;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Reference;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Procs
|
||||
{
|
||||
//======================================================================
|
||||
public partial class SaxOrganisationEventsLookup : SaxProcess_Web
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
public enum Task
|
||||
{
|
||||
LookupEvents
|
||||
}
|
||||
|
||||
public new class Config : SaxProcess_Web.Config
|
||||
{
|
||||
public Config(SaxSessionContext sessionContext, DbUpdateInfo dbUpdateInfo) : base(sessionContext, dbUpdateInfo)
|
||||
{ }
|
||||
/// <summary>Organisations.</summary>
|
||||
public HashSet<SysId> OrganisationIdsIncluded { get; } = new HashSet<SysId>();
|
||||
/// <summary>Tasks to be run under this instance of the USGA Data Lookup process.</summary>
|
||||
public HashSet<Task> Tasks { get; } = new HashSet<Task>();
|
||||
}
|
||||
|
||||
private class EventData
|
||||
{
|
||||
public DateTime EventAt { get; set; }
|
||||
public String Caption { get; set; }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Config config;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SaxOrganisationEventsLookup(Config config) : base(config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
|
||||
public override string ProcessDescription => "Organisation Events Lookup";
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Run the process.
|
||||
protected override bool Run()
|
||||
{
|
||||
// Execute selected tasks.
|
||||
foreach (Task task in config.Tasks)
|
||||
{
|
||||
switch (task)
|
||||
{
|
||||
case Task.LookupEvents:
|
||||
LookupEventsForOrganisations();
|
||||
break;
|
||||
}
|
||||
if (!OK) break;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Event lookup.
|
||||
//------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Organisation event lookup.
|
||||
/// </summary>
|
||||
private bool LookupEventsForOrganisations()
|
||||
{
|
||||
if (!OK) return false;
|
||||
|
||||
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
|
||||
{
|
||||
foreach (SysId organisationId in config.OrganisationIdsIncluded)
|
||||
{
|
||||
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
|
||||
if (organisation == null)
|
||||
{
|
||||
AddWarningMessage($"Organisation not found for Id: {organisationId}.");
|
||||
continue;
|
||||
}
|
||||
LookupOrganisationEvents(organisation);
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Organisation event lookup.
|
||||
/// </summary>
|
||||
private bool LookupOrganisationEvents(SaxOrganisation organisation)
|
||||
{
|
||||
List<EventData> eventsData = new List<EventData>();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
// Query Countries on USGA website.
|
||||
CancellationToken cancelToken = new CancellationToken();
|
||||
SacsHttpRequestData request = new SacsHttpRequestData(cancelToken) { Url = "https://ncrdb.usga.org/" };
|
||||
SacsHttpResponseData response = SacsHttpClient.DoRequest(request).Result;
|
||||
String data = response.Data;
|
||||
|
||||
if (!StringUtil.IsBlank(response.Data))
|
||||
{
|
||||
// Parse HTML.
|
||||
HtmlDocument htmlDoc = new HtmlDocument();
|
||||
htmlDoc.LoadHtml(response.Data);
|
||||
|
||||
// Identify type of event schedule on page.
|
||||
HtmlNode mobiscrollEventCalendarNode = htmlDoc.GetElementbyId("mobiscroll-event-calendar");
|
||||
if (mobiscrollEventCalendarNode != null)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (eventsData.Any())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} {ExceptionUtil.GetLogMessage(exc, "querying organisation events", 8)}");
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Organisation event lookup by MobiScroll calendar.
|
||||
/// </summary>
|
||||
private bool LookupOrganisationEvents_MobiScrollCalendar(SaxOrganisation organisation, List<EventData> eventsData, HtmlNode calendarNode)
|
||||
{
|
||||
|
||||
HtmlNode headerNode = calendarNode.Descendants("div").First(node => node.HasClass("mbsc-calendar-header"));
|
||||
if (headerNode != null)
|
||||
{
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
}
|
||||
//======================================================================
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Proc.Excel;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Procs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Procs
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxProcess_Web : SaxProcess_Base
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
public new class Config : SaxProcess_Base.Config
|
||||
{
|
||||
protected Config(SaxSessionContext sessionContext, DbUpdateInfo dbUpdateInfo) : base(sessionContext, dbUpdateInfo)
|
||||
{ }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Config config;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
protected SaxProcess_Web(Config config) : base(config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Excel Spreadsheet utilities.
|
||||
//-------------------------------------------------------------------
|
||||
protected override SaMsExcelColumnHeading_Config GetExcelColumnHeadingConfig()
|
||||
{
|
||||
SaMsExcelColumnHeading_Config configHeading = base.GetExcelColumnHeadingConfig();
|
||||
return configHeading;
|
||||
}
|
||||
|
||||
protected override SaMsExcelStyleAttr GetExcelDefaultWorkbookStyle()
|
||||
{
|
||||
SaMsExcelStyleAttr styleAttr = base.GetExcelDefaultWorkbookStyle();
|
||||
return styleAttr;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>Saxonite.Net.Core.Web</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\Common-VS\Workspace\SacsCoreWeb\SacsCoreWeb.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\Common-VS\Workspace\SacsStandardBase\SacsStandardBase.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\Common-VS\Workspace\SacsStandardDbas\SacsStandardDbas.csproj" />
|
||||
<ProjectReference Include="..\SaxoniteStandardBase\SaxoniteStandardBase.csproj" />
|
||||
<ProjectReference Include="..\SaxoniteStandardDbas\SaxoniteStandardDbas.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Base\Core\" />
|
||||
<Folder Include="Resources\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.13.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.13" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\**\*.*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user