From 9f18c460d8e221770ed0b59a1a2255b99827b656 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 13 May 2025 09:24:17 -0400 Subject: [PATCH] add time zone support for sites and users --- .../OqtaneServiceCollectionExtensions.cs | 1 + .../Modules/Admin/Register/Index.razor | 18 ++ Oqtane.Client/Modules/Admin/Site/Index.razor | 26 ++- .../Modules/Admin/UserProfile/Index.razor | 187 ++++++++++-------- Oqtane.Client/Modules/Admin/Users/Add.razor | 50 +++-- Oqtane.Client/Modules/Admin/Users/Edit.razor | 154 ++++++++------- .../Modules/Admin/Register/Index.resx | 6 + .../Resources/Modules/Admin/Site/Index.resx | 6 + .../Modules/Admin/UserProfile/Index.resx | 6 + .../Resources/Modules/Admin/Users/Add.resx | 6 + .../Resources/Modules/Admin/Users/Edit.resx | 6 + .../Services/Interfaces/ITimeZoneService.cs | 18 ++ Oqtane.Client/Services/TimeZoneService.cs | 22 +++ .../Controllers/TimeZoneController.cs | 29 +++ Oqtane.Server/Controllers/UserController.cs | 1 + .../OqtaneServiceCollectionExtensions.cs | 1 + .../Migrations/Tenant/06010301_AddTimeZone.cs | 31 +++ Oqtane.Shared/Models/Site.cs | 6 + Oqtane.Shared/Models/TimeZone.cs | 10 + Oqtane.Shared/Models/User.cs | 5 + 20 files changed, 417 insertions(+), 172 deletions(-) create mode 100644 Oqtane.Client/Services/Interfaces/ITimeZoneService.cs create mode 100644 Oqtane.Client/Services/TimeZoneService.cs create mode 100644 Oqtane.Server/Controllers/TimeZoneController.cs create mode 100644 Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs create mode 100644 Oqtane.Shared/Models/TimeZone.cs diff --git a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs index c5590d51..9a89bce8 100644 --- a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs @@ -54,6 +54,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // providers services.AddScoped(); diff --git a/Oqtane.Client/Modules/Admin/Register/Index.razor b/Oqtane.Client/Modules/Admin/Register/Index.razor index 88ccdd56..13920441 100644 --- a/Oqtane.Client/Modules/Admin/Register/Index.razor +++ b/Oqtane.Client/Modules/Admin/Register/Index.razor @@ -3,6 +3,7 @@ @inherits ModuleBase @inject NavigationManager NavigationManager @inject IUserService UserService +@inject ITimeZoneService TimeZoneService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @inject ISettingService SettingService @@ -56,6 +57,18 @@ +
+ +
+ +
+

@@ -77,6 +90,7 @@ else } @code { + private List _timezones; private string _passwordrequirements; private string _username = string.Empty; private ElementReference form; @@ -87,6 +101,7 @@ else private string _confirm = string.Empty; private string _email = string.Empty; private string _displayname = string.Empty; + private string _timezoneid = string.Empty; private bool _userCreated = false; private bool _allowsitelogin = true; @@ -96,6 +111,8 @@ else { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true")); + _timezones = await TimeZoneService.GetTimeZonesAsync(); + _timezoneid = PageState.Site.TimeZoneId; } protected override void OnParametersSet() @@ -124,6 +141,7 @@ else Password = _password, Email = _email, DisplayName = (_displayname == string.Empty ? _username : _displayname), + TimeZoneId = _timezoneid, PhotoFileId = null }; user = await UserService.AddUserAsync(user); diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 5333cd3b..172c4225 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -10,6 +10,7 @@ @inject IAliasService AliasService @inject IThemeService ThemeService @inject ISettingService SettingService +@inject ITimeZoneService TimeZoneService @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @inject INotificationService NotificationService @@ -41,6 +42,18 @@ +
+ +
+ +
+
@@ -133,7 +146,7 @@
- +
@@ -416,9 +429,11 @@ private List _themes = new List(); private List _containers = new List(); private List _pages; + private List _timezones; private string _name = string.Empty; private string _homepageid = "-"; + private string _timezoneid = string.Empty; private string _isdeleted; private string _sitemap = ""; private string _siteguid = ""; @@ -435,7 +450,7 @@ private Dictionary _textEditors = new Dictionary(); private string _textEditor = ""; - private string _imageFiles = string.Empty; + private string _imagefiles = string.Empty; private string _headcontent = string.Empty; private string _bodycontent = string.Empty; @@ -493,11 +508,13 @@ Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null) { + _timezones = await TimeZoneService.GetTimeZonesAsync(); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); _name = site.Name; + _timezoneid = site.TimeZoneId; if (site.HomePageId != null) { _homepageid = site.HomePageId.Value.ToString(); @@ -531,8 +548,8 @@ _textEditors.Add(textEditor.Name, Utilities.GetFullTypeName(textEditor.GetType().AssemblyQualifiedName)); } _textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor); - _imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles); - _imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles; + _imagefiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles); + _imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles; // page content _headcontent = site.HeadContent; @@ -650,6 +667,7 @@ if (site != null) { site.Name = _name; + site.TimeZoneId = _timezoneid; site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null); site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index 63486fc7..2517c538 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -9,6 +9,7 @@ @inject INotificationService NotificationService @inject IFileService FileService @inject IFolderService FolderService +@inject ITimeZoneService TimeZoneService @inject IJSRuntime jsRuntime @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @@ -16,9 +17,9 @@ @if (_initialized) { - @if (PageState.User != null && photo != null) + @if (PageState.User != null && _photo != null) { - @displayname + @_displayname } else { @@ -31,7 +32,7 @@
- +
@@ -47,17 +48,17 @@
- +
- @if (allowtwofactor) + @if (_allowtwofactor) {
- @@ -67,19 +68,31 @@
- +
- +
- +
- + +
+
+
+ +
+
@@ -91,17 +104,17 @@
- @foreach (Profile profile in profiles) + @foreach (Profile profile in _profiles) { var p = profile; if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { - if (p.Category != category) + if (p.Category != _category) {
@p.Category
- category = p.Category; + _category = p.Category; }
@@ -150,12 +163,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } } else @@ -163,12 +176,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } } } @@ -179,12 +192,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } } else @@ -192,12 +205,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } } } @@ -220,11 +233,11 @@
- @if (filter == "to") + @if (_filter == "to") { - @if (notifications.Any()) + @if (_notifications.Any()) { - +
    @@ -260,15 +273,15 @@ context.Body = context.Body.Replace("\n", ""); context.Body = context.Body.Replace("\r", ""); } - notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; + _notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; } @if (context.IsRead) { - @notificationSummary + @_notificationSummary } else { - @notificationSummary + @_notificationSummary } @@ -285,9 +298,9 @@ } else { - @if (notifications.Any()) + @if (_notifications.Any()) { - +
@@ -324,15 +337,15 @@ context.Body = context.Body.Replace("\n", ""); context.Body = context.Body.Replace("\r", ""); } - notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; + _notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; } @if (context.IsRead) { - @notificationSummary + @_notificationSummary } else { - @notificationSummary + @_notificationSummary } @@ -354,29 +367,32 @@ } @code { + private List _timezones; private bool _initialized = false; private string _passwordrequirements; - private string username = string.Empty; + private string _username = string.Empty; private string _password = string.Empty; private string _passwordtype = "password"; private string _togglepassword = string.Empty; - private string confirm = string.Empty; - private bool allowtwofactor = false; - private string twofactor = "False"; - private string email = string.Empty; - private string displayname = string.Empty; - private FileManager filemanager; - private int folderid = -1; - private int photofileid = -1; - private File photo = null; - private string _ImageFiles = string.Empty; - private List profiles; - private Dictionary userSettings; - private string category = string.Empty; + private string _confirm = string.Empty; + private bool _allowtwofactor = false; + private string _twofactor = "False"; + private string _email = string.Empty; + private string _displayname = string.Empty; + private FileManager _filemanager; + private int _folderid = -1; + private string _timezoneid = string.Empty; + private int _photofileid = -1; + private File _photo = null; + private string _imagefiles = string.Empty; - private string filter = "to"; - private List notifications; - private string notificationSummary = string.Empty; + private List _profiles; + private Dictionary _userSettings; + private string _category = string.Empty; + + private string _filter = "to"; + private List _notifications; + private string _notificationSummary = string.Empty; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; @@ -386,17 +402,19 @@ { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _togglepassword = SharedLocalizer["ShowPassword"]; - allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true"); - profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); + _allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true"); + _profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); + _timezones = await TimeZoneService.GetTimeZonesAsync(); if (PageState.User != null) { - username = PageState.User.Username; - twofactor = PageState.User.TwoFactorRequired.ToString(); - email = PageState.User.Email; - displayname = PageState.User.DisplayName; + _username = PageState.User.Username; + _twofactor = PageState.User.TwoFactorRequired.ToString(); + _email = PageState.User.Email; + _displayname = PageState.User.DisplayName; + _timezoneid = PageState.User.TimeZoneId; - if (string.IsNullOrEmpty(email)) + if (string.IsNullOrEmpty(_email)) { AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning); } @@ -405,24 +423,24 @@ var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath); if (folder != null) { - folderid = folder.FolderId; + _folderid = folder.FolderId; } if (PageState.User.PhotoFileId != null) { - photofileid = PageState.User.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); + _photofileid = PageState.User.PhotoFileId.Value; + _photo = await FileService.GetFileAsync(_photofileid); } else { - photofileid = -1; - photo = null; + _photofileid = -1; + _photo = null; } - userSettings = PageState.User.Settings; + _userSettings = PageState.User.Settings; var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); - _ImageFiles = SettingService.GetSetting(userSettings, "ImageFiles", Constants.ImageFiles); - _ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles; + _imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles); + _imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles; await LoadNotificationsAsync(); @@ -442,13 +460,13 @@ private async Task LoadNotificationsAsync() { - notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId); - notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList(); + _notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId); + _notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList(); } private string GetProfileValue(string SettingName, string DefaultValue) { - string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue); + string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue); if (value.Contains("]")) { value = value.Substring(value.IndexOf("]") + 1); @@ -460,38 +478,39 @@ { try { - if (username != string.Empty && email != string.Empty) + if (_username != string.Empty && _email != string.Empty) { - if (_password == confirm) + if (_password == _confirm) { if (ValidateProfiles()) { var user = PageState.User; - user.Username = username; + user.Username = _username; user.Password = _password; - user.TwoFactorRequired = bool.Parse(twofactor); - user.Email = email; - user.DisplayName = (displayname == string.Empty ? username : displayname); - user.PhotoFileId = filemanager.GetFileId(); + user.TwoFactorRequired = bool.Parse(_twofactor); + user.Email = _email; + user.DisplayName = (_displayname == string.Empty ? _username : _displayname); + user.TimeZoneId = _timezoneid; + user.PhotoFileId = _filemanager.GetFileId(); if (user.PhotoFileId == -1) { user.PhotoFileId = null; } if (user.PhotoFileId != null) { - photofileid = user.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); + _photofileid = user.PhotoFileId.Value; + _photo = await FileService.GetFileAsync(_photofileid); } else { - photofileid = -1; - photo = null; + _photofileid = -1; + _photo = null; } user = await UserService.UpdateUserAsync(user); if (user != null) { - await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId); + await SettingService.UpdateUserSettingsAsync(_userSettings, PageState.User.UserId); await logger.LogInformation("User Profile Saved"); if (!string.IsNullOrEmpty(PageState.ReturnUrl)) @@ -557,12 +576,12 @@ private bool ValidateProfiles() { - foreach (Profile profile in profiles) + foreach (Profile profile in _profiles) { var value = GetProfileValue(profile.Name, string.Empty); if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { - userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue); + _userSettings = SettingService.SetSetting(_userSettings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { @@ -594,7 +613,7 @@ private void ProfileChanged(ChangeEventArgs e, string SettingName) { var value = (string)e.Value; - userSettings = SettingService.SetSetting(userSettings, SettingName, value); + _userSettings = SettingService.SetSetting(_userSettings, SettingName, value); } private async Task Delete(Notification Notification) @@ -624,7 +643,7 @@ private async void FilterChanged(ChangeEventArgs e) { - filter = (string)e.Value; + _filter = (string)e.Value; await LoadNotificationsAsync(); StateHasChanged(); } @@ -634,7 +653,7 @@ try { ShowProgressIndicator(); - foreach(var Notification in notifications) + foreach(var Notification in _notifications) { if (!Notification.IsDeleted) { diff --git a/Oqtane.Client/Modules/Admin/Users/Add.razor b/Oqtane.Client/Modules/Admin/Users/Add.razor index 10cf19e8..510c87e7 100644 --- a/Oqtane.Client/Modules/Admin/Users/Add.razor +++ b/Oqtane.Client/Modules/Admin/Users/Add.razor @@ -5,6 +5,7 @@ @inject IUserService UserService @inject IProfileService ProfileService @inject ISettingService SettingService +@inject ITimeZoneService TimeZoneService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -12,7 +13,7 @@ { - @if (profiles != null) + @if (_profiles != null) {
@@ -33,6 +34,18 @@
+
+ +
+ +
+
@@ -48,20 +61,20 @@
- @foreach (Profile profile in profiles) + @foreach (Profile profile in _profiles) { var p = profile; - if (p.Category != category) + if (p.Category != _category) {
@p.Category
- category = p.Category; + _category = p.Category; }
-
- @if (!string.IsNullOrEmpty(p.Options)) +
+ @if (!string.IsNullOrEmpty(p.Options)) { +
@@ -35,7 +36,7 @@
- +
@@ -43,13 +44,25 @@
- +
- + +
+
+
+ +
+
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) @@ -57,7 +70,7 @@
- @@ -67,13 +80,13 @@
- +
- +
@@ -81,15 +94,15 @@
- @foreach (Profile profile in profiles) + @foreach (Profile profile in _profiles) { var p = profile; - if (p.Category != category) + if (p.Category != _category) {
@p.Category
- category = p.Category; + _category = p.Category; }
@@ -131,44 +144,46 @@ @SharedLocalizer["Cancel"] - @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !ishost) + @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost) { } - @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && isdeleted == "True") + @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True") { }

- + } @code { + private List _timezones; private bool _initialized = false; private string _passwordrequirements; - private int userid; - private string username = string.Empty; + private int _userid; + private string _username = string.Empty; private string _password = string.Empty; private string _passwordtype = "password"; private string _togglepassword = string.Empty; - private string confirm = string.Empty; - private string email = string.Empty; - private string displayname = string.Empty; - private string isdeleted; - private string lastlogin; - private string lastipaddress; - private bool ishost = false; + private string _confirm = string.Empty; + private string _email = string.Empty; + private string _displayname = string.Empty; + private string _timezoneid = string.Empty; + private string _isdeleted; + private string _lastlogin; + private string _lastipaddress; + private bool _ishost = false; - private List profiles; - private Dictionary userSettings; - private string category = string.Empty; + private List _profiles; + private Dictionary _settings; + private string _category = string.Empty; - private string createdby; - private DateTime createdon; - private string modifiedby; - private DateTime modifiedon; - private string deletedby; - private DateTime? deletedon; + private string _createdby; + private DateTime _createdon; + private string _modifiedby; + private DateTime _modifiedon; + private string _deletedby; + private DateTime? _deletedon; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; @@ -178,29 +193,31 @@ { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _togglepassword = SharedLocalizer["ShowPassword"]; - profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); + _profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); + _timezones = await TimeZoneService.GetTimeZonesAsync(); if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId)) { - userid = UserId; - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + _userid = UserId; + var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId); if (user != null) { - username = user.Username; - email = user.Email; - displayname = user.DisplayName; - isdeleted = user.IsDeleted.ToString(); - lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); - lastipaddress = user.LastIPAddress; - ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host); + _username = user.Username; + _email = user.Email; + _displayname = user.DisplayName; + _timezoneid = PageState.User.TimeZoneId; + _isdeleted = user.IsDeleted.ToString(); + _lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); + _lastipaddress = user.LastIPAddress; + _ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host); - userSettings = user.Settings; - createdby = user.CreatedBy; - createdon = user.CreatedOn; - modifiedby = user.ModifiedBy; - modifiedon = user.ModifiedOn; - deletedby = user.DeletedBy; - deletedon = user.DeletedOn; + _settings = user.Settings; + _createdby = user.CreatedBy; + _createdon = user.CreatedOn; + _modifiedby = user.ModifiedBy; + _modifiedon = user.ModifiedOn; + _deletedby = user.DeletedBy; + _deletedon = user.DeletedOn; } } @@ -208,14 +225,14 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message); + await logger.LogError(ex, "Error Loading User {UserId} {Error}", _userid, ex.Message); AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error); } } private string GetProfileValue(string SettingName, string DefaultValue) { - string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue); + string value = SettingService.GetSetting(_settings, SettingName, DefaultValue); if (value.Contains("]")) { value = value.Substring(value.IndexOf("]") + 1); @@ -227,27 +244,28 @@ { try { - if (username != string.Empty && email != string.Empty) + if (_username != string.Empty && _email != string.Empty) { - if (_password == confirm) + if (_password == _confirm) { if (ValidateProfiles()) { - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId); user.SiteId = PageState.Site.SiteId; - user.Username = username; + user.Username = _username; user.Password = _password; - user.Email = email; - user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; + user.Email = _email; + user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname; + user.TimeZoneId = _timezoneid; if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { - user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted)); + user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); } user = await UserService.UpdateUserAsync(user); if (user != null) { - await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId); + await SettingService.UpdateUserSettingsAsync(_settings, user.UserId); await logger.LogInformation("User Saved {User}", user); NavigationManager.NavigateTo(NavigateUrl()); } @@ -269,7 +287,7 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", username, email, ex.Message); + await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", _username, _email, ex.Message); AddModuleMessage(Localizer["Error.User.Save"], MessageType.Error); } } @@ -278,17 +296,17 @@ { try { - await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", username, PageState.User.Username); + await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", _username, PageState.User.Username); // post back to the server so that the cookies are set correctly var interop = new Interop(JSRuntime); - var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = username, returnurl = PageState.Alias.Path }; + var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, returnurl = PageState.Alias.Path }; string url = Utilities.TenantUrl(PageState.Alias, "/pages/impersonate/"); await interop.SubmitForm(url, fields); } catch (Exception ex) { - await logger.LogError(ex, "Error Impersonating User {Username} {Error}", username, ex.Message); + await logger.LogError(ex, "Error Impersonating User {Username} {Error}", _username, ex.Message); AddModuleMessage(Localizer["Error.User.Impersonate"], MessageType.Error); } } @@ -297,9 +315,9 @@ { try { - if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && userid != PageState.User.UserId) + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _userid != PageState.User.UserId) { - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId); await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId); await logger.LogInformation("User Permanently Deleted {User}", user); NavigationManager.NavigateTo(NavigateUrl()); @@ -307,19 +325,19 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", userid, ex.Message); + await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", _userid, ex.Message); AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error); } } private bool ValidateProfiles() { - foreach (Profile profile in profiles) + foreach (Profile profile in _profiles) { var value = GetProfileValue(profile.Name, string.Empty); if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { - userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue); + _settings = SettingService.SetSetting(_settings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { @@ -346,7 +364,7 @@ private void ProfileChanged(ChangeEventArgs e, string SettingName) { var value = (string)e.Value; - userSettings = SettingService.SetSetting(userSettings, SettingName, value); + _settings = SettingService.SetSetting(_settings, SettingName, value); } private void TogglePassword() diff --git a/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx index 5a0af9ed..47e95add 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx @@ -180,4 +180,10 @@ Already have account? Login now. + + Time Zone: + + + Your time zone + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 0127c7cc..acfd022e 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -447,4 +447,10 @@ Site Map Cache Cleared + + Time Zone: + + + The default time zone for the site + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx index a6f0a739..d6136ee8 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx @@ -246,4 +246,10 @@ Logout Everywhere + + Time Zone: + + + Your time zone + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx index b25b2477..536de9b4 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx @@ -156,4 +156,10 @@ Notify? + + Time Zone: + + + The user's time zone + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx index 3dbd1d1e..df4ccc95 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx @@ -210,4 +210,10 @@ Unable To Impersonate User + + Time Zone: + + + The user's time zone + \ No newline at end of file diff --git a/Oqtane.Client/Services/Interfaces/ITimeZoneService.cs b/Oqtane.Client/Services/Interfaces/ITimeZoneService.cs new file mode 100644 index 00000000..c31f90b6 --- /dev/null +++ b/Oqtane.Client/Services/Interfaces/ITimeZoneService.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Oqtane.Models; + +namespace Oqtane.Services +{ + /// + /// Service to store and retrieve entries + /// + public interface ITimeZoneService + { + /// + /// Get the list of time zones + /// + /// + Task> GetTimeZonesAsync(); + } +} diff --git a/Oqtane.Client/Services/TimeZoneService.cs b/Oqtane.Client/Services/TimeZoneService.cs new file mode 100644 index 00000000..f7983b14 --- /dev/null +++ b/Oqtane.Client/Services/TimeZoneService.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Oqtane.Documentation; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + [PrivateApi("Don't show in the documentation, as everything should use the Interface")] + public class TimeZoneService : ServiceBase, ITimeZoneService + { + public TimeZoneService(HttpClient http, SiteState siteState) : base(http, siteState) { } + + private string Apiurl => CreateApiUrl("TimeZone"); + + public async Task> GetTimeZonesAsync() + { + return await GetJsonAsync>($"{Apiurl}"); + } + } +} diff --git a/Oqtane.Server/Controllers/TimeZoneController.cs b/Oqtane.Server/Controllers/TimeZoneController.cs new file mode 100644 index 00000000..158d9f72 --- /dev/null +++ b/Oqtane.Server/Controllers/TimeZoneController.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Controllers +{ + [Route(ControllerRoutes.ApiRoute)] + public class TimeZoneController : Controller + { + public TimeZoneController() {} + + // GET: api/ + [HttpGet] + public IEnumerable Get() + { + return TimeZoneInfo.GetSystemTimeZones() + .Select(item => new Models.TimeZone + { + Id = item.Id, + DisplayName = item.DisplayName + }) + .OrderBy(item => item.DisplayName); + } + } +} diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 92aef5c5..7a0f3bfd 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -135,6 +135,7 @@ namespace Oqtane.Controllers if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == user.UserId) { filtered.Email = user.Email; + filtered.TimeZoneId = user.TimeZoneId; filtered.PhotoFileId = user.PhotoFileId; filtered.LastLoginOn = user.LastLoginOn; filtered.LastIPAddress = user.LastIPAddress; diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 9b4b09e9..98a392cc 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -104,6 +104,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // providers services.AddScoped(); diff --git a/Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs b/Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs new file mode 100644 index 00000000..0e7d40c0 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.06.01.03.01")] + public class AddTimeZone : MultiDatabaseMigration + { + public AddTimeZone(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.AddStringColumn("TimeZoneId", 50, true); + + var userEntityBuilder = new UserEntityBuilder(migrationBuilder, ActiveDatabase); + userEntityBuilder.AddStringColumn("TimeZoneId", 50, true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Shared/Models/Site.cs b/Oqtane.Shared/Models/Site.cs index aeb6e37b..e674be2e 100644 --- a/Oqtane.Shared/Models/Site.cs +++ b/Oqtane.Shared/Models/Site.cs @@ -26,6 +26,11 @@ namespace Oqtane.Models /// public string Name { get; set; } + /// + /// The default time zone for the site + /// + public string TimeZoneId { get; set; } + /// /// Reference to a which has the Logo for this site. /// Should be an image. @@ -200,6 +205,7 @@ namespace Oqtane.Models SiteId = SiteId, TenantId = TenantId, Name = Name, + TimeZoneId = TimeZoneId, LogoFileId = LogoFileId, FaviconFileId = FaviconFileId, DefaultThemeType = DefaultThemeType, diff --git a/Oqtane.Shared/Models/TimeZone.cs b/Oqtane.Shared/Models/TimeZone.cs new file mode 100644 index 00000000..a2ff00f0 --- /dev/null +++ b/Oqtane.Shared/Models/TimeZone.cs @@ -0,0 +1,10 @@ +namespace Oqtane.Models +{ + public class TimeZone + { + public string Id { get; set; } + + public string DisplayName { get; set; } + + } +} diff --git a/Oqtane.Shared/Models/User.cs b/Oqtane.Shared/Models/User.cs index d5009fb5..7b6b398b 100644 --- a/Oqtane.Shared/Models/User.cs +++ b/Oqtane.Shared/Models/User.cs @@ -29,6 +29,11 @@ namespace Oqtane.Models /// public string Email { get; set; } + /// + /// User time zone + /// + public string TimeZoneId { get; set; } + /// /// Reference to a containing the users photo. ///