Activity index, Db schema validation.
This commit is contained in:
2021-05-31 20:27:40 +01:00
parent 63473c725b
commit 7b0a710b13
36 changed files with 1008 additions and 208 deletions
@@ -159,6 +159,9 @@
<data name="dialog.title.signout" xml:space="preserve">
<value>Sign-out</value>
</data>
<data name="home.caption.activities" xml:space="preserve">
<value>Activities</value>
</data>
<data name="home.caption.application" xml:space="preserve">
<value>Online</value>
</data>
@@ -216,6 +219,9 @@
<data name="label.delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="label.effective" xml:space="preserve">
<value>Effective</value>
</data>
<data name="label.email.address" xml:space="preserve">
<value>Email address</value>
</data>
@@ -1,11 +1,15 @@
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 Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.App
@@ -32,6 +36,54 @@ namespace Saxonite.Net.Core.Web.Online.App
/// <summary>User - JSON deserialisation route.</summary>
public new UserData User { get { return base.User; } set { SetUser(value); } }
//-------------------------------------------------------------------
// User Profile.
// Protected constructors & setters for JSON deserialisation.
public class UserProfileData
{
public UserProfileData() { }
// Sports.
public class SportData
{
public SportData(SysId sportId, SaStatus status)
{
SportId = sportId;
Status = status;
}
public SysId SportId { get; protected set; }
public SaStatus Status { get; protected set; }
}
private class SportData_Online : SportData
{
// Internal class to allow values to be updated.
public SportData_Online(SysId sportId, SaStatus status) : base(sportId, status) { }
public SportData_Online(SportData sportData) : base(sportData.SportId, sportData.Status) { }
public void SetSportId(SysId sportId) { SportId = sportId; }
public void SetStatus(SaStatus status) { Status = status; }
}
[JsonIgnore]
private readonly List<SportData_Online> sports = new List<SportData_Online>();
[JsonIgnore]
public int SportCount { get { return sports.Count; } }
public List<SportData> Sports { get { return new List<SportData>(sports); } protected set { sports.Clear(); sports.AddRange(value.Select(item => new SportData_Online(item))); } }
public void AddSport(SysId sportId, SaStatus status)
{
if (!SysId.IsValid(sportId)) throw new NotSupportedException("Session Profile Sport must have a valid Id");
SportData_Online sport = this.sports.Find(item => item.SportId == sportId);
if (sport != null)
{
sport.SetStatus(status);
}
else
this.sports.Add(new SportData_Online(sportId, status));
}
public void ClearSports()
{
this.sports.Clear();
}
}
public UserProfileData Profile { get; protected set; } = new UserProfileData();
//-------------------------------------------------------------------
// Filter properties.
public class FilterData
@@ -69,6 +121,25 @@ namespace Saxonite.Net.Core.Web.Online.App
}
public FilterData Filter { get; private set; } = new FilterData();
//-------------------------------------------------------------------
// JSON Deserialisation.
[JsonExtensionData]
#pragma warning disable IDE0044 // Add readonly modifier
private IDictionary<string, JToken> _additionalData;
#pragma warning restore IDE0044 // Add readonly modifier
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (_additionalData != null)
{
foreach (String name in _additionalData.Keys)
{
JToken value = _additionalData[name];
SaLogger.Info("OnDeserialized Name: " + name + ". Value: " + value.ToString());
}
}
}
//-------------------------------------------------------------------
}
//=======================================================================
@@ -5,6 +5,7 @@ using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Online.App;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
@@ -25,26 +26,19 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
// Construction.
public ActivityController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{}
{ }
//-------------------------------------------------------------------
// Data definitions.
public class ActivityResponseData
{
public short ItemType { get; set; }
// NB Need to mask the Item Id by storing with XRef to alternative generated value in User's session.
public int ItemIdx { get; set; }
public String Caption{ get; set; }
}
public class ArticleActivityResponseData : ActivityResponseData
{
public String Headline { get; set; }
public String Summary { get; set; }
}
public class OpportunityActivityResponseData : ActivityResponseData
{
public String Position { get; set; }
public String Outline { get; set; }
public long ItemIdx { get; set; }
public String Caption { get; set; }
public String Blurb { get; set; }
public String Sport { get; set; }
public String Region { get; set; }
public String PublishedOnFmt{ get; set; }
}
//-------------------------------------------------------------------
@@ -52,7 +46,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
//-------------------------------------------------------------------
public class AjaxResponse_ActivityList : AjaxResponse_Base
{
public List<SaxModel_Online.RegionData> Regions { get; } = new List<SaxModel_Online.RegionData>();
public List<ActivityResponseData> Activities { get; } = new List<ActivityResponseData>();
}
//-------------------------------------------------------------------
[HttpPost]
@@ -70,13 +64,34 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
return Forbid();
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
SaxSessionData_Online sessionData = SessionData_Online;
// User context.
SaxActivityConsole.IndexContext indexContext = new SaxActivityConsole.IndexContext();
// Regions.
if (SysId.IsValid(sessionData.User.RegionIdProvince))
indexContext.RegionIds.Add(sessionData.User.RegionIdProvince);
else if (SysId.IsValid(sessionData.User.RegionIdCountry))
indexContext.RegionIds.Add(sessionData.User.RegionIdCountry);
// Sports.
indexContext.SportIds.AddRange(sessionData.Profile.Sports.Select(item => item.SportId));
// Populate activities.
SaxActivityConsole.IndexContext indexContext = new SaxActivityConsole.IndexContext();
SaxActivityConsole.IndexResults indexResults = ActivityConsole.QueryIndex(indexContext);
foreach (SaxActivityConsole.IndexResults.ItemData itemData in indexResults.Items)
foreach (SaxActivityConsole.IndexResults.ActivityData itemData in indexResults.Items)
{
ActivityResponseData activityData = new ActivityResponseData();
activityData.ItemType = itemData.ItemId.Type;
// ToDo Mask activity index Id with cross-reference stored in session data.
activityData.ItemIdx = SysId.ToLong(itemData.ItemId.Id);
activityData.Caption = itemData.Caption;
activityData.Blurb = itemData.Blurb;
activityData.Sport = Localiser.GetSportName(itemData.SportId, itemData.SportName);
activityData.Region = Localiser.GetRegionName(itemData.RegionId, itemData.RegionName);
activityData.PublishedOnFmt = Localiser.FormatDate(itemData.EffectiveOn);
responseData.Activities.Add(activityData);
}
// Complete response.
@@ -2,6 +2,7 @@
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.Utils;
using Saxonite.Net.Core.Web.Base.App;
@@ -86,6 +87,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
// Create session.
OpenUserSession(SessionContext, userAccount, userRoles);
RefreshSessionProfileSports();
// Send the 'Keep Logged In' cookie if requested.
if (userAccount.KeepLoggedIn_Online)
@@ -94,6 +96,39 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
return true;
}
//-------------------------------------------------------------------
/// <summary>Populate the user's profile sports to the session.</summary>
protected bool RefreshSessionProfileSports()
{
String fn = GetType().Name + ".RefreshSessionProfileSports";
SaxSessionData_Online sessionData = SessionData_Online;
SysId userAccountId = sessionData?.User.UserAccountId ?? null;
if (!SysId.IsValid(userAccountId)) return false;
sessionData.Profile.ClearSports();
// Read discrete values to avoid loading the entire UserSport records which can be substantial.
ColumnDfn colSportId = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.SportId);
ColumnDfn colSportName = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.Sport_Name);
ColumnDfn colStatus = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.Status);
SaxUserSport.Params prmUserSport = new SaxUserSport.Params();
prmUserSport.UserAccountIdsIncluded.Add(userAccountId);
prmUserSport.StatusCat = SaxUserSport.StatusCat.ActiveOnly;
using (DbResultSet resultSet = AccountMgr.ExecuteUserSportQuery(new List<ColumnDfn>() { colSportId, colSportName, colStatus }, prmUserSport))
{
while (resultSet != null && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId sportId = new SysId(values.GetLong(colSportId.DataName));
String sportName = values.GetString(colSportName.DataName);
SaStatus status = SacsSystem.GetStatusForCode(values.GetString(colStatus.DataName));
sessionData.Profile.AddSport(sportId, status);
}
}
SaLogger.Debug(fn+" Profile Sports: " + sessionData.Profile.SportCount);
return true;
}
//-------------------------------------------------------------------
/// <summary>Start a new user session on sign-in.</summary>
protected bool OpenUserSession(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles)
@@ -56,9 +56,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
String errorMessage = "";
if (!SignUserIn(userAccount, AccountMgr.GetRolesForUser(userAccount.Id), model.KeepMeSignedIn, ref errorMessage))
{
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
}
// Construct response.
responseData.Success = SessionData_Online?.ValidUserSession ?? false;
@@ -15,6 +15,8 @@ namespace Saxonite.Net.Core.Web.Online.Models
public Model_Home(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_CSS.Add("css/sax-home.css");
AdditionalFiles_JS.Add("js/sax-home.js");
}
//-------------------------------------------------------------------
+52 -3
View File
@@ -4,6 +4,55 @@
ViewData["Title"] = "Home";
}
<div class="text-center">
<h1 class="display-4">@Localiser["home.caption.welcome"]</h1>
</div>
@if (Model.AwaitingLogin)
{
<div class="text-center">
<h1 class="display-4">@Localiser["home.caption.welcome"]</h1>
</div>
}
else
{
<div class="sax-panel-filter">
<div class="sax-header row">
<div class="col"><div class="sax-caption">@Localiser["home.caption.activities"]</div></div>
<div class="fixed row">
</div>
</div>
<div class="sax-content">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmActivityFilter", @class = "sax-form", @action = "#", @method = "POST", onsubmit = "return false;" }))
{
<div class="row">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.search"]</div></div>
<div class="col-sm-8"><input type="search" name="Filter.Activity" id="ifActivityFilter" class="ActivityFilter sax-input" maxlength="64" /></div>
</div>
}
</div>
</div>
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Activity">
<div class="sax-header row">
<div class="col">
<div class="sax-caption"><div class="sax-action ActivityCaption"></div></div>
</div>
<div class="fixed sax-actions">
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-12"><div class="ActivityBlurb"></div></div>
<div class="col-sm-6">
<div class="sax-note">
<span class="ActivitySport"></span> - <span class="ActivityRegion"></span>
</div>
</div>
<div class="col-sm-6 right"><div class="sax-note ActivityPublished">@Localiser["label.published"]:&nbsp;<span class="ActivityPublishedOn"></span></div></div>
</div>
</div>
</div>
</div>
</div>
}
+10 -5
View File
@@ -122,29 +122,29 @@
border-bottom: solid 1px #979797;
}
.sax-panel-item .sax-caption
.sax-panel-item > .sax-header .sax-caption
{
margin-top: 4px;
font-size: 1.2rem;
font-weight: bold;
}
.sax-panel-item .sax-header .sax-action
.sax-panel-item > .sax-header .sax-action
{
border-radius: 4px;
}
.sax-panel-item .sax-header .sax-caption .sax-action:hover
.sax-panel-item > .sax-header .sax-caption .sax-action:hover
{
color: var(--color-primary);
}
.sax-panel-item .sax-header .sax-actions .sax-action
.sax-panel-item > .sax-header .sax-actions .sax-action
{
margin: 2px 0 0 8px;
}
.sax-panel-item .sax-header .sax-actions .sax-action:hover
.sax-panel-item > .sax-header .sax-actions .sax-action:hover
{
background-color: #c0c0c0;
box-shadow: 0 0 4px 2px #c0c0c080;
@@ -156,6 +156,11 @@
padding: 4px 4px;
}
.sax-panel-item .sax-note
{
font-size: 0.8rem;
}
/* Profile Sports */
.sax-panel-item.ProfileSport,
@@ -0,0 +1,6 @@
/*
Home stylesheet.
*/
/* The End */
@@ -0,0 +1,70 @@
/*
* Home scripts.
*/
(function (win, doc, undef)
{
"use strict";
//--------------------------------------------------------------
sax.refreshResults = function (delay)
{
var $results = $('.sax-panel-results');
if (!sax.awaitingRefreshResponse)
{
var $items = $('.sax-panel-item-list', $results).fadeOut(200).empty();
var $msgs = $('.sax-panel-messages', $results).hide().empty().append($('<div class="sax-message">' + sax.msg.dataQuerying + '</div>')).fadeIn(200);
}
sax.ajaxRefreshResults = sax.ajaxRefreshResults || { url: sax.url.base + 'Activity/Ajax/GetList', formId: 'frmActivityFilter', callbackFn: sax.doRefreshResults };
sax.doAjaxRequest(sax.ajaxRefreshResults, delay || 50);
sax.awaitingRefreshResponse = true;
}
sax.doRefreshResults = function (response)
{
sax.debug('doRefreshResults', response);
delete sax.awaitingRefreshResponse;
var $results = $('.sax-panel-results');
var $msgs = $('.sax-panel-messages', $results).hide().empty();
var $items = $('.sax-panel-item-list', $results).hide().empty();
var $tmpl = $('.sax-panel-item-template .sax-panel-item', $results);
if (!response.success) return;
var items = response.activities;
if (!items.length)
return $msgs.append($('<div class="sax-message">' + sax.msg.dataNoneFound + '</div>')).fadeIn(500);
for (let itemIdx = 0; itemIdx < items.length; itemIdx++)
{
let item = items[itemIdx];
let $item = $tmpl.clone();
sax.populateItem($item, item);
$items.append($item);
}
$items.fadeIn(50);
}
sax.populateItem = function ($item, item)
{
if (!item) return;
$item.prop('id', sax.itemIdPrefix + item.id);
$('.ActivityCaption', $item).text(item.caption);
$('.ActivityBlurb', $item).text(item.blurb);
$('.ActivitySport', $item).text(item.sport);
$('.ActivityRegion', $item).text(item.region);
$('.ActivityPublishedOn', $item).text(item.publishedOnFmt);
$item.off('click').on('click', function () { sax.expandItem(item.id) })
$('.sax-panel-results .sax-panel-messages').hide().empty();
}
//--------------------------------------------------------------
sax.expandItem = function (id)
{
sax.debug('expandItem(' + id + ')');
}
//--------------------------------------------------------------
$(function ()
{
setTimeout(sax.refreshResults, 250);
});
}(window, window.document));
/* The End */
@@ -49,7 +49,6 @@
sax.populateItem($item, item);
$items.append($item);
}
$('.sax-action.sax-item-update', $items).toggle(sax.data.userCanManageOpportunities);
$items.fadeIn(50);
}
sax.populateItem = function ($item, item)
@@ -67,7 +66,8 @@
$('.OpportunityExpiresOn', $item).text(item.expiresOnFmt);
$('.OpportunityExpires', $item).toggle(!!item.expiresOnFmt);
$('.OpportunityStatusDesc', $item).text(item.statusDesc);
$('.sax-caption .sax-action, .sax-actions .sax-action.sax-item-update', $item).off('click').on('click', function () { sax.updateItem(item.id) });
if (sax.data.userCanManageOpportunities)
$('.sax-caption .sax-action, .sax-actions .sax-action.sax-item-update', $item).off('click').on('click', function () { sax.updateItem(item.id) }).removeClass('hidden');
$item.off('dblclick').on('dblclick', function () { sax.updateItem(item.id) });
$('.sax-panel-results .sax-panel-messages').hide().empty();
}