Merge pull request #1895 from sbwalker/dev

added support for default alias specification, alias auto registration, alias redirect, alias line break delimiters
This commit is contained in:
Shaun Walker
2021-12-22 15:34:49 -05:00
committed by GitHub
11 changed files with 329 additions and 227 deletions

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Admin.Site @namespace Oqtane.Modules.Admin.Site
@inherits ModuleBase @inherits ModuleBase
@using System.Text.RegularExpressions
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject ISiteService SiteService @inject ISiteService SiteService
@inject ITenantService TenantService @inject ITenantService TenantService
@ -21,31 +22,6 @@
<input id="name" class="form-control" @bind="@_name" maxlength="200" required /> <input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
}
else
{
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" readonly></textarea>
}
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label> <Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -94,9 +70,17 @@
} }
</select> </select>
</div> </div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div> </div>
</div> </div>
</Section>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings"> <Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -176,6 +160,27 @@
</Section> </Section>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
<Section Name="Aliases" Heading="Aliases" ResourceKey="Aliases">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultalias" HelpText="The default alias for the site. Requests for non-default aliases will be redirected to the default alias." ResourceKey="DefaultAlias">Default Alias: </Label>
<div class="col-sm-9">
<select id="defaultalias" class="form-select" @bind="@_defaultalias" required>
@foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
<option value="@name">@name</option>
}
</select>
</div>
</div>
</div>
</Section>
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting"> <Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -238,7 +243,8 @@
private List<ThemeControl> _themes = new List<ThemeControl>(); private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty; private string _name = string.Empty;
private List<Alias> _aliasList; private List<Alias> _aliases;
private string _defaultalias = string.Empty;
private string _urls = string.Empty; private string _urls = string.Empty;
private string _runtime = ""; private string _runtime = "";
private string _prerender = ""; private string _prerender = "";
@ -288,13 +294,7 @@
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
_aliasList = await AliasService.GetAliasesAsync(); await GetAliases();
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{
_urls += alias.Name + ",";
}
_urls = _urls.Substring(0, _urls.Length - 1);
} }
if (site.LogoFileId != null) if (site.LogoFileId != null)
@ -395,14 +395,16 @@
{ {
if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-") if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-")
{ {
_urls = Regex.Replace(_urls, @"\r\n?|\n", ","); // convert line breaks to commas
var unique = true; var unique = true;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray())
{ {
if (_aliasList.Exists(item => item.Name == name && item.SiteId != PageState.Alias.SiteId && item.TenantId != PageState.Alias.TenantId)) var alias = _aliases.Where(item => item.Name == name).FirstOrDefault();
if (alias != null && unique)
{ {
unique = false; unique = (alias.TenantId == PageState.Site.TenantId && alias.SiteId == PageState.Site.SiteId);
} }
} }
} }
@ -486,27 +488,40 @@
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList()) .Select(sValue => sValue.Trim()).ToArray();
foreach (Alias alias in _aliases.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{ {
if (!names.Contains(alias.Name)) if (!names.Contains(alias.Name))
{ {
await AliasService.DeleteAliasAsync(alias.AliasId); await AliasService.DeleteAliasAsync(alias.AliasId);
} }
} }
if (!names.Contains(_defaultalias)) { _defaultalias = names[0]; }
foreach (string name in names) foreach (string name in names)
{ {
if (!_aliasList.Exists(item => item.Name == name)) var alias = _aliases.Find(item => item.Name == name);
if (alias == null)
{ {
Alias alias = new Alias(); alias = new Alias();
alias.Name = name; alias.Name = name;
alias.TenantId = site.TenantId; alias.TenantId = site.TenantId;
alias.SiteId = site.SiteId; alias.SiteId = site.SiteId;
alias.IsDefault = (name == _defaultalias);
await AliasService.AddAliasAsync(alias); await AliasService.AddAliasAsync(alias);
} }
else
{
if (alias.IsDefault != (alias.Name == _defaultalias))
{
alias.IsDefault = (name == _defaultalias);
await AliasService.UpdateAliasAsync(alias);
} }
} }
}
await GetAliases();
}
await logger.LogInformation("Site Settings Saved {Site}", site); await logger.LogInformation("Site Settings Saved {Site}", site);
@ -602,4 +617,19 @@
AddModuleMessage(Localizer["Message.required.Smtp"], MessageType.Warning); AddModuleMessage(Localizer["Message.required.Smtp"], MessageType.Warning);
} }
} }
private async Task GetAliases()
{
_urls = string.Empty;
_defaultalias = string.Empty;
_aliases = await AliasService.GetAliasesAsync();
foreach (Alias alias in _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).ToList())
{
_urls += (_urls == string.Empty) ? alias.Name : ", " + alias.Name;
if (alias.IsDefault && string.IsNullOrEmpty(_defaultalias))
{
_defaultalias = alias.Name;
}
}
}
} }

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Admin.Sites @namespace Oqtane.Modules.Admin.Sites
@using Oqtane.Interfaces @using Oqtane.Interfaces
@using System.Text.RegularExpressions
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject ITenantService TenantService @inject ITenantService TenantService
@ -282,6 +283,7 @@ else
{ {
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-") if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
{ {
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
var duplicates = new List<string>(); var duplicates = new List<string>();
var aliases = await AliasService.GetAliasesAsync(); var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))

View File

@ -43,7 +43,7 @@ else
_sites = new List<Alias>(); _sites = new List<Alias>();
foreach (Alias alias in aliases) foreach (Alias alias in aliases)
{ {
if (!_sites.Exists(item => item.TenantId == alias.TenantId && item.SiteId == alias.SiteId)) if (alias.IsDefault && !_sites.Exists(item => item.TenantId == alias.TenantId && item.SiteId == alias.SiteId))
{ {
_sites.Add(alias); _sites.Add(alias);
} }
@ -52,16 +52,11 @@ else
private void Edit(string name) private void Edit(string name)
{ {
if (name.Equals("*")) NavigationManager.NavigateTo(_scheme + name + "/admin/site", true);
{
var uri = new Uri(NavigationManager.Uri);
name = uri.Authority;
}
NavigationManager.NavigateTo(_scheme + name + "/admin/site/?reload");
} }
private void Browse(string name) private void Browse(string name)
{ {
NavigationManager.NavigateTo(_scheme + name + "/?reload"); NavigationManager.NavigateTo(_scheme + name, true);
} }
} }

View File

@ -129,9 +129,6 @@
<data name="DefaultContainer.Text" xml:space="preserve"> <data name="DefaultContainer.Text" xml:space="preserve">
<value>Default Container: </value> <value>Default Container: </value>
</data> </data>
<data name="Appearance.Heading" xml:space="preserve">
<value>Appearance</value>
</data>
<data name="DefaultAdminContainer" xml:space="preserve"> <data name="DefaultAdminContainer" xml:space="preserve">
<value>Default Admin Container</value> <value>Default Admin Container</value>
</data> </data>
@ -223,7 +220,7 @@
<value>Aliases: </value> <value>Aliases: </value>
</data> </data>
<data name="IsDeleted.Text" xml:space="preserve"> <data name="IsDeleted.Text" xml:space="preserve">
<value>Is Deleted? </value> <value>Deleted? </value>
</data> </data>
<data name="Logo.Text" xml:space="preserve"> <data name="Logo.Text" xml:space="preserve">
<value>Logo: </value> <value>Logo: </value>
@ -318,4 +315,13 @@
<data name="DeleteSite.Text" xml:space="preserve"> <data name="DeleteSite.Text" xml:space="preserve">
<value>Delete Site</value> <value>Delete Site</value>
</data> </data>
<data name="DefaultAlias.HelpText" xml:space="preserve">
<value>The default alias for the site. Requests for non-default aliases will be redirected to the default alias.</value>
</data>
<data name="DefaultAlias.Text" xml:space="preserve">
<value>Default Alias: </value>
</data>
<data name="Aliases.Heading" xml:space="preserve">
<value>Aliases</value>
</data>
</root> </root>

View File

@ -152,7 +152,7 @@ namespace Oqtane.Services
} }
else else
{ {
if (setting.SettingValue != kvp.Value) if (setting.SettingValue != value || setting.IsPublic != ispublic)
{ {
setting.SettingValue = value; setting.SettingValue = value;
setting.IsPublic = ispublic; setting.IsPublic = ispublic;

View File

@ -367,7 +367,9 @@ namespace Oqtane.Infrastructure
tenant = db.Tenant.FirstOrDefault(item => item.Name == install.TenantName); tenant = db.Tenant.FirstOrDefault(item => item.Name == install.TenantName);
} }
foreach (var aliasName in install.Aliases.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)) var aliasNames = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray();
var firstAlias = aliasNames[0];
foreach (var aliasName in aliasNames)
{ {
if (tenant != null) if (tenant != null)
{ {
@ -376,6 +378,7 @@ namespace Oqtane.Infrastructure
Name = aliasName, Name = aliasName,
TenantId = tenant.TenantId, TenantId = tenant.TenantId,
SiteId = -1, SiteId = -1,
IsDefault = (aliasName == firstAlias),
CreatedBy = "", CreatedBy = "",
CreatedOn = DateTime.UtcNow, CreatedOn = DateTime.UtcNow,
ModifiedBy = "", ModifiedBy = "",
@ -558,7 +561,8 @@ namespace Oqtane.Infrastructure
{ {
// set the alias explicitly so the tenant can be resolved // set the alias explicitly so the tenant can be resolved
var aliases = scope.ServiceProvider.GetRequiredService<IAliasRepository>(); var aliases = scope.ServiceProvider.GetRequiredService<IAliasRepository>();
var firstAlias = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0]; var aliasNames = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray();
var firstAlias = aliasNames[0];
var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstAlias); var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstAlias);
var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>(); var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>();
tenantManager.SetAlias(alias); tenantManager.SetAlias(alias);
@ -650,7 +654,7 @@ namespace Oqtane.Infrastructure
} }
} }
foreach (var aliasName in install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (var aliasName in aliasNames)
{ {
alias = aliases.GetAliases().FirstOrDefault(item => item.Name == aliasName); alias = aliases.GetAliases().FirstOrDefault(item => item.Name == aliasName);
if (alias != null) if (alias != null)

View File

@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Databases.Interfaces;
using Oqtane.Migrations.EntityBuilders;
using Oqtane.Repository;
namespace Oqtane.Migrations.Master
{
[DbContext(typeof(MasterDBContext))]
[Migration("Master.03.00.02.01")]
public class AddAliasRedirect : MultiDatabaseMigration
{
public AddAliasRedirect(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
//Add Column to Alias table
var aliasEntityBuilder = new AliasEntityBuilder(migrationBuilder, ActiveDatabase);
aliasEntityBuilder.AddBooleanColumn("IsDefault", true);
aliasEntityBuilder.UpdateColumn("IsDefault", "1", "bool", "");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
var aliasEntityBuilder = new AliasEntityBuilder(migrationBuilder, ActiveDatabase);
aliasEntityBuilder.DropColumn("IsDefault");
}
}
}

View File

@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System.Security.Claims; using System.Security.Claims;
using System.Net;
namespace Oqtane.Pages namespace Oqtane.Pages
{ {
@ -32,8 +33,9 @@ namespace Oqtane.Pages
private readonly IPageRepository _pages; private readonly IPageRepository _pages;
private readonly IUrlMappingRepository _urlMappings; private readonly IUrlMappingRepository _urlMappings;
private readonly IVisitorRepository _visitors; private readonly IVisitorRepository _visitors;
private readonly IAliasRepository _aliases;
public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors) public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases)
{ {
_configuration = configuration; _configuration = configuration;
_tenantManager = tenantManager; _tenantManager = tenantManager;
@ -44,6 +46,7 @@ namespace Oqtane.Pages
_pages = pages; _pages = pages;
_urlMappings = urlMappings; _urlMappings = urlMappings;
_visitors = visitors; _visitors = visitors;
_aliases = aliases;
} }
public string AntiForgeryToken = ""; public string AntiForgeryToken = "";
@ -77,11 +80,25 @@ namespace Oqtane.Pages
var alias = _tenantManager.GetAlias(); var alias = _tenantManager.GetAlias();
if (alias != null) if (alias != null)
{ {
Route route = new Route(HttpContext.Request.GetEncodedUrl(), alias.Path); var url = WebUtility.UrlDecode(HttpContext.Request.GetEncodedUrl());
// redirect non-default alias
if (!alias.IsDefault)
{
var redirect = _aliases.GetAliases()
.Where(item => item.TenantId == alias.TenantId && item.SiteId == alias.SiteId && item.IsDefault)
.FirstOrDefault();
if (redirect != null)
{
return RedirectPermanent(url.Replace(alias.Name, redirect.Name));
}
}
var site = _sites.GetSite(alias.SiteId); var site = _sites.GetSite(alias.SiteId);
if (site != null) if (site != null)
{ {
Route route = new Route(url, alias.Path);
if (!string.IsNullOrEmpty(site.Runtime)) if (!string.IsNullOrEmpty(site.Runtime))
{ {
Runtime = site.Runtime; Runtime = site.Runtime;
@ -128,7 +145,7 @@ namespace Oqtane.Pages
else else
{ {
// page does not exist // page does not exist
var url = route.SiteUrl + "/" + route.PagePath; url = route.SiteUrl + "/" + route.PagePath;
var urlMapping = _urlMappings.GetUrlMapping(site.SiteId, url); var urlMapping = _urlMappings.GetUrlMapping(site.SiteId, url);
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
{ {

View File

@ -4,6 +4,7 @@ using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Repository namespace Oqtane.Repository
{ {
@ -72,7 +73,7 @@ namespace Oqtane.Repository
int start = segments.Length; int start = segments.Length;
for (int i = 0; i < segments.Length; i++) for (int i = 0; i < segments.Length; i++)
{ {
if (segments[i] == "api" || segments[i] == "pages" || segments[i] == "*") if (segments[i] == "api" || segments[i] == "pages" || segments[i] == Constants.ModuleDelimiter)
{ {
start = i; start = i;
break; break;
@ -89,8 +90,18 @@ namespace Oqtane.Repository
} }
} }
// return fallback alias if none found // auto register alias if there is only a single tenant/site
return alias ?? aliases.Find(item => item.Name.Equals("*")); if (alias == null && aliases.Select(item => new { item.TenantId, item.SiteId }).Distinct().Count() == 1)
{
alias = new Alias();
alias.TenantId = aliases.First().TenantId;
alias.SiteId = aliases.First().SiteId;
alias.Name = url;
alias.IsDefault = false;
alias = AddAlias(alias);
}
return alias;
} }
public void DeleteAlias(int aliasId) public void DeleteAlias(int aliasId)

View File

@ -30,6 +30,11 @@ namespace Oqtane.Models
/// </summary> /// </summary>
public int SiteId { get; set; } public int SiteId { get; set; }
/// <summary>
/// Specifies if the alias is the default for the tenant/site. Requests for non-default aliases are redirected to the default alias.
/// </summary>
public bool IsDefault { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public string CreatedBy { get; set; } public string CreatedBy { get; set; }
@ -62,5 +67,6 @@ namespace Oqtane.Models
} }
} }
} }
} }
} }

View File

@ -30,7 +30,7 @@ namespace Oqtane.Models
Action = ""; Action = "";
UrlParameters = ""; UrlParameters = "";
if (AliasPath.Length != 0) if (AliasPath.Length != 0 && PagePath.StartsWith("/" + AliasPath))
{ {
PagePath = PagePath.Substring(AliasPath.Length + 1); PagePath = PagePath.Substring(AliasPath.Length + 1);
} }