Files
andy 9f22b522df 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>
2026-10-16 18:24:16 +01:00

524 lines
24 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Saxonite.Net.Standard.Base.App;
using MailKit;
using Org.BouncyCastle.Asn1.Mozilla;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Account Controller - User Eligibility - Ajax actions.
//=======================================================================
partial class AccountController
{
//-------------------------------------------------------------------
// Data definitions.
private class UserEligibilityResponseListData : AjaxResponse_Data.ItemData_Base
{
public String UserAccountId { get; set; }
public String RegionName { get; set; }
public String EligibilityTypeDesc { get; set; }
public String Info { get; set; }
public String ExpiresOnFmt { get; set; }
}
private class UserEligibilityResponseItemData : UserEligibilityResponseListData
{
public String RegionId { get; set; }
public String EligibilityType { get; set; }
public String ExpiresOnFrm { get; set; }
}
private void PopulateUserEligibilityResponseListData(SaxModel_Online model, SaxUserEligibility userEligibility, UserEligibilityResponseListData data)
{
data.Id = model.EncryptId(userEligibility.Id);
data.UserAccountId = model.EncryptId(userEligibility.UserAccountId);
data.RegionName = model.GetRegionCaption(userEligibility.RegionId);
data.EligibilityTypeDesc = Localiser.GetEligibilityTypeCaption(userEligibility.EligibilityType);
data.Info = userEligibility.Info;
data.ExpiresOnFmt = model.FormatDateForDisplay(userEligibility.ExpiresOn);
}
private void PopulateUserEligibilityResponseItemData(SaxModel_Online model, SaxUserEligibility userEligibility, UserEligibilityResponseItemData data)
{
PopulateUserEligibilityResponseListData(model, userEligibility, data);
data.RegionId = model.GetEncryptedRegionId(userEligibility.RegionId);
data.EligibilityType = EligibilityTypeCodes.GetCodeForType(userEligibility.EligibilityType);
data.ExpiresOnFrm = model.FormatDateForForm(userEligibility.ExpiresOn);
data.CreatedOnFmt = model.FormatDateForDisplay(userEligibility.CreateStamp.On);
data.StatusCode = userEligibility.StatusCode;
data.StatusDesc = userEligibility.StatusDesc;
}
//-------------------------------------------------------------------
// Account - Get User Eligibility list.
//-------------------------------------------------------------------
private class EligibilityTypeData
{
public String Code { get; set; }
public String Desc { get; set; }
/// <summary>Does the eligibility type allow a date range.</summary>
public bool? Dates { get; set; } = null;
}
private class AjaxResponse_UserEligibilityListData : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserEligibilityListData(SaxModel_Online model) : base(model) { }
// Eligibility list.
public List<UserEligibilityResponseListData> Eligibilities { get; } = new List<UserEligibilityResponseListData>();
// Eligibility types.
public List<EligibilityTypeData> Types { get; } = new List<EligibilityTypeData>();
// User configuration.
public class ConfigData
{
public bool? hideInfo { get; set; } = null;
}
public ConfigData Config { get; } = new ConfigData();
}
// Ajax request.
[HttpPost]
[Route("Account/Ajax/GetUserEligibilities")]
[Route("Account/Ajax/GetUserEligibilityList")]
public IActionResult GetUserEligibilities(Model_UserEligibility model)
{
String fn = GetType().Name + ".GetUserEligibilities";
AjaxResponse_UserEligibilityListData responseData = new AjaxResponse_UserEligibilityListData(model);
String messageTitle = Localiser["account.caption.profile.eligibility"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Eligibility types.
foreach (Sax.EligibilityType eligibilityType in EnumUtil.GetValues<Sax.EligibilityType>())
{
EligibilityTypeData typeData = new EligibilityTypeData();
typeData.Code = model.EligibilityTypeCodes.GetCodeForType(eligibilityType);
if (StringUtil.IsBlank(typeData.Code)) continue;
typeData.Desc = Localiser.GetEligibilityTypeCaption(eligibilityType);
if (SaxUserEligibility.AllowsApplicablePeriod(eligibilityType))
typeData.Dates = true;
responseData.Types.Add(typeData);
}
// User account.
SysId userAccountId = SysId.ToSysId(model.UserAccount.Id);
if (!SysId.IsValid(userAccountId))
userAccountId = SessionUserAccountId;
// User configuration.
if (SessionData_Online.UserConfig.Profile.Eligibility.HideInfo)
responseData.Config.hideInfo = true;
// Item filter.
SaxUserEligibility.Params prmUserEligibility = new SaxUserEligibility.Params();
prmUserEligibility.UserAccountIdsIncluded.Add(userAccountId);
prmUserEligibility.StatusCat = SaxUserEligibility.StatusCat.NotDeleted;
prmUserEligibility.SortColumns.Add(AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.EligibilityType));
model.Paging.ItemTotal = (ulong)AccountMgr.GetUserEligibilityCount(prmUserEligibility);
model.PopulatePagingTo(prmUserEligibility.ResultsPaging);
// Item columns.
ColumnDfn colId = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.Id);
ColumnDfn colUserAccountId = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.UserAccountId);
ColumnDfn colRegionName = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.Region_Name);
ColumnDfn colEligibilityType = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.EligibilityType);
ColumnDfn colInfo = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.Info);
ColumnDfn colExpires = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.ExpiresOn);
ColumnDfn colStatus = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId,colUserAccountId,
colRegionName, colEligibilityType,
colInfo, colExpires,
colStatus,
};
// Sort columns.
prmUserEligibility.SortColumns.Add(colRegionName);
// Query items.
using (DbResultSet resultSet = AccountMgr.ExecuteUserEligibilityQuery(columns, prmUserEligibility))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
Sax.EligibilityType eligibilityType = (Sax.EligibilityType)values.GetLong(colEligibilityType.DataName);
UserEligibilityResponseListData userEligibilityData = new UserEligibilityResponseListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
UserAccountId = model.EncryptId(new SysId(values.GetLong(colUserAccountId.DataName))),
RegionName = values.GetString(colRegionName.DataName),
EligibilityTypeDesc = Localiser.GetEligibilityTypeCaption(eligibilityType),
Info = values.GetString(colInfo.DataName),
ExpiresOnFmt = model.FormatDateForDisplay(values.GetDate(colExpires.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
responseData.Eligibilities.Add(userEligibilityData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Eligibilities.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get User Eligibility ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_UserEligibility : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserEligibility(SaxModel_Online model) : base(model) { }
// Eligibility details.
public UserEligibilityResponseItemData Eligibility { get; } = new UserEligibilityResponseItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/Ajax/GetUserEligibility")]
public IActionResult Ajax_GetUserEligibility(Model_UserEligibility model)
{
String fn = GetType().Name + ".Ajax_GetUserEligibility";
String messageTitle = Localiser["account.caption.profile.eligibility"];
AjaxResponse_UserEligibility responseData = new AjaxResponse_UserEligibility(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Identify item.
SysId userEligibilityId = model.ExtractSysId(model.Eligibility.Id);
if (!SysId.IsValid(userEligibilityId))
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxUserEligibility userEligibility = AccountMgr.GetUserEligibilityById(userEligibilityId);
disposableItems.Add(userEligibility);
if (userEligibility == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Eligibility for Id: '" + model.Eligibility.Id + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateUserEligibilityResponseItemData(model, userEligibility, responseData.Eligibility);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Store User Eligibility ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_StoreUserEligibility : AjaxResponse_Data
{
// Construction.
public AjaxResponse_StoreUserEligibility(SaxModel_Online model) : base(model) { }
// Eligibility details.
public UserEligibilityResponseItemData Eligibility { get; } = new UserEligibilityResponseItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/Ajax/StoreUserEligibility")]
public IActionResult Ajax_StoreUserEligibility(Model_UserEligibility model)
{
String fn = GetType().Name + ".Ajax_StoreUserEligibility";
String messageTitle = Localiser["account.caption.profile.eligibility"];
AjaxResponse_StoreUserEligibility responseData = new AjaxResponse_StoreUserEligibility(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate item properties.
model.ValidateEligibilityDetails(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// User account.
SysId userAccountId = SysId.ToSysId(model.UserAccount.Id);
if (!SysId.IsValid(userAccountId))
userAccountId = SessionUserAccountId;
// Validate User Eligibility Id against 'Exists' flag.
SysId userEligibilityId = model.ExtractSysId(model.Eligibility.Id);
bool eligibilityExists = StringUtil.ToBool(model.Eligibility.Exists);
if (eligibilityExists && !SysId.IsValid(userEligibilityId))
{
SaLogger.Warn(fn + " Id not specified for existing User Eligibility.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
if (!eligibilityExists && SysId.IsValid(userEligibilityId))
{
SaLogger.Warn(fn + " Id specified for new User Eligibility: '" + model.Eligibility.Id + "'");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve existing item.
SaxUserEligibility userEligibility = AccountMgr.GetUserEligibilityById(userEligibilityId);
disposableItems.Add(userEligibility);
if (userEligibility == null && SysId.IsValid(userEligibilityId))
{
SaLogger.Warn(fn + " Failed to retrieve User Eligibility for Id: '" + model.Eligibility.Id + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate user eligibility properties.
if (userEligibility != null)
{
if (!userEligibility.UserAccountId.Equals(userAccountId))
{
SaLogger.Warn(fn + " Inconsistent User Account Id specified for User Eligibility. Expected: " + userEligibility.UserAccountId + ". Found: " + userAccountId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
// Region.
SysId regionId = model.Eligibility.RegionId != null ? model.DecryptId(model.Eligibility.RegionId) : userEligibility != null ? userEligibility.RegionId : null;
if (!SysId.IsValid(regionId))
{
// Shouldn't get here.
SaLogger.Warn(fn + " Region Id not supplied for " + (eligibilityExists ? "existing" : "new") + " eligibility.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
SaxRegion.Data region = Internals.GetRegion(regionId);
// Eligibility type.
Sax.EligibilityType eligibilityType = model.Eligibility.Type != null ? model.Eligibility.getEligibilityType() : userEligibility != null ? userEligibility.EligibilityType : 0;
if (eligibilityType <= 0)
{
// Shouldn't get here.
SaLogger.Warn(fn + " Eligibility type not specified for " + (eligibilityExists ? "existing" : "new") + " eligibility.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Check for duplicate data.
// ToDo Include date range in checking for duplicate eligibility?
{
// First check without region.
SaxUserEligibility.Params prmUserEligibility = new SaxUserEligibility.Params();
if (userEligibility != null)
prmUserEligibility.IdsExcluded.Add(userEligibility.Id);
prmUserEligibility.UserAccountIdsIncluded.Add(userAccountId);
prmUserEligibility.EligibilityTypesIncluded.Add(eligibilityType);
prmUserEligibility.StatusCat = SaxUserEligibility.StatusCat.NotDeleted;
long eligibilityCount = AccountMgr.GetUserEligibilityCount(prmUserEligibility);
// Selected Eligibility types have a limit on the number of instances allowed.
if (eligibilityCount >= SaxUserEligibility.GetMaxInstances(eligibilityType))
{
SaLogger.Warn(fn + " Eligibility type instance limit reached. Type: " + eligibilityType.ToString() + ". Instances: " + eligibilityCount + ". " + GetLogSuffix());
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.duplicate"]));
}
// Now include region - only allow one eligibility of each type.
prmUserEligibility.RegionIdsIncluded.Add(regionId);
eligibilityCount = AccountMgr.GetUserEligibilityCount(prmUserEligibility);
if (eligibilityCount >= 1)
{
SaLogger.Warn(fn + " Duplicate Eligibility exists. RegionId: " + SysId.ToString(regionId) + ". Type: " + eligibilityType.ToString() + ". " + GetLogSuffix());
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.duplicate"]));
}
}
// If region or eligibility have changed, check whether an equivalent deleted eligibility exists.
// NB Can't modify the current eligibility with the modified values as it would conflict with the
// deleted item - unique on user, region and eligibility.
if (userEligibility != null && (!userEligibility.RegionId.Equals(regionId) || userEligibility.EligibilityType != eligibilityType))
{
SaxUserEligibility.Params prmUserEligibility = new SaxUserEligibility.Params();
prmUserEligibility.IdsExcluded.Add(userEligibility.Id);
prmUserEligibility.UserAccountIdsIncluded.Add(userAccountId);
prmUserEligibility.RegionIdsIncluded.Add(regionId);
prmUserEligibility.EligibilityTypesIncluded.Add(eligibilityType);
prmUserEligibility.StatusCat = SaxUserEligibility.StatusCat.DeletedOnly;
SaxUserEligibility userEligibilityDeleted = AccountMgr.GetUserEligibility(prmUserEligibility);
if (userEligibilityDeleted != null)
{
disposableItems.Add(userEligibilityDeleted);
SaLogger.Info(fn + " Resurrecting User Eligibility with status: " + userEligibilityDeleted.StatusDesc + ". Id: " + SysId.ToString(userEligibilityDeleted.Id) + ". " + GetLogSuffix());
userEligibilityDeleted.Status = SaxUserEligibility.DefaultStatus;
// Delete current item and replace with previously deleted item.
if (!AccountMgr.DeleteUserEligibility(userEligibility, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to delete user eligibility: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
userEligibility = userEligibilityDeleted;
}
}
// If eligibility doesn't exist for the user, it may have been previously deleted.
if (userEligibility == null)
{
// See if there is an existing item that matches user, region and eligibility.
SaxUserEligibility.Params prmUserEligibility = new SaxUserEligibility.Params();
prmUserEligibility.UserAccountIdsIncluded.Add(userAccountId);
prmUserEligibility.RegionIdsIncluded.Add(regionId);
prmUserEligibility.EligibilityTypesIncluded.Add(eligibilityType);
prmUserEligibility.StatusCat = SaxUserEligibility.StatusCat.DeletedOnly;
userEligibility = AccountMgr.GetUserEligibility(prmUserEligibility);
if (userEligibility == null)
{
// Try again for the user & region without the eligibility type.
prmUserEligibility.EligibilityTypesIncluded.Clear();
userEligibility = AccountMgr.GetUserEligibility(prmUserEligibility);
}
if (userEligibility == null)
{
// Try again for the user & eligibility type without the region.
prmUserEligibility.RegionIdsIncluded.Clear();
prmUserEligibility.EligibilityTypesIncluded.Add(eligibilityType);
userEligibility = AccountMgr.GetUserEligibility(prmUserEligibility);
}
if (userEligibility == null)
{
// Try again just for the user.
prmUserEligibility.RegionIdsIncluded.Clear();
prmUserEligibility.EligibilityTypesIncluded.Clear();
userEligibility = AccountMgr.GetUserEligibility(prmUserEligibility);
}
if (userEligibility != null)
{
disposableItems.Add(userEligibility);
SaLogger.Info(fn + " Resurrecting User Eligibility with status: " + userEligibility.StatusDesc + ". Id: " + SysId.ToString(userEligibility.Id) + ". " + GetLogSuffix());
userEligibility.Status = SaxUserEligibility.DefaultStatus;
}
else
{
userEligibility = AccountMgr.CreateUserEligibility(userAccountId, regionId, eligibilityType);
disposableItems.Add(userEligibility);
}
}
// Populate item properties.
userEligibility.RegionId = regionId;
userEligibility.EligibilityType = eligibilityType;
if (model.Eligibility.Info != null)
userEligibility.Info = model.Eligibility.Info;
if (model.Eligibility.ExpiresOn != null)
userEligibility.ExpiresOn = model.ParseDateFromForm(model.Eligibility.ExpiresOn);
// Store item.
if (!SysId.IsValid(userEligibility.Id))
{
if (!AccountMgr.InsertUserEligibility(userEligibility, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert user eligibility: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (!AccountMgr.UpdateUserEligibility(userEligibility, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update user eligibility [Id:" + SysId.ToString(userEligibility.Id) + "]: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
}
// Construct response.
PopulateUserEligibilityResponseItemData(model, userEligibility, responseData.Eligibility);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Remove User Eligibility ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_RemoveUserEligibility : AjaxResponse_Data
{
// Construction.
public AjaxResponse_RemoveUserEligibility(SaxModel_Online model) : base(model) { }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/Ajax/RemoveUserEligibility")]
public IActionResult Ajax_RemoveUserEligibility(Model_UserEligibility model)
{
String fn = GetType().Name + ".Ajax_RemoveUserEligibility";
String messageTitle = Localiser["account.caption.profile.eligibility"];
AjaxResponse_UserEligibility responseData = new AjaxResponse_UserEligibility(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// User account.
SysId userAccountId = SysId.ToSysId(model.UserAccount.Id);
if (!SysId.IsValid(userAccountId))
userAccountId = SessionUserAccountId;
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// User Eligibility Id.
SysId userEligibilityId = model.ExtractSysId(model.Eligibility.Id);
if (!SysId.IsValid(userEligibilityId))
{
SaLogger.Warn(fn + " Id not specified to remove User Eligibility.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve existing item.
SaxUserEligibility userEligibility = AccountMgr.GetUserEligibilityById(userEligibilityId);
disposableItems.Add(userEligibility);
if (userEligibility == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Eligibility for Id: '" + model.Eligibility.Id + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate user eligibility properties.
if (!userEligibility.UserAccountId.Equals(userAccountId))
{
SaLogger.Warn(fn + " Inconsistent User Account Id specified for User Eligibility. Expected: " + userEligibility.UserAccountId + ". Found: " + userAccountId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Delete item.
if (!AccountMgr.DeleteUserEligibility(userEligibility, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to delete user eligibility [Id:" + SysId.ToString(userEligibility.Id) + "]: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
// Construct response.
PopulateUserEligibilityResponseItemData(model, userEligibility, responseData.Eligibility);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}