diff --git a/FJPSite/Areas/Admin/Controllers/HomeController.cs b/FJPSite/Areas/Admin/Controllers/HomeController.cs
new file mode 100644
index 0000000..918f109
--- /dev/null
+++ b/FJPSite/Areas/Admin/Controllers/HomeController.cs
@@ -0,0 +1,17 @@
+using FJPSite.Data.Identity;
+using FJPSite.Enums;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+
+namespace FJPSite.Areas.Admin.Controllers;
+
+[Area("Admin")]
+[Authorize]
+public class HomeController : Controller
+{
+ public IActionResult Index()
+ {
+ return View();
+ }
+}
diff --git a/FJPSite/Areas/Admin/Controllers/PermissionController.cs b/FJPSite/Areas/Admin/Controllers/PermissionController.cs
new file mode 100644
index 0000000..96eb6ae
--- /dev/null
+++ b/FJPSite/Areas/Admin/Controllers/PermissionController.cs
@@ -0,0 +1,23 @@
+using FJPSite.Attributes;
+using FJPSite.Enums;
+using FJPSite.Interfaces;
+using Microsoft.AspNetCore.Mvc;
+
+namespace FJPSite.Areas.Admin.Controllers;
+
+public class PermissionController : Controller
+{
+ private readonly IPermissionService _permissionService;
+
+ public PermissionController(IPermissionService permissionService)
+ {
+ _permissionService = permissionService;
+ }
+
+ [HasPermission(FeatureEnum.Permissions, ActionEnum.View)]
+ public IActionResult Index()
+ {
+
+ return View();
+ }
+}
diff --git a/FJPSite/Areas/Admin/Views/Home/Index.cshtml b/FJPSite/Areas/Admin/Views/Home/Index.cshtml
new file mode 100644
index 0000000..067b0b1
--- /dev/null
+++ b/FJPSite/Areas/Admin/Views/Home/Index.cshtml
@@ -0,0 +1,6 @@
+@*
+ For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
+*@
+@{
+}
+THIS IS ADMIN HOME
\ No newline at end of file
diff --git a/FJPSite/Areas/Admin/Views/Shared/_Layout.cshtml b/FJPSite/Areas/Admin/Views/Shared/_Layout.cshtml
new file mode 100644
index 0000000..c014aec
--- /dev/null
+++ b/FJPSite/Areas/Admin/Views/Shared/_Layout.cshtml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ @ViewBag.Title
+
+
+ DOPE
+
+ @RenderBody()
+
+
+
diff --git a/FJPSite/Areas/Admin/Views/_ViewImports.cshtml b/FJPSite/Areas/Admin/Views/_ViewImports.cshtml
new file mode 100644
index 0000000..107cd3a
--- /dev/null
+++ b/FJPSite/Areas/Admin/Views/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@using FJPSite
+@using FJPSite.Models
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
\ No newline at end of file
diff --git a/FJPSite/Areas/Admin/Views/_ViewStart.cshtml b/FJPSite/Areas/Admin/Views/_ViewStart.cshtml
new file mode 100644
index 0000000..a5f1004
--- /dev/null
+++ b/FJPSite/Areas/Admin/Views/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "_Layout";
+}
diff --git a/FJPSite/Attributes/HasPermissionAttribute.cs b/FJPSite/Attributes/HasPermissionAttribute.cs
new file mode 100644
index 0000000..17c5207
--- /dev/null
+++ b/FJPSite/Attributes/HasPermissionAttribute.cs
@@ -0,0 +1,20 @@
+using FJPSite.Enums;
+using Microsoft.AspNetCore.Authorization;
+
+namespace FJPSite.Attributes;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+public class HasPermissionAttribute : AuthorizeAttribute
+{
+ public FeatureEnum Feature { get; }
+ public ActionEnum Action { get; }
+ public HasPermissionAttribute(FeatureEnum feature, ActionEnum action)
+ {
+ Feature = feature;
+ Action = action;
+ Policy = BuildPolicyName(feature, action);
+ }
+ private static string BuildPolicyName(FeatureEnum feature, ActionEnum action)
+ {
+ return $"Permission.{feature}.{action}";
+ }
\ No newline at end of file
diff --git a/FJPSite/Attributes/SeedMigrationAttribute.cs b/FJPSite/Attributes/SeedMigrationAttribute.cs
new file mode 100644
index 0000000..8cc677a
--- /dev/null
+++ b/FJPSite/Attributes/SeedMigrationAttribute.cs
@@ -0,0 +1,7 @@
+namespace FJPSite.Attributes;
+
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+public class SeedMigrationAttribute(string migration) : Attribute
+{
+ public string Migration { get; set; } = migration;
+}
diff --git a/FJPSite/Controllers/HomeController.cs b/FJPSite/Controllers/HomeController.cs
index ff97c00..294c377 100644
--- a/FJPSite/Controllers/HomeController.cs
+++ b/FJPSite/Controllers/HomeController.cs
@@ -1,25 +1,25 @@
+using FJPSite.Enums;
using FJPSite.Models;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
-namespace FJPSite.Controllers
+namespace FJPSite.Controllers;
+
+public class HomeController : Controller
{
- public class HomeController : Controller
+ public IActionResult Index()
{
- public IActionResult Index()
- {
- return View();
- }
+ return View();
+ }
- public IActionResult Privacy()
- {
- return View();
- }
+ public IActionResult Privacy()
+ {
+ return View();
+ }
- [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
- public IActionResult Error()
- {
- return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
- }
+ [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
+ public IActionResult Error()
+ {
+ return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
diff --git a/FJPSite/Data/ApplicationDbContext.cs b/FJPSite/Data/ApplicationDbContext.cs
index 62af6cc..41556c0 100644
--- a/FJPSite/Data/ApplicationDbContext.cs
+++ b/FJPSite/Data/ApplicationDbContext.cs
@@ -1,9 +1,32 @@
-using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
+using FJPSite.Data.Authorisation;
+using FJPSite.Data.Identity;
+using FJPSite.Factories;
+using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
-namespace FJPSite.Data
+namespace FJPSite.Data;
+
+public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options)
{
- public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options)
+ public DbSet Features { get; set; }
+ public DbSet FeatureActions { get; set; }
+ public DbSet Modules { get; set; }
+ public DbSet ModuleFeatures { get; set; }
+ public DbSet Actions { get; set; }
+ public DbSet RolePermissions { get; set; }
+ public DbSet UserPermissions { get; set; }
+ protected override void OnModelCreating(ModelBuilder builder)
{
+ base.OnModelCreating(builder);
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ optionsBuilder.UseSeeding((context, _) =>
+ {
+ DbSeederFactory.SeedDatabase(this);
+ context.SaveChanges();
+ });
}
}
+
diff --git a/FJPSite/Data/Authorisation/Feature.cs b/FJPSite/Data/Authorisation/Feature.cs
new file mode 100644
index 0000000..daacbef
--- /dev/null
+++ b/FJPSite/Data/Authorisation/Feature.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace FJPSite.Data.Authorisation;
+
+public class Feature
+{
+ [Key]
+ public int Id { get; set; }
+
+ [Required]
+ public required string Name { get; set; }
+ public ICollection Actions { get; set; } = new List();
+ public ICollection Modules { get; set; } = new List();
+}
diff --git a/FJPSite/Data/Authorisation/FeatureAction.cs b/FJPSite/Data/Authorisation/FeatureAction.cs
new file mode 100644
index 0000000..efd4e31
--- /dev/null
+++ b/FJPSite/Data/Authorisation/FeatureAction.cs
@@ -0,0 +1,22 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace FJPSite.Data.Authorisation;
+
+public class FeatureAction
+{
+ [Key]
+ public int Id { get; set; }
+
+ [Required, ForeignKey(nameof(Feature))]
+ public int FeatureId { get; set; }
+
+ public Feature Feature { get; set; }
+
+ [Required, ForeignKey(nameof(Action))]
+ public int ActionId { get; set; }
+
+ public PermissionAction Action { get; set; }
+
+ public ICollection RolePermissions { get; set; } = new List();
+}
diff --git a/FJPSite/Data/Authorisation/Module.cs b/FJPSite/Data/Authorisation/Module.cs
new file mode 100644
index 0000000..2eabd70
--- /dev/null
+++ b/FJPSite/Data/Authorisation/Module.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace FJPSite.Data.Authorisation;
+
+public class Module : SoftDeleteEntityBase
+{
+ [Key]
+ public int Id { get; set; }
+
+ [Required]
+ public required string Name { get; set; }
+
+ public ICollection Features { get; set; } = new List();
+}
diff --git a/FJPSite/Data/Authorisation/ModuleFeature.cs b/FJPSite/Data/Authorisation/ModuleFeature.cs
new file mode 100644
index 0000000..9f89354
--- /dev/null
+++ b/FJPSite/Data/Authorisation/ModuleFeature.cs
@@ -0,0 +1,21 @@
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace FJPSite.Data.Authorisation;
+
+public class ModuleFeature
+{
+ [Key]
+ public int Id { get; set; }
+
+ [Required, ForeignKey(nameof(Module))]
+ public int ModuleId { get; set; }
+
+ public Module Module { get; set; }
+
+ [ForeignKey(nameof(Feature))]
+ public int FeatureId { get; set; }
+
+ public Feature Feature { get; set; }
+}
diff --git a/FJPSite/Data/Authorisation/PermissionAction.cs b/FJPSite/Data/Authorisation/PermissionAction.cs
new file mode 100644
index 0000000..ba8cb30
--- /dev/null
+++ b/FJPSite/Data/Authorisation/PermissionAction.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace FJPSite.Data.Authorisation;
+
+public class PermissionAction
+{
+ [Key]
+ public int Id { get; set; }
+
+ [Required]
+ public required string Name { get; set; }
+
+ public ICollection Features { get; set; } = new List();
+}
diff --git a/FJPSite/Data/Authorisation/RolePermission.cs b/FJPSite/Data/Authorisation/RolePermission.cs
new file mode 100644
index 0000000..ae4dd29
--- /dev/null
+++ b/FJPSite/Data/Authorisation/RolePermission.cs
@@ -0,0 +1,21 @@
+using FJPSite.Data.Identity;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace FJPSite.Data.Authorisation;
+
+public class RolePermission
+{
+ [Key]
+ public int Id { get; set; }
+
+ [Required, ForeignKey(nameof(Role))]
+ public string RoleId { get; set; }
+
+ public RoleEntity Role { get; set; }
+
+ [Required, ForeignKey(nameof(FeatureAction))]
+ public int FeatureActionId { get; set; }
+
+ public FeatureAction FeatureAction { get; set; }
+}
diff --git a/FJPSite/Data/Authorisation/UserPermission.cs b/FJPSite/Data/Authorisation/UserPermission.cs
new file mode 100644
index 0000000..78dae00
--- /dev/null
+++ b/FJPSite/Data/Authorisation/UserPermission.cs
@@ -0,0 +1,22 @@
+using FJPSite.Data.Identity;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace FJPSite.Data.Authorisation;
+
+public class UserPermission
+{
+ [Key]
+ public int Id { get; set; }
+
+ [Required, ForeignKey(nameof(User))]
+ public string UserId { get; set; }
+
+ public UserEntity User { get; set; }
+
+ [Required, ForeignKey(nameof(FeatureAction))]
+ public int FeatureActionId { get; set; }
+
+ public FeatureAction FeatureAction { get; set; }
+}
+
diff --git a/FJPSite/Data/Identity/RoleEntity.cs b/FJPSite/Data/Identity/RoleEntity.cs
new file mode 100644
index 0000000..2b14886
--- /dev/null
+++ b/FJPSite/Data/Identity/RoleEntity.cs
@@ -0,0 +1,9 @@
+using FJPSite.Data.Authorisation;
+using Microsoft.AspNetCore.Identity;
+
+namespace FJPSite.Data.Identity;
+
+public class RoleEntity : IdentityRole
+{
+ public ICollection Permissions { get; set; } = new List();
+}
diff --git a/FJPSite/Data/Identity/UserEntity.cs b/FJPSite/Data/Identity/UserEntity.cs
new file mode 100644
index 0000000..85ffe05
--- /dev/null
+++ b/FJPSite/Data/Identity/UserEntity.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Identity;
+using System.ComponentModel.DataAnnotations;
+
+namespace FJPSite.Data.Identity;
+
+public class UserEntity : IdentityUser
+{
+ [Required] public required string Firstname { get; set; }
+ [Required] public required string Surname { get; set; }
+}
diff --git a/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs b/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs
deleted file mode 100644
index d379412..0000000
--- a/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs
+++ /dev/null
@@ -1,277 +0,0 @@
-//
-using FJPSite.Data;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using System;
-
-namespace FJPSite.Data.Migrations
-{
- [DbContext(typeof(ApplicationDbContext))]
- [Migration("00000000000000_CreateIdentitySchema")]
- partial class CreateIdentitySchema
- {
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "3.0.0")
- .HasAnnotation("Relational:MaxIdentifierLength", 128)
- .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
- {
- b.Property("Id")
- .HasColumnType("nvarchar(450)");
-
- b.Property("ConcurrencyStamp")
- .IsConcurrencyToken()
- .HasColumnType("nvarchar(max)");
-
- b.Property("Name")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.Property("NormalizedName")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.HasKey("Id");
-
- b.HasIndex("NormalizedName")
- .IsUnique()
- .HasName("RoleNameIndex")
- .HasFilter("[NormalizedName] IS NOT NULL");
-
- b.ToTable("AspNetRoles");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("int")
- .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
-
- b.Property("ClaimType")
- .HasColumnType("nvarchar(max)");
-
- b.Property("ClaimValue")
- .HasColumnType("nvarchar(max)");
-
- b.Property("RoleId")
- .IsRequired()
- .HasColumnType("nvarchar(450)");
-
- b.HasKey("Id");
-
- b.HasIndex("RoleId");
-
- b.ToTable("AspNetRoleClaims");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
- {
- b.Property("Id")
- .HasColumnType("nvarchar(450)");
-
- b.Property("AccessFailedCount")
- .HasColumnType("int");
-
- b.Property("ConcurrencyStamp")
- .IsConcurrencyToken()
- .HasColumnType("nvarchar(max)");
-
- b.Property("Email")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.Property("EmailConfirmed")
- .HasColumnType("bit");
-
- b.Property("LockoutEnabled")
- .HasColumnType("bit");
-
- b.Property("LockoutEnd")
- .HasColumnType("datetimeoffset");
-
- b.Property("NormalizedEmail")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.Property("NormalizedUserName")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.Property("PasswordHash")
- .HasColumnType("nvarchar(max)");
-
- b.Property("PhoneNumber")
- .HasColumnType("nvarchar(max)");
-
- b.Property("PhoneNumberConfirmed")
- .HasColumnType("bit");
-
- b.Property("SecurityStamp")
- .HasColumnType("nvarchar(max)");
-
- b.Property("TwoFactorEnabled")
- .HasColumnType("bit");
-
- b.Property("UserName")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.HasKey("Id");
-
- b.HasIndex("NormalizedEmail")
- .HasName("EmailIndex");
-
- b.HasIndex("NormalizedUserName")
- .IsUnique()
- .HasName("UserNameIndex")
- .HasFilter("[NormalizedUserName] IS NOT NULL");
-
- b.ToTable("AspNetUsers");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("int")
- .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
-
- b.Property("ClaimType")
- .HasColumnType("nvarchar(max)");
-
- b.Property("ClaimValue")
- .HasColumnType("nvarchar(max)");
-
- b.Property("UserId")
- .IsRequired()
- .HasColumnType("nvarchar(450)");
-
- b.HasKey("Id");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserClaims");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
- {
- b.Property("LoginProvider")
- .HasColumnType("nvarchar(128)")
- .HasMaxLength(128);
-
- b.Property("ProviderKey")
- .HasColumnType("nvarchar(128)")
- .HasMaxLength(128);
-
- b.Property("ProviderDisplayName")
- .HasColumnType("nvarchar(max)");
-
- b.Property("UserId")
- .IsRequired()
- .HasColumnType("nvarchar(450)");
-
- b.HasKey("LoginProvider", "ProviderKey");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserLogins");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
- {
- b.Property("UserId")
- .HasColumnType("nvarchar(450)");
-
- b.Property("RoleId")
- .HasColumnType("nvarchar(450)");
-
- b.HasKey("UserId", "RoleId");
-
- b.HasIndex("RoleId");
-
- b.ToTable("AspNetUserRoles");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
- {
- b.Property("UserId")
- .HasColumnType("nvarchar(450)");
-
- b.Property("LoginProvider")
- .HasColumnType("nvarchar(128)")
- .HasMaxLength(128);
-
- b.Property("Name")
- .HasColumnType("nvarchar(128)")
- .HasMaxLength(128);
-
- b.Property("Value")
- .HasColumnType("nvarchar(max)");
-
- b.HasKey("UserId", "LoginProvider", "Name");
-
- b.ToTable("AspNetUserTokens");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
- .WithMany()
- .HasForeignKey("RoleId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
- .WithMany()
- .HasForeignKey("RoleId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.cs b/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.cs
deleted file mode 100644
index ce0cff0..0000000
--- a/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.cs
+++ /dev/null
@@ -1,220 +0,0 @@
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-using System;
-
-namespace FJPSite.Data.Migrations
-{
- public partial class CreateIdentitySchema : Migration
- {
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "AspNetRoles",
- columns: table => new
- {
- Id = table.Column(nullable: false),
- Name = table.Column(maxLength: 256, nullable: true),
- NormalizedName = table.Column(maxLength: 256, nullable: true),
- ConcurrencyStamp = table.Column(nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetRoles", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUsers",
- columns: table => new
- {
- Id = table.Column(nullable: false),
- UserName = table.Column(maxLength: 256, nullable: true),
- NormalizedUserName = table.Column(maxLength: 256, nullable: true),
- Email = table.Column(maxLength: 256, nullable: true),
- NormalizedEmail = table.Column(maxLength: 256, nullable: true),
- EmailConfirmed = table.Column(nullable: false),
- PasswordHash = table.Column(nullable: true),
- SecurityStamp = table.Column(nullable: true),
- ConcurrencyStamp = table.Column(nullable: true),
- PhoneNumber = table.Column(nullable: true),
- PhoneNumberConfirmed = table.Column(nullable: false),
- TwoFactorEnabled = table.Column(nullable: false),
- LockoutEnd = table.Column(nullable: true),
- LockoutEnabled = table.Column(nullable: false),
- AccessFailedCount = table.Column(nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUsers", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetRoleClaims",
- columns: table => new
- {
- Id = table.Column(nullable: false)
- .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
- RoleId = table.Column(nullable: false),
- ClaimType = table.Column(nullable: true),
- ClaimValue = table.Column(nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
- table.ForeignKey(
- name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
- column: x => x.RoleId,
- principalTable: "AspNetRoles",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUserClaims",
- columns: table => new
- {
- Id = table.Column(nullable: false)
- .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
- UserId = table.Column(nullable: false),
- ClaimType = table.Column(nullable: true),
- ClaimValue = table.Column(nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
- table.ForeignKey(
- name: "FK_AspNetUserClaims_AspNetUsers_UserId",
- column: x => x.UserId,
- principalTable: "AspNetUsers",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUserLogins",
- columns: table => new
- {
- LoginProvider = table.Column(maxLength: 128, nullable: false),
- ProviderKey = table.Column(maxLength: 128, nullable: false),
- ProviderDisplayName = table.Column(nullable: true),
- UserId = table.Column(nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
- table.ForeignKey(
- name: "FK_AspNetUserLogins_AspNetUsers_UserId",
- column: x => x.UserId,
- principalTable: "AspNetUsers",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUserRoles",
- columns: table => new
- {
- UserId = table.Column(nullable: false),
- RoleId = table.Column(nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
- table.ForeignKey(
- name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
- column: x => x.RoleId,
- principalTable: "AspNetRoles",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- table.ForeignKey(
- name: "FK_AspNetUserRoles_AspNetUsers_UserId",
- column: x => x.UserId,
- principalTable: "AspNetUsers",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUserTokens",
- columns: table => new
- {
- UserId = table.Column(nullable: false),
- LoginProvider = table.Column(maxLength: 128, nullable: false),
- Name = table.Column(maxLength: 128, nullable: false),
- Value = table.Column(nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
- table.ForeignKey(
- name: "FK_AspNetUserTokens_AspNetUsers_UserId",
- column: x => x.UserId,
- principalTable: "AspNetUsers",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateIndex(
- name: "IX_AspNetRoleClaims_RoleId",
- table: "AspNetRoleClaims",
- column: "RoleId");
-
- migrationBuilder.CreateIndex(
- name: "RoleNameIndex",
- table: "AspNetRoles",
- column: "NormalizedName",
- unique: true,
- filter: "[NormalizedName] IS NOT NULL");
-
- migrationBuilder.CreateIndex(
- name: "IX_AspNetUserClaims_UserId",
- table: "AspNetUserClaims",
- column: "UserId");
-
- migrationBuilder.CreateIndex(
- name: "IX_AspNetUserLogins_UserId",
- table: "AspNetUserLogins",
- column: "UserId");
-
- migrationBuilder.CreateIndex(
- name: "IX_AspNetUserRoles_RoleId",
- table: "AspNetUserRoles",
- column: "RoleId");
-
- migrationBuilder.CreateIndex(
- name: "EmailIndex",
- table: "AspNetUsers",
- column: "NormalizedEmail");
-
- migrationBuilder.CreateIndex(
- name: "UserNameIndex",
- table: "AspNetUsers",
- column: "NormalizedUserName",
- unique: true,
- filter: "[NormalizedUserName] IS NOT NULL");
- }
-
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "AspNetRoleClaims");
-
- migrationBuilder.DropTable(
- name: "AspNetUserClaims");
-
- migrationBuilder.DropTable(
- name: "AspNetUserLogins");
-
- migrationBuilder.DropTable(
- name: "AspNetUserRoles");
-
- migrationBuilder.DropTable(
- name: "AspNetUserTokens");
-
- migrationBuilder.DropTable(
- name: "AspNetRoles");
-
- migrationBuilder.DropTable(
- name: "AspNetUsers");
- }
- }
-}
diff --git a/FJPSite/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/FJPSite/Data/Migrations/ApplicationDbContextModelSnapshot.cs
deleted file mode 100644
index 9fbdc5c..0000000
--- a/FJPSite/Data/Migrations/ApplicationDbContextModelSnapshot.cs
+++ /dev/null
@@ -1,275 +0,0 @@
-//
-using FJPSite.Data;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using System;
-
-namespace FJPSite.Data.Migrations
-{
- [DbContext(typeof(ApplicationDbContext))]
- partial class ApplicationDbContextModelSnapshot : ModelSnapshot
- {
- protected override void BuildModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "3.0.0")
- .HasAnnotation("Relational:MaxIdentifierLength", 128)
- .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
- {
- b.Property("Id")
- .HasColumnType("nvarchar(450)");
-
- b.Property("ConcurrencyStamp")
- .IsConcurrencyToken()
- .HasColumnType("nvarchar(max)");
-
- b.Property("Name")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.Property("NormalizedName")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.HasKey("Id");
-
- b.HasIndex("NormalizedName")
- .IsUnique()
- .HasName("RoleNameIndex")
- .HasFilter("[NormalizedName] IS NOT NULL");
-
- b.ToTable("AspNetRoles");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("int")
- .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
-
- b.Property("ClaimType")
- .HasColumnType("nvarchar(max)");
-
- b.Property("ClaimValue")
- .HasColumnType("nvarchar(max)");
-
- b.Property("RoleId")
- .IsRequired()
- .HasColumnType("nvarchar(450)");
-
- b.HasKey("Id");
-
- b.HasIndex("RoleId");
-
- b.ToTable("AspNetRoleClaims");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
- {
- b.Property("Id")
- .HasColumnType("nvarchar(450)");
-
- b.Property("AccessFailedCount")
- .HasColumnType("int");
-
- b.Property("ConcurrencyStamp")
- .IsConcurrencyToken()
- .HasColumnType("nvarchar(max)");
-
- b.Property("Email")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.Property("EmailConfirmed")
- .HasColumnType("bit");
-
- b.Property("LockoutEnabled")
- .HasColumnType("bit");
-
- b.Property("LockoutEnd")
- .HasColumnType("datetimeoffset");
-
- b.Property("NormalizedEmail")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.Property("NormalizedUserName")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.Property("PasswordHash")
- .HasColumnType("nvarchar(max)");
-
- b.Property("PhoneNumber")
- .HasColumnType("nvarchar(max)");
-
- b.Property("PhoneNumberConfirmed")
- .HasColumnType("bit");
-
- b.Property("SecurityStamp")
- .HasColumnType("nvarchar(max)");
-
- b.Property("TwoFactorEnabled")
- .HasColumnType("bit");
-
- b.Property("UserName")
- .HasColumnType("nvarchar(256)")
- .HasMaxLength(256);
-
- b.HasKey("Id");
-
- b.HasIndex("NormalizedEmail")
- .HasName("EmailIndex");
-
- b.HasIndex("NormalizedUserName")
- .IsUnique()
- .HasName("UserNameIndex")
- .HasFilter("[NormalizedUserName] IS NOT NULL");
-
- b.ToTable("AspNetUsers");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("int")
- .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
-
- b.Property("ClaimType")
- .HasColumnType("nvarchar(max)");
-
- b.Property("ClaimValue")
- .HasColumnType("nvarchar(max)");
-
- b.Property("UserId")
- .IsRequired()
- .HasColumnType("nvarchar(450)");
-
- b.HasKey("Id");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserClaims");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
- {
- b.Property("LoginProvider")
- .HasColumnType("nvarchar(128)")
- .HasMaxLength(128);
-
- b.Property("ProviderKey")
- .HasColumnType("nvarchar(128)")
- .HasMaxLength(128);
-
- b.Property("ProviderDisplayName")
- .HasColumnType("nvarchar(max)");
-
- b.Property("UserId")
- .IsRequired()
- .HasColumnType("nvarchar(450)");
-
- b.HasKey("LoginProvider", "ProviderKey");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserLogins");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
- {
- b.Property("UserId")
- .HasColumnType("nvarchar(450)");
-
- b.Property("RoleId")
- .HasColumnType("nvarchar(450)");
-
- b.HasKey("UserId", "RoleId");
-
- b.HasIndex("RoleId");
-
- b.ToTable("AspNetUserRoles");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
- {
- b.Property("UserId")
- .HasColumnType("nvarchar(450)");
-
- b.Property("LoginProvider")
- .HasColumnType("nvarchar(128)")
- .HasMaxLength(128);
-
- b.Property("Name")
- .HasColumnType("nvarchar(128)")
- .HasMaxLength(128);
-
- b.Property("Value")
- .HasColumnType("nvarchar(max)");
-
- b.HasKey("UserId", "LoginProvider", "Name");
-
- b.ToTable("AspNetUserTokens");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
- .WithMany()
- .HasForeignKey("RoleId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
- .WithMany()
- .HasForeignKey("RoleId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/FJPSite/Data/SoftDeleteEntityBase.cs b/FJPSite/Data/SoftDeleteEntityBase.cs
new file mode 100644
index 0000000..0fe1d18
--- /dev/null
+++ b/FJPSite/Data/SoftDeleteEntityBase.cs
@@ -0,0 +1,11 @@
+using FJPSite.Interfaces;
+using System.ComponentModel.DataAnnotations;
+
+namespace FJPSite.Data
+{
+ public class SoftDeleteEntityBase : ISoftDeleteEntity
+ {
+ [Required]
+ public bool IsActive { get; set; } = true;
+ }
+}
diff --git a/FJPSite/Enums/ActionEnum.cs b/FJPSite/Enums/ActionEnum.cs
new file mode 100644
index 0000000..a82fd82
--- /dev/null
+++ b/FJPSite/Enums/ActionEnum.cs
@@ -0,0 +1,16 @@
+namespace FJPSite.Enums;
+
+public enum ActionEnum
+{
+ Denied,
+ View,
+ Create,
+ Edit,
+ Delete,
+ Export,
+ Import,
+ Assign,
+ Review,
+ Approve,
+ Reject
+}
diff --git a/FJPSite/Enums/FeatureEnum.cs b/FJPSite/Enums/FeatureEnum.cs
new file mode 100644
index 0000000..5d64385
--- /dev/null
+++ b/FJPSite/Enums/FeatureEnum.cs
@@ -0,0 +1,11 @@
+namespace FJPSite.Enums;
+
+public enum FeatureEnum
+{
+ Users,
+ Roles,
+ Permissions,
+ Pages,
+ Settings,
+ Funerals,
+}
diff --git a/FJPSite/Enums/ModuleEnum.cs b/FJPSite/Enums/ModuleEnum.cs
new file mode 100644
index 0000000..1b94d7a
--- /dev/null
+++ b/FJPSite/Enums/ModuleEnum.cs
@@ -0,0 +1,8 @@
+namespace FJPSite.Enums;
+
+public enum ModuleEnum
+{
+ Authentication,
+ Site,
+ Funerals
+}
diff --git a/FJPSite/Enums/RoleEnum.cs b/FJPSite/Enums/RoleEnum.cs
new file mode 100644
index 0000000..a3dabb9
--- /dev/null
+++ b/FJPSite/Enums/RoleEnum.cs
@@ -0,0 +1,8 @@
+namespace FJPSite.Enums;
+
+public enum RoleEnum
+{
+ Admin,
+ Director,
+ User
+}
diff --git a/FJPSite/Extensions/DbContextExtension.cs b/FJPSite/Extensions/DbContextExtension.cs
new file mode 100644
index 0000000..e3d991b
--- /dev/null
+++ b/FJPSite/Extensions/DbContextExtension.cs
@@ -0,0 +1,11 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace FJPSite.Extensions;
+
+public static class DbContextExtension
+{
+ public static bool MigrationApplied(this DbContext context, string migrationName)
+ {
+ return context.Database.GetAppliedMigrations().FirstOrDefault(f => f == migrationName) != null;
+ }
+}
diff --git a/FJPSite/Factories/DbSeederFactory.cs b/FJPSite/Factories/DbSeederFactory.cs
new file mode 100644
index 0000000..51c6dae
--- /dev/null
+++ b/FJPSite/Factories/DbSeederFactory.cs
@@ -0,0 +1,39 @@
+using FJPSite.Attributes;
+using FJPSite.Data;
+using FJPSite.Extensions;
+using FJPSite.Interfaces;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using System.Reflection;
+
+namespace FJPSite.Factories;
+
+public static class DbSeederFactory
+{
+ public static void SeedDatabase(ApplicationDbContext context)
+ {
+ //TODO: Code works, just not sure if want to auto load/inject due to some needing to be done in a specific order.
+ var serviceDescriptors = typeof(Program).Assembly//.GetTypes().Where(type => type is {IsAbstract: false, IsInterface: false } && type.IsAssignableFrom(typeof(IEntitySeederFactory))).OrderBy(o => o.GetCustomAttribute().Order)
+ .DefinedTypes
+ .Where(type => type is { IsAbstract: false, IsInterface: false } &&
+ type.IsAssignableTo(typeof(IEntitySeederFactory))).OrderBy(o => o.GetCustomAttribute().Migration)
+ .ToArray();
+ //
+ foreach (var serviceDescriptor in serviceDescriptors)
+ {
+ if (context.MigrationApplied(serviceDescriptor.GetCustomAttribute().Migration))
+ {
+ IEntitySeederFactory entitySeederFactory = Activator.CreateInstance(serviceDescriptor) as IEntitySeederFactory;
+ entitySeederFactory.Seed(context);
+ }
+ }
+
+ /*Reflection
+ IdentitySeederFactory identitySeeder = new IdentitySeederFactory();
+ identitySeeder.Seed(context);
+
+ PermissionSeederFactory permissionSeeder = new PermissionSeederFactory();
+ permissionSeeder.Seed(context);*/
+ }
+}
diff --git a/FJPSite/Factories/EntitySeeders/EntitySeederFactory.cs b/FJPSite/Factories/EntitySeeders/EntitySeederFactory.cs
new file mode 100644
index 0000000..926e5a1
--- /dev/null
+++ b/FJPSite/Factories/EntitySeeders/EntitySeederFactory.cs
@@ -0,0 +1,13 @@
+using FJPSite.Attributes;
+using FJPSite.Data;
+using FJPSite.Interfaces;
+using Microsoft.EntityFrameworkCore;
+
+namespace FJPSite.Factories.EntitySeeders;
+
+
+[SeedMigration("NotImplemented")]
+public abstract class EntitySeederFactory : IEntitySeederFactory
+{
+ public abstract void Seed(ApplicationDbContext context);
+}
diff --git a/FJPSite/Factories/EntitySeeders/IdentitySeederFactory.cs b/FJPSite/Factories/EntitySeeders/IdentitySeederFactory.cs
new file mode 100644
index 0000000..bc34d7c
--- /dev/null
+++ b/FJPSite/Factories/EntitySeeders/IdentitySeederFactory.cs
@@ -0,0 +1,90 @@
+using FJPSite.Attributes;
+using FJPSite.Data;
+using FJPSite.Data.Identity;
+using FJPSite.Enums;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace FJPSite.Factories.EntitySeeders;
+
+[SeedMigration("20260409010423_CreateIdentity")]
+public class IdentitySeederFactory : EntitySeederFactory
+{
+ private struct UserSeed
+ {
+ public string Email { get; set; }
+ public string Firstname { get; set; }
+ public string Surname { get; set; }
+
+ public RoleEnum Role { get; set; }
+ }
+ private static string DefaultPassword = "Potters144A!";
+ private static UserSeed[] Users = [
+ new UserSeed {Email = "chris@fjp.com.au", Firstname = "Chris", Surname = "McInnes", Role = RoleEnum.Admin},
+ new UserSeed {Email = "kim@fjp.com.au", Firstname = "Kim", Surname = "Fell", Role = RoleEnum.Admin},
+ new UserSeed {Email = "amanda@fjp.com.au", Firstname = "Amanda", Surname = "Schenk", Role = RoleEnum.Director},
+ new UserSeed {Email = "reception@fjp.com.au", Firstname = "Lucy", Surname = "Fox", Role = RoleEnum.User}
+ ];
+
+ private static void SeedRoles(RoleManager roleManager)
+ {
+ foreach (var role in (RoleEnum[])Enum.GetValues(typeof(RoleEnum)))
+ {
+ var rolename = Enum.GetName(role);
+ var roleEntity = roleManager.FindByNameAsync(rolename).Result;
+ if (roleEntity == null)
+ {
+ var result = roleManager.CreateAsync(new RoleEntity { Name = rolename }).Result;
+ if (result.Succeeded == false)
+ {
+ throw new Exception("Failed while seeding roles in database");
+ }
+ }
+ }
+ }
+
+ private static void SeedUsers(UserManager userManager)
+ {
+ foreach (UserSeed userSeed in Users)
+ {
+ var user = userManager.FindByNameAsync(userSeed.Email).Result;
+ if (user == null)
+ {
+ var result = userManager.CreateAsync(new UserEntity()
+ {
+ UserName = userSeed.Email,
+ Email = userSeed.Email,
+ Surname = userSeed.Surname,
+ Firstname = userSeed.Firstname,
+ EmailConfirmed = true
+ }, DefaultPassword).Result;
+ if (result.Succeeded == false)
+ {
+ throw new Exception("Failed while seeding users in database");
+ }
+ user = userManager.FindByNameAsync(userSeed.Email).Result;
+ result = userManager.SetLockoutEnabledAsync(user, false).Result;
+ }
+
+ if (userManager.IsInRoleAsync(user, Enum.GetName(userSeed.Role)).Result == false)
+ {
+ var roles = userManager.GetRolesAsync(user).Result;
+ userManager.RemoveFromRolesAsync(user, roles).Wait();
+ userManager.AddToRoleAsync(user, Enum.GetName(userSeed.Role)).Wait();
+ }
+
+ }
+ }
+ public override void Seed(ApplicationDbContext context)
+ {
+ var userManager = context.GetService>();
+ var roleManager = context.GetService>();
+
+ SeedRoles(roleManager);
+ SeedUsers(userManager);
+
+ context.SaveChanges();
+ }
+}
diff --git a/FJPSite/Factories/EntitySeeders/PermissionSeederFactory.cs b/FJPSite/Factories/EntitySeeders/PermissionSeederFactory.cs
new file mode 100644
index 0000000..f154bef
--- /dev/null
+++ b/FJPSite/Factories/EntitySeeders/PermissionSeederFactory.cs
@@ -0,0 +1,174 @@
+using FJPSite.Attributes;
+using FJPSite.Data;
+using FJPSite.Data.Authorisation;
+using FJPSite.Data.Identity;
+using FJPSite.Enums;
+using Microsoft.Build.Framework;
+
+namespace FJPSite.Factories.EntitySeeders;
+
+[SeedMigration("20260409123724_CreatePermissions")]
+public class PermissionSeederFactory : EntitySeederFactory
+{
+ private struct FeatureActionLink
+ {
+ public FeatureEnum linkFeature { get; set; }
+ public IList linkActions { get; set; }
+ }
+
+ private void SeedModules(ApplicationDbContext context)
+ {
+ foreach (var moduleName in Enum.GetNames(typeof(ModuleEnum)))
+ {
+ if (context.Modules.FirstOrDefault(m => m.Name == moduleName) == null)
+ {
+ context.Modules.Add(new Module { Name = moduleName, IsActive = true });
+ }
+ }
+ context.SaveChanges();
+ }
+
+ private void SeedFeatures(ApplicationDbContext context)
+ {
+ foreach (var featureName in Enum.GetNames(typeof(FeatureEnum)))
+ {
+ if (context.Features.FirstOrDefault(m => m.Name == featureName) == null)
+ {
+ context.Features.Add(new Feature { Name = featureName });
+ }
+ }
+ context.SaveChanges();
+ }
+
+ private void SeedActions(ApplicationDbContext context)
+ {
+ foreach (var actionName in Enum.GetNames(typeof(ActionEnum)))
+ {
+ if (context.Actions.FirstOrDefault(m => m.Name == actionName) == null)
+ {
+ context.Actions.Add(new PermissionAction { Name = actionName });
+ }
+ }
+ context.SaveChanges();
+ }
+
+ private void SeedModuleFeatures(ApplicationDbContext context)
+ {
+ Dictionary> moduleFeatureList = new Dictionary>
+ {
+ {ModuleEnum.Authentication, new List{FeatureEnum.Permissions, FeatureEnum.Users} },
+ {ModuleEnum.Funerals, new List{FeatureEnum.Funerals} },
+ {ModuleEnum.Site, new List{FeatureEnum.Settings, FeatureEnum.Pages} }
+ };
+
+ foreach (var module in moduleFeatureList)
+ {
+ var moduleEntity = context.Modules.First(f => f.Name == Enum.GetName(module.Key));
+ foreach (var feature in module.Value)
+ {
+ var featureEntity = context.Features.First(f => f.Name == Enum.GetName(feature));
+ if (context.ModuleFeatures.FirstOrDefault(f => f.ModuleId == moduleEntity.Id && f.FeatureId == featureEntity.Id) == null)
+ {
+ context.ModuleFeatures.Add(new ModuleFeature
+ {
+ ModuleId = moduleEntity.Id,
+ FeatureId = featureEntity.Id,
+ });
+ }
+ }
+ }
+ context.SaveChanges();
+ }
+
+ private void SeedFeatureAction(ApplicationDbContext context)
+ {
+ Dictionary> featureActionList = new Dictionary>
+ {
+ {FeatureEnum.Permissions, new List{ActionEnum.View, ActionEnum.Assign } },
+ {FeatureEnum.Roles, new List{ ActionEnum.View, ActionEnum.Assign} },
+ {FeatureEnum.Pages, new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } },
+ {FeatureEnum.Users, new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } },
+ {FeatureEnum.Settings, new List{ ActionEnum.View, ActionEnum.Edit } },
+ {FeatureEnum.Funerals, new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } },
+ };
+
+ foreach (var feature in featureActionList)
+ {
+ var featureEntity = context.Features.First(f => f.Name == Enum.GetName(feature.Key));
+ foreach (var action in feature.Value)
+ {
+ var actionEntity = context.Actions.First(f => f.Name == Enum.GetName(action));
+ if (context.FeatureActions.FirstOrDefault(f => f.FeatureId == featureEntity.Id && f.ActionId == actionEntity.Id) == null)
+ {
+ context.FeatureActions.Add(new FeatureAction
+ {
+ ActionId = actionEntity.Id,
+ FeatureId = featureEntity.Id,
+ });
+ }
+ }
+ }
+
+ context.SaveChanges();
+ }
+
+ private void SeedRolePermission(ApplicationDbContext context)
+ {
+ Dictionary> rolePermissions = new Dictionary>
+ {
+ { RoleEnum.Admin, new List
+ {
+ new FeatureActionLink {linkFeature = FeatureEnum.Permissions, linkActions = new List { ActionEnum.View, ActionEnum.Assign } },
+ new FeatureActionLink {linkFeature = FeatureEnum.Roles, linkActions = new List { ActionEnum.View, ActionEnum.Assign } },
+ new FeatureActionLink {linkFeature = FeatureEnum.Pages, linkActions = new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } },
+ new FeatureActionLink {linkFeature = FeatureEnum.Users, linkActions = new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } },
+ new FeatureActionLink {linkFeature = FeatureEnum.Settings, linkActions = new List{ ActionEnum.View, ActionEnum.Edit } },
+ new FeatureActionLink {linkFeature = FeatureEnum.Funerals, linkActions = new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } },
+ }
+ },
+ { RoleEnum.Director, new List
+ {
+ new FeatureActionLink {linkFeature = FeatureEnum.Funerals, linkActions = new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } },
+ }
+ },
+ { RoleEnum.User, new List
+ {
+ new FeatureActionLink {linkFeature = FeatureEnum.Funerals, linkActions = new List{ ActionEnum.View, ActionEnum.Create } },
+ }
+ },
+ };
+
+ foreach (var roleFeatures in rolePermissions)
+ {
+ var roleEntity = context.Roles.First(f => f.Name == Enum.GetName(roleFeatures.Key));
+ foreach (var feature in roleFeatures.Value)
+ {
+ var featureEntity = context.Features.First(f => f.Name == Enum.GetName(feature.linkFeature));
+ foreach (var action in feature.linkActions)
+ {
+ var actionEntity = context.Actions.First(f => f.Name == Enum.GetName(action));
+ var featureActionEntity = context.FeatureActions.First(f => f.FeatureId == featureEntity.Id && f.ActionId == actionEntity.Id);
+ if (context.RolePermissions.FirstOrDefault(f => f.RoleId == roleEntity.Id && f.FeatureActionId == featureActionEntity.Id) == null)
+ {
+ context.RolePermissions.Add(new RolePermission
+ {
+ FeatureActionId = featureActionEntity.Id,
+ RoleId = roleEntity.Id
+ });
+ }
+ }
+ }
+ }
+ context.SaveChanges();
+ }
+
+ public override void Seed(ApplicationDbContext context)
+ {
+ SeedModules(context);
+ SeedFeatures(context);
+ SeedActions(context);
+ SeedModuleFeatures(context);
+ SeedFeatureAction(context);
+ SeedRolePermission(context);
+ }
+}
diff --git a/FJPSite/Factories/UserClaimFactory.cs b/FJPSite/Factories/UserClaimFactory.cs
new file mode 100644
index 0000000..051abbb
--- /dev/null
+++ b/FJPSite/Factories/UserClaimFactory.cs
@@ -0,0 +1,23 @@
+using FJPSite.Data.Identity;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.Options;
+using System.Security.Claims;
+
+namespace FJPSite.Factories;
+
+public class UserClaimFactory : UserClaimsPrincipalFactory
+{
+ public UserClaimFactory(UserManager userManager, RoleManager roleManager, IOptions options) : base(userManager, roleManager, options)
+ {
+ }
+
+ public async override Task CreateAsync(UserEntity user)
+ {
+ var principal = await base.CreateAsync(user);
+ ((ClaimsIdentity)principal.Identity).AddClaims(new[] {
+ new Claim(ClaimTypes.GivenName, user.Firstname),
+ new Claim(ClaimTypes.Surname, user.Surname)
+ });
+ return principal;
+ }
+}
diff --git a/FJPSite/Handlers/PermissionAuthorizationHandler.cs b/FJPSite/Handlers/PermissionAuthorizationHandler.cs
new file mode 100644
index 0000000..72b0642
--- /dev/null
+++ b/FJPSite/Handlers/PermissionAuthorizationHandler.cs
@@ -0,0 +1,45 @@
+using FJPSite.Helpers;
+using Microsoft.AspNetCore.Authorization;
+using System.Security.Claims;
+
+namespace FJPSite.Handlers;
+
+public class PermissionAuthorizationHandler : AuthorizationHandler
+{
+ private readonly IPermissionService _permissionService;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ public PermissionAuthorizationHandler(
+ IPermissionService permissionService,
+ IHttpContextAccessor httpContextAccessor)
+ {
+ _permissionService = permissionService;
+ _httpContextAccessor = httpContextAccessor;
+ }
+ protected override async Task HandleRequirementAsync(
+ AuthorizationHandlerContext context,
+ PermissionRequirement requirement)
+ {
+ if (context.User == null)
+ {
+ context.Fail();
+ return;
+ }
+ // Get user ID from claims
+ var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
+ if (string.IsNullOrEmpty(userId))
+ {
+ context.Fail();
+ return;
+ }
+ // Check if user has the required permission
+ bool hasPermission = await _permissionService
+ .UserHasPermissionAsync(userId, requirement.Feature, requirement.Action);
+ if (hasPermission)
+ {
+ context.Succeed(requirement);
+ }
+ else
+ {
+ context.Fail();
+ }
+ }
\ No newline at end of file
diff --git a/FJPSite/Helpers/PermissionRequirement.cs b/FJPSite/Helpers/PermissionRequirement.cs
new file mode 100644
index 0000000..2794d89
--- /dev/null
+++ b/FJPSite/Helpers/PermissionRequirement.cs
@@ -0,0 +1,15 @@
+using FJPSite.Enums;
+using Microsoft.AspNetCore.Authorization;
+
+namespace FJPSite.Helpers;
+
+public class PermissionRequirement : IAuthorizationRequirement
+{
+ public FeatureEnum Feature { get; }
+ public ActionEnum Action { get; }
+ public PermissionRequirement(FeatureEnum feature, ActionEnum action)
+ {
+ Feature = feature;
+ Action = action;
+ }
+}
diff --git a/FJPSite/Interfaces/IEntitySeederFactory.cs b/FJPSite/Interfaces/IEntitySeederFactory.cs
new file mode 100644
index 0000000..4af2cb2
--- /dev/null
+++ b/FJPSite/Interfaces/IEntitySeederFactory.cs
@@ -0,0 +1,9 @@
+using FJPSite.Data;
+using Microsoft.EntityFrameworkCore;
+
+namespace FJPSite.Interfaces;
+
+public interface IEntitySeederFactory
+{
+ public void Seed(ApplicationDbContext context);
+}
diff --git a/FJPSite/Interfaces/IPermissionService.cs b/FJPSite/Interfaces/IPermissionService.cs
new file mode 100644
index 0000000..d079afa
--- /dev/null
+++ b/FJPSite/Interfaces/IPermissionService.cs
@@ -0,0 +1,13 @@
+using FJPSite.Enums;
+using FJPSite.Models;
+
+namespace FJPSite.Interfaces;
+
+public interface IPermissionService
+{
+ Task UserHasPermissionAsync(string userId, FeatureEnum feature, ActionEnum action);
+ /*Task> GetUserPermissionsAsync(string userId);
+ Task RoleHasPermissionAsync(string roleId, FeatureEnum feature, ActionEnum action);
+ Task AssignPermissionsToRoleAsync(string roleId, List assignments);*/
+ Task> GetPermissionStructureAsync(int moduleId);
+}
\ No newline at end of file
diff --git a/FJPSite/Interfaces/ISoftDeleteEntity.cs b/FJPSite/Interfaces/ISoftDeleteEntity.cs
new file mode 100644
index 0000000..f9e96f6
--- /dev/null
+++ b/FJPSite/Interfaces/ISoftDeleteEntity.cs
@@ -0,0 +1,10 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace FJPSite.Interfaces;
+
+public interface ISoftDeleteEntity
+{
+ [Required, DefaultValue(true)]
+ public bool IsActive { get; set; }
+}
diff --git a/FJPSite/Migrations/20260409010423_CreateIdentity.Designer.cs b/FJPSite/Migrations/20260409010423_CreateIdentity.Designer.cs
new file mode 100644
index 0000000..dd29ce2
--- /dev/null
+++ b/FJPSite/Migrations/20260409010423_CreateIdentity.Designer.cs
@@ -0,0 +1,499 @@
+//
+using System;
+using FJPSite.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace FJPSite.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20260409010423_CreateIdentity")]
+ partial class CreateIdentity
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.5")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("FJPSite.Data.Authorisation.Action", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Action");
+ });
+
+ modelBuilder.Entity("FJPSite.Data.Authorisation.Feature", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Feature");
+ });
+
+ modelBuilder.Entity("FJPSite.Data.Authorisation.FeatureAction", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ActionId")
+ .HasColumnType("int");
+
+ b.Property("FeatureId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ActionId");
+
+ b.HasIndex("FeatureId");
+
+ b.ToTable("FeatureAction");
+ });
+
+ modelBuilder.Entity("FJPSite.Data.Authorisation.Module", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Module");
+ });
+
+ modelBuilder.Entity("FJPSite.Data.Authorisation.ModuleFeature", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("FeatureId")
+ .HasColumnType("int");
+
+ b.Property("ModuleId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FeatureId");
+
+ b.HasIndex("ModuleId");
+
+ b.ToTable("ModuleFeature");
+ });
+
+ modelBuilder.Entity("FJPSite.Data.Authorisation.RolePermission", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("FeatureActionId")
+ .HasColumnType("int");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FeatureActionId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("RolePermission");
+ });
+
+ modelBuilder.Entity("FJPSite.Data.Identity.UserEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("Firstname")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Surname")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("bit");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("FJPSite.Data.Identity.UserRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex")
+ .HasFilter("[NormalizedName] IS NOT NULL");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin