Merge pull request #1861 from sbwalker/dev

added support for url mapping and viitors
This commit is contained in:
Shaun Walker
2021-12-09 08:40:15 -05:00
committed by GitHub
45 changed files with 2212 additions and 127 deletions

View File

@ -164,6 +164,14 @@ namespace Oqtane.Controllers
authorized = User.IsInRole(RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
}
break;
case EntityNames.Visitor:
authorized = false;
var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId))
{
authorized = (visitorId == entityId);
}
break;
}
return authorized;
}

View File

@ -0,0 +1,119 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Oqtane.Enums;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using System.Net;
namespace Oqtane.Controllers
{
[Route(ControllerRoutes.ApiRoute)]
public class UrlMappingController : Controller
{
private readonly IUrlMappingRepository _urlMappings;
private readonly ILogManager _logger;
private readonly Alias _alias;
public UrlMappingController(IUrlMappingRepository urlMappings, ILogManager logger, ITenantManager tenantManager)
{
_urlMappings = urlMappings;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET: api/<controller>?siteid=x&ismapped=y
[HttpGet]
[Authorize(Roles = RoleNames.Admin)]
public IEnumerable<UrlMapping> Get(string siteid, string ismapped)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
return _urlMappings.GetUrlMappings(SiteId, bool.Parse(ismapped));
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Get Attempt {SiteId}", siteid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Roles = RoleNames.Admin)]
public UrlMapping Get(int id)
{
var urlMapping = _urlMappings.GetUrlMapping(id);
if (urlMapping != null && (urlMapping.SiteId == _alias.SiteId))
{
return urlMapping;
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Get Attempt {UrlMappingId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Admin)]
public UrlMapping Post([FromBody] UrlMapping urlMapping)
{
if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId)
{
urlMapping = _urlMappings.AddUrlMapping(urlMapping);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "UrlMapping Added {UrlMapping}", urlMapping);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Post Attempt {Role}", urlMapping);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
urlMapping = null;
}
return urlMapping;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Admin)]
public UrlMapping Put(int id, [FromBody] UrlMapping urlMapping)
{
if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId && _urlMappings.GetUrlMapping(urlMapping.UrlMappingId, false) != null)
{
urlMapping = _urlMappings.UpdateUrlMapping(urlMapping);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "UrlMapping Updated {UrlMapping}", urlMapping);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Put Attempt {UrlMapping}", urlMapping);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
urlMapping = null;
}
return urlMapping;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)]
public void Delete(int id)
{
var urlMapping = _urlMappings.GetUrlMapping(id);
if (urlMapping != null && urlMapping.SiteId == _alias.SiteId)
{
_urlMappings.DeleteUrlMapping(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "UrlMapping Deleted {UrlMappingId}", id);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Delete Attempt {UrlMappingId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@ -0,0 +1,46 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Oqtane.Enums;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using System.Net;
using System;
namespace Oqtane.Controllers
{
[Route(ControllerRoutes.ApiRoute)]
public class VisitorController : Controller
{
private readonly IVisitorRepository _visitors;
private readonly ILogManager _logger;
private readonly Alias _alias;
public VisitorController(IVisitorRepository visitors, ILogManager logger, ITenantManager tenantManager)
{
_visitors = visitors;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET: api/<controller>?siteid=x&fromdate=y
[HttpGet]
[Authorize(Roles = RoleNames.Admin)]
public IEnumerable<Visitor> Get(string siteid, string fromdate)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
return _visitors.GetVisitors(SiteId, DateTime.Parse(fromdate));
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Visitor Get Attempt {SiteId}", siteid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
}
}

View File

@ -98,6 +98,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddTransient<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>();
services.AddTransient<ILanguageRepository, LanguageRepository>();
services.AddTransient<IVisitorRepository, VisitorRepository>();
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
// obsolete - replaced by ITenantManager
services.AddTransient<ITenantResolver, TenantResolver>();

View File

@ -582,12 +582,19 @@ namespace Oqtane.Infrastructure
TenantId = tenant.TenantId,
Name = install.SiteName,
LogoFileId = null,
FaviconFileId = null,
PwaIsEnabled = false,
PwaAppIconFileId = null,
PwaSplashIconFileId = null,
AllowRegistration = false,
CaptureBrokenUrls = true,
VisitorTracking = true,
DefaultThemeType = (!string.IsNullOrEmpty(install.DefaultTheme)) ? install.DefaultTheme : Constants.DefaultTheme,
DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer,
AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer,
SiteTemplateType = install.SiteTemplate,
Runtime = (!string.IsNullOrEmpty(install.Runtime)) ? install.Runtime : _configManager.GetSection("Runtime").Value,
RenderMode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value
RenderMode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value,
};
site = sites.AddSite(site);

View File

@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Oqtane.Extensions;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
@ -37,9 +38,6 @@ namespace Oqtane.Infrastructure
switch (version)
{
case "1.0.0":
Upgrade_1_0_0(tenant, scope);
break;
case "2.0.2":
Upgrade_2_0_2(tenant, scope);
break;
@ -49,61 +47,13 @@ namespace Oqtane.Infrastructure
case "2.2.0":
Upgrade_2_2_0(tenant, scope);
break;
case "3.0.1":
Upgrade_3_0_1(tenant, scope);
break;
}
}
}
/// <summary>
/// **Note: this code is commented out on purpose - it provides an example of how to programmatically add a page to all existing sites on upgrade
/// </summary>
/// <param name="tenant"></param>
/// <param name="scope"></param>
private void Upgrade_1_0_0(Tenant tenant, IServiceScope scope)
{
//var pageTemplates = new List<PageTemplate>();
//
//pageTemplates.Add(new PageTemplate
//{
// Name = "Test",
// Parent = "",
// Order = 1,
// Path = "test",
// Icon = Icons.Badge,
// IsNavigation = true,
// IsPersonalizable = false,
// IsClickable = true,
// PagePermissions = new List<Permission>
// {
// new Permission(PermissionNames.View, RoleNames.Admin, true),
// new Permission(PermissionNames.View, RoleNames.Everyone, true),
// new Permission(PermissionNames.Edit, RoleNames.Admin, true)
// }.EncodePermissions(),
// PageTemplateModules = new List<PageTemplateModule>
// {
// new PageTemplateModule
// {
// ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Login.Index).ToModuleDefinitionName(), Title = "Test", Pane = "Content",
// ModulePermissions = new List<Permission>
// {
// new Permission(PermissionNames.View, RoleNames.Admin, true),
// new Permission(PermissionNames.View, RoleNames.Everyone, true),
// new Permission(PermissionNames.Edit, RoleNames.Admin, true)
// }.EncodePermissions(),
// Content = ""
// }
// }
//});
//
//if (pageTemplates.Count != 0)
//{
// var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
// foreach (Site site in sites.GetSites().ToList())
// {
// sites.CreatePages(site, pageTemplates);
// }
//}
}
private void Upgrade_2_0_2(Tenant tenant, IServiceScope scope)
{
if (tenant.Name == TenantNames.Master)
@ -163,5 +113,74 @@ namespace Oqtane.Infrastructure
}
}
}
private void Upgrade_3_0_1(Tenant tenant, IServiceScope scope)
{
var pageTemplates = new List<PageTemplate>();
pageTemplates.Add(new PageTemplate
{
Name = "Url Mappings",
Parent = "Admin",
Order = 33,
Path = "admin/urlmappings",
Icon = Icons.LinkBroken,
IsNavigation = true,
IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = ""
}
}
});
pageTemplates.Add(new PageTemplate
{
Name = "Visitor Management",
Parent = "Admin",
Order = 35,
Path = "admin/visitors",
Icon = Icons.Eye,
IsNavigation = true,
IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = ""
}
}
});
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
foreach (Site site in sites.GetSites().ToList())
{
sites.CreatePages(site, pageTemplates);
}
}
}
}

View File

@ -0,0 +1,51 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
using Oqtane.Databases.Interfaces;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Oqtane.Migrations.EntityBuilders
{
public class UrlMappingEntityBuilder : BaseEntityBuilder<UrlMappingEntityBuilder>
{
private const string _entityTableName = "UrlMapping";
private readonly PrimaryKey<UrlMappingEntityBuilder> _primaryKey = new("PK_UrlMapping", x => x.UrlMappingId);
private readonly ForeignKey<UrlMappingEntityBuilder> _urlMappingForeignKey = new("FK_UrlMapping_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
public UrlMappingEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
ForeignKeys.Add(_urlMappingForeignKey);
}
protected override UrlMappingEntityBuilder BuildTable(ColumnsBuilder table)
{
UrlMappingId = AddAutoIncrementColumn(table, "UrlMappingId");
SiteId = AddIntegerColumn(table, "SiteId");
Url = AddStringColumn(table, "Url", 500);
MappedUrl = AddStringColumn(table, "MappedUrl", 500);
Requests = AddIntegerColumn(table, "Requests");
CreatedOn = AddDateTimeColumn(table, "CreatedOn");
RequestedOn = AddDateTimeColumn(table, "RequestedOn");
return this;
}
public OperationBuilder<AddColumnOperation> UrlMappingId { get; private set; }
public OperationBuilder<AddColumnOperation> SiteId { get; private set; }
public OperationBuilder<AddColumnOperation> Url { get; private set; }
public OperationBuilder<AddColumnOperation> MappedUrl { get; private set; }
public OperationBuilder<AddColumnOperation> Requests { get; private set; }
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
public OperationBuilder<AddColumnOperation> RequestedOn { get; private set; }
}
}

View File

@ -0,0 +1,57 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
using Oqtane.Databases.Interfaces;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Oqtane.Migrations.EntityBuilders
{
public class VisitorEntityBuilder : BaseEntityBuilder<VisitorEntityBuilder>
{
private const string _entityTableName = "Visitor";
private readonly PrimaryKey<VisitorEntityBuilder> _primaryKey = new("PK_Visitor", x => x.VisitorId);
private readonly ForeignKey<VisitorEntityBuilder> _visitorForeignKey = new("FK_Visitor_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
public VisitorEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
ForeignKeys.Add(_visitorForeignKey);
}
protected override VisitorEntityBuilder BuildTable(ColumnsBuilder table)
{
VisitorId = AddAutoIncrementColumn(table, "VisitorId");
SiteId = AddIntegerColumn(table, "SiteId");
UserId = AddIntegerColumn(table, "UserId", true);
Visits = AddIntegerColumn(table, "Visits");
IPAddress = AddStringColumn(table,"IPAddress", 50);
UserAgent = AddStringColumn(table, "UserAgent", 256);
Language = AddStringColumn(table, "Language", 50);
CreatedOn = AddDateTimeColumn(table, "CreatedOn");
VisitedOn = AddDateTimeColumn(table, "VisitedOn");
return this;
}
public OperationBuilder<AddColumnOperation> VisitorId { get; private set; }
public OperationBuilder<AddColumnOperation> SiteId { get; private set; }
public OperationBuilder<AddColumnOperation> UserId { get; private set; }
public OperationBuilder<AddColumnOperation> Visits { get; private set; }
public OperationBuilder<AddColumnOperation> IPAddress { get; private set; }
public OperationBuilder<AddColumnOperation> UserAgent { get; private set; }
public OperationBuilder<AddColumnOperation> Language { get; private set; }
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
public OperationBuilder<AddColumnOperation> VisitedOn { get; private set; }
}
}

View File

@ -0,0 +1,29 @@
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.03.00.01.02")]
public class AddVisitorTable : MultiDatabaseMigration
{
public AddVisitorTable(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var visitorEntityBuilder = new VisitorEntityBuilder(migrationBuilder, ActiveDatabase);
visitorEntityBuilder.Create();
}
protected override void Down(MigrationBuilder migrationBuilder)
{
var visitorEntityBuilder = new VisitorEntityBuilder(migrationBuilder, ActiveDatabase);
visitorEntityBuilder.Drop();
}
}
}

View File

@ -0,0 +1,30 @@
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.03.00.01.03")]
public class AddUrlMappingTable : MultiDatabaseMigration
{
public AddUrlMappingTable(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var urlMappingEntityBuilder = new UrlMappingEntityBuilder(migrationBuilder, ActiveDatabase);
urlMappingEntityBuilder.Create();
urlMappingEntityBuilder.AddIndex("IX_UrlMapping", new[] { "SiteId", "Url" }, true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
var urlMappingEntityBuilder = new UrlMappingEntityBuilder(migrationBuilder, ActiveDatabase);
urlMappingEntityBuilder.Drop();
}
}
}

View File

@ -0,0 +1,35 @@
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.03.00.01.04")]
public class AddSiteVisitorTracking : MultiDatabaseMigration
{
public AddSiteVisitorTracking(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
siteEntityBuilder.AddBooleanColumn("VisitorTracking", true);
siteEntityBuilder.UpdateColumn("VisitorTracking", "1", "bool", "");
siteEntityBuilder.AddBooleanColumn("CaptureBrokenUrls", true);
siteEntityBuilder.UpdateColumn("CaptureBrokenUrls", "1", "bool", "");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
siteEntityBuilder.DropColumn("VisitorTracking");
siteEntityBuilder.DropColumn("CaptureBrokenUrls");
}
}
}

View File

@ -22,7 +22,7 @@
<body>
@(Html.AntiForgeryToken())
<app>
<component type="typeof(Oqtane.App)" render-mode="@Model.RenderMode" param-AntiForgeryToken="@Model.AntiForgeryToken" param-Runtime="@Model.Runtime" param-RenderMode="@Model.RenderMode.ToString()" />
<component type="typeof(Oqtane.App)" render-mode="@Model.RenderMode" param-AntiForgeryToken="@Model.AntiForgeryToken" param-Runtime="@Model.Runtime" param-RenderMode="@Model.RenderMode.ToString()" param-VisitorId="@Model.VisitorId" />
</app>
<div id="blazor-error-ui">

View File

@ -14,6 +14,10 @@ using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
namespace Oqtane.Pages
{
@ -26,8 +30,10 @@ namespace Oqtane.Pages
private readonly IAntiforgery _antiforgery;
private readonly ISiteRepository _sites;
private readonly IPageRepository _pages;
private readonly IUrlMappingRepository _urlMappings;
private readonly IVisitorRepository _visitors;
public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages)
public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors)
{
_configuration = configuration;
_tenantManager = tenantManager;
@ -36,11 +42,14 @@ namespace Oqtane.Pages
_antiforgery = antiforgery;
_sites = sites;
_pages = pages;
_urlMappings = urlMappings;
_visitors = visitors;
}
public string AntiForgeryToken = "";
public string Runtime = "Server";
public RenderMode RenderMode = RenderMode.Server;
public int VisitorId = -1;
public string HeadResources = "";
public string BodyResources = "";
public string Title = "";
@ -48,7 +57,7 @@ namespace Oqtane.Pages
public string PWAScript = "";
public string ThemeType = "";
public void OnGet()
public IActionResult OnGet()
{
AntiForgeryToken = _antiforgery.GetAndStoreTokens(HttpContext).RequestToken;
@ -92,6 +101,11 @@ namespace Oqtane.Pages
Title = site.Name;
ThemeType = site.DefaultThemeType;
if (site.VisitorTracking)
{
TrackVisitor(site.SiteId);
}
var page = _pages.GetPage(route.PagePath, site.SiteId);
if (page != null)
{
@ -111,6 +125,37 @@ namespace Oqtane.Pages
ThemeType = page.ThemeType;
}
}
else
{
// page does not exist
var url = route.SiteUrl + "/" + route.PagePath;
var urlMapping = _urlMappings.GetUrlMapping(site.SiteId, url);
if (urlMapping == null)
{
if (site.CaptureBrokenUrls)
{
urlMapping = new UrlMapping();
urlMapping.SiteId = site.SiteId;
urlMapping.Url = url;
urlMapping.MappedUrl = "";
urlMapping.Requests = 1;
urlMapping.CreatedOn = DateTime.UtcNow;
urlMapping.RequestedOn = DateTime.UtcNow;
_urlMappings.AddUrlMapping(urlMapping);
}
}
else
{
urlMapping.Requests += 1;
urlMapping.RequestedOn = DateTime.UtcNow;
_urlMappings.UpdateUrlMapping(urlMapping);
if (!string.IsNullOrEmpty(urlMapping.MappedUrl))
{
return RedirectPermanent(urlMapping.MappedUrl);
}
}
}
}
// include global resources
@ -139,6 +184,64 @@ namespace Oqtane.Pages
}
}
}
return Page();
}
private void TrackVisitor(int SiteId)
{
var VisitorCookie = "APP_VISITOR_" + SiteId.ToString();
if (!int.TryParse(Request.Cookies[VisitorCookie], out VisitorId))
{
var visitor = new Visitor();
visitor.SiteId = SiteId;
visitor.IPAddress = HttpContext.Connection.RemoteIpAddress.ToString();
visitor.UserAgent = Request.Headers[HeaderNames.UserAgent];
visitor.Language = Request.Headers[HeaderNames.AcceptLanguage];
if (visitor.Language.Contains(","))
{
visitor.Language = visitor.Language.Substring(0, visitor.Language.IndexOf(","));
}
visitor.UserId = null;
visitor.Visits = 1;
visitor.CreatedOn = DateTime.UtcNow;
visitor.VisitedOn = DateTime.UtcNow;
visitor = _visitors.AddVisitor(visitor);
Response.Cookies.Append(
VisitorCookie,
visitor.VisitorId.ToString(),
new CookieOptions()
{
Expires = DateTimeOffset.UtcNow.AddYears(1),
IsEssential = true
}
);
}
else
{
var visitor = _visitors.GetVisitor(VisitorId);
if (visitor != null)
{
visitor.IPAddress = HttpContext.Connection.RemoteIpAddress.ToString();
visitor.UserAgent = Request.Headers[HeaderNames.UserAgent];
visitor.Language = Request.Headers[HeaderNames.AcceptLanguage];
if (visitor.Language.Contains(","))
{
visitor.Language = visitor.Language.Substring(0, visitor.Language.IndexOf(","));
}
if (User.HasClaim(item => item.Type == ClaimTypes.PrimarySid))
{
visitor.UserId = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value);
}
visitor.Visits += 1;
visitor.VisitedOn = DateTime.UtcNow;
_visitors.UpdateVisitor(visitor);
}
else
{
Response.Cookies.Delete(VisitorCookie);
}
}
}
private string CreatePWAScript(Alias alias, Site site, Route route)

View File

@ -29,5 +29,7 @@ namespace Oqtane.Repository
public virtual DbSet<Folder> Folder { get; set; }
public virtual DbSet<File> File { get; set; }
public virtual DbSet<Language> Language { get; set; }
public virtual DbSet<Visitor> Visitor { get; set; }
public virtual DbSet<UrlMapping> UrlMapping { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface IUrlMappingRepository
{
IEnumerable<UrlMapping> GetUrlMappings(int siteId, bool isMapped);
UrlMapping AddUrlMapping(UrlMapping urlMapping);
UrlMapping UpdateUrlMapping(UrlMapping urlMapping);
UrlMapping GetUrlMapping(int urlMappingId);
UrlMapping GetUrlMapping(int urlMappingId, bool tracking);
UrlMapping GetUrlMapping(int siteId, string url);
void DeleteUrlMapping(int urlMappingId);
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface IVisitorRepository
{
IEnumerable<Visitor> GetVisitors(int siteId, DateTime fromDate);
Visitor AddVisitor(Visitor visitor);
Visitor UpdateVisitor(Visitor visitor);
Visitor GetVisitor(int visitorId);
void DeleteVisitor(int visitorId);
}
}

View File

@ -615,13 +615,70 @@ namespace Oqtane.Repository
}
}
});
pageTemplates.Add(new PageTemplate
{
Name = "Url Mappings",
Parent = "Admin",
Order = 15,
Path = "admin/urlmappings",
Icon = Icons.LinkBroken,
IsNavigation = true,
IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = ""
}
}
});
pageTemplates.Add(new PageTemplate
{
Name = "Visitor Management",
Parent = "Admin",
Order = 17,
Path = "admin/visitors",
Icon = Icons.Eye,
IsNavigation = true,
IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = ""
}
}
});
// host pages
pageTemplates.Add(new PageTemplate
{
Name = "Event Log",
Parent = "Admin",
Order = 15,
Order = 19,
Path = "admin/log",
Icon = Icons.MagnifyingGlass,
IsNavigation = false,
@ -649,7 +706,7 @@ namespace Oqtane.Repository
{
Name = "Site Management",
Parent = "Admin",
Order = 17,
Order = 21,
Path = "admin/sites",
Icon = Icons.Globe,
IsNavigation = false,
@ -677,7 +734,7 @@ namespace Oqtane.Repository
{
Name = "Module Management",
Parent = "Admin",
Order = 19,
Order = 23,
Path = "admin/modules",
Icon = Icons.Browser,
IsNavigation = false,
@ -705,7 +762,7 @@ namespace Oqtane.Repository
{
Name = "Theme Management",
Parent = "Admin",
Order = 21,
Order = 25,
Path = "admin/themes",
Icon = Icons.Brush,
IsNavigation = false,
@ -733,7 +790,7 @@ namespace Oqtane.Repository
{
Name = "Language Management",
Parent = "Admin",
Order = 23,
Order = 27,
Path = "admin/languages",
Icon = Icons.Text,
IsNavigation = false,
@ -765,7 +822,7 @@ namespace Oqtane.Repository
{
Name = "Scheduled Jobs",
Parent = "Admin",
Order = 25,
Order = 29,
Path = "admin/jobs",
Icon = Icons.Timer,
IsNavigation = false,
@ -793,7 +850,7 @@ namespace Oqtane.Repository
{
Name = "Sql Management",
Parent = "Admin",
Order = 27,
Order = 31,
Path = "admin/sql",
Icon = Icons.Spreadsheet,
IsNavigation = false,
@ -821,7 +878,7 @@ namespace Oqtane.Repository
{
Name = "System Info",
Parent = "Admin",
Order = 29,
Order = 33,
Path = "admin/system",
Icon = Icons.MedicalCross,
IsNavigation = false,
@ -849,7 +906,7 @@ namespace Oqtane.Repository
{
Name = "System Update",
Parent = "Admin",
Order = 31,
Order = 35,
Path = "admin/update",
Icon = Icons.Aperture,
IsNavigation = false,

View File

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
namespace Oqtane.Repository
{
public class UrlMappingRepository : IUrlMappingRepository
{
private TenantDBContext _db;
public UrlMappingRepository(TenantDBContext context)
{
_db = context;
}
public IEnumerable<UrlMapping> GetUrlMappings(int siteId, bool isMapped)
{
if (isMapped)
{
return _db.UrlMapping.Where(item => item.SiteId == siteId && !string.IsNullOrEmpty(item.MappedUrl)).Take(200);
}
else
{
return _db.UrlMapping.Where(item => item.SiteId == siteId && string.IsNullOrEmpty(item.MappedUrl)).Take(200);
}
}
public UrlMapping AddUrlMapping(UrlMapping urlMapping)
{
_db.UrlMapping.Add(urlMapping);
_db.SaveChanges();
return urlMapping;
}
public UrlMapping UpdateUrlMapping(UrlMapping urlMapping)
{
_db.Entry(urlMapping).State = EntityState.Modified;
_db.SaveChanges();
return urlMapping;
}
public UrlMapping GetUrlMapping(int urlMappingId)
{
return GetUrlMapping(urlMappingId, true);
}
public UrlMapping GetUrlMapping(int urlMappingId, bool tracking)
{
if (tracking)
{
return _db.UrlMapping.Find(urlMappingId);
}
else
{
return _db.UrlMapping.AsNoTracking().FirstOrDefault(item => item.UrlMappingId == urlMappingId);
}
}
public UrlMapping GetUrlMapping(int siteId, string url)
{
return _db.UrlMapping.Where(item => item.SiteId == siteId && item.Url == url).FirstOrDefault();
}
public void DeleteUrlMapping(int urlMappingId)
{
UrlMapping urlMapping = _db.UrlMapping.Find(urlMappingId);
_db.UrlMapping.Remove(urlMapping);
_db.SaveChanges();
}
}
}

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
namespace Oqtane.Repository
{
public class VisitorRepository : IVisitorRepository
{
private TenantDBContext _db;
public VisitorRepository(TenantDBContext context)
{
_db = context;
}
public IEnumerable<Visitor> GetVisitors(int siteId, DateTime fromDate)
{
return _db.Visitor.AsNoTracking()
.Include(item => item.User) // eager load users
.Where(item => item.SiteId == siteId && item.VisitedOn >= fromDate);
}
public Visitor AddVisitor(Visitor visitor)
{
_db.Visitor.Add(visitor);
_db.SaveChanges();
return visitor;
}
public Visitor UpdateVisitor(Visitor visitor)
{
_db.Entry(visitor).State = EntityState.Modified;
_db.SaveChanges();
return visitor;
}
public Visitor GetVisitor(int visitorId)
{
return _db.Visitor.Find(visitorId);
}
public void DeleteVisitor(int visitorId)
{
Visitor visitor = _db.Visitor.Find(visitorId);
_db.Visitor.Remove(visitor);
_db.SaveChanges();
}
}
}