notification service and user management improvements

This commit is contained in:
Shaun Walker
2020-02-03 16:43:37 -05:00
parent d8d5e768b2
commit 0aed11e71c
50 changed files with 2077 additions and 284 deletions

View File

@ -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/<controller>?siteid=x
[HttpGet]
public IEnumerable<Folder> Get(string siteid)
{
if (siteid == "")
{
return Folders.GetFolders();
}
else
{
return Folders.GetFolders(int.Parse(siteid));
}
}
// GET api/<controller>/5?userid=x
[HttpGet("{id}")]
public Folder Get(int id)
{
return Folders.GetFolder(id);
}
// POST api/<controller>
[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/<controller>/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/<controller>/?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<Folder> 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/<controller>/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);
}
}
}
}

View File

@ -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/<controller>?siteid=x&type=y&userid=z
[HttpGet]
[Authorize(Roles = Constants.RegisteredRole)]
public IEnumerable<Notification> Get(string siteid, string direction, string userid)
{
IEnumerable<Notification> 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/<controller>/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/<controller>
[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/<controller>/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/<controller>/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;
}
}
}

View File

@ -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<IdentityUser> IdentityUserManager;
private readonly SignInManager<IdentityUser> IdentitySignInManager;
private readonly ITenantResolver Tenants;
private readonly INotificationRepository Notifications;
private readonly ILogManager logger;
public UserController(IUserRepository Users, IRoleRepository Roles, IUserRoleRepository UserRoles, UserManager<IdentityUser> IdentityUserManager, SignInManager<IdentityUser> IdentitySignInManager, ILogManager logger)
public UserController(IUserRepository Users, IRoleRepository Roles, IUserRoleRepository UserRoles, UserManager<IdentityUser> IdentityUserManager, SignInManager<IdentityUser> 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<Role> 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/<controller>/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/<controller>/reset
[HttpPost("reset")]
public async Task<User> 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/<controller>/current
[HttpGet("authenticate")]
public User Authenticate()

View File

@ -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<IAliasRepository>();
List<Alias> 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>();
sitestate.Alias = alias;
// get services which require tenant resolution
var Sites = provider.GetRequiredService<ISiteRepository>();
var Settings = provider.GetRequiredService<ISettingRepository>();
var Notifications = provider.GetRequiredService<INotificationRepository>();
// iterate through sites
List<Site> sites = Sites.GetSites().ToList();
foreach (Site site in sites)
{
log += "Processing Notifications For Site: " + site.Name + "\n\n";
// get site settings
List<Setting> sitesettings = Settings.GetSettings("Site", site.SiteId).ToList();
Dictionary<string, string> 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<Notification> 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<string, string> GetSettings(List<Setting> Settings)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
foreach (Setting setting in Settings.OrderBy(item => item.SettingName).ToList())
{
dictionary.Add(setting.SettingName, setting.SettingValue);
}
return dictionary;
}
}
}

View File

@ -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<IAliasRepository>();
Alias alias = Aliases.GetAliases().FirstOrDefault();
// use the SiteState to set the Alias explicitly so the tenant can be resolved
var sitestate = provider.GetRequiredService<SiteState>();
sitestate.Alias = alias;
// call a repository service which requires tenant resolution
var Sites = provider.GetRequiredService<ISiteRepository>();
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;
}
}
}

View File

@ -41,8 +41,8 @@
<PackageReference Include="Microsoft.AspNetCore.Blazor.Server" Version="3.1.0-preview4.19579.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc3" />
</ItemGroup>

View File

@ -1,3 +1,3 @@
@page "/login"
@page "/pages/login"
@namespace Oqtane.Pages
@model Oqtane.Pages.LoginModel

View File

@ -1,3 +1,3 @@
@page "/logout"
@page "/pages/logout"
@namespace Oqtane.Pages
@model Oqtane.Pages.LogoutModel

View File

@ -0,0 +1,3 @@
@page "/pages/verify"
@namespace Oqtane.Pages
@model Oqtane.Pages.VerifyModel

View File

@ -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<IdentityUser> IdentityUserManager;
public VerifyModel(IUserRepository Users, UserManager<IdentityUser> IdentityUserManager)
{
this.Users = Users;
this.IdentityUserManager = IdentityUserManager;
}
public async Task<IActionResult> 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());
}
}
}

View File

@ -17,6 +17,8 @@ namespace Oqtane.Repository
public virtual DbSet<Permission> Permission { get; set; }
public virtual DbSet<Setting> Setting { get; set; }
public virtual DbSet<Log> Log { get; set; }
public virtual DbSet<Notification> Notification { get; set; }
public virtual DbSet<Folder> Folder { get; set; }
public TenantDBContext(ITenantResolver TenantResolver, IHttpContextAccessor accessor) : base(TenantResolver, accessor)
{

View File

@ -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<Folder> GetFolders()
{
return db.Folder.ToList();
}
public IEnumerable<Folder> GetFolders(int SiteId)
{
IEnumerable<Permission> permissions = Permissions.GetPermissions(SiteId, "Folder").ToList();
IEnumerable<Folder> 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<Permission> 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();
}
}
}

View File

@ -0,0 +1,15 @@
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface IFolderRepository
{
IEnumerable<Folder> GetFolders();
IEnumerable<Folder> GetFolders(int SiteId);
Folder AddFolder(Folder Folder);
Folder UpdateFolder(Folder Folder);
Folder GetFolder(int FolderId);
void DeleteFolder(int FolderId);
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface INotificationRepository
{
IEnumerable<Notification> GetNotifications(int SiteId, int FromUserId, int ToUserId);
Notification AddNotification(Notification Notification);
Notification UpdateNotification(Notification Notification);
Notification GetNotification(int NotificationId);
void DeleteNotification(int NotificationId);
}
}

View File

@ -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<Notification> 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();
}
}
}

View File

@ -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<PageTemplateModule> {
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<PageTemplateModule> {
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<PageTemplateModule> {
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 = "" }
}});

View File

@ -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<UserRole> GetUserRoles(int UserId, int SiteId)

View File

@ -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

View File

@ -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

View File

@ -106,6 +106,7 @@ namespace Oqtane.Server
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<INotificationService, NotificationService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
@ -179,6 +180,7 @@ namespace Oqtane.Server
services.AddTransient<ILogManager, LogManager>();
services.AddTransient<IJobRepository, JobRepository>();
services.AddTransient<IJobLogRepository, JobLogRepository>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddOqtaneModules();
services.AddOqtaneThemes();
@ -326,6 +328,7 @@ namespace Oqtane.Server
services.AddTransient<ILogManager, LogManager>();
services.AddTransient<IJobRepository, JobRepository>();
services.AddTransient<IJobLogRepository, JobLogRepository>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddOqtaneModules();
services.AddOqtaneThemes();

View File

@ -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;
}