diff --git a/HopFrame.sln b/HopFrame.sln index f9217e8..2e65007 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web", "src\HopFram EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontendTest", "test\FrontendTest\FrontendTest.csproj", "{8F983A37-63CF-48D5-988D-58B78EF8AECD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web.Admin", "src\HopFrame.Web.Admin\HopFrame.Web.Admin.csproj", "{02D9F10A-664A-4EF7-BF19-310C26FF4DEB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +44,10 @@ Global {8F983A37-63CF-48D5-988D-58B78EF8AECD}.Debug|Any CPU.Build.0 = Debug|Any CPU {8F983A37-63CF-48D5-988D-58B78EF8AECD}.Release|Any CPU.ActiveCfg = Release|Any CPU {8F983A37-63CF-48D5-988D-58B78EF8AECD}.Release|Any CPU.Build.0 = Release|Any CPU + {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution EndGlobalSection diff --git a/src/HopFrame.Database/HopFrame.Database.csproj b/src/HopFrame.Database/HopFrame.Database.csproj index f2bcba7..1216958 100644 --- a/src/HopFrame.Database/HopFrame.Database.csproj +++ b/src/HopFrame.Database/HopFrame.Database.csproj @@ -22,4 +22,8 @@ + + + + diff --git a/src/HopFrame.Database/Models/PermissionGroup.cs b/src/HopFrame.Database/Models/PermissionGroup.cs index 7a70ebd..0a0c669 100644 --- a/src/HopFrame.Database/Models/PermissionGroup.cs +++ b/src/HopFrame.Database/Models/PermissionGroup.cs @@ -1,22 +1,27 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using HopFrame.Web.Admin.Attributes; +using HopFrame.Web.Admin.Attributes.Members; namespace HopFrame.Database.Models; +[AdminName("Groups")] +[AdminDescription("On this page you can view, create, edit and delete permission groups.")] public class PermissionGroup : IPermissionOwner { [Key, Required, MaxLength(50)] public string Name { get; init; } - [Required, DefaultValue(false)] + [Required, DefaultValue(false), AdminUnsortable] public bool IsDefaultGroup { get; set; } [MaxLength(500)] public string Description { get; set; } - [Required] + [Required, AdminUneditable] public DateTime CreatedAt { get; set; } + [AdminIgnore(onlyForListing: true)] public virtual IList Permissions { get; set; } } \ No newline at end of file diff --git a/src/HopFrame.Database/Models/User.cs b/src/HopFrame.Database/Models/User.cs index 971d899..ab944f0 100644 --- a/src/HopFrame.Database/Models/User.cs +++ b/src/HopFrame.Database/Models/User.cs @@ -1,8 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; +using HopFrame.Web.Admin.Attributes; +using HopFrame.Web.Admin.Attributes.Members; namespace HopFrame.Database.Models; +[AdminDescription("On this page you can manage all user accounts.")] public class User : IPermissionOwner { [Key, Required, MinLength(36), MaxLength(36)] @@ -14,15 +17,16 @@ public class User : IPermissionOwner { [Required, MaxLength(50), EmailAddress] public string Email { get; set; } - [Required, MinLength(8), MaxLength(255), JsonIgnore] + [Required, MinLength(8), MaxLength(255), JsonIgnore, AdminIgnore(onlyForListing: true), AdminHideValue] public string Password { get; set; } - [Required] + [Required, AdminUneditable] public DateTime CreatedAt { get; set; } + [AdminIgnore(onlyForListing: true)] public virtual IList Permissions { get; set; } - [JsonIgnore] + [JsonIgnore, AdminIgnore] public virtual IList Tokens { get; set; } } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/AdminPagesContext.cs b/src/HopFrame.Web.Admin/AdminPagesContext.cs new file mode 100644 index 0000000..7be251e --- /dev/null +++ b/src/HopFrame.Web.Admin/AdminPagesContext.cs @@ -0,0 +1,9 @@ +using HopFrame.Web.Admin.Generators; + +namespace HopFrame.Web.Admin; + +public abstract class AdminPagesContext { + + public abstract void OnModelCreating(IAdminContextGenerator generator); + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs b/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs new file mode 100644 index 0000000..843f250 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs @@ -0,0 +1,6 @@ +namespace HopFrame.Web.Admin.Attributes; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] +public class AdminDescriptionAttribute(string description) : Attribute { + public string Description { get; set; } = description; +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs b/src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs new file mode 100644 index 0000000..aaa8bac --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs @@ -0,0 +1,6 @@ +namespace HopFrame.Web.Admin.Attributes; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] +public class AdminNameAttribute(string name) : Attribute { + public string Name { get; set; } = name; +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs new file mode 100644 index 0000000..2995164 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs @@ -0,0 +1,8 @@ +namespace HopFrame.Web.Admin.Attributes.Classes; + +[AttributeUsage(AttributeTargets.Class)] +public class AdminButtonConfigAttribute(bool showCreateButton = true, bool showDeleteButton = true, bool showUpdateButton = true) : Attribute { + public bool ShowCreateButton { get; set; } = showCreateButton; + public bool ShowDeleteButton { get; set; } = showDeleteButton; + public bool ShowUpdateButton { get; set; } = showUpdateButton; +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs new file mode 100644 index 0000000..59b0747 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs @@ -0,0 +1,8 @@ +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web.Admin.Attributes.Classes; + +[AttributeUsage(AttributeTargets.Class)] +public class AdminPermissionsAttribute(AdminPagePermissions permissions) : Attribute { + public AdminPagePermissions Permissions { get; set; } = permissions; +} diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs new file mode 100644 index 0000000..70fa63e --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminBoldAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs new file mode 100644 index 0000000..c40288d --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminHideValueAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs new file mode 100644 index 0000000..6022cf3 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs @@ -0,0 +1,6 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminIgnoreAttribute(bool onlyForListing = false) : Attribute { + public bool OnlyForListing { get; set; } = onlyForListing; +} diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs new file mode 100644 index 0000000..f29e575 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminUneditableAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs new file mode 100644 index 0000000..6ddb865 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminUnsortableAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs new file mode 100644 index 0000000..49cf115 --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs @@ -0,0 +1,7 @@ +namespace HopFrame.Web.Admin.Generators; + +public interface IAdminContextGenerator { + + IAdminPageGenerator Page(); + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs new file mode 100644 index 0000000..c4f7c36 --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs @@ -0,0 +1,26 @@ +using System.ComponentModel; +using System.Linq.Expressions; + +namespace HopFrame.Web.Admin.Generators; + +public interface IAdminPageGenerator { + + IAdminPageGenerator Title(string title); + IAdminPageGenerator Description(string description); + + IAdminPageGenerator ViewPermission(string permission); + IAdminPageGenerator CreatePermission(string permission); + IAdminPageGenerator UpdatePermission(string permission); + IAdminPageGenerator DeletePermission(string permission); + + IAdminPageGenerator ShowCreateButton(bool show); + IAdminPageGenerator ShowDeleteButton(bool show); + IAdminPageGenerator ShowUpdateButton(bool show); + + IAdminPageGenerator DefaultSort(Expression> propertyExpression, ListSortDirection direction); + + IAdminPageGenerator ConfigureRepository() where TRepository : IModelRepository; + + IAdminPropertyGenerator Property(Expression> propertyExpression); + +} diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs new file mode 100644 index 0000000..d6b644d --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -0,0 +1,14 @@ +namespace HopFrame.Web.Admin.Generators; + +public interface IAdminPropertyGenerator { + + IAdminPropertyGenerator Sortable(bool sortable); + IAdminPropertyGenerator Editable(bool editable); + IAdminPropertyGenerator DisplayValueWhileEditing(bool display); + IAdminPropertyGenerator DisplayInListing(bool display = true); + IAdminPropertyGenerator Bold(bool isBold = true); + + IAdminPropertyGenerator DisplayName(string displayName); + IAdminPropertyGenerator Description(string description); + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IGenerator.cs b/src/HopFrame.Web.Admin/Generators/IGenerator.cs new file mode 100644 index 0000000..5cf9d6f --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/IGenerator.cs @@ -0,0 +1,7 @@ +namespace HopFrame.Web.Admin.Generators; + +public interface IGenerator { + + TGeneratedType Compile(); + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs new file mode 100644 index 0000000..314c490 --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs @@ -0,0 +1,91 @@ +using System.Reflection; +using HopFrame.Web.Admin.Attributes; +using HopFrame.Web.Admin.Attributes.Classes; +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web.Admin.Generators.Implementation; + +internal class AdminContextGenerator : IAdminContextGenerator { + + private readonly IDictionary _adminPages = new Dictionary(); + + public IAdminPageGenerator Page() { + if (_adminPages.TryGetValue(typeof(TModel), out var pageGenerator)) + return pageGenerator as IAdminPageGenerator; + + var generator = Activator.CreateInstance(typeof(IAdminPageGenerator)) as IAdminPageGenerator; + + ApplyConfigurationFromAttributes(generator, typeof(TModel).GetCustomAttributes(false)); + + _adminPages.Add(typeof(TModel), generator); + + return generator; + } + + public AdminPage CompilePage() { + var generator = _adminPages[typeof(TModel)]; + if (generator is null) return null; + + return (generator as AdminPageGenerator)?.Compile(); + } + + public TContext CompileContext() where TContext : AdminPagesContext { + var type = typeof(TContext); + var compileMethod = typeof(AdminContextGenerator).GetMethod(nameof(CompilePage)); + + var properties = type.GetProperties(); + + var context = Activator.CreateInstance(); + + foreach (var property in properties) { + var propertyType = property.PropertyType.GenericTypeArguments[0]; + var pageGeneratorType = typeof(AdminPageGenerator<>).MakeGenericType(propertyType); + var generatorInstance = Activator.CreateInstance(pageGeneratorType, [propertyType.Name]); + + var populatorMethod = typeof(AdminContextGenerator) + .GetMethod(nameof(ApplyConfigurationFromAttributes))? + .MakeGenericMethod(propertyType); + populatorMethod?.Invoke(this, [generatorInstance, propertyType.GetCustomAttributes()]); + + _adminPages.Add(propertyType, generatorInstance); + } + + context.OnModelCreating(this); + + foreach (var property in properties) { + var modelType = property.PropertyType.GenericTypeArguments[0]; + var method = compileMethod?.MakeGenericMethod(modelType); + property.SetValue(context, method?.Invoke(this, [])); + } + + return context; + } + + public void ApplyConfigurationFromAttributes(IAdminPageGenerator generator, object[] attributes) { + if (attributes.Any(a => a is AdminNameAttribute)) { + var attribute = attributes.Single(a => a is AdminNameAttribute) as AdminNameAttribute; + generator.Title(attribute?.Name); + } + + if (attributes.Any(a => a is AdminDescriptionAttribute)) { + var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; + generator.Description(attribute?.Description); + } + + if (attributes.Any(a => a is AdminPermissionsAttribute)) { + var attribute = attributes.Single(a => a is AdminPermissionsAttribute) as AdminPermissionsAttribute; + generator.CreatePermission(attribute?.Permissions.Create); + generator.UpdatePermission(attribute?.Permissions.Update); + generator.ViewPermission(attribute?.Permissions.View); + generator.DeletePermission(attribute?.Permissions.Delete); + } + + if (attributes.Any(a => a is AdminButtonConfigAttribute)) { + var attribute = attributes.Single(a => a is AdminButtonConfigAttribute) as AdminButtonConfigAttribute; + generator.ShowCreateButton(attribute?.ShowCreateButton == true); + generator.ShowUpdateButton(attribute?.ShowUpdateButton == true); + generator.ShowDeleteButton(attribute?.ShowDeleteButton == true); + } + } + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs new file mode 100644 index 0000000..a2219ce --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -0,0 +1,180 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq.Expressions; +using System.Reflection; +using HopFrame.Web.Admin.Attributes; +using HopFrame.Web.Admin.Attributes.Members; +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web.Admin.Generators.Implementation; + +internal sealed class AdminPageGenerator : IAdminPageGenerator, IGenerator> { + + private readonly AdminPage _page; + private readonly IDictionary _propertyGenerators; + + public AdminPageGenerator() { + _page = new AdminPage { + Permissions = new AdminPagePermissions() + }; + _propertyGenerators = new Dictionary(); + + var type = typeof(TModel); + var properties = type.GetProperties(); + + foreach (var property in properties) { + var attributes = property.GetCustomAttributes(false); + var ignoreProperty = attributes + .SingleOrDefault(a => a is AdminIgnoreAttribute) as AdminIgnoreAttribute; + if (ignoreProperty?.OnlyForListing == false) continue; + + var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), [property.Name, property.PropertyType]) as AdminPropertyGenerator; + + ApplyConfigurationFromAttributes(generator, attributes, property); + + _propertyGenerators.Add(property.Name, generator); + } + } + + public AdminPageGenerator(string title) : this() { + Title(title); + } + + public IAdminPageGenerator Title(string title) { + _page.Title = title; + return this; + } + + public IAdminPageGenerator Description(string description) { + _page.Description = description; + return this; + } + + public IAdminPageGenerator ViewPermission(string permission) { + _page.Permissions.View = permission; + return this; + } + + public IAdminPageGenerator CreatePermission(string permission) { + _page.Permissions.Create = permission; + return this; + } + + public IAdminPageGenerator UpdatePermission(string permission) { + _page.Permissions.Update = permission; + return this; + } + + public IAdminPageGenerator DeletePermission(string permission) { + _page.Permissions.Delete = permission; + return this; + } + + public IAdminPageGenerator ShowCreateButton(bool show) { + _page.ShowCreateButton = show; + return this; + } + + public IAdminPageGenerator ShowDeleteButton(bool show) { + _page.ShowDeleteButton = show; + return this; + } + + public IAdminPageGenerator ShowUpdateButton(bool show) { + _page.ShowUpdateButton = show; + return this; + } + + public IAdminPageGenerator DefaultSort(Expression> propertyExpression, ListSortDirection direction) { + var property = GetPropertyInfo(propertyExpression); + + _page.DefaultSortPropertyName = property.Name; + _page.DefaultSortDirection = direction; + return this; + } + + public IAdminPageGenerator ConfigureRepository() where TRepository : IModelRepository { + _page.RepositoryProvider = typeof(TRepository); + return this; + } + + public IAdminPropertyGenerator Property(Expression> propertyExpression) { + var property = GetPropertyInfo(propertyExpression); + + if (_propertyGenerators.TryGetValue(property.Name, out var propertyGenerator)) + return propertyGenerator; + + var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), new { property.Name, property.PropertyType }) as AdminPropertyGenerator; + ApplyConfigurationFromAttributes(generator, property.GetCustomAttributes(false), property); + _propertyGenerators.Add(property.Name, generator); + + return generator; + } + + public AdminPage Compile() { + var properties = new List(); + + foreach (var generator in _propertyGenerators.Values){ + properties.Add(generator.Compile()); + } + + _page.Properties = properties; + + return _page; + } + + private void ApplyConfigurationFromAttributes(AdminPropertyGenerator generator, object[] attributes, PropertyInfo property) { + if (attributes.Any(a => a is KeyAttribute)) { + _page.DefaultSortPropertyName = property.Name; + generator.Bold(); + generator.Editable(false); + } + + if (attributes.Any(a => a is AdminUnsortableAttribute)) + generator.Sortable(false); + + if (attributes.Any(a => a is AdminUneditableAttribute)) + generator.Editable(false); + + if (attributes.Any(a => a is AdminBoldAttribute)) + generator.Bold(); + + if (attributes.Any(a => a is AdminIgnoreAttribute)) + generator.DisplayInListing(false); + + if (attributes.Any(a => a is AdminHideValueAttribute)) + generator.DisplayValueWhileEditing(false); + + if (attributes.Any(a => a is AdminNameAttribute)) { + var attribute = attributes.Single(a => a is AdminNameAttribute) as AdminNameAttribute; + generator.DisplayName(attribute?.Name); + } + + if (attributes.Any(a => a is AdminDescriptionAttribute)) { + var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; + generator.Description(attribute?.Description); + } + } + + private static PropertyInfo GetPropertyInfo(Expression> propertyLambda) { + if (propertyLambda.Body is not MemberExpression member) { + throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property."); + } + + if (member.Member is not PropertyInfo propInfo) { + throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property."); + } + + Type type = typeof(TSource); + if (propInfo.ReflectedType != null && type != propInfo.ReflectedType && + !type.IsSubclassOf(propInfo.ReflectedType)) { + throw new ArgumentException($"Expression '{propertyLambda}' refers to a property that is not from type {type}."); + } + + if (propInfo.Name is null) + throw new ArgumentException($"Expression '{propertyLambda}' refers a not existing property."); + + return propInfo; + } + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs new file mode 100644 index 0000000..f37cd80 --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -0,0 +1,52 @@ +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web.Admin.Generators.Implementation; + +internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPropertyGenerator, IGenerator { + + private readonly AdminPageProperty _property = new() { + Name = name, + Type = type + }; + + public IAdminPropertyGenerator Sortable(bool sortable) { + _property.Sortable = sortable; + return this; + } + + public IAdminPropertyGenerator Editable(bool editable) { + _property.Editable = editable; + return this; + } + + public IAdminPropertyGenerator DisplayValueWhileEditing(bool display) { + _property.EditDisplayValue = display; + return this; + } + + public IAdminPropertyGenerator DisplayInListing(bool display = true) { + _property.DisplayInListing = display; + return this; + } + + public IAdminPropertyGenerator Bold(bool isBold = true) { + _property.Bold = isBold; + return this; + } + + public IAdminPropertyGenerator DisplayName(string displayName) { + _property.DisplayName = displayName; + return this; + } + + public IAdminPropertyGenerator Description(string description) { + _property.Description = description; + return this; + } + + public AdminPageProperty Compile() { + _property.DisplayName ??= _property.Name; + + return _property; + } +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/HopFrame.Web.Admin.csproj b/src/HopFrame.Web.Admin/HopFrame.Web.Admin.csproj new file mode 100644 index 0000000..096a35f --- /dev/null +++ b/src/HopFrame.Web.Admin/HopFrame.Web.Admin.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + disable + + + + + + + diff --git a/src/HopFrame.Web.Admin/IModelRepository.cs b/src/HopFrame.Web.Admin/IModelRepository.cs new file mode 100644 index 0000000..6bdffe0 --- /dev/null +++ b/src/HopFrame.Web.Admin/IModelRepository.cs @@ -0,0 +1,8 @@ +namespace HopFrame.Web.Admin; + +public interface IModelRepository { + Task> ReadAll(); + Task Create(TModel model); + Task Update(TModel model); + Task Delete(TModel model); +} diff --git a/src/HopFrame.Web.Admin/Models/AdminPage.cs b/src/HopFrame.Web.Admin/Models/AdminPage.cs new file mode 100644 index 0000000..bea528f --- /dev/null +++ b/src/HopFrame.Web.Admin/Models/AdminPage.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; +using System.Text.Json.Serialization; + +namespace HopFrame.Web.Admin.Models; + +public class AdminPage : IAdminPageEntry { + public string Title { get; set; } + public string Description { get; set; } + public AdminPagePermissions Permissions { get; set; } + public IList Properties { get; set; } + [JsonIgnore] + public Type RepositoryProvider { get; set; } + + public string DefaultSortPropertyName { get; set; } + public ListSortDirection DefaultSortDirection { get; set; } + + public bool ShowCreateButton { get; set; } = true; + public bool ShowDeleteButton { get; set; } = true; + public bool ShowUpdateButton { get; set; } = true; +} + +public interface IAdminPageEntry; diff --git a/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs b/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs new file mode 100644 index 0000000..fc99101 --- /dev/null +++ b/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs @@ -0,0 +1,8 @@ +namespace HopFrame.Web.Admin.Models; + +public class AdminPagePermissions { + public string View { get; set; } + public string Create { get; set; } + public string Update { get; set; } + public string Delete { get; set; } +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs new file mode 100644 index 0000000..5444fae --- /dev/null +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace HopFrame.Web.Admin.Models; + +public class AdminPageProperty { + public string Name { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + + public bool DisplayInListing { get; set; } = true; + public bool Sortable { get; set; } = true; + public bool Editable { get; set; } = true; + public bool EditDisplayValue { get; set; } = true; + public bool Bold { get; set; } + [JsonIgnore] + public Type Type { get; set; } +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs b/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..4b3c007 --- /dev/null +++ b/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs @@ -0,0 +1,17 @@ +using HopFrame.Web.Admin.Generators.Implementation; +using Microsoft.Extensions.DependencyInjection; + +namespace HopFrame.Web.Admin; + +public static class ServiceCollectionExtensions { + + public static IServiceCollection AddAdminContext(this IServiceCollection services) where TContext : AdminPagesContext { + services.AddSingleton(_ => { + var generator = new AdminContextGenerator(); + return generator.CompileContext(); + }); + + return services; + } + +} \ No newline at end of file diff --git a/src/HopFrame.Web/HopFrame.Web.csproj b/src/HopFrame.Web/HopFrame.Web.csproj index ab3c31a..bec6f04 100644 --- a/src/HopFrame.Web/HopFrame.Web.csproj +++ b/src/HopFrame.Web/HopFrame.Web.csproj @@ -20,6 +20,7 @@ + diff --git a/test/FrontendTest/FrontendTest.csproj b/test/FrontendTest/FrontendTest.csproj index 312aa4b..e043848 100644 --- a/test/FrontendTest/FrontendTest.csproj +++ b/test/FrontendTest/FrontendTest.csproj @@ -2,7 +2,7 @@ net8.0 - enable + disable enable diff --git a/test/FrontendTest/Program.cs b/test/FrontendTest/Program.cs index af54f28..77989c6 100644 --- a/test/FrontendTest/Program.cs +++ b/test/FrontendTest/Program.cs @@ -1,6 +1,7 @@ using FrontendTest; using FrontendTest.Components; using HopFrame.Web; +using HopFrame.Web.Admin; var builder = WebApplication.CreateBuilder(args); diff --git a/test/RestApiTest/AdminContext.cs b/test/RestApiTest/AdminContext.cs new file mode 100644 index 0000000..00bf00e --- /dev/null +++ b/test/RestApiTest/AdminContext.cs @@ -0,0 +1,26 @@ +using System.ComponentModel; +using HopFrame.Database.Models; +using HopFrame.Web.Admin; +using HopFrame.Web.Admin.Generators; +using HopFrame.Web.Admin.Models; + +namespace RestApiTest; + +public class AdminContext : AdminPagesContext { + + public AdminPage Users { get; set; } + public AdminPage Groups { get; set; } + + public override void OnModelCreating(IAdminContextGenerator generator) { + + /*generator.Page() + .Title("Users") + .Description("On this page you can manage all user accounts.") + .UpdatePermission("update") + .ViewPermission("view") + .DeletePermission("delete") + .CreatePermission("create") + .DefaultSort(u => u.Id, ListSortDirection.Descending);*/ + + } +} \ No newline at end of file diff --git a/test/RestApiTest/Controllers/TestController.cs b/test/RestApiTest/Controllers/TestController.cs index 092784f..0d5a1f6 100644 --- a/test/RestApiTest/Controllers/TestController.cs +++ b/test/RestApiTest/Controllers/TestController.cs @@ -10,7 +10,7 @@ namespace RestApiTest.Controllers; [ApiController] [Route("test")] -public class TestController(ITokenContext userContext, DatabaseContext context) : ControllerBase { +public class TestController(ITokenContext userContext, DatabaseContext context, AdminContext adminContext) : ControllerBase { [HttpGet("permissions"), Authorized] public ActionResult> Permissions() { @@ -50,5 +50,10 @@ public class TestController(ITokenContext userContext, DatabaseContext context) public async Task>> GetAddresses() { return LogicResult>.Ok(await context.Addresses.Include(e => e.Employee).ToListAsync()); } + + [HttpGet("adminContext")] + public ActionResult GetAdminContext() { + return LogicResult.Ok(adminContext); + } } \ No newline at end of file diff --git a/test/RestApiTest/Program.cs b/test/RestApiTest/Program.cs index bfcbc38..632e62f 100644 --- a/test/RestApiTest/Program.cs +++ b/test/RestApiTest/Program.cs @@ -1,5 +1,6 @@ using RestApiTest; using HopFrame.Api.Extensions; +using HopFrame.Web.Admin; using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); @@ -13,6 +14,7 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddDbContext(); +builder.Services.AddAdminContext(); builder.Services.AddSwaggerGen(c => { c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {