diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor
new file mode 100644
index 00000000..8ad0f1ca
--- /dev/null
+++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor
@@ -0,0 +1,85 @@
+@namespace Oqtane.Modules.Admin.Files
+@inherits ModuleBase
+@inject IFolderService FolderService
+@inject NavigationManager NavigationManager
+
+
@@ -87,6 +95,7 @@
int userid;
string username = "";
string password = "";
+ string confirm = "";
string email = "";
string displayname = "";
List profiles;
@@ -139,19 +148,33 @@
{
try
{
- User user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
- user.SiteId = PageState.Site.SiteId;
- user.Username = username;
- user.Password = password;
- user.Email = email;
- user.DisplayName = string.IsNullOrWhiteSpace(user.DisplayName) ? user.Username : user.DisplayName;
- user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
+ if (username != "" && password != "" && confirm != "" && email != "")
+ {
+ if (password == confirm)
+ {
+ User user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
+ user.SiteId = PageState.Site.SiteId;
+ user.Username = username;
+ user.Password = password;
+ user.Email = email;
+ user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
+ user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
- user = await UserService.UpdateUserAsync(user);
- await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
- await logger.LogInformation("User Saved {User}", user);
+ user = await UserService.UpdateUserAsync(user);
+ await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
+ await logger.LogInformation("User Saved {User}", user);
- NavigationManager.NavigateTo(NavigateUrl());
+ NavigationManager.NavigateTo(NavigateUrl());
+ }
+ else
+ {
+ AddModuleMessage("Passwords Entered Do Not Match", MessageType.Warning);
+ }
+ }
+ else
+ {
+ AddModuleMessage("You Must Provide A Username, Password, and Email Address", MessageType.Warning);
+ }
}
catch (Exception ex)
{
diff --git a/Oqtane.Client/Modules/Controls/ActionDialog.razor b/Oqtane.Client/Modules/Controls/ActionDialog.razor
index 96aac6c1..3a8a94ce 100644
--- a/Oqtane.Client/Modules/Controls/ActionDialog.razor
+++ b/Oqtane.Client/Modules/Controls/ActionDialog.razor
@@ -50,10 +50,14 @@
[Parameter]
public string Class { get; set; } // optional
+ [Parameter]
+ public string EditMode { get; set; } // optional - specifies if a user must be in edit mode to see the action - default is true
+
[Parameter]
public Action OnClick { get; set; } // required if an Action is specified - executes a method in the calling component
bool visible = false;
+ bool editmode = true;
bool authorized = false;
protected override void OnParametersSet()
@@ -66,13 +70,17 @@
{
Class = "btn btn-success";
}
+ if (!string.IsNullOrEmpty(EditMode))
+ {
+ editmode = bool.Parse(EditMode);
+ }
authorized = IsAuthorized();
}
private bool IsAuthorized()
{
bool authorized = false;
- if (PageState.EditMode)
+ if (PageState.EditMode || !editmode)
{
SecurityAccessLevel security = SecurityAccessLevel.Host;
if (Security == null)
diff --git a/Oqtane.Client/Modules/Controls/ActionLink.razor b/Oqtane.Client/Modules/Controls/ActionLink.razor
index afb0a8bb..d3c3a08b 100644
--- a/Oqtane.Client/Modules/Controls/ActionLink.razor
+++ b/Oqtane.Client/Modules/Controls/ActionLink.razor
@@ -26,11 +26,15 @@
[Parameter]
public string Style { get; set; } // optional
+ [Parameter]
+ public string EditMode { get; set; } // optional - specifies if a user must be in edit mode to see the action - default is true
+
string text = "";
string url = "";
string parameters = "";
string classname = "btn btn-primary";
string style = "";
+ bool editmode = true;
bool authorized = false;
protected override void OnParametersSet()
@@ -56,6 +60,11 @@
style = Style;
}
+ if (!string.IsNullOrEmpty(EditMode))
+ {
+ editmode = bool.Parse(EditMode);
+ }
+
url = EditUrl(Action, parameters);
authorized = IsAuthorized();
}
@@ -63,7 +72,7 @@
private bool IsAuthorized()
{
bool authorized = false;
- if (PageState.EditMode)
+ if (PageState.EditMode || !editmode)
{
SecurityAccessLevel security = SecurityAccessLevel.Host;
if (Security == null)
diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor
index b9bded82..59547490 100644
--- a/Oqtane.Client/Modules/Controls/Pager.razor
+++ b/Oqtane.Client/Modules/Controls/Pager.razor
@@ -13,6 +13,10 @@
@foreach (var item in ItemList)
{
@Row(item)
+ @if (Detail != null)
+ {
+ @Detail(item)
+ }
}
@@ -24,6 +28,10 @@
@foreach (var item in ItemList)
{
@Row(item)
+ @if (Detail != null)
+ {
+ @Detail(item)
+ }
}
}
@@ -72,6 +80,9 @@
[Parameter]
public RenderFragment Row { get; set; }
+ [Parameter]
+ public RenderFragment Detail { get; set; }
+
[Parameter]
public IEnumerable Items { get; set; }
diff --git a/Oqtane.Client/Services/FolderService.cs b/Oqtane.Client/Services/FolderService.cs
new file mode 100644
index 00000000..4ece5538
--- /dev/null
+++ b/Oqtane.Client/Services/FolderService.cs
@@ -0,0 +1,93 @@
+using Oqtane.Models;
+using System.Threading.Tasks;
+using System.Linq;
+using System.Net.Http;
+using Microsoft.AspNetCore.Components;
+using System.Collections.Generic;
+using Oqtane.Shared;
+using System;
+
+namespace Oqtane.Services
+{
+ public class FolderService : ServiceBase, IFolderService
+ {
+ private readonly HttpClient http;
+ private readonly SiteState sitestate;
+ private readonly NavigationManager NavigationManager;
+
+ public FolderService(HttpClient http, SiteState sitestate, NavigationManager NavigationManager)
+ {
+ this.http = http;
+ this.sitestate = sitestate;
+ this.NavigationManager = NavigationManager;
+ }
+
+ private string apiurl
+ {
+ get { return CreateApiUrl(sitestate.Alias, NavigationManager.Uri, "Folder"); }
+ }
+
+ public async Task> GetFoldersAsync(int SiteId)
+ {
+ List folders = await http.GetJsonAsync>(apiurl + "?siteid=" + SiteId.ToString());
+ folders = GetFoldersHierarchy(folders);
+ return folders;
+ }
+
+ public async Task GetFolderAsync(int FolderId)
+ {
+ return await http.GetJsonAsync(apiurl + "/" + FolderId.ToString());
+ }
+
+ public async Task AddFolderAsync(Folder Folder)
+ {
+ return await http.PostJsonAsync(apiurl, Folder);
+ }
+
+ public async Task UpdateFolderAsync(Folder Folder)
+ {
+ return await http.PutJsonAsync(apiurl + "/" + Folder.FolderId.ToString(), Folder);
+ }
+
+ public async Task UpdateFolderOrderAsync(int SiteId, int FolderId, int? ParentId)
+ {
+ await http.PutJsonAsync(apiurl + "/?siteid=" + SiteId.ToString() + "&folderid=" + FolderId.ToString() + "&parentid=" + ((ParentId == null) ? "" : ParentId.ToString()), null);
+ }
+
+ public async Task DeleteFolderAsync(int FolderId)
+ {
+ await http.DeleteAsync(apiurl + "/" + FolderId.ToString());
+ }
+
+ private static List GetFoldersHierarchy(List Folders)
+ {
+ List hierarchy = new List();
+ Action, Folder> GetPath = null;
+ GetPath = (List folders, Folder folder) =>
+ {
+ IEnumerable children;
+ int level;
+ if (folder == null)
+ {
+ level = -1;
+ children = Folders.Where(item => item.ParentId == null);
+ }
+ else
+ {
+ level = folder.Level;
+ children = Folders.Where(item => item.ParentId == folder.FolderId);
+ }
+ foreach (Folder child in children)
+ {
+ child.Level = level + 1;
+ child.HasChildren = Folders.Where(item => item.ParentId == child.FolderId).Any();
+ hierarchy.Add(child);
+ GetPath(folders, child);
+ }
+ };
+ Folders = Folders.OrderBy(item => item.Order).ToList();
+ GetPath(Folders, null);
+ return hierarchy;
+ }
+ }
+}
diff --git a/Oqtane.Client/Services/Interfaces/IFolderService.cs b/Oqtane.Client/Services/Interfaces/IFolderService.cs
new file mode 100644
index 00000000..d91e253e
--- /dev/null
+++ b/Oqtane.Client/Services/Interfaces/IFolderService.cs
@@ -0,0 +1,16 @@
+using Oqtane.Models;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Oqtane.Services
+{
+ public interface IFolderService
+ {
+ Task> GetFoldersAsync(int SiteId);
+ Task GetFolderAsync(int FolderId);
+ Task AddFolderAsync(Folder Folder);
+ Task UpdateFolderAsync(Folder Folder);
+ Task UpdateFolderOrderAsync(int SiteId, int FolderId, int? ParentId);
+ Task DeleteFolderAsync(int FolderId);
+ }
+}
diff --git a/Oqtane.Client/Services/Interfaces/INotificationService.cs b/Oqtane.Client/Services/Interfaces/INotificationService.cs
new file mode 100644
index 00000000..37072f1a
--- /dev/null
+++ b/Oqtane.Client/Services/Interfaces/INotificationService.cs
@@ -0,0 +1,19 @@
+using Oqtane.Models;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Oqtane.Services
+{
+ public interface INotificationService
+ {
+ Task> GetNotificationsAsync(int SiteId, string Direction, int UserId);
+
+ Task GetNotificationAsync(int NotificationId);
+
+ Task AddNotificationAsync(Notification Notification);
+
+ Task UpdateNotificationAsync(Notification Notification);
+
+ Task DeleteNotificationAsync(int NotificationId);
+ }
+}
diff --git a/Oqtane.Client/Services/Interfaces/IUserService.cs b/Oqtane.Client/Services/Interfaces/IUserService.cs
index cb643489..31589eee 100644
--- a/Oqtane.Client/Services/Interfaces/IUserService.cs
+++ b/Oqtane.Client/Services/Interfaces/IUserService.cs
@@ -23,5 +23,9 @@ namespace Oqtane.Services
Task LoginUserAsync(User User, bool SetCookie, bool IsPersistent);
Task LogoutUserAsync(User User);
+
+ Task ForgotPasswordAsync(User User);
+
+ Task ResetPasswordAsync(User User, string Token);
}
}
diff --git a/Oqtane.Client/Services/NotificationService.cs b/Oqtane.Client/Services/NotificationService.cs
new file mode 100644
index 00000000..c43f6f3e
--- /dev/null
+++ b/Oqtane.Client/Services/NotificationService.cs
@@ -0,0 +1,55 @@
+using Oqtane.Models;
+using System.Threading.Tasks;
+using System.Net.Http;
+using Microsoft.AspNetCore.Components;
+using Oqtane.Shared;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Oqtane.Services
+{
+ public class NotificationService : ServiceBase, INotificationService
+ {
+ private readonly HttpClient http;
+ private readonly SiteState sitestate;
+ private readonly NavigationManager NavigationManager;
+
+ public NotificationService(HttpClient http, SiteState sitestate, NavigationManager NavigationManager)
+ {
+ this.http = http;
+ this.sitestate = sitestate;
+ this.NavigationManager = NavigationManager;
+ }
+
+ private string apiurl
+ {
+ get { return CreateApiUrl(sitestate.Alias, NavigationManager.Uri, "Notification"); }
+ }
+
+ public async Task> GetNotificationsAsync(int SiteId, string Direction, int UserId)
+ {
+ string querystring = "?siteid=" + SiteId.ToString() + "&direction=" + Direction.ToLower() + "&userid=" + UserId.ToString();
+ List Notifications = await http.GetJsonAsync>(apiurl + querystring);
+ return Notifications.OrderByDescending(item => item.CreatedOn).ToList();
+ }
+
+ public async Task GetNotificationAsync(int NotificationId)
+ {
+ return await http.GetJsonAsync(apiurl + "/" + NotificationId.ToString());
+ }
+
+ public async Task AddNotificationAsync(Notification Notification)
+ {
+ return await http.PostJsonAsync(apiurl, Notification);
+ }
+
+ public async Task UpdateNotificationAsync(Notification Notification)
+ {
+ return await http.PutJsonAsync(apiurl + "/" + Notification.NotificationId.ToString(), Notification);
+ }
+ public async Task DeleteNotificationAsync(int NotificationId)
+ {
+ await http.DeleteAsync(apiurl + "/" + NotificationId.ToString());
+ }
+ }
+}
diff --git a/Oqtane.Client/Services/RoleService.cs b/Oqtane.Client/Services/RoleService.cs
index 3e09d677..78720cd4 100644
--- a/Oqtane.Client/Services/RoleService.cs
+++ b/Oqtane.Client/Services/RoleService.cs
@@ -50,7 +50,7 @@ namespace Oqtane.Services
public async Task UpdateRoleAsync(Role Role)
{
- return await http.PutJsonAsync(apiurl + "/" + Role.SiteId.ToString(), Role);
+ return await http.PutJsonAsync(apiurl + "/" + Role.RoleId.ToString(), Role);
}
public async Task DeleteRoleAsync(int RoleId)
{
diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs
index a64cb0dc..16d9a62b 100644
--- a/Oqtane.Client/Services/UserService.cs
+++ b/Oqtane.Client/Services/UserService.cs
@@ -86,5 +86,16 @@ namespace Oqtane.Services
// best practices recommend post is preferrable to get for logout
await http.PostJsonAsync(apiurl + "/logout", User);
}
+
+ public async Task ForgotPasswordAsync(User User)
+ {
+ await http.PostJsonAsync(apiurl + "/forgot", User);
+ }
+
+ public async Task ResetPasswordAsync(User User, string Token)
+ {
+ return await http.PostJsonAsync(apiurl + "/reset?token=" + Token, User);
+ }
+
}
}
diff --git a/Oqtane.Client/Shared/Installer.razor b/Oqtane.Client/Shared/Installer.razor
index dbd8b02b..b4962476 100644
--- a/Oqtane.Client/Shared/Installer.razor
+++ b/Oqtane.Client/Shared/Installer.razor
@@ -59,7 +59,7 @@
|
-
+
|
@@ -123,8 +131,9 @@
private string DatabaseName = "Oqtane-" + DateTime.Now.ToString("yyyyMMddHHmm");
private string Username = "";
private string Password = "";
- private string HostUsername = "host";
+ private string HostUsername = Constants.HostUser;
private string HostPassword = "";
+ private string ConfirmPassword = "";
private string HostEmail = "";
private string Message = "";
@@ -145,7 +154,7 @@
private async Task Install()
{
- if (HostUsername != "" & HostPassword.Length >= 6 & HostEmail != "")
+ if (HostUsername != "" && HostPassword.Length >= 6 && HostPassword == ConfirmPassword && HostEmail != "")
{
LoadingDisplay = "";
StateHasChanged();
@@ -198,7 +207,7 @@
}
else
{
- Message = " Username And Email Must Be Provided And Password Must Be Greater Than 5 Characters ";
+ Message = "Please Enter All Fields And Ensure Passwords Match And Are Greater Than 5 Characters In Length ";
}
}
}
diff --git a/Oqtane.Client/Startup.cs b/Oqtane.Client/Startup.cs
index 1abf39d8..b5c57b17 100644
--- a/Oqtane.Client/Startup.cs
+++ b/Oqtane.Client/Startup.cs
@@ -56,6 +56,7 @@ namespace Oqtane.Client
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
// dynamically register module contexts and repository services
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
diff --git a/Oqtane.Client/Themes/Controls/Login.razor b/Oqtane.Client/Themes/Controls/Login.razor
index b40e93c4..c2dff055 100644
--- a/Oqtane.Client/Themes/Controls/Login.razor
+++ b/Oqtane.Client/Themes/Controls/Login.razor
@@ -40,7 +40,7 @@
var interop = new Interop(jsRuntime);
string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken");
var fields = new { __RequestVerificationToken = antiforgerytoken, returnurl = (PageState.Alias.Path + "/" + PageState.Page.Path) };
- await interop.SubmitForm("/logout/", fields);
+ await interop.SubmitForm("/pages/logout/", fields);
}
else
{
diff --git a/Oqtane.Client/wwwroot/css/app.css b/Oqtane.Client/wwwroot/css/app.css
index 2e5c6113..ed6583dd 100644
--- a/Oqtane.Client/wwwroot/css/app.css
+++ b/Oqtane.Client/wwwroot/css/app.css
@@ -92,3 +92,21 @@ app {
height: 1px;
background-color: gray;
}
+
+.app-link-unstyled, .app-link-unstyled:visited, .app-link-unstyled:hover, .app-link-unstyled:active, .app-link-unstyled:focus, .app-link-unstyled:active:hover {
+ font-style: inherit;
+ color: inherit;
+ background-color: transparent;
+ font-size: inherit;
+ text-decoration: none;
+ font-variant: inherit;
+ font-weight: inherit;
+ line-height: inherit;
+ font-family: inherit;
+ border-radius: inherit;
+ border: inherit;
+ outline: inherit;
+ box-shadow: inherit;
+ padding: inherit;
+ vertical-align: inherit;
+}
diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs
new file mode 100644
index 00000000..f04eda49
--- /dev/null
+++ b/Oqtane.Server/Controllers/FolderController.cs
@@ -0,0 +1,108 @@
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using Oqtane.Repository;
+using Oqtane.Models;
+using Oqtane.Shared;
+using System.Linq;
+using Oqtane.Infrastructure;
+using Oqtane.Security;
+
+namespace Oqtane.Controllers
+{
+ [Route("{site}/api/[controller]")]
+ public class FolderController : Controller
+ {
+ private readonly IFolderRepository Folders;
+ private readonly IUserPermissions UserPermissions;
+ private readonly ILogManager logger;
+
+ public FolderController(IFolderRepository Folders, IUserPermissions UserPermissions, ILogManager logger)
+ {
+ this.Folders = Folders;
+ this.UserPermissions = UserPermissions;
+ this.logger = logger;
+ }
+
+ // GET: api/?siteid=x
+ [HttpGet]
+ public IEnumerable Get(string siteid)
+ {
+ if (siteid == "")
+ {
+ return Folders.GetFolders();
+ }
+ else
+ {
+ return Folders.GetFolders(int.Parse(siteid));
+ }
+ }
+
+ // GET api//5?userid=x
+ [HttpGet("{id}")]
+ public Folder Get(int id)
+ {
+ return Folders.GetFolder(id);
+ }
+
+ // POST api/
+ [HttpPost]
+ [Authorize(Roles = Constants.RegisteredRole)]
+ public Folder Post([FromBody] Folder Folder)
+ {
+ if (ModelState.IsValid && UserPermissions.IsAuthorized(User, "Edit", Folder.Permissions))
+ {
+ Folder = Folders.AddFolder(Folder);
+ logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", Folder);
+ }
+ return Folder;
+ }
+
+ // PUT api//5
+ [HttpPut("{id}")]
+ [Authorize(Roles = Constants.RegisteredRole)]
+ public Folder Put(int id, [FromBody] Folder Folder)
+ {
+ if (ModelState.IsValid && UserPermissions.IsAuthorized(User, "Folder", Folder.FolderId, "Edit"))
+ {
+ Folder = Folders.UpdateFolder(Folder);
+ logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", Folder);
+ }
+ return Folder;
+ }
+
+ // PUT api//?siteid=x&folderid=y&parentid=z
+ [HttpPut]
+ [Authorize(Roles = Constants.RegisteredRole)]
+ public void Put(int siteid, int folderid, int? parentid)
+ {
+ if (UserPermissions.IsAuthorized(User, "Folder", folderid, "Edit"))
+ {
+ int order = 1;
+ List folders = Folders.GetFolders(siteid).ToList();
+ foreach (Folder folder in folders.Where(item => item.ParentId == parentid).OrderBy(item => item.Order))
+ {
+ if (folder.Order != order)
+ {
+ folder.Order = order;
+ Folders.UpdateFolder(folder);
+ }
+ order += 2;
+ }
+ logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Order Updated {SiteId} {FolderId} {ParentId}", siteid, folderid, parentid);
+ }
+ }
+
+ // DELETE api//5
+ [HttpDelete("{id}")]
+ [Authorize(Roles = Constants.RegisteredRole)]
+ public void Delete(int id)
+ {
+ if (UserPermissions.IsAuthorized(User, "Folder", id, "Edit"))
+ {
+ Folders.DeleteFolder(id);
+ logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id);
+ }
+ }
+ }
+}
diff --git a/Oqtane.Server/Controllers/NotificationController.cs b/Oqtane.Server/Controllers/NotificationController.cs
new file mode 100644
index 00000000..8333f53f
--- /dev/null
+++ b/Oqtane.Server/Controllers/NotificationController.cs
@@ -0,0 +1,110 @@
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using Oqtane.Repository;
+using Oqtane.Models;
+using Oqtane.Shared;
+using Oqtane.Infrastructure;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Http;
+
+namespace Oqtane.Controllers
+{
+ [Route("{site}/api/[controller]")]
+ public class NotificationController : Controller
+ {
+ private readonly INotificationRepository Notifications;
+ private readonly IHttpContextAccessor Accessor;
+ private readonly ILogManager logger;
+
+ public NotificationController(INotificationRepository Notifications, IHttpContextAccessor Accessor, ILogManager logger)
+ {
+ this.Notifications = Notifications;
+ this.Accessor = Accessor;
+ this.logger = logger;
+ }
+
+ // GET: api/?siteid=x&type=y&userid=z
+ [HttpGet]
+ [Authorize(Roles = Constants.RegisteredRole)]
+ public IEnumerable Get(string siteid, string direction, string userid)
+ {
+ IEnumerable notifications = null;
+ if (IsAuthorized(int.Parse(userid)))
+ {
+ if (direction == "to")
+ {
+ notifications = Notifications.GetNotifications(int.Parse(siteid), -1, int.Parse(userid));
+ }
+ else
+ {
+ notifications = Notifications.GetNotifications(int.Parse(siteid), int.Parse(userid), -1);
+ }
+ }
+ return notifications;
+ }
+
+ // GET api//5
+ [HttpGet("{id}")]
+ [Authorize(Roles = Constants.RegisteredRole)]
+ public Notification Get(int id)
+ {
+ Notification Notification = Notifications.GetNotification(id);
+ if (!(IsAuthorized(Notification.FromUserId) || IsAuthorized(Notification.ToUserId)))
+ {
+ Notification = null;
+ }
+ return Notification;
+ }
+
+ // POST api/
+ [HttpPost]
+ [Authorize(Roles = Constants.RegisteredRole)]
+ public Notification Post([FromBody] Notification Notification)
+ {
+ if (IsAuthorized(Notification.FromUserId))
+ {
+ Notification = Notifications.AddNotification(Notification);
+ logger.Log(LogLevel.Information, this, LogFunction.Create, "Notification Added {Notification}", Notification);
+ }
+ return Notification;
+ }
+
+ // PUT api//5
+ [HttpPut("{id}")]
+ [Authorize(Roles = Constants.RegisteredRole)]
+ public Notification Put(int id, [FromBody] Notification Notification)
+ {
+ if (IsAuthorized(Notification.FromUserId))
+ {
+ Notification = Notifications.UpdateNotification(Notification);
+ logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {Folder}", Notification);
+ }
+ return Notification;
+ }
+
+ // DELETE api//5
+ [HttpDelete("{id}")]
+ [Authorize(Roles = Constants.RegisteredRole)]
+ public void Delete(int id)
+ {
+ Notification Notification = Notifications.GetNotification(id);
+ if (IsAuthorized(Notification.FromUserId) || IsAuthorized(Notification.ToUserId))
+ {
+ Notifications.DeleteNotification(id);
+ logger.Log(LogLevel.Information, this, LogFunction.Delete, "Notification Deleted {NotificationId}", id);
+ }
+ }
+
+ private bool IsAuthorized(int? userid)
+ {
+ bool authorized = true;
+ if (userid != null)
+ {
+ authorized = (int.Parse(Accessor.HttpContext.User.FindFirst(ClaimTypes.PrimarySid).Value) == userid);
+ }
+ return authorized;
+ }
+
+ }
+}
diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs
index c8804303..5229ca54 100644
--- a/Oqtane.Server/Controllers/UserController.cs
+++ b/Oqtane.Server/Controllers/UserController.cs
@@ -10,6 +10,9 @@ using System.Linq;
using System.Security.Claims;
using Oqtane.Shared;
using Oqtane.Infrastructure;
+using System;
+using Microsoft.AspNetCore.Http;
+using System.Net;
namespace Oqtane.Controllers
{
@@ -21,15 +24,19 @@ namespace Oqtane.Controllers
private readonly IUserRoleRepository UserRoles;
private readonly UserManager IdentityUserManager;
private readonly SignInManager IdentitySignInManager;
+ private readonly ITenantResolver Tenants;
+ private readonly INotificationRepository Notifications;
private readonly ILogManager logger;
- public UserController(IUserRepository Users, IRoleRepository Roles, IUserRoleRepository UserRoles, UserManager IdentityUserManager, SignInManager IdentitySignInManager, ILogManager logger)
+ public UserController(IUserRepository Users, IRoleRepository Roles, IUserRoleRepository UserRoles, UserManager IdentityUserManager, SignInManager IdentitySignInManager, ITenantResolver Tenants, INotificationRepository Notifications, ILogManager logger)
{
this.Users = Users;
this.Roles = Roles;
this.UserRoles = UserRoles;
this.IdentityUserManager = IdentityUserManager;
this.IdentitySignInManager = IdentitySignInManager;
+ this.Tenants = Tenants;
+ this.Notifications = Notifications;
this.logger = logger;
}
@@ -74,10 +81,11 @@ namespace Oqtane.Controllers
if (ModelState.IsValid)
{
- int hostroleid = -1;
- if (!Users.GetUsers().Any())
+ bool verified = true;
+ // users created by non-administrators must be verified
+ if (!base.User.IsInRole(Constants.AdminRole) && User.Username != Constants.HostUser)
{
- hostroleid = Roles.GetRoles(User.SiteId, true).Where(item => item.Name == Constants.HostRole).FirstOrDefault().RoleId;
+ verified = false;
}
IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(User.Username);
@@ -86,14 +94,34 @@ namespace Oqtane.Controllers
identityuser = new IdentityUser();
identityuser.UserName = User.Username;
identityuser.Email = User.Email;
+ identityuser.EmailConfirmed = verified;
var result = await IdentityUserManager.CreateAsync(identityuser, User.Password);
if (result.Succeeded)
{
user = Users.AddUser(User);
-
- // assign to host role if this is the initial installation
- if (hostroleid != -1)
+ if (!verified)
{
+ Notification notification = new Notification();
+ notification.SiteId = User.SiteId;
+ notification.FromUserId = null;
+ notification.ToUserId = user.UserId;
+ notification.ToEmail = "";
+ notification.Subject = "User Account Verification";
+ string token = await IdentityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
+ string alias = Tenants.GetAlias().Path;
+ string url = HttpContext.Request.Scheme + "://" + HttpContext.Request.Host + "/pages/verify?name=" + User.Username + "&token=" + WebUtility.UrlEncode(token) + "&returnurl=" + (alias == "" ? "/" : alias);
+ notification.Body = "Dear " + User.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
+ notification.ParentId = null;
+ notification.CreatedOn = DateTime.Now;
+ notification.IsDelivered = false;
+ notification.DeliveredOn = null;
+ Notifications.AddNotification(notification);
+ }
+
+ // assign to host role if this is the host user ( initial installation )
+ if (User.Username == Constants.HostUser)
+ {
+ int hostroleid = Roles.GetRoles(User.SiteId, true).Where(item => item.Name == Constants.HostRole).FirstOrDefault().RoleId;
UserRole userrole = new UserRole();
userrole.UserId = user.UserId;
userrole.RoleId = hostroleid;
@@ -112,7 +140,7 @@ namespace Oqtane.Controllers
}
}
- if (user != null && hostroleid == -1)
+ if (user != null && User.Username != Constants.HostUser)
{
// add auto assigned roles to user for site
List roles = Roles.GetRoles(User.SiteId).Where(item => item.IsAutoAssigned == true).ToList();
@@ -192,11 +220,18 @@ namespace Oqtane.Controllers
user = Users.GetUser(identityuser.UserName);
if (user != null)
{
- user.IsAuthenticated = true;
- logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful {Username}", User.Username);
- if (SetCookie)
+ if (identityuser.EmailConfirmed)
{
- await IdentitySignInManager.SignInAsync(identityuser, IsPersistent);
+ user.IsAuthenticated = true;
+ logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful {Username}", User.Username);
+ if (SetCookie)
+ {
+ await IdentitySignInManager.SignInAsync(identityuser, IsPersistent);
+ }
+ }
+ else
+ {
+ logger.Log(LogLevel.Information, this, LogFunction.Security, "User Not Verified {Username}", User.Username);
}
}
}
@@ -219,6 +254,68 @@ namespace Oqtane.Controllers
logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", User.Username);
}
+ // POST api//forgot
+ [HttpPost("forgot")]
+ public async Task Forgot([FromBody] User User)
+ {
+ if (ModelState.IsValid)
+ {
+ IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(User.Username);
+ if (identityuser != null)
+ {
+ Notification notification = new Notification();
+ notification.SiteId = User.SiteId;
+ notification.FromUserId = null;
+ notification.ToUserId = User.UserId;
+ notification.ToEmail = "";
+ notification.Subject = "User Password Reset";
+ string token = await IdentityUserManager.GeneratePasswordResetTokenAsync(identityuser);
+ string url = HttpContext.Request.Scheme + "://" + Tenants.GetAlias().Name + "/reset?name=" + User.Username + "&token=" + WebUtility.UrlEncode(token);
+ notification.Body = "Dear " + User.DisplayName + ",\n\nPlease Click The Link Displayed Below To Reset Your Password:\n\n" + url + "\n\nThank You!";
+ notification.ParentId = null;
+ notification.CreatedOn = DateTime.Now;
+ notification.IsDelivered = false;
+ notification.DeliveredOn = null;
+ Notifications.AddNotification(notification);
+ logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", User.Username);
+ }
+ else
+ {
+ logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Notification Failed For {Username}", User.Username);
+ }
+ }
+ }
+
+ // POST api//reset
+ [HttpPost("reset")]
+ public async Task Reset([FromBody] User User, string token)
+ {
+ User user = null;
+ if (ModelState.IsValid)
+ {
+ IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(User.Username);
+ if (identityuser != null && !string.IsNullOrEmpty(token))
+ {
+ var result = await IdentityUserManager.ResetPasswordAsync(identityuser, token, User.Password);
+ if (result.Succeeded)
+ {
+ user = User;
+ user.Password = "";
+ logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset For {Username}", User.Username);
+ }
+ else
+ {
+ logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username}", User.Username);
+ }
+ }
+ else
+ {
+ logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username}", User.Username);
+ }
+ }
+ return user;
+ }
+
// GET api//current
[HttpGet("authenticate")]
public User Authenticate()
diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs
new file mode 100644
index 00000000..f1914990
--- /dev/null
+++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Mail;
+using Microsoft.Extensions.DependencyInjection;
+using Oqtane.Models;
+using Oqtane.Repository;
+using Oqtane.Shared;
+
+namespace Oqtane.Infrastructure
+{
+ public class NotificationJob : HostedServiceBase
+ {
+ // JobType = "Oqtane.Infrastructure.NotificationJob, Oqtane.Server"
+
+ public NotificationJob(IServiceScopeFactory ServiceScopeFactory) : base(ServiceScopeFactory) {}
+
+ public override string ExecuteJob(IServiceProvider provider)
+ {
+ string log = "";
+
+ // iterate through aliases in this installation
+ var Aliases = provider.GetRequiredService();
+ List aliases = Aliases.GetAliases().ToList();
+ foreach (Alias alias in aliases)
+ {
+ // use the SiteState to set the Alias explicitly so the tenant can be resolved
+ var sitestate = provider.GetRequiredService();
+ sitestate.Alias = alias;
+
+ // get services which require tenant resolution
+ var Sites = provider.GetRequiredService();
+ var Settings = provider.GetRequiredService();
+ var Notifications = provider.GetRequiredService();
+
+ // iterate through sites
+ List sites = Sites.GetSites().ToList();
+ foreach (Site site in sites)
+ {
+ log += "Processing Notifications For Site: " + site.Name + "\n\n";
+
+ // get site settings
+ List sitesettings = Settings.GetSettings("Site", site.SiteId).ToList();
+ Dictionary settings = GetSettings(sitesettings);
+ if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "")
+ {
+ // construct SMTP Client
+ var client = new SmtpClient()
+ {
+ DeliveryMethod = SmtpDeliveryMethod.Network,
+ UseDefaultCredentials = false,
+ Host = settings["SMTPHost"],
+ Port = int.Parse(settings["SMTPPort"]),
+ EnableSsl = bool.Parse(settings["SMTPSSL"])
+ };
+ if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "")
+ {
+ client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]);
+ }
+
+ // iterate through notifications
+ int sent = 0;
+ List notifications = Notifications.GetNotifications(site.SiteId, -1, -1).ToList();
+ foreach (Notification notification in notifications)
+ {
+ MailMessage mailMessage = new MailMessage();
+ mailMessage.From = new MailAddress(settings["SMTPUsername"], site.Name);
+
+ if (notification.FromUserId != null)
+ {
+ mailMessage.Body = "From: " + notification.FromUser.DisplayName + "<" + notification.FromUser.Email + ">" + "\n";
+ }
+ else
+ {
+ mailMessage.Body = "From: " + site.Name + "\n";
+ }
+ mailMessage.Body += "Sent: " + notification.CreatedOn.ToString() + "\n";
+ if (notification.ToUserId != null)
+ {
+ mailMessage.To.Add(new MailAddress(notification.ToUser.Email, notification.ToUser.DisplayName));
+ mailMessage.Body += "To: " + notification.ToUser.DisplayName + "<" + notification.ToUser.Email + ">" + "\n";
+ }
+ else
+ {
+ mailMessage.To.Add(new MailAddress(notification.ToEmail));
+ mailMessage.Body += "To: " + notification.ToEmail + "\n";
+ }
+ mailMessage.Body += "Subject: " + notification.Subject + "\n\n";
+ mailMessage.Body += notification.Body;
+
+ // send mail
+ try
+ {
+ client.Send(mailMessage);
+ sent = sent++;
+ notification.IsDelivered = true;
+ notification.DeliveredOn = DateTime.Now;
+ Notifications.UpdateNotification(notification);
+ }
+ catch (Exception ex)
+ {
+ // error
+ log += ex.Message.ToString() + "\n\n";
+ }
+ }
+ log += "Notifications Delivered: " + sent.ToString() + "\n\n";
+ }
+ else
+ {
+ log += "SMTP Not Configured" + "\n\n";
+ }
+ }
+ }
+
+ return log;
+ }
+
+
+ private Dictionary GetSettings(List Settings)
+ {
+ Dictionary dictionary = new Dictionary();
+ foreach (Setting setting in Settings.OrderBy(item => item.SettingName).ToList())
+ {
+ dictionary.Add(setting.SettingName, setting.SettingValue);
+ }
+ return dictionary;
+ }
+ }
+}
diff --git a/Oqtane.Server/Infrastructure/Jobs/SampleJob.cs b/Oqtane.Server/Infrastructure/Jobs/SampleJob.cs
deleted file mode 100644
index 816fbaef..00000000
--- a/Oqtane.Server/Infrastructure/Jobs/SampleJob.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Linq;
-using Microsoft.Extensions.DependencyInjection;
-using Oqtane.Models;
-using Oqtane.Repository;
-using Oqtane.Shared;
-
-namespace Oqtane.Infrastructure
-{
- public class SampleJob : HostedServiceBase
- {
- // JobType = "Oqtane.Infrastructure.SampleJob, Oqtane.Server"
-
- public SampleJob(IServiceScopeFactory ServiceScopeFactory) : base(ServiceScopeFactory) {}
-
- public override string ExecuteJob(IServiceProvider provider)
- {
- // get the first alias for this installation
- var Aliases = provider.GetRequiredService();
- Alias alias = Aliases.GetAliases().FirstOrDefault();
-
- // use the SiteState to set the Alias explicitly so the tenant can be resolved
- var sitestate = provider.GetRequiredService();
- sitestate.Alias = alias;
-
- // call a repository service which requires tenant resolution
- var Sites = provider.GetRequiredService();
- Site site = Sites.GetSites().FirstOrDefault();
-
- return "You Should Include Any Notes Related To The Execution Of The Schedule Job. This Job Simply Reports That The Default Site Is " + site.Name;
- }
- }
-}
diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj
index 48bb4789..b9c304de 100644
--- a/Oqtane.Server/Oqtane.Server.csproj
+++ b/Oqtane.Server/Oqtane.Server.csproj
@@ -41,8 +41,8 @@
-
-
+
+
diff --git a/Oqtane.Server/Pages/Login.cshtml b/Oqtane.Server/Pages/Login.cshtml
index b0240ac6..f2eab15f 100644
--- a/Oqtane.Server/Pages/Login.cshtml
+++ b/Oqtane.Server/Pages/Login.cshtml
@@ -1,3 +1,3 @@
-@page "/login"
+@page "/pages/login"
@namespace Oqtane.Pages
@model Oqtane.Pages.LoginModel
diff --git a/Oqtane.Server/Pages/Logout.cshtml b/Oqtane.Server/Pages/Logout.cshtml
index 75a9fd7e..f114ee1e 100644
--- a/Oqtane.Server/Pages/Logout.cshtml
+++ b/Oqtane.Server/Pages/Logout.cshtml
@@ -1,3 +1,3 @@
-@page "/logout"
+@page "/pages/logout"
@namespace Oqtane.Pages
@model Oqtane.Pages.LogoutModel
diff --git a/Oqtane.Server/Pages/Verify.cshtml b/Oqtane.Server/Pages/Verify.cshtml
new file mode 100644
index 00000000..250c5543
--- /dev/null
+++ b/Oqtane.Server/Pages/Verify.cshtml
@@ -0,0 +1,3 @@
+@page "/pages/verify"
+@namespace Oqtane.Pages
+@model Oqtane.Pages.VerifyModel
diff --git a/Oqtane.Server/Pages/Verify.cshtml.cs b/Oqtane.Server/Pages/Verify.cshtml.cs
new file mode 100644
index 00000000..8875da8e
--- /dev/null
+++ b/Oqtane.Server/Pages/Verify.cshtml.cs
@@ -0,0 +1,42 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Oqtane.Models;
+using Oqtane.Repository;
+using System.Threading.Tasks;
+
+namespace Oqtane.Pages
+{
+ [AllowAnonymous]
+ public class VerifyModel : PageModel
+ {
+ private readonly IUserRepository Users;
+ private readonly UserManager IdentityUserManager;
+
+ public VerifyModel(IUserRepository Users, UserManager IdentityUserManager)
+ {
+ this.Users = Users;
+ this.IdentityUserManager = IdentityUserManager;
+ }
+
+ public async Task OnGet(string name, string token, string returnurl)
+ {
+ int verified = 0;
+ IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(name);
+ if (identityuser != null)
+ {
+ var result = await IdentityUserManager.ConfirmEmailAsync(identityuser, token);
+ if (result.Succeeded)
+ {
+ verified = 1;
+ }
+ }
+ if (!returnurl.StartsWith("/"))
+ {
+ returnurl += "/" + returnurl;
+ }
+ return Redirect(HttpContext.Request.Scheme + "://" + HttpContext.Request.Host + returnurl + "login?verified=" + verified.ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs
index 490c5eff..b531ecb1 100644
--- a/Oqtane.Server/Repository/Context/TenantDBContext.cs
+++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs
@@ -17,6 +17,8 @@ namespace Oqtane.Repository
public virtual DbSet Permission { get; set; }
public virtual DbSet Setting { get; set; }
public virtual DbSet Log { get; set; }
+ public virtual DbSet Notification { get; set; }
+ public virtual DbSet Folder { get; set; }
public TenantDBContext(ITenantResolver TenantResolver, IHttpContextAccessor accessor) : base(TenantResolver, accessor)
{
diff --git a/Oqtane.Server/Repository/FolderRepository.cs b/Oqtane.Server/Repository/FolderRepository.cs
new file mode 100644
index 00000000..577639b6
--- /dev/null
+++ b/Oqtane.Server/Repository/FolderRepository.cs
@@ -0,0 +1,70 @@
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Linq;
+using Oqtane.Models;
+
+namespace Oqtane.Repository
+{
+ public class FolderRepository : IFolderRepository
+ {
+ private TenantDBContext db;
+ private readonly IPermissionRepository Permissions;
+
+ public FolderRepository(TenantDBContext context, IPermissionRepository Permissions)
+ {
+ db = context;
+ this.Permissions = Permissions;
+ }
+
+ public IEnumerable GetFolders()
+ {
+ return db.Folder.ToList();
+ }
+
+ public IEnumerable GetFolders(int SiteId)
+ {
+ IEnumerable permissions = Permissions.GetPermissions(SiteId, "Folder").ToList();
+ IEnumerable folders = db.Folder.Where(item => item.SiteId == SiteId);
+ foreach(Folder folder in folders)
+ {
+ folder.Permissions = Permissions.EncodePermissions(folder.FolderId, permissions);
+ }
+ return folders;
+ }
+
+ public Folder AddFolder(Folder Folder)
+ {
+ db.Folder.Add(Folder);
+ db.SaveChanges();
+ Permissions.UpdatePermissions(Folder.SiteId, "Folder", Folder.FolderId, Folder.Permissions);
+ return Folder;
+ }
+
+ public Folder UpdateFolder(Folder Folder)
+ {
+ db.Entry(Folder).State = EntityState.Modified;
+ db.SaveChanges();
+ Permissions.UpdatePermissions(Folder.SiteId, "Folder", Folder.FolderId, Folder.Permissions);
+ return Folder;
+ }
+
+ public Folder GetFolder(int FolderId)
+ {
+ Folder folder = db.Folder.Find(FolderId);
+ if (folder != null)
+ {
+ IEnumerable permissions = Permissions.GetPermissions("Folder", folder.FolderId);
+ folder.Permissions = Permissions.EncodePermissions(folder.FolderId, permissions);
+ }
+ return folder;
+ }
+
+ public void DeleteFolder(int FolderId)
+ {
+ Folder Folder = db.Folder.Find(FolderId);
+ Permissions.DeletePermissions(Folder.SiteId, "Folder", FolderId);
+ db.Folder.Remove(Folder);
+ db.SaveChanges();
+ }
+ }
+}
diff --git a/Oqtane.Server/Repository/Interfaces/IFolderRepository.cs b/Oqtane.Server/Repository/Interfaces/IFolderRepository.cs
new file mode 100644
index 00000000..d5619331
--- /dev/null
+++ b/Oqtane.Server/Repository/Interfaces/IFolderRepository.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using Oqtane.Models;
+
+namespace Oqtane.Repository
+{
+ public interface IFolderRepository
+ {
+ IEnumerable GetFolders();
+ IEnumerable GetFolders(int SiteId);
+ Folder AddFolder(Folder Folder);
+ Folder UpdateFolder(Folder Folder);
+ Folder GetFolder(int FolderId);
+ void DeleteFolder(int FolderId);
+ }
+}
diff --git a/Oqtane.Server/Repository/Interfaces/INotificationRepository.cs b/Oqtane.Server/Repository/Interfaces/INotificationRepository.cs
new file mode 100644
index 00000000..9b1a6a56
--- /dev/null
+++ b/Oqtane.Server/Repository/Interfaces/INotificationRepository.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using Oqtane.Models;
+
+namespace Oqtane.Repository
+{
+ public interface INotificationRepository
+ {
+ IEnumerable GetNotifications(int SiteId, int FromUserId, int ToUserId);
+ Notification AddNotification(Notification Notification);
+ Notification UpdateNotification(Notification Notification);
+ Notification GetNotification(int NotificationId);
+ void DeleteNotification(int NotificationId);
+ }
+}
diff --git a/Oqtane.Server/Repository/NotificationRepository.cs b/Oqtane.Server/Repository/NotificationRepository.cs
new file mode 100644
index 00000000..3c2f9f98
--- /dev/null
+++ b/Oqtane.Server/Repository/NotificationRepository.cs
@@ -0,0 +1,67 @@
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Linq;
+using Oqtane.Models;
+
+namespace Oqtane.Repository
+{
+ public class NotificationRepository : INotificationRepository
+ {
+ private TenantDBContext db;
+
+ public NotificationRepository(TenantDBContext context)
+ {
+ db = context;
+ }
+
+ public IEnumerable GetNotifications(int SiteId, int FromUserId, int ToUserId)
+ {
+ if (ToUserId == -1 && FromUserId == -1)
+ {
+ return db.Notification
+ .Where(item => item.SiteId == SiteId)
+ .Where(item => item.IsDelivered == false)
+ .Include(item => item.FromUser)
+ .Include(item => item.ToUser)
+ .ToList();
+ }
+ else
+ {
+ return db.Notification
+ .Where(item => item.SiteId == SiteId)
+ .Where(item => item.ToUserId == ToUserId || ToUserId == -1)
+ .Where(item => item.FromUserId == FromUserId || FromUserId == -1)
+ .Include(item => item.FromUser)
+ .Include(item => item.ToUser)
+ .ToList();
+ }
+ }
+
+ public Notification AddNotification(Notification Notification)
+ {
+ db.Notification.Add(Notification);
+ db.SaveChanges();
+ return Notification;
+ }
+
+ public Notification UpdateNotification(Notification Notification)
+ {
+ db.Entry(Notification).State = EntityState.Modified;
+ db.SaveChanges();
+ return Notification;
+ }
+
+ public Notification GetNotification(int NotificationId)
+ {
+ return db.Notification.Find(NotificationId);
+ }
+
+ public void DeleteNotification(int NotificationId)
+ {
+ Notification Notification = db.Notification.Find(NotificationId);
+ db.Notification.Remove(Notification);
+ db.SaveChanges();
+ }
+ }
+
+}
diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs
index 59a7233f..c1233b5d 100644
--- a/Oqtane.Server/Repository/SiteRepository.cs
+++ b/Oqtane.Server/Repository/SiteRepository.cs
@@ -104,6 +104,9 @@ namespace Oqtane.Repository
SiteTemplate.Add(new PageTemplate { Name = "Register", Parent = "", Path = "register", Icon = "person", IsNavigation = false, IsPersonalizable = false, EditMode = false, PagePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", PageTemplateModules = new List {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.Register, Oqtane.Client", Title = "User Registration", Pane = "Content", ModulePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", Content = "" }
}});
+ SiteTemplate.Add(new PageTemplate { Name = "Reset", Parent = "", Path = "reset", Icon = "person", IsNavigation = false, IsPersonalizable = false, EditMode = false, PagePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", PageTemplateModules = new List {
+ new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.Reset, Oqtane.Client", Title = "Password Reset", Pane = "Content", ModulePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", Content = "" }
+ }});
SiteTemplate.Add(new PageTemplate { Name = "Profile", Parent = "", Path = "profile", Icon = "person", IsNavigation = false, IsPersonalizable = false, EditMode = false, PagePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", PageTemplateModules = new List {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.UserProfile, Oqtane.Client", Title = "User Profile", Pane = "Content", ModulePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", Content = "" }
}});
diff --git a/Oqtane.Server/Repository/UserRoleRepository.cs b/Oqtane.Server/Repository/UserRoleRepository.cs
index 3d4a262a..bf2c6613 100644
--- a/Oqtane.Server/Repository/UserRoleRepository.cs
+++ b/Oqtane.Server/Repository/UserRoleRepository.cs
@@ -23,7 +23,7 @@ namespace Oqtane.Repository
return db.UserRole
.Include(item => item.Role) // eager load roles
.Include(item => item.User) // eager load users
- .Where(item => item.Role.SiteId == SiteId);
+ .Where(item => item.Role.SiteId == SiteId || item.Role.SiteId == null);
}
public IEnumerable GetUserRoles(int UserId, int SiteId)
diff --git a/Oqtane.Server/Scripts/00.00.00.sql b/Oqtane.Server/Scripts/00.00.00.sql
index f5c4e281..7f820d2c 100644
--- a/Oqtane.Server/Scripts/00.00.00.sql
+++ b/Oqtane.Server/Scripts/00.00.00.sql
@@ -214,7 +214,7 @@ CREATE TABLE [dbo].[Log] (
[PageId] [int] NULL,
[ModuleId] [int] NULL,
[UserId] [int] NULL,
- [Url] [nvarchar](200) NOT NULL,
+ [Url] [nvarchar](2048) NOT NULL,
[Server] [nvarchar](200) NOT NULL,
[Category] [nvarchar](200) NOT NULL,
[Feature] [nvarchar](200) NOT NULL,
@@ -232,6 +232,49 @@ CREATE TABLE [dbo].[Log] (
)
GO
+CREATE TABLE [dbo].[Notification](
+ [NotificationId] [int] IDENTITY(1,1) NOT NULL,
+ [SiteId] [int] NOT NULL,
+ [FromUserId] [int] NULL,
+ [ToUserId] [int] NULL,
+ [ToEmail] [nvarchar](256) NOT NULL,
+ [Subject] [nvarchar](256) NOT NULL,
+ [Body] [nvarchar](max) NOT NULL,
+ [ParentId] [int] NULL,
+ [CreatedOn] [datetime] NOT NULL,
+ [IsDelivered] [bit] NOT NULL,
+ [DeliveredOn] [datetime] NULL,
+ [DeletedBy] [nvarchar](256) NULL,
+ [DeletedOn] [datetime] NULL,
+ [IsDeleted][bit] NOT NULL,
+ CONSTRAINT [PK_Notification] PRIMARY KEY CLUSTERED
+ (
+ [NotificationId] ASC
+ )
+)
+GO
+
+CREATE TABLE [dbo].[Folder](
+ [FolderId] [int] IDENTITY(1,1) NOT NULL,
+ [SiteId] [int] NOT NULL,
+ [Path] [nvarchar](50) NOT NULL,
+ [Name] [nvarchar](50) NOT NULL,
+ [ParentId] [int] NULL,
+ [Order] [int] NOT NULL,
+ [CreatedBy] [nvarchar](256) NOT NULL,
+ [CreatedOn] [datetime] NOT NULL,
+ [ModifiedBy] [nvarchar](256) NOT NULL,
+ [ModifiedOn] [datetime] NOT NULL,
+ [DeletedBy] [nvarchar](256) NULL,
+ [DeletedOn] [datetime] NULL,
+ [IsDeleted][bit] NOT NULL,
+ CONSTRAINT [PK_Folder] PRIMARY KEY CLUSTERED
+ (
+ [FolderId] ASC
+ )
+)
+GO
+
CREATE TABLE [dbo].[HtmlText](
[HtmlTextId] [int] IDENTITY(1,1) NOT NULL,
[ModuleId] [int] NOT NULL,
@@ -308,6 +351,16 @@ REFERENCES [dbo].[Site] ([SiteId])
ON DELETE CASCADE
GO
+ALTER TABLE [dbo].[Notification] WITH CHECK ADD CONSTRAINT [FK_Notification_Site] FOREIGN KEY([SiteId])
+REFERENCES [dbo].[Site] ([SiteId])
+ON DELETE CASCADE
+GO
+
+ALTER TABLE [dbo].[Folder] WITH CHECK ADD CONSTRAINT [FK_Folder_Site] FOREIGN KEY([SiteId])
+REFERENCES [dbo].[Site] ([SiteId])
+ON DELETE CASCADE
+GO
+
ALTER TABLE [dbo].[HtmlText] WITH CHECK ADD CONSTRAINT [FK_HtmlText_Module] FOREIGN KEY([ModuleId])
REFERENCES [dbo].[Module] ([ModuleId])
ON DELETE CASCADE
@@ -360,3 +413,9 @@ CREATE UNIQUE NONCLUSTERED INDEX IX_UserRole ON dbo.UserRole
) ON [PRIMARY]
GO
+CREATE UNIQUE NONCLUSTERED INDEX IX_Folder ON dbo.Folder
+ (
+ SiteId,
+ [Path]
+ ) ON [PRIMARY]
+GO
diff --git a/Oqtane.Server/Scripts/Master.sql b/Oqtane.Server/Scripts/Master.sql
index b2f05b65..0db09f12 100644
--- a/Oqtane.Server/Scripts/Master.sql
+++ b/Oqtane.Server/Scripts/Master.sql
@@ -139,7 +139,7 @@ GO
SET IDENTITY_INSERT [dbo].[Job] ON
GO
INSERT [dbo].[Job] ([JobId], [Name], [JobType], [Frequency], [Interval], [StartDate], [EndDate], [IsEnabled], [IsStarted], [IsExecuting], [NextExecution], [RetentionHistory], [CreatedBy], [CreatedOn], [ModifiedBy], [ModifiedOn])
-VALUES (1, N'Sample Daily Job', N'Oqtane.Infrastructure.SampleJob, Oqtane.Server', N'd', 1, null, null, 1, 0, 0, null, 10, '', getdate(), '', getdate())
+VALUES (1, N'Notification Job', N'Oqtane.Infrastructure.NotificationJob, Oqtane.Server', N'm', 1, null, null, 1, 0, 0, null, 10, '', getdate(), '', getdate())
GO
SET IDENTITY_INSERT [dbo].[Job] OFF
GO
diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs
index 9739eade..3e65ec1c 100644
--- a/Oqtane.Server/Startup.cs
+++ b/Oqtane.Server/Startup.cs
@@ -106,6 +106,7 @@ namespace Oqtane.Server
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddSingleton();
@@ -179,6 +180,7 @@ namespace Oqtane.Server
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
services.AddOqtaneModules();
services.AddOqtaneThemes();
@@ -326,6 +328,7 @@ namespace Oqtane.Server
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
services.AddOqtaneModules();
services.AddOqtaneThemes();
diff --git a/Oqtane.Server/wwwroot/css/app.css b/Oqtane.Server/wwwroot/css/app.css
index ff45a8b4..e4253c17 100644
--- a/Oqtane.Server/wwwroot/css/app.css
+++ b/Oqtane.Server/wwwroot/css/app.css
@@ -92,3 +92,21 @@ app {
height: 1px;
background-color: gray;
}
+
+.app-link-unstyled, .app-link-unstyled:visited, .app-link-unstyled:hover, .app-link-unstyled:active, .app-link-unstyled:focus, .app-link-unstyled:active:hover {
+ font-style: inherit;
+ color: inherit;
+ background-color: transparent;
+ font-size: inherit;
+ text-decoration: none;
+ font-variant: inherit;
+ font-weight: inherit;
+ line-height: inherit;
+ font-family: inherit;
+ border-radius: inherit;
+ border: inherit;
+ outline: inherit;
+ box-shadow: inherit;
+ padding: inherit;
+ vertical-align: inherit;
+}
diff --git a/Oqtane.Shared/Models/Folder.cs b/Oqtane.Shared/Models/Folder.cs
new file mode 100644
index 00000000..c632eac4
--- /dev/null
+++ b/Oqtane.Shared/Models/Folder.cs
@@ -0,0 +1,30 @@
+using System;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Oqtane.Models
+{
+ public class Folder : IAuditable
+ {
+ public int FolderId { get; set; }
+ public int SiteId { get; set; }
+ public int? ParentId { get; set; }
+ public string Name { get; set; }
+ public string Path { get; set; }
+ public int Order { get; set; }
+
+ public string CreatedBy { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public string ModifiedBy { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public string DeletedBy { get; set; }
+ public DateTime? DeletedOn { get; set; }
+ public bool IsDeleted { get; set; }
+
+ [NotMapped]
+ public string Permissions { get; set; }
+ [NotMapped]
+ public int Level { get; set; }
+ [NotMapped]
+ public bool HasChildren { get; set; }
+ }
+}
diff --git a/Oqtane.Shared/Models/Notification.cs b/Oqtane.Shared/Models/Notification.cs
new file mode 100644
index 00000000..0d804e41
--- /dev/null
+++ b/Oqtane.Shared/Models/Notification.cs
@@ -0,0 +1,29 @@
+using System;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Oqtane.Models
+{
+ public class Notification : IDeletable
+ {
+ public int NotificationId { get; set; }
+ public int SiteId { get; set; }
+ public int? FromUserId { get; set; }
+ public int? ToUserId { get; set; }
+ public string ToEmail { get; set; }
+ public int? ParentId { get; set; }
+ public string Subject { get; set; }
+ public string Body { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public bool IsDelivered { get; set; }
+ public DateTime? DeliveredOn { get; set; }
+ public string DeletedBy { get; set; }
+ public DateTime? DeletedOn { get; set; }
+ public bool IsDeleted { get; set; }
+
+ [ForeignKey("FromUserId")]
+ public User FromUser { get; set; }
+ [ForeignKey("ToUserId")]
+ public User ToUser { get; set; }
+ }
+
+}
diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs
index faf458f0..e6c2fcca 100644
--- a/Oqtane.Shared/Shared/Constants.cs
+++ b/Oqtane.Shared/Shared/Constants.cs
@@ -25,6 +25,8 @@
public const string PageManagementModule = "Oqtane.Modules.Admin.Pages, Oqtane.Client";
public const string ModuleMessageComponent = "Oqtane.Modules.Controls.ModuleMessage, Oqtane.Client";
+ public const string HostUser = "host";
+
public const string AllUsersRole = "All Users";
public const string HostRole = "Host Users";
public const string AdminRole = "Administrators";
|