ability to specify if a theme is enabled for a site

This commit is contained in:
sbwalker
2023-05-24 13:09:10 -04:00
parent 666f9c2db9
commit 98c2f012ee
23 changed files with 564 additions and 231 deletions

View File

@ -256,7 +256,7 @@ namespace Oqtane.Controllers
}
// remove module definition
_moduleDefinitions.DeleteModuleDefinition(id, siteid);
_moduleDefinitions.DeleteModuleDefinition(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
}

View File

@ -12,6 +12,8 @@ using Oqtane.Infrastructure;
using Oqtane.Repository;
using System.Text.Json;
using System.Net;
using System.Reflection.Metadata;
using System;
// ReSharper disable StringIndexOfIsCultureSpecific.1
@ -23,14 +25,20 @@ namespace Oqtane.Controllers
private readonly IThemeRepository _themes;
private readonly IInstallationManager _installationManager;
private readonly IWebHostEnvironment _environment;
private readonly ITenantManager _tenantManager;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ILogManager logger)
public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
{
_themes = themes;
_installationManager = installationManager;
_environment = environment;
_tenantManager = tenantManager;
_syncManager = syncManager;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET: api/<controller>
@ -41,6 +49,41 @@ namespace Oqtane.Controllers
return _themes.GetThemes();
}
// GET api/<controller>/5?siteid=x
[HttpGet("{id}")]
public Theme Get(int id, string siteid)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
return _themes.GetTheme(id, SiteId);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Get Attempt {ThemeId} {SiteId}", id, siteid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Admin)]
public void Put(int id, [FromBody] Theme theme)
{
if (ModelState.IsValid && theme.SiteId == _alias.SiteId && _themes.GetTheme(theme.ThemeId,theme.SiteId) != null)
{
_themes.UpdateTheme(theme);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Theme, theme.ThemeId, SyncEventActions.Update);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Theme Updated {Theme}", theme);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Put Attempt {Theme}", theme);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
// DELETE api/<controller>/xxx
[HttpDelete("{themename}")]
[Authorize(Roles = RoleNames.Host)]
@ -74,7 +117,7 @@ namespace Oqtane.Controllers
}
// remove theme
_themes.DeleteTheme(theme.ThemeName);
//_themes.DeleteTheme(theme.ThemeName);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Removed For {ThemeName}", theme.ThemeName);
}
else

View File

@ -0,0 +1,36 @@
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 ThemeEntityBuilder : AuditableBaseEntityBuilder<ThemeEntityBuilder>
{
private const string _entityTableName = "Theme";
private readonly PrimaryKey<ThemeEntityBuilder> _primaryKey = new("PK_Theme", x => x.ThemeId);
public ThemeEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
}
protected override ThemeEntityBuilder BuildTable(ColumnsBuilder table)
{
ThemeId = AddAutoIncrementColumn(table, "ThemeId");
ThemeName = AddStringColumn(table, "ThemeName", 200);
AddAuditableColumns(table);
return this;
}
public OperationBuilder<AddColumnOperation> ThemeId { get; private set; }
public OperationBuilder<AddColumnOperation> ThemeName { get; private set; }
}
}

View File

@ -0,0 +1,28 @@
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.04.00.00.01")]
public class AddThemeTable : MultiDatabaseMigration
{
public AddThemeTable(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var themeEntityBuilder = new ThemeEntityBuilder(migrationBuilder, ActiveDatabase);
themeEntityBuilder.Create();
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@ -68,6 +68,7 @@ namespace Oqtane.Repository
public virtual DbSet<Job> Job { get; set; }
public virtual DbSet<JobLog> JobLog { get; set; }
public virtual DbSet<Setting> Setting { get; set; }
public virtual DbSet<Theme> Theme { get; set; }
public override int SaveChanges()
{

View File

@ -9,7 +9,7 @@ namespace Oqtane.Repository
IEnumerable<ModuleDefinition> GetModuleDefinitions(int siteId);
ModuleDefinition GetModuleDefinition(int moduleDefinitionId, int siteId);
void UpdateModuleDefinition(ModuleDefinition moduleDefinition);
void DeleteModuleDefinition(int moduleDefinitionId, int siteId);
void DeleteModuleDefinition(int moduleDefinitionId);
ModuleDefinition FilterModuleDefinition(ModuleDefinition moduleDefinition);
}
}

View File

@ -6,7 +6,9 @@ namespace Oqtane.Repository
public interface IThemeRepository
{
IEnumerable<Theme> GetThemes();
Theme GetTheme(int themeId, int siteId);
void UpdateTheme(Theme theme);
void DeleteTheme(int themeId);
List<Theme> FilterThemes(List<Theme> themes);
void DeleteTheme(string ThemeName);
}
}

View File

@ -68,7 +68,7 @@ namespace Oqtane.Repository
_cache.Remove($"moduledefinitions:{_tenants.GetAlias().SiteKey}");
}
public void DeleteModuleDefinition(int moduleDefinitionId,int siteId)
public void DeleteModuleDefinition(int moduleDefinitionId)
{
ModuleDefinition moduleDefinition = _db.ModuleDefinition.Find(moduleDefinitionId);
_settings.DeleteSettings(EntityNames.ModuleDefinition, moduleDefinitionId);
@ -126,48 +126,48 @@ namespace Oqtane.Repository
private List<ModuleDefinition> ProcessModuleDefinitions(int siteId)
{
// get module assemblies
List<ModuleDefinition> moduleDefinitions = LoadModuleDefinitionsFromAssemblies();
List<ModuleDefinition> ModuleDefinitions = LoadModuleDefinitionsFromAssemblies();
// get module definitions in database
List<ModuleDefinition> moduledefs = _db.ModuleDefinition.ToList();
List<ModuleDefinition> moduledefinitions = _db.ModuleDefinition.ToList();
// sync module assemblies with database
foreach (ModuleDefinition moduledefinition in moduleDefinitions)
foreach (ModuleDefinition ModuleDefinition in ModuleDefinitions)
{
ModuleDefinition moduledef = moduledefs.Where(item => item.ModuleDefinitionName == moduledefinition.ModuleDefinitionName).FirstOrDefault();
if (moduledef == null)
ModuleDefinition moduledefinition = moduledefinitions.Where(item => item.ModuleDefinitionName == ModuleDefinition.ModuleDefinitionName).FirstOrDefault();
if (moduledefinition == null)
{
// new module definition
moduledef = new ModuleDefinition { ModuleDefinitionName = moduledefinition.ModuleDefinitionName };
_db.ModuleDefinition.Add(moduledef);
moduledefinition = new ModuleDefinition { ModuleDefinitionName = ModuleDefinition.ModuleDefinitionName };
_db.ModuleDefinition.Add(moduledefinition);
_db.SaveChanges();
moduledefinition.Version = "";
ModuleDefinition.Version = "";
}
else
{
// override user customizable property values
moduledefinition.Name = (!string.IsNullOrEmpty(moduledef.Name)) ? moduledef.Name : moduledefinition.Name;
moduledefinition.Description = (!string.IsNullOrEmpty(moduledef.Description)) ? moduledef.Description : moduledefinition.Description;
moduledefinition.Categories = (!string.IsNullOrEmpty(moduledef.Categories)) ? moduledef.Categories : moduledefinition.Categories;
ModuleDefinition.Name = (!string.IsNullOrEmpty(moduledefinition.Name)) ? moduledefinition.Name : ModuleDefinition.Name;
ModuleDefinition.Description = (!string.IsNullOrEmpty(moduledefinition.Description)) ? moduledefinition.Description : ModuleDefinition.Description;
ModuleDefinition.Categories = (!string.IsNullOrEmpty(moduledefinition.Categories)) ? moduledefinition.Categories : ModuleDefinition.Categories;
// manage releaseversions in cases where it was not provided or is lower than the module version
if (string.IsNullOrEmpty(moduledefinition.ReleaseVersions) || Version.Parse(moduledefinition.Version).CompareTo(Version.Parse(moduledefinition.ReleaseVersions.Split(',').Last())) > 0)
if (string.IsNullOrEmpty(ModuleDefinition.ReleaseVersions) || Version.Parse(ModuleDefinition.Version).CompareTo(Version.Parse(ModuleDefinition.ReleaseVersions.Split(',').Last())) > 0)
{
moduledefinition.ReleaseVersions = moduledefinition.Version;
ModuleDefinition.ReleaseVersions = ModuleDefinition.Version;
}
moduledefinition.Version = moduledef.Version;
ModuleDefinition.Version = moduledefinition.Version;
// remove module definition from list as it is already synced
moduledefs.Remove(moduledef);
moduledefinitions.Remove(moduledefinition);
}
moduledefinition.ModuleDefinitionId = moduledef.ModuleDefinitionId;
moduledefinition.CreatedBy = moduledef.CreatedBy;
moduledefinition.CreatedOn = moduledef.CreatedOn;
moduledefinition.ModifiedBy = moduledef.ModifiedBy;
moduledefinition.ModifiedOn = moduledef.ModifiedOn;
ModuleDefinition.ModuleDefinitionId = moduledefinition.ModuleDefinitionId;
ModuleDefinition.CreatedBy = moduledefinition.CreatedBy;
ModuleDefinition.CreatedOn = moduledefinition.CreatedOn;
ModuleDefinition.ModifiedBy = moduledefinition.ModifiedBy;
ModuleDefinition.ModifiedOn = moduledefinition.ModifiedOn;
}
// any remaining module definitions are orphans
foreach (ModuleDefinition moduledefinition in moduledefs)
foreach (ModuleDefinition moduledefinition in moduledefinitions)
{
_db.ModuleDefinition.Remove(moduledefinition); // delete
_db.SaveChanges();
@ -181,8 +181,8 @@ namespace Oqtane.Repository
// get settings for site
var settings = _settings.GetSettings(EntityNames.ModuleDefinition).ToList();
// populate module definition permissions
foreach (ModuleDefinition moduledefinition in moduleDefinitions)
// populate module definition site settings and permissions
foreach (ModuleDefinition moduledefinition in ModuleDefinitions)
{
moduledefinition.SiteId = siteId;
@ -218,7 +218,7 @@ namespace Oqtane.Repository
}
// clean up any orphaned permissions
var ids = new HashSet<int>(moduleDefinitions.Select(item => item.ModuleDefinitionId));
var ids = new HashSet<int>(ModuleDefinitions.Select(item => item.ModuleDefinitionId));
foreach (var permission in permissions.Where(item => !ids.Contains(item.EntityId)))
{
try
@ -232,7 +232,7 @@ namespace Oqtane.Repository
}
}
return moduleDefinitions;
return ModuleDefinitions;
}
private List<ModuleDefinition> LoadModuleDefinitionsFromAssemblies()

View File

@ -4,39 +4,168 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using System.Security;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Themes;
using System.Reflection.Metadata;
namespace Oqtane.Repository
{
public class ThemeRepository : IThemeRepository
{
private MasterDBContext _db;
private readonly IMemoryCache _cache;
private readonly ITenantManager _tenants;
private readonly ISettingRepository _settings;
private readonly string settingprefix = "SiteEnabled:";
public ThemeRepository(IMemoryCache cache)
public ThemeRepository(MasterDBContext context, IMemoryCache cache, ITenantManager tenants, ISettingRepository settings)
{
_db = context;
_cache = cache;
_tenants = tenants;
_settings = settings;
}
public IEnumerable<Theme> GetThemes()
{
return LoadThemes();
// for consistency siteid should be passed in as parameter, but this would require breaking change
return LoadThemes(_tenants.GetAlias().SiteId);
}
private List<Theme> LoadThemes()
public Theme GetTheme(int themeId, int siteId)
{
// get module definitions
List<Theme> themes = _cache.GetOrCreate("themes", entry =>
List<Theme> themes = LoadThemes(siteId);
return themes.Find(item => item.ThemeId == themeId);
}
public void UpdateTheme(Theme theme)
{
_db.Entry(theme).State = EntityState.Modified;
_db.SaveChanges();
var settingname = $"{settingprefix}{_tenants.GetAlias().SiteKey}";
var setting = _settings.GetSetting(EntityNames.Theme, theme.ThemeId, settingname);
if (setting == null)
{
_settings.AddSetting(new Setting { EntityName = EntityNames.Theme, EntityId = theme.ThemeId, SettingName = settingname, SettingValue = theme.IsEnabled.ToString(), IsPrivate = true });
}
else
{
setting.SettingValue = theme.IsEnabled.ToString();
_settings.UpdateSetting(setting);
}
_cache.Remove($"themes:{_tenants.GetAlias().SiteKey}");
}
public void DeleteTheme(int themeId)
{
Theme theme = _db.Theme.Find(themeId);
_settings.DeleteSettings(EntityNames.Theme, themeId);
_db.Theme.Remove(theme);
_db.SaveChanges();
_cache.Remove($"themes:{_tenants.GetAlias().SiteKey}");
}
public List<Theme> FilterThemes(List<Theme> themes)
{
var Themes = new List<Theme>();
foreach (Theme theme in themes.Where(item => item.IsEnabled))
{
var Theme = new Theme();
Theme.ThemeName = theme.ThemeName;
Theme.Name = theme.Name;
Theme.Resources = theme.Resources;
Theme.Themes = theme.Themes;
Theme.Containers = theme.Containers;
Themes.Add(Theme);
}
return Themes;
}
private List<Theme> LoadThemes(int siteId)
{
// get themes
List<Theme> themes = _cache.GetOrCreate($"themes:{_tenants.GetAlias().SiteKey}", entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
return LoadThemesFromAssemblies();
return ProcessThemes(siteId);
});
return themes;
}
private List<Theme> ProcessThemes(int siteId)
{
// get themes
List<Theme> Themes = LoadThemesFromAssemblies();
// get themes in database
List<Theme> themes = _db.Theme.ToList();
// sync theme assemblies with database
foreach (Theme Theme in Themes)
{
Theme theme = themes.Where(item => item.ThemeName == Theme.ThemeName).FirstOrDefault();
if (theme == null)
{
// new theme
theme = new Theme { ThemeName = Theme.ThemeName };
_db.Theme.Add(theme);
_db.SaveChanges();
}
else
{
// remove theme from list as it is already synced
themes.Remove(theme);
}
Theme.ThemeId = theme.ThemeId;
Theme.CreatedBy = theme.CreatedBy;
Theme.CreatedOn = theme.CreatedOn;
Theme.ModifiedBy = theme.ModifiedBy;
Theme.ModifiedOn = theme.ModifiedOn;
}
// any remaining themes are orphans
foreach (Theme theme in themes)
{
_db.Theme.Remove(theme); // delete
_db.SaveChanges();
}
if (siteId != -1)
{
// get settings for site
var settings = _settings.GetSettings(EntityNames.Theme).ToList();
// populate theme site settings
foreach (Theme theme in Themes)
{
theme.SiteId = siteId;
var setting = settings.FirstOrDefault(item => item.EntityId == theme.ThemeId && item.SettingName == $"{settingprefix}{_tenants.GetAlias().SiteKey}");
if (setting != null)
{
theme.IsEnabled = bool.Parse(setting.SettingValue);
}
else
{
theme.IsEnabled = theme.IsAutoEnabled;
}
}
}
return Themes;
}
private List<Theme> LoadThemesFromAssemblies()
{
List<Theme> themes = new List<Theme>();
@ -143,28 +272,5 @@ namespace Oqtane.Repository
}
return themes;
}
public List<Theme> FilterThemes(List<Theme> themes)
{
var Themes = new List<Theme>();
foreach (Theme theme in themes)
{
var Theme = new Theme();
Theme.ThemeName = theme.ThemeName;
Theme.Name = theme.Name;
Theme.Resources = theme.Resources;
Theme.Themes = theme.Themes;
Theme.Containers = theme.Containers;
Themes.Add(Theme);
}
return Themes;
}
public void DeleteTheme(string ThemeName)
{
_cache.Remove("themes");
}
}
}