Spaces:
Running
Running
User commited on
Commit ·
4c550c2
1
Parent(s): 01be06d
add: new db tables Permission, RoleMenusLegacy, Menu, Fix RolePerms and Perms, adjust Roleservice so it writes to RoleMenus and RolePermissions separately. Menuservice adjust to read from Menus and Permissions instead of the old menu tables.
Browse files- TaskTrackingSystem.Database/AppDbContextModels/AppDbContext.cs +106 -4
- TaskTrackingSystem.Database/AppDbContextModels/{AdminMenu.cs → Menu.cs} +21 -5
- TaskTrackingSystem.Database/AppDbContextModels/Permission.cs +37 -0
- TaskTrackingSystem.Database/AppDbContextModels/Role.cs +4 -0
- TaskTrackingSystem.Database/AppDbContextModels/RoleMenu.cs +10 -8
- TaskTrackingSystem.Database/AppDbContextModels/RoleMenusLegacy.cs +25 -0
- TaskTrackingSystem.Database/AppDbContextModels/RolePermission.cs +27 -0
- TaskTrackingSystem.WebApi/Features/Menu/MenuController.cs +13 -0
- TaskTrackingSystem.WebApi/Features/Menu/MenuService.cs +124 -103
- TaskTrackingSystem.WebApi/Features/Role/RoleService.cs +119 -54
- TaskTrackingSystem.WebApp/Components/Layout/NavMenu.razor +43 -4
- TaskTrackingSystem.WebApp/MenuAuthorizationService.cs +58 -5
- TaskTrackingSystem.WebApp/UserSessionState.cs +7 -0
- permission-schema-upgrade.sql +329 -0
TaskTrackingSystem.Database/AppDbContextModels/AppDbContext.cs
CHANGED
|
@@ -21,10 +21,14 @@ public partial class AppDbContext : DbContext
|
|
| 21 |
|
| 22 |
public virtual DbSet<FileAttachment> FileAttachments { get; set; }
|
| 23 |
|
|
|
|
|
|
|
| 24 |
public virtual DbSet<MenuAdmin> MenuAdmins { get; set; }
|
| 25 |
|
| 26 |
public virtual DbSet<MenuAdminDetail> MenuAdminDetails { get; set; }
|
| 27 |
|
|
|
|
|
|
|
| 28 |
public virtual DbSet<Project> Projects { get; set; }
|
| 29 |
|
| 30 |
public virtual DbSet<ProjectMember> ProjectMembers { get; set; }
|
|
@@ -33,6 +37,10 @@ public partial class AppDbContext : DbContext
|
|
| 33 |
|
| 34 |
public virtual DbSet<RoleMenu> RoleMenus { get; set; }
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
public virtual DbSet<Task> Tasks { get; set; }
|
| 37 |
|
| 38 |
public virtual DbSet<TaskHistory> TaskHistories { get; set; }
|
|
@@ -42,8 +50,8 @@ public partial class AppDbContext : DbContext
|
|
| 42 |
public virtual DbSet<User> Users { get; set; }
|
| 43 |
|
| 44 |
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
| 45 |
-
|
| 46 |
-
|
| 47 |
|
| 48 |
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
| 49 |
{
|
|
@@ -103,6 +111,27 @@ public partial class AppDbContext : DbContext
|
|
| 103 |
.HasConstraintName("FK_FileAttachments_Tasks");
|
| 104 |
});
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
modelBuilder.Entity<MenuAdmin>(entity =>
|
| 107 |
{
|
| 108 |
entity.HasKey(e => e.AdminMenuId);
|
|
@@ -128,6 +157,28 @@ public partial class AppDbContext : DbContext
|
|
| 128 |
entity.Property(e => e.ParentMenuCode).HasMaxLength(50);
|
| 129 |
});
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
modelBuilder.Entity<Project>(entity =>
|
| 132 |
{
|
| 133 |
entity.HasIndex(e => e.CreatedById, "IX_Projects_CreatedById");
|
|
@@ -181,12 +232,63 @@ public partial class AppDbContext : DbContext
|
|
| 181 |
|
| 182 |
modelBuilder.Entity<RoleMenu>(entity =>
|
| 183 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
entity.Property(e => e.RoleMenuId).HasMaxLength(50);
|
| 185 |
entity.Property(e => e.CreatedUserId).HasMaxLength(50);
|
| 186 |
entity.Property(e => e.MenuCode).HasMaxLength(50);
|
| 187 |
-
entity.Property(e => e.RoleCode).HasMaxLength(50);
|
| 188 |
entity.Property(e => e.ModifiedUserId).HasMaxLength(50);
|
| 189 |
-
entity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
});
|
| 191 |
|
| 192 |
modelBuilder.Entity<Task>(entity =>
|
|
|
|
| 21 |
|
| 22 |
public virtual DbSet<FileAttachment> FileAttachments { get; set; }
|
| 23 |
|
| 24 |
+
public virtual DbSet<Menu> Menus { get; set; }
|
| 25 |
+
|
| 26 |
public virtual DbSet<MenuAdmin> MenuAdmins { get; set; }
|
| 27 |
|
| 28 |
public virtual DbSet<MenuAdminDetail> MenuAdminDetails { get; set; }
|
| 29 |
|
| 30 |
+
public virtual DbSet<Permission> Permissions { get; set; }
|
| 31 |
+
|
| 32 |
public virtual DbSet<Project> Projects { get; set; }
|
| 33 |
|
| 34 |
public virtual DbSet<ProjectMember> ProjectMembers { get; set; }
|
|
|
|
| 37 |
|
| 38 |
public virtual DbSet<RoleMenu> RoleMenus { get; set; }
|
| 39 |
|
| 40 |
+
public virtual DbSet<RoleMenusLegacy> RoleMenusLegacies { get; set; }
|
| 41 |
+
|
| 42 |
+
public virtual DbSet<RolePermission> RolePermissions { get; set; }
|
| 43 |
+
|
| 44 |
public virtual DbSet<Task> Tasks { get; set; }
|
| 45 |
|
| 46 |
public virtual DbSet<TaskHistory> TaskHistories { get; set; }
|
|
|
|
| 50 |
public virtual DbSet<User> Users { get; set; }
|
| 51 |
|
| 52 |
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
| 53 |
+
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
|
| 54 |
+
=> optionsBuilder.UseSqlServer("Server=.;Database=TTS;User Id=sa;Password=sasa@123;TrustServerCertificate=True;");
|
| 55 |
|
| 56 |
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
| 57 |
{
|
|
|
|
| 111 |
.HasConstraintName("FK_FileAttachments_Tasks");
|
| 112 |
});
|
| 113 |
|
| 114 |
+
modelBuilder.Entity<Menu>(entity =>
|
| 115 |
+
{
|
| 116 |
+
entity.HasIndex(e => e.ParentMenuId, "IX_Menus_ParentMenuId");
|
| 117 |
+
|
| 118 |
+
entity.HasIndex(e => e.MenuCode, "UQ_Menus_MenuCode").IsUnique();
|
| 119 |
+
|
| 120 |
+
entity.Property(e => e.CreatedAt)
|
| 121 |
+
.HasPrecision(0)
|
| 122 |
+
.HasDefaultValueSql("(sysutcdatetime())");
|
| 123 |
+
entity.Property(e => e.Icon).HasMaxLength(50);
|
| 124 |
+
entity.Property(e => e.MenuCode).HasMaxLength(50);
|
| 125 |
+
entity.Property(e => e.MenuName).HasMaxLength(100);
|
| 126 |
+
entity.Property(e => e.MenuUrl).HasMaxLength(200);
|
| 127 |
+
entity.Property(e => e.UpdatedAt).HasPrecision(0);
|
| 128 |
+
entity.Property(e => e.Visible).HasDefaultValue(true);
|
| 129 |
+
|
| 130 |
+
entity.HasOne(d => d.ParentMenu).WithMany(p => p.InverseParentMenu)
|
| 131 |
+
.HasForeignKey(d => d.ParentMenuId)
|
| 132 |
+
.HasConstraintName("FK_Menus_ParentMenu");
|
| 133 |
+
});
|
| 134 |
+
|
| 135 |
modelBuilder.Entity<MenuAdmin>(entity =>
|
| 136 |
{
|
| 137 |
entity.HasKey(e => e.AdminMenuId);
|
|
|
|
| 157 |
entity.Property(e => e.ParentMenuCode).HasMaxLength(50);
|
| 158 |
});
|
| 159 |
|
| 160 |
+
modelBuilder.Entity<Permission>(entity =>
|
| 161 |
+
{
|
| 162 |
+
entity.HasIndex(e => e.MenuId, "IX_Permissions_MenuId");
|
| 163 |
+
|
| 164 |
+
entity.HasIndex(e => e.PermissionCode, "UQ_Permissions_PermissionCode").IsUnique();
|
| 165 |
+
|
| 166 |
+
entity.Property(e => e.ActionName).HasMaxLength(100);
|
| 167 |
+
entity.Property(e => e.ApiName).HasMaxLength(100);
|
| 168 |
+
entity.Property(e => e.CreatedAt)
|
| 169 |
+
.HasPrecision(0)
|
| 170 |
+
.HasDefaultValueSql("(sysutcdatetime())");
|
| 171 |
+
entity.Property(e => e.HttpMethod).HasMaxLength(20);
|
| 172 |
+
entity.Property(e => e.PermissionCode).HasMaxLength(50);
|
| 173 |
+
entity.Property(e => e.UpdatedAt).HasPrecision(0);
|
| 174 |
+
entity.Property(e => e.Visible).HasDefaultValue(true);
|
| 175 |
+
|
| 176 |
+
entity.HasOne(d => d.Menu).WithMany(p => p.Permissions)
|
| 177 |
+
.HasForeignKey(d => d.MenuId)
|
| 178 |
+
.OnDelete(DeleteBehavior.ClientSetNull)
|
| 179 |
+
.HasConstraintName("FK_Permissions_Menu");
|
| 180 |
+
});
|
| 181 |
+
|
| 182 |
modelBuilder.Entity<Project>(entity =>
|
| 183 |
{
|
| 184 |
entity.HasIndex(e => e.CreatedById, "IX_Projects_CreatedById");
|
|
|
|
| 232 |
|
| 233 |
modelBuilder.Entity<RoleMenu>(entity =>
|
| 234 |
{
|
| 235 |
+
entity.HasKey(e => e.RoleMenuId).HasName("PK_RoleMenus_New");
|
| 236 |
+
|
| 237 |
+
entity.HasIndex(e => new { e.RoleId, e.MenuId }, "UQ_RoleMenus_Role_Menu").IsUnique();
|
| 238 |
+
|
| 239 |
+
entity.Property(e => e.CreatedAt)
|
| 240 |
+
.HasPrecision(0)
|
| 241 |
+
.HasDefaultValueSql("(sysutcdatetime())");
|
| 242 |
+
entity.Property(e => e.UpdatedAt).HasPrecision(0);
|
| 243 |
+
|
| 244 |
+
entity.HasOne(d => d.Menu).WithMany(p => p.RoleMenus)
|
| 245 |
+
.HasForeignKey(d => d.MenuId)
|
| 246 |
+
.OnDelete(DeleteBehavior.ClientSetNull)
|
| 247 |
+
.HasConstraintName("FK_RoleMenus_Menus");
|
| 248 |
+
|
| 249 |
+
entity.HasOne(d => d.Role).WithMany(p => p.RoleMenus)
|
| 250 |
+
.HasForeignKey(d => d.RoleId)
|
| 251 |
+
.OnDelete(DeleteBehavior.ClientSetNull)
|
| 252 |
+
.HasConstraintName("FK_RoleMenus_Roles");
|
| 253 |
+
});
|
| 254 |
+
|
| 255 |
+
modelBuilder.Entity<RoleMenusLegacy>(entity =>
|
| 256 |
+
{
|
| 257 |
+
entity.HasKey(e => e.RoleMenuId).HasName("PK_RoleMenus");
|
| 258 |
+
|
| 259 |
+
entity.ToTable("RoleMenus_Legacy");
|
| 260 |
+
|
| 261 |
+
entity.HasIndex(e => e.RoleId, "IX_RoleMenus_RoleId");
|
| 262 |
+
|
| 263 |
entity.Property(e => e.RoleMenuId).HasMaxLength(50);
|
| 264 |
entity.Property(e => e.CreatedUserId).HasMaxLength(50);
|
| 265 |
entity.Property(e => e.MenuCode).HasMaxLength(50);
|
|
|
|
| 266 |
entity.Property(e => e.ModifiedUserId).HasMaxLength(50);
|
| 267 |
+
entity.Property(e => e.RoleCode).HasMaxLength(50);
|
| 268 |
+
});
|
| 269 |
+
|
| 270 |
+
modelBuilder.Entity<RolePermission>(entity =>
|
| 271 |
+
{
|
| 272 |
+
entity.HasIndex(e => e.PermissionId, "IX_RolePermissions_PermissionId");
|
| 273 |
+
|
| 274 |
+
entity.HasIndex(e => e.RoleId, "IX_RolePermissions_RoleId");
|
| 275 |
+
|
| 276 |
+
entity.HasIndex(e => new { e.RoleId, e.PermissionId }, "UQ_RolePermissions_Role_Permission").IsUnique();
|
| 277 |
+
|
| 278 |
+
entity.Property(e => e.CreatedAt)
|
| 279 |
+
.HasPrecision(0)
|
| 280 |
+
.HasDefaultValueSql("(sysutcdatetime())");
|
| 281 |
+
entity.Property(e => e.UpdatedAt).HasPrecision(0);
|
| 282 |
+
|
| 283 |
+
entity.HasOne(d => d.Permission).WithMany(p => p.RolePermissions)
|
| 284 |
+
.HasForeignKey(d => d.PermissionId)
|
| 285 |
+
.OnDelete(DeleteBehavior.ClientSetNull)
|
| 286 |
+
.HasConstraintName("FK_RolePermissions_Permissions");
|
| 287 |
+
|
| 288 |
+
entity.HasOne(d => d.Role).WithMany(p => p.RolePermissions)
|
| 289 |
+
.HasForeignKey(d => d.RoleId)
|
| 290 |
+
.OnDelete(DeleteBehavior.ClientSetNull)
|
| 291 |
+
.HasConstraintName("FK_RolePermissions_Roles");
|
| 292 |
});
|
| 293 |
|
| 294 |
modelBuilder.Entity<Task>(entity =>
|
TaskTrackingSystem.Database/AppDbContextModels/{AdminMenu.cs → Menu.cs}
RENAMED
|
@@ -3,23 +3,39 @@ using System.Collections.Generic;
|
|
| 3 |
|
| 4 |
namespace TaskTrackingSystem.Database.AppDbContextModels;
|
| 5 |
|
| 6 |
-
public partial class
|
| 7 |
{
|
| 8 |
-
public
|
| 9 |
|
| 10 |
public string MenuCode { get; set; } = null!;
|
| 11 |
|
| 12 |
-
public
|
| 13 |
|
| 14 |
public string MenuName { get; set; } = null!;
|
| 15 |
|
| 16 |
public string? MenuUrl { get; set; }
|
| 17 |
|
|
|
|
|
|
|
| 18 |
public bool Visible { get; set; }
|
| 19 |
|
| 20 |
public int OrderNo { get; set; }
|
| 21 |
|
| 22 |
-
public
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
public
|
| 25 |
}
|
|
|
|
| 3 |
|
| 4 |
namespace TaskTrackingSystem.Database.AppDbContextModels;
|
| 5 |
|
| 6 |
+
public partial class Menu
|
| 7 |
{
|
| 8 |
+
public long MenuId { get; set; }
|
| 9 |
|
| 10 |
public string MenuCode { get; set; } = null!;
|
| 11 |
|
| 12 |
+
public long? ParentMenuId { get; set; }
|
| 13 |
|
| 14 |
public string MenuName { get; set; } = null!;
|
| 15 |
|
| 16 |
public string? MenuUrl { get; set; }
|
| 17 |
|
| 18 |
+
public string? Icon { get; set; }
|
| 19 |
+
|
| 20 |
public bool Visible { get; set; }
|
| 21 |
|
| 22 |
public int OrderNo { get; set; }
|
| 23 |
|
| 24 |
+
public bool IsDeleted { get; set; }
|
| 25 |
+
|
| 26 |
+
public long? CreatedById { get; set; }
|
| 27 |
+
|
| 28 |
+
public DateTime CreatedAt { get; set; }
|
| 29 |
+
|
| 30 |
+
public long? UpdatedById { get; set; }
|
| 31 |
+
|
| 32 |
+
public DateTime? UpdatedAt { get; set; }
|
| 33 |
+
|
| 34 |
+
public virtual ICollection<Menu> InverseParentMenu { get; set; } = new List<Menu>();
|
| 35 |
+
|
| 36 |
+
public virtual Menu? ParentMenu { get; set; }
|
| 37 |
+
|
| 38 |
+
public virtual ICollection<Permission> Permissions { get; set; } = new List<Permission>();
|
| 39 |
|
| 40 |
+
public virtual ICollection<RoleMenu> RoleMenus { get; set; } = new List<RoleMenu>();
|
| 41 |
}
|
TaskTrackingSystem.Database/AppDbContextModels/Permission.cs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using System;
|
| 2 |
+
using System.Collections.Generic;
|
| 3 |
+
|
| 4 |
+
namespace TaskTrackingSystem.Database.AppDbContextModels;
|
| 5 |
+
|
| 6 |
+
public partial class Permission
|
| 7 |
+
{
|
| 8 |
+
public long PermissionId { get; set; }
|
| 9 |
+
|
| 10 |
+
public string PermissionCode { get; set; } = null!;
|
| 11 |
+
|
| 12 |
+
public long MenuId { get; set; }
|
| 13 |
+
|
| 14 |
+
public string ActionName { get; set; } = null!;
|
| 15 |
+
|
| 16 |
+
public string ApiName { get; set; } = null!;
|
| 17 |
+
|
| 18 |
+
public string? HttpMethod { get; set; }
|
| 19 |
+
|
| 20 |
+
public bool Visible { get; set; }
|
| 21 |
+
|
| 22 |
+
public int OrderNo { get; set; }
|
| 23 |
+
|
| 24 |
+
public bool IsDeleted { get; set; }
|
| 25 |
+
|
| 26 |
+
public long? CreatedById { get; set; }
|
| 27 |
+
|
| 28 |
+
public DateTime CreatedAt { get; set; }
|
| 29 |
+
|
| 30 |
+
public long? UpdatedById { get; set; }
|
| 31 |
+
|
| 32 |
+
public DateTime? UpdatedAt { get; set; }
|
| 33 |
+
|
| 34 |
+
public virtual Menu Menu { get; set; } = null!;
|
| 35 |
+
|
| 36 |
+
public virtual ICollection<RolePermission> RolePermissions { get; set; } = new List<RolePermission>();
|
| 37 |
+
}
|
TaskTrackingSystem.Database/AppDbContextModels/Role.cs
CHANGED
|
@@ -21,5 +21,9 @@ public partial class Role
|
|
| 21 |
|
| 22 |
public bool IsDeleted { get; set; }
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
public virtual ICollection<User> Users { get; set; } = new List<User>();
|
| 25 |
}
|
|
|
|
| 21 |
|
| 22 |
public bool IsDeleted { get; set; }
|
| 23 |
|
| 24 |
+
public virtual ICollection<RoleMenu> RoleMenus { get; set; } = new List<RoleMenu>();
|
| 25 |
+
|
| 26 |
+
public virtual ICollection<RolePermission> RolePermissions { get; set; } = new List<RolePermission>();
|
| 27 |
+
|
| 28 |
public virtual ICollection<User> Users { get; set; } = new List<User>();
|
| 29 |
}
|
TaskTrackingSystem.Database/AppDbContextModels/RoleMenu.cs
CHANGED
|
@@ -5,21 +5,23 @@ namespace TaskTrackingSystem.Database.AppDbContextModels;
|
|
| 5 |
|
| 6 |
public partial class RoleMenu
|
| 7 |
{
|
| 8 |
-
public
|
| 9 |
|
| 10 |
public long RoleId { get; set; }
|
| 11 |
|
| 12 |
-
public
|
| 13 |
|
| 14 |
-
public
|
| 15 |
|
| 16 |
-
public
|
| 17 |
|
| 18 |
-
public
|
| 19 |
|
| 20 |
-
public
|
| 21 |
|
| 22 |
-
public
|
| 23 |
|
| 24 |
-
public
|
|
|
|
|
|
|
| 25 |
}
|
|
|
|
| 5 |
|
| 6 |
public partial class RoleMenu
|
| 7 |
{
|
| 8 |
+
public long RoleMenuId { get; set; }
|
| 9 |
|
| 10 |
public long RoleId { get; set; }
|
| 11 |
|
| 12 |
+
public long MenuId { get; set; }
|
| 13 |
|
| 14 |
+
public bool IsDeleted { get; set; }
|
| 15 |
|
| 16 |
+
public long? CreatedById { get; set; }
|
| 17 |
|
| 18 |
+
public DateTime CreatedAt { get; set; }
|
| 19 |
|
| 20 |
+
public long? UpdatedById { get; set; }
|
| 21 |
|
| 22 |
+
public DateTime? UpdatedAt { get; set; }
|
| 23 |
|
| 24 |
+
public virtual Menu Menu { get; set; } = null!;
|
| 25 |
+
|
| 26 |
+
public virtual Role Role { get; set; } = null!;
|
| 27 |
}
|
TaskTrackingSystem.Database/AppDbContextModels/RoleMenusLegacy.cs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using System;
|
| 2 |
+
using System.Collections.Generic;
|
| 3 |
+
|
| 4 |
+
namespace TaskTrackingSystem.Database.AppDbContextModels;
|
| 5 |
+
|
| 6 |
+
public partial class RoleMenusLegacy
|
| 7 |
+
{
|
| 8 |
+
public string RoleMenuId { get; set; } = null!;
|
| 9 |
+
|
| 10 |
+
public string RoleCode { get; set; } = null!;
|
| 11 |
+
|
| 12 |
+
public string MenuCode { get; set; } = null!;
|
| 13 |
+
|
| 14 |
+
public int DelFlag { get; set; }
|
| 15 |
+
|
| 16 |
+
public string? CreatedUserId { get; set; }
|
| 17 |
+
|
| 18 |
+
public DateTime CreatedDateTime { get; set; }
|
| 19 |
+
|
| 20 |
+
public string? ModifiedUserId { get; set; }
|
| 21 |
+
|
| 22 |
+
public DateTime? ModifiedDateTime { get; set; }
|
| 23 |
+
|
| 24 |
+
public long RoleId { get; set; }
|
| 25 |
+
}
|
TaskTrackingSystem.Database/AppDbContextModels/RolePermission.cs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using System;
|
| 2 |
+
using System.Collections.Generic;
|
| 3 |
+
|
| 4 |
+
namespace TaskTrackingSystem.Database.AppDbContextModels;
|
| 5 |
+
|
| 6 |
+
public partial class RolePermission
|
| 7 |
+
{
|
| 8 |
+
public long RolePermissionId { get; set; }
|
| 9 |
+
|
| 10 |
+
public long RoleId { get; set; }
|
| 11 |
+
|
| 12 |
+
public long PermissionId { get; set; }
|
| 13 |
+
|
| 14 |
+
public bool IsDeleted { get; set; }
|
| 15 |
+
|
| 16 |
+
public long? CreatedById { get; set; }
|
| 17 |
+
|
| 18 |
+
public DateTime CreatedAt { get; set; }
|
| 19 |
+
|
| 20 |
+
public long? UpdatedById { get; set; }
|
| 21 |
+
|
| 22 |
+
public DateTime? UpdatedAt { get; set; }
|
| 23 |
+
|
| 24 |
+
public virtual Permission Permission { get; set; } = null!;
|
| 25 |
+
|
| 26 |
+
public virtual Role Role { get; set; } = null!;
|
| 27 |
+
}
|
TaskTrackingSystem.WebApi/Features/Menu/MenuController.cs
CHANGED
|
@@ -47,5 +47,18 @@ namespace TaskTrackingSystem.WebApi.Features.Menu
|
|
| 47 |
var menus = await _menuService.GetAllMenusAsync();
|
| 48 |
return Ok(menus);
|
| 49 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
}
|
| 51 |
}
|
|
|
|
| 47 |
var menus = await _menuService.GetAllMenusAsync();
|
| 48 |
return Ok(menus);
|
| 49 |
}
|
| 50 |
+
|
| 51 |
+
/// <summary>
|
| 52 |
+
/// Returns a lightweight version string for the current user's role permissions.
|
| 53 |
+
/// Clients poll this to detect when an admin has changed permissions without fetching the full menu list.
|
| 54 |
+
/// </summary>
|
| 55 |
+
[HttpGet("version")]
|
| 56 |
+
public async Task<ActionResult<string>> GetMenuVersion()
|
| 57 |
+
{
|
| 58 |
+
var roleId = User.GetRoleId();
|
| 59 |
+
var roleName = User.GetRoleName() ?? string.Empty;
|
| 60 |
+
var version = await _menuService.GetMenuVersionAsync(roleId, roleName);
|
| 61 |
+
return Ok(version);
|
| 62 |
+
}
|
| 63 |
}
|
| 64 |
}
|
TaskTrackingSystem.WebApi/Features/Menu/MenuService.cs
CHANGED
|
@@ -25,7 +25,7 @@ namespace TaskTrackingSystem.WebApi.Features.Menu
|
|
| 25 |
return new List<MenuDto>();
|
| 26 |
}
|
| 27 |
|
| 28 |
-
return await GetMenusForRoleAsync(role.Id
|
| 29 |
}
|
| 30 |
|
| 31 |
public async Task<List<MenuDto>> GetMenusByRoleNameAsync(string roleName)
|
|
@@ -41,131 +41,120 @@ namespace TaskTrackingSystem.WebApi.Features.Menu
|
|
| 41 |
return new List<MenuDto>();
|
| 42 |
}
|
| 43 |
|
| 44 |
-
return await GetMenusForRoleAsync(role.Id
|
| 45 |
}
|
| 46 |
|
| 47 |
-
private async Task<List<MenuDto>> GetMenusForRoleAsync(long roleId
|
| 48 |
{
|
| 49 |
-
var
|
| 50 |
-
|
| 51 |
-
{
|
| 52 |
-
return new List<MenuDto>();
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
var allVisibleMenus = await _db.MenuAdmins
|
| 56 |
-
.Where(m => m.Visible && m.DelFlag == 0)
|
| 57 |
.OrderBy(m => m.OrderNo)
|
| 58 |
-
.Select(m => new MenuDto
|
| 59 |
-
{
|
| 60 |
-
MenuCode = m.MenuCode,
|
| 61 |
-
ParentCode = m.ParentCode,
|
| 62 |
-
MenuName = m.MenuName,
|
| 63 |
-
MenuUrl = m.MenuUrl,
|
| 64 |
-
OrderNo = m.OrderNo,
|
| 65 |
-
Icon = m.Icon
|
| 66 |
-
})
|
| 67 |
.ToListAsync();
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
-
if (
|
| 73 |
{
|
| 74 |
return new List<MenuDto>();
|
| 75 |
}
|
| 76 |
|
| 77 |
-
var visibleMenuLookup =
|
| 78 |
-
|
| 79 |
-
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
|
| 80 |
|
| 81 |
-
var
|
| 82 |
-
|
| 83 |
-
.Where(m => expandedMenuCodes.Contains(m.MenuCode))
|
| 84 |
.OrderBy(m => m.OrderNo)
|
| 85 |
.ToList();
|
| 86 |
|
| 87 |
-
return BuildHierarchy(
|
| 88 |
}
|
| 89 |
|
| 90 |
-
private static List<MenuDto> BuildHierarchy(List<
|
| 91 |
{
|
| 92 |
if (menus.Count == 0)
|
| 93 |
{
|
| 94 |
return new List<MenuDto>();
|
| 95 |
}
|
| 96 |
|
| 97 |
-
var menuLookup = menus
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
-
foreach (var
|
| 102 |
{
|
| 103 |
-
|
| 104 |
}
|
| 105 |
|
| 106 |
var roots = new List<MenuDto>();
|
| 107 |
|
| 108 |
-
foreach (var menu in
|
| 109 |
{
|
| 110 |
-
var
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
{
|
| 115 |
-
roots.Add(
|
| 116 |
continue;
|
| 117 |
}
|
| 118 |
|
| 119 |
-
|
| 120 |
}
|
| 121 |
|
| 122 |
SortMenuTree(roots);
|
| 123 |
return roots;
|
| 124 |
}
|
| 125 |
|
| 126 |
-
private
|
| 127 |
{
|
| 128 |
-
|
| 129 |
-
.Where(rm => rm.RoleId == roleId && rm.DelFlag == 0)
|
| 130 |
-
.Select(rm => rm.MenuCode)
|
| 131 |
-
.ToListAsync();
|
| 132 |
-
|
| 133 |
-
if (allowedMenuCodes.Count > 0)
|
| 134 |
{
|
| 135 |
-
return
|
| 136 |
}
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
.Where(rm => rm.RoleCode == roleName && rm.DelFlag == 0)
|
| 142 |
-
.Select(rm => rm.MenuCode)
|
| 143 |
-
.ToListAsync();
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
return allowedMenuCodes.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
| 147 |
}
|
| 148 |
|
| 149 |
-
private static HashSet<
|
| 150 |
{
|
| 151 |
-
var expanded = new HashSet<
|
| 152 |
|
| 153 |
-
foreach (var
|
| 154 |
{
|
| 155 |
-
var
|
| 156 |
|
| 157 |
-
while (
|
| 158 |
{
|
| 159 |
-
if (!menuLookup.TryGetValue(
|
| 160 |
{
|
| 161 |
break;
|
| 162 |
}
|
| 163 |
|
| 164 |
-
|
| 165 |
-
if (string.IsNullOrWhiteSpace(currentCode) || currentCode == "0")
|
| 166 |
-
{
|
| 167 |
-
break;
|
| 168 |
-
}
|
| 169 |
}
|
| 170 |
}
|
| 171 |
|
|
@@ -174,7 +163,11 @@ namespace TaskTrackingSystem.WebApi.Features.Menu
|
|
| 174 |
|
| 175 |
private static void SortMenuTree(IList<MenuDto> menus)
|
| 176 |
{
|
| 177 |
-
var ordered = menus
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
menus.Clear();
|
| 179 |
|
| 180 |
foreach (var menu in ordered)
|
|
@@ -188,50 +181,78 @@ namespace TaskTrackingSystem.WebApi.Features.Menu
|
|
| 188 |
}
|
| 189 |
}
|
| 190 |
|
| 191 |
-
|
| 192 |
{
|
| 193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
}
|
|
|
|
| 195 |
public async Task<List<MenuAdminDto>> GetAllMenusAsync()
|
| 196 |
{
|
| 197 |
-
var menus = await _db.
|
| 198 |
-
.Where(m => m.
|
| 199 |
.OrderBy(m => m.OrderNo)
|
| 200 |
-
.Select(m => new MenuAdminDto
|
| 201 |
-
{
|
| 202 |
-
MenuCode = m.MenuCode,
|
| 203 |
-
ParentCode = m.ParentCode,
|
| 204 |
-
MenuName = m.MenuName,
|
| 205 |
-
MenuUrl = m.MenuUrl,
|
| 206 |
-
OrderNo = m.OrderNo,
|
| 207 |
-
Icon = m.Icon,
|
| 208 |
-
Visible = m.Visible
|
| 209 |
-
})
|
| 210 |
.ToListAsync();
|
| 211 |
|
| 212 |
-
var
|
| 213 |
-
.Where(
|
| 214 |
-
.OrderBy(
|
| 215 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
{
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
})
|
| 225 |
-
.
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
-
foreach (var
|
| 228 |
{
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
}
|
| 233 |
|
| 234 |
-
return
|
| 235 |
}
|
| 236 |
}
|
| 237 |
}
|
|
|
|
| 25 |
return new List<MenuDto>();
|
| 26 |
}
|
| 27 |
|
| 28 |
+
return await GetMenusForRoleAsync(role.Id);
|
| 29 |
}
|
| 30 |
|
| 31 |
public async Task<List<MenuDto>> GetMenusByRoleNameAsync(string roleName)
|
|
|
|
| 41 |
return new List<MenuDto>();
|
| 42 |
}
|
| 43 |
|
| 44 |
+
return await GetMenusForRoleAsync(role.Id);
|
| 45 |
}
|
| 46 |
|
| 47 |
+
private async Task<List<MenuDto>> GetMenusForRoleAsync(long roleId)
|
| 48 |
{
|
| 49 |
+
var visibleMenus = await _db.Menus
|
| 50 |
+
.Where(m => !m.IsDeleted && m.Visible)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
.OrderBy(m => m.OrderNo)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
.ToListAsync();
|
| 53 |
|
| 54 |
+
var assignedMenuIds = await _db.RoleMenus
|
| 55 |
+
.Where(rm => rm.RoleId == roleId && !rm.IsDeleted)
|
| 56 |
+
.Select(rm => rm.MenuId)
|
| 57 |
+
.ToListAsync();
|
| 58 |
+
|
| 59 |
+
var assignedPermissionMenuIds = await _db.RolePermissions
|
| 60 |
+
.Where(rp => rp.RoleId == roleId && !rp.IsDeleted)
|
| 61 |
+
.Select(rp => rp.Permission.MenuId)
|
| 62 |
+
.ToListAsync();
|
| 63 |
+
|
| 64 |
+
var allowedMenuIds = assignedMenuIds
|
| 65 |
+
.Concat(assignedPermissionMenuIds)
|
| 66 |
+
.Distinct()
|
| 67 |
+
.ToHashSet();
|
| 68 |
|
| 69 |
+
if (allowedMenuIds.Count == 0)
|
| 70 |
{
|
| 71 |
return new List<MenuDto>();
|
| 72 |
}
|
| 73 |
|
| 74 |
+
var visibleMenuLookup = visibleMenus.ToDictionary(m => m.MenuId);
|
| 75 |
+
var expandedMenuIds = ExpandWithAncestors(allowedMenuIds, visibleMenuLookup);
|
|
|
|
| 76 |
|
| 77 |
+
var filteredMenus = visibleMenus
|
| 78 |
+
.Where(m => expandedMenuIds.Contains(m.MenuId))
|
|
|
|
| 79 |
.OrderBy(m => m.OrderNo)
|
| 80 |
.ToList();
|
| 81 |
|
| 82 |
+
return BuildHierarchy(filteredMenus);
|
| 83 |
}
|
| 84 |
|
| 85 |
+
private static List<MenuDto> BuildHierarchy(List<Menu> menus)
|
| 86 |
{
|
| 87 |
if (menus.Count == 0)
|
| 88 |
{
|
| 89 |
return new List<MenuDto>();
|
| 90 |
}
|
| 91 |
|
| 92 |
+
var menuLookup = menus.ToDictionary(m => m.MenuId);
|
| 93 |
+
var dtoLookup = menus.ToDictionary(
|
| 94 |
+
m => m.MenuId,
|
| 95 |
+
m => new MenuDto
|
| 96 |
+
{
|
| 97 |
+
MenuCode = m.MenuCode,
|
| 98 |
+
ParentCode = GetParentCode(m, menuLookup),
|
| 99 |
+
MenuName = m.MenuName,
|
| 100 |
+
MenuUrl = m.MenuUrl,
|
| 101 |
+
OrderNo = m.OrderNo,
|
| 102 |
+
Icon = m.Icon
|
| 103 |
+
});
|
| 104 |
|
| 105 |
+
foreach (var dto in dtoLookup.Values)
|
| 106 |
{
|
| 107 |
+
dto.SubMenus = new List<MenuDto>();
|
| 108 |
}
|
| 109 |
|
| 110 |
var roots = new List<MenuDto>();
|
| 111 |
|
| 112 |
+
foreach (var menu in menus.OrderBy(m => m.OrderNo).ThenBy(m => m.MenuName, StringComparer.OrdinalIgnoreCase))
|
| 113 |
{
|
| 114 |
+
var dto = dtoLookup[menu.MenuId];
|
| 115 |
+
var parentId = menu.ParentMenuId;
|
| 116 |
+
|
| 117 |
+
if (!parentId.HasValue || !dtoLookup.TryGetValue(parentId.Value, out var parentDto))
|
| 118 |
{
|
| 119 |
+
roots.Add(dto);
|
| 120 |
continue;
|
| 121 |
}
|
| 122 |
|
| 123 |
+
parentDto.SubMenus.Add(dto);
|
| 124 |
}
|
| 125 |
|
| 126 |
SortMenuTree(roots);
|
| 127 |
return roots;
|
| 128 |
}
|
| 129 |
|
| 130 |
+
private static string GetParentCode(Menu menu, IReadOnlyDictionary<long, Menu> menuLookup)
|
| 131 |
{
|
| 132 |
+
if (!menu.ParentMenuId.HasValue)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
{
|
| 134 |
+
return string.Empty;
|
| 135 |
}
|
| 136 |
|
| 137 |
+
return menuLookup.TryGetValue(menu.ParentMenuId.Value, out var parent)
|
| 138 |
+
? parent.MenuCode
|
| 139 |
+
: string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
}
|
| 141 |
|
| 142 |
+
private static HashSet<long> ExpandWithAncestors(IEnumerable<long> menuIds, IReadOnlyDictionary<long, Menu> menuLookup)
|
| 143 |
{
|
| 144 |
+
var expanded = new HashSet<long>();
|
| 145 |
|
| 146 |
+
foreach (var menuId in menuIds)
|
| 147 |
{
|
| 148 |
+
var currentId = menuId;
|
| 149 |
|
| 150 |
+
while (currentId > 0 && expanded.Add(currentId))
|
| 151 |
{
|
| 152 |
+
if (!menuLookup.TryGetValue(currentId, out var currentMenu) || !currentMenu.ParentMenuId.HasValue)
|
| 153 |
{
|
| 154 |
break;
|
| 155 |
}
|
| 156 |
|
| 157 |
+
currentId = currentMenu.ParentMenuId.Value;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
}
|
| 159 |
}
|
| 160 |
|
|
|
|
| 163 |
|
| 164 |
private static void SortMenuTree(IList<MenuDto> menus)
|
| 165 |
{
|
| 166 |
+
var ordered = menus
|
| 167 |
+
.OrderBy(m => m.OrderNo)
|
| 168 |
+
.ThenBy(m => m.MenuName, StringComparer.OrdinalIgnoreCase)
|
| 169 |
+
.ToList();
|
| 170 |
+
|
| 171 |
menus.Clear();
|
| 172 |
|
| 173 |
foreach (var menu in ordered)
|
|
|
|
| 181 |
}
|
| 182 |
}
|
| 183 |
|
| 184 |
+
public async Task<string> GetMenuVersionAsync(long roleId, string roleName)
|
| 185 |
{
|
| 186 |
+
var latestMenuChange = await _db.RoleMenus
|
| 187 |
+
.Where(rm => rm.RoleId == roleId && !rm.IsDeleted)
|
| 188 |
+
.Select(rm => (DateTime?)(rm.UpdatedAt ?? rm.CreatedAt))
|
| 189 |
+
.MaxAsync();
|
| 190 |
+
|
| 191 |
+
var latestPermissionChange = await _db.RolePermissions
|
| 192 |
+
.Where(rp => rp.RoleId == roleId && !rp.IsDeleted)
|
| 193 |
+
.Select(rp => (DateTime?)(rp.UpdatedAt ?? rp.CreatedAt))
|
| 194 |
+
.MaxAsync();
|
| 195 |
+
|
| 196 |
+
var latest = latestMenuChange ?? latestPermissionChange;
|
| 197 |
+
if (latestPermissionChange.HasValue && (!latest.HasValue || latestPermissionChange.Value > latest.Value))
|
| 198 |
+
{
|
| 199 |
+
latest = latestPermissionChange;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
return latest.HasValue ? latest.Value.Ticks.ToString() : "0";
|
| 203 |
}
|
| 204 |
+
|
| 205 |
public async Task<List<MenuAdminDto>> GetAllMenusAsync()
|
| 206 |
{
|
| 207 |
+
var menus = await _db.Menus
|
| 208 |
+
.Where(m => !m.IsDeleted)
|
| 209 |
.OrderBy(m => m.OrderNo)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
.ToListAsync();
|
| 211 |
|
| 212 |
+
var permissions = await _db.Permissions
|
| 213 |
+
.Where(p => !p.IsDeleted)
|
| 214 |
+
.OrderBy(p => p.OrderNo)
|
| 215 |
+
.ToListAsync();
|
| 216 |
+
|
| 217 |
+
var menuLookup = menus.ToDictionary(m => m.MenuId);
|
| 218 |
+
var menuDtoLookup = menus
|
| 219 |
+
.Select(m => new
|
| 220 |
{
|
| 221 |
+
m.MenuId,
|
| 222 |
+
Dto = new MenuAdminDto
|
| 223 |
+
{
|
| 224 |
+
MenuCode = m.MenuCode,
|
| 225 |
+
ParentCode = GetParentCode(m, menuLookup),
|
| 226 |
+
MenuName = m.MenuName,
|
| 227 |
+
MenuUrl = m.MenuUrl,
|
| 228 |
+
OrderNo = m.OrderNo,
|
| 229 |
+
Icon = m.Icon,
|
| 230 |
+
Visible = m.Visible
|
| 231 |
+
}
|
| 232 |
})
|
| 233 |
+
.ToList();
|
| 234 |
+
|
| 235 |
+
var menuDtos = menuDtoLookup.Select(x => x.Dto).ToList();
|
| 236 |
+
var dtoLookup = menuDtoLookup.ToDictionary(x => x.MenuId, x => x.Dto);
|
| 237 |
|
| 238 |
+
foreach (var permission in permissions)
|
| 239 |
{
|
| 240 |
+
if (dtoLookup.TryGetValue(permission.MenuId, out var dto))
|
| 241 |
+
{
|
| 242 |
+
dto.Actions.Add(new MenuAdminDetailDto
|
| 243 |
+
{
|
| 244 |
+
MenuAdminDetailId = permission.PermissionId.ToString(),
|
| 245 |
+
MenuDetailCode = permission.PermissionCode,
|
| 246 |
+
ParentMenuCode = menuLookup.TryGetValue(permission.MenuId, out var parentMenu) ? parentMenu.MenuCode : string.Empty,
|
| 247 |
+
ActionName = permission.ActionName,
|
| 248 |
+
ApiName = permission.ApiName,
|
| 249 |
+
Visible = permission.Visible,
|
| 250 |
+
OrderNo = permission.OrderNo
|
| 251 |
+
});
|
| 252 |
+
}
|
| 253 |
}
|
| 254 |
|
| 255 |
+
return menuDtos;
|
| 256 |
}
|
| 257 |
}
|
| 258 |
}
|
TaskTrackingSystem.WebApi/Features/Role/RoleService.cs
CHANGED
|
@@ -28,15 +28,17 @@ namespace TaskTrackingSystem.WebApi.Features.Role
|
|
| 28 |
Name = r.Name,
|
| 29 |
Description = r.Description,
|
| 30 |
CreatedAt = r.CreatedAt ?? DateTime.UtcNow
|
| 31 |
-
})
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
public async Task<RoleDto?> GetRoleByIdAsync(long id)
|
| 35 |
{
|
| 36 |
-
var role = await _db.Roles
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
| 40 |
|
| 41 |
return new RoleDto
|
| 42 |
{
|
|
@@ -69,7 +71,7 @@ namespace TaskTrackingSystem.WebApi.Features.Role
|
|
| 69 |
}
|
| 70 |
}
|
| 71 |
|
| 72 |
-
var role = new
|
| 73 |
{
|
| 74 |
Name = dto.Name,
|
| 75 |
Description = dto.Description,
|
|
@@ -86,15 +88,13 @@ namespace TaskTrackingSystem.WebApi.Features.Role
|
|
| 86 |
return Result<RoleDto>.Failure(assignResult.ErrorMessage ?? ResultMessages.FailedToCreateRole, assignResult.StatusCode);
|
| 87 |
}
|
| 88 |
|
| 89 |
-
|
| 90 |
{
|
| 91 |
Id = role.Id,
|
| 92 |
Name = role.Name,
|
| 93 |
Description = role.Description,
|
| 94 |
CreatedAt = role.CreatedAt ?? DateTime.UtcNow
|
| 95 |
-
};
|
| 96 |
-
|
| 97 |
-
return Result<RoleDto>.Success(resultDto, 201);
|
| 98 |
}
|
| 99 |
|
| 100 |
public async Task<Result> UpdateRoleAsync(long id, UpdateRoleDto dto, long? currentUserId = null)
|
|
@@ -105,7 +105,10 @@ namespace TaskTrackingSystem.WebApi.Features.Role
|
|
| 105 |
}
|
| 106 |
|
| 107 |
var role = await _db.Roles.FirstOrDefaultAsync(r => r.Id == id && r.IsDeleted != true);
|
| 108 |
-
if (role == null)
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
var nameExists = await _db.Roles.AnyAsync(r => r.Name == dto.Name && r.Id != id && r.IsDeleted != true);
|
| 111 |
if (nameExists)
|
|
@@ -113,21 +116,11 @@ namespace TaskTrackingSystem.WebApi.Features.Role
|
|
| 113 |
return Result.Failure("Role name is already taken by another role.", 400);
|
| 114 |
}
|
| 115 |
|
| 116 |
-
var oldName = role.Name;
|
| 117 |
role.Name = dto.Name;
|
| 118 |
role.Description = dto.Description;
|
| 119 |
role.UpdatedAt = DateTime.UtcNow;
|
| 120 |
role.UpdatedBy = currentUserId;
|
| 121 |
|
| 122 |
-
var relatedRoleMenus = await _db.RoleMenus
|
| 123 |
-
.Where(rm => rm.RoleId == role.Id || rm.RoleCode == oldName)
|
| 124 |
-
.ToListAsync();
|
| 125 |
-
|
| 126 |
-
foreach (var roleMenu in relatedRoleMenus)
|
| 127 |
-
{
|
| 128 |
-
roleMenu.RoleCode = dto.Name;
|
| 129 |
-
}
|
| 130 |
-
|
| 131 |
_db.Roles.Update(role);
|
| 132 |
await _db.SaveChangesAsync();
|
| 133 |
return Result.Success(200);
|
|
@@ -136,7 +129,10 @@ namespace TaskTrackingSystem.WebApi.Features.Role
|
|
| 136 |
public async Task<Result> SoftDeleteRoleAsync(long id)
|
| 137 |
{
|
| 138 |
var role = await _db.Roles.FirstOrDefaultAsync(r => r.Id == id && r.IsDeleted != true);
|
| 139 |
-
if (role == null)
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
role.IsDeleted = true;
|
| 142 |
_db.Roles.Update(role);
|
|
@@ -144,7 +140,6 @@ namespace TaskTrackingSystem.WebApi.Features.Role
|
|
| 144 |
return Result.Success(200);
|
| 145 |
}
|
| 146 |
|
| 147 |
-
|
| 148 |
public async Task<Result<List<string>>> GetAssignedMenusByRoleIdAsync(long roleId)
|
| 149 |
{
|
| 150 |
var role = await _db.Roles.FirstOrDefaultAsync(r => r.Id == roleId && r.IsDeleted != true);
|
|
@@ -154,19 +149,21 @@ namespace TaskTrackingSystem.WebApi.Features.Role
|
|
| 154 |
}
|
| 155 |
|
| 156 |
var menuCodes = await _db.RoleMenus
|
| 157 |
-
.Where(rm => rm.RoleId == role.Id && rm.
|
| 158 |
-
.Select(rm => rm.MenuCode)
|
| 159 |
.ToListAsync();
|
| 160 |
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
| 168 |
|
| 169 |
-
return Result<List<string>>.Success(
|
| 170 |
}
|
| 171 |
|
| 172 |
public async Task<Result> AssignMenusToRoleAsync(long roleId, AssignMenusDto dto)
|
|
@@ -182,34 +179,83 @@ namespace TaskTrackingSystem.WebApi.Features.Role
|
|
| 182 |
return Result.Failure("MenuCodes cannot be null", 400);
|
| 183 |
}
|
| 184 |
|
| 185 |
-
var
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
if (!validCodesResult.IsSuccess)
|
| 187 |
{
|
| 188 |
return Result.Failure(validCodesResult.ErrorMessage ?? ResultMessages.FailedToUpdateRole, validCodesResult.StatusCode);
|
| 189 |
}
|
| 190 |
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
.ToListAsync();
|
| 195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
if (existingRoleMenus.Any())
|
| 197 |
{
|
| 198 |
_db.RoleMenus.RemoveRange(existingRoleMenus);
|
| 199 |
}
|
| 200 |
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
| 204 |
{
|
| 205 |
_db.RoleMenus.Add(new RoleMenu
|
| 206 |
{
|
| 207 |
-
RoleMenuId = $"RM_{role.Id}_{i++}_{DateTime.UtcNow.Ticks}",
|
| 208 |
RoleId = role.Id,
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
});
|
| 214 |
}
|
| 215 |
|
|
@@ -217,24 +263,43 @@ namespace TaskTrackingSystem.WebApi.Features.Role
|
|
| 217 |
return Result.Success(200);
|
| 218 |
}
|
| 219 |
|
| 220 |
-
private
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
{
|
| 222 |
-
var
|
| 223 |
-
.Where(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
.Select(m => m.MenuCode)
|
| 225 |
.ToListAsync();
|
| 226 |
|
| 227 |
-
var
|
| 228 |
-
.Where(
|
| 229 |
-
.Select(
|
| 230 |
.ToListAsync();
|
| 231 |
|
| 232 |
var validCodes = validMenuCodes
|
| 233 |
-
.Concat(
|
| 234 |
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
| 235 |
|
| 236 |
-
var invalidCodes =
|
| 237 |
-
.Where(code => !string.IsNullOrWhiteSpace(code))
|
| 238 |
.Where(code => !validCodes.Contains(code))
|
| 239 |
.Distinct(StringComparer.OrdinalIgnoreCase)
|
| 240 |
.ToList();
|
|
|
|
| 28 |
Name = r.Name,
|
| 29 |
Description = r.Description,
|
| 30 |
CreatedAt = r.CreatedAt ?? DateTime.UtcNow
|
| 31 |
+
})
|
| 32 |
+
.ToListAsync();
|
| 33 |
}
|
| 34 |
|
| 35 |
public async Task<RoleDto?> GetRoleByIdAsync(long id)
|
| 36 |
{
|
| 37 |
+
var role = await _db.Roles.FirstOrDefaultAsync(r => r.Id == id && r.IsDeleted != true);
|
| 38 |
+
if (role == null)
|
| 39 |
+
{
|
| 40 |
+
return null;
|
| 41 |
+
}
|
| 42 |
|
| 43 |
return new RoleDto
|
| 44 |
{
|
|
|
|
| 71 |
}
|
| 72 |
}
|
| 73 |
|
| 74 |
+
var role = new Role
|
| 75 |
{
|
| 76 |
Name = dto.Name,
|
| 77 |
Description = dto.Description,
|
|
|
|
| 88 |
return Result<RoleDto>.Failure(assignResult.ErrorMessage ?? ResultMessages.FailedToCreateRole, assignResult.StatusCode);
|
| 89 |
}
|
| 90 |
|
| 91 |
+
return Result<RoleDto>.Success(new RoleDto
|
| 92 |
{
|
| 93 |
Id = role.Id,
|
| 94 |
Name = role.Name,
|
| 95 |
Description = role.Description,
|
| 96 |
CreatedAt = role.CreatedAt ?? DateTime.UtcNow
|
| 97 |
+
}, 201);
|
|
|
|
|
|
|
| 98 |
}
|
| 99 |
|
| 100 |
public async Task<Result> UpdateRoleAsync(long id, UpdateRoleDto dto, long? currentUserId = null)
|
|
|
|
| 105 |
}
|
| 106 |
|
| 107 |
var role = await _db.Roles.FirstOrDefaultAsync(r => r.Id == id && r.IsDeleted != true);
|
| 108 |
+
if (role == null)
|
| 109 |
+
{
|
| 110 |
+
return Result.Failure(ResultMessages.RoleNotFound(id), 404);
|
| 111 |
+
}
|
| 112 |
|
| 113 |
var nameExists = await _db.Roles.AnyAsync(r => r.Name == dto.Name && r.Id != id && r.IsDeleted != true);
|
| 114 |
if (nameExists)
|
|
|
|
| 116 |
return Result.Failure("Role name is already taken by another role.", 400);
|
| 117 |
}
|
| 118 |
|
|
|
|
| 119 |
role.Name = dto.Name;
|
| 120 |
role.Description = dto.Description;
|
| 121 |
role.UpdatedAt = DateTime.UtcNow;
|
| 122 |
role.UpdatedBy = currentUserId;
|
| 123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
_db.Roles.Update(role);
|
| 125 |
await _db.SaveChangesAsync();
|
| 126 |
return Result.Success(200);
|
|
|
|
| 129 |
public async Task<Result> SoftDeleteRoleAsync(long id)
|
| 130 |
{
|
| 131 |
var role = await _db.Roles.FirstOrDefaultAsync(r => r.Id == id && r.IsDeleted != true);
|
| 132 |
+
if (role == null)
|
| 133 |
+
{
|
| 134 |
+
return Result.Failure(ResultMessages.RoleNotFound(id), 404);
|
| 135 |
+
}
|
| 136 |
|
| 137 |
role.IsDeleted = true;
|
| 138 |
_db.Roles.Update(role);
|
|
|
|
| 140 |
return Result.Success(200);
|
| 141 |
}
|
| 142 |
|
|
|
|
| 143 |
public async Task<Result<List<string>>> GetAssignedMenusByRoleIdAsync(long roleId)
|
| 144 |
{
|
| 145 |
var role = await _db.Roles.FirstOrDefaultAsync(r => r.Id == roleId && r.IsDeleted != true);
|
|
|
|
| 149 |
}
|
| 150 |
|
| 151 |
var menuCodes = await _db.RoleMenus
|
| 152 |
+
.Where(rm => rm.RoleId == role.Id && !rm.IsDeleted)
|
| 153 |
+
.Select(rm => rm.Menu.MenuCode)
|
| 154 |
.ToListAsync();
|
| 155 |
|
| 156 |
+
var permissionCodes = await _db.RolePermissions
|
| 157 |
+
.Where(rp => rp.RoleId == role.Id && !rp.IsDeleted)
|
| 158 |
+
.Select(rp => rp.Permission.PermissionCode)
|
| 159 |
+
.ToListAsync();
|
| 160 |
+
|
| 161 |
+
var assignedCodes = menuCodes
|
| 162 |
+
.Concat(permissionCodes)
|
| 163 |
+
.Distinct(StringComparer.OrdinalIgnoreCase)
|
| 164 |
+
.ToList();
|
| 165 |
|
| 166 |
+
return Result<List<string>>.Success(assignedCodes);
|
| 167 |
}
|
| 168 |
|
| 169 |
public async Task<Result> AssignMenusToRoleAsync(long roleId, AssignMenusDto dto)
|
|
|
|
| 179 |
return Result.Failure("MenuCodes cannot be null", 400);
|
| 180 |
}
|
| 181 |
|
| 182 |
+
var selectedCodes = dto.MenuCodes
|
| 183 |
+
.Where(code => !string.IsNullOrWhiteSpace(code))
|
| 184 |
+
.Select(code => code.Trim())
|
| 185 |
+
.Distinct(StringComparer.OrdinalIgnoreCase)
|
| 186 |
+
.ToList();
|
| 187 |
+
|
| 188 |
+
var validCodesResult = await ValidatePermissionCodesAsync(selectedCodes);
|
| 189 |
if (!validCodesResult.IsSuccess)
|
| 190 |
{
|
| 191 |
return Result.Failure(validCodesResult.ErrorMessage ?? ResultMessages.FailedToUpdateRole, validCodesResult.StatusCode);
|
| 192 |
}
|
| 193 |
|
| 194 |
+
var selectedMenus = await _db.Menus
|
| 195 |
+
.Where(m => !m.IsDeleted && selectedCodes.Contains(m.MenuCode))
|
| 196 |
+
.Select(m => new { m.MenuId, m.MenuCode, m.ParentMenuId })
|
| 197 |
+
.ToListAsync();
|
| 198 |
+
|
| 199 |
+
var selectedPermissions = await _db.Permissions
|
| 200 |
+
.Where(p => !p.IsDeleted && selectedCodes.Contains(p.PermissionCode))
|
| 201 |
+
.Select(p => new { p.PermissionId, p.PermissionCode, p.MenuId })
|
| 202 |
+
.ToListAsync();
|
| 203 |
+
|
| 204 |
+
var menuLookup = await _db.Menus
|
| 205 |
+
.Where(m => !m.IsDeleted)
|
| 206 |
+
.Select(m => new { m.MenuId, m.ParentMenuId })
|
| 207 |
.ToListAsync();
|
| 208 |
|
| 209 |
+
var menuIdLookup = menuLookup.ToDictionary(x => x.MenuId, x => x.ParentMenuId);
|
| 210 |
+
var menuIdsToPersist = new HashSet<long>();
|
| 211 |
+
|
| 212 |
+
foreach (var menu in selectedMenus)
|
| 213 |
+
{
|
| 214 |
+
AddWithAncestors(menu.MenuId, menuIdLookup, menuIdsToPersist);
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
foreach (var permission in selectedPermissions)
|
| 218 |
+
{
|
| 219 |
+
AddWithAncestors(permission.MenuId, menuIdLookup, menuIdsToPersist);
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
var permissionIdsToPersist = selectedPermissions
|
| 223 |
+
.Select(p => p.PermissionId)
|
| 224 |
+
.Distinct()
|
| 225 |
+
.ToList();
|
| 226 |
+
|
| 227 |
+
var existingRoleMenus = await _db.RoleMenus.Where(rm => rm.RoleId == role.Id).ToListAsync();
|
| 228 |
+
var existingRolePermissions = await _db.RolePermissions.Where(rp => rp.RoleId == role.Id).ToListAsync();
|
| 229 |
+
|
| 230 |
if (existingRoleMenus.Any())
|
| 231 |
{
|
| 232 |
_db.RoleMenus.RemoveRange(existingRoleMenus);
|
| 233 |
}
|
| 234 |
|
| 235 |
+
if (existingRolePermissions.Any())
|
| 236 |
+
{
|
| 237 |
+
_db.RolePermissions.RemoveRange(existingRolePermissions);
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
foreach (var menuId in menuIdsToPersist)
|
| 241 |
{
|
| 242 |
_db.RoleMenus.Add(new RoleMenu
|
| 243 |
{
|
|
|
|
| 244 |
RoleId = role.Id,
|
| 245 |
+
MenuId = menuId,
|
| 246 |
+
IsDeleted = false,
|
| 247 |
+
CreatedAt = DateTime.UtcNow
|
| 248 |
+
});
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
foreach (var permissionId in permissionIdsToPersist)
|
| 252 |
+
{
|
| 253 |
+
_db.RolePermissions.Add(new RolePermission
|
| 254 |
+
{
|
| 255 |
+
RoleId = role.Id,
|
| 256 |
+
PermissionId = permissionId,
|
| 257 |
+
IsDeleted = false,
|
| 258 |
+
CreatedAt = DateTime.UtcNow
|
| 259 |
});
|
| 260 |
}
|
| 261 |
|
|
|
|
| 263 |
return Result.Success(200);
|
| 264 |
}
|
| 265 |
|
| 266 |
+
private static void AddWithAncestors(long menuId, IReadOnlyDictionary<long, long?> parentLookup, ISet<long> target)
|
| 267 |
+
{
|
| 268 |
+
var current = menuId;
|
| 269 |
+
|
| 270 |
+
while (current > 0 && target.Add(current))
|
| 271 |
+
{
|
| 272 |
+
if (!parentLookup.TryGetValue(current, out var parentId) || !parentId.HasValue)
|
| 273 |
+
{
|
| 274 |
+
break;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
current = parentId.Value;
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
private async Task<Result> ValidatePermissionCodesAsync(IEnumerable<string> codes)
|
| 282 |
{
|
| 283 |
+
var normalizedCodes = codes
|
| 284 |
+
.Where(code => !string.IsNullOrWhiteSpace(code))
|
| 285 |
+
.Select(code => code.Trim())
|
| 286 |
+
.ToList();
|
| 287 |
+
|
| 288 |
+
var validMenuCodes = await _db.Menus
|
| 289 |
+
.Where(m => !m.IsDeleted)
|
| 290 |
.Select(m => m.MenuCode)
|
| 291 |
.ToListAsync();
|
| 292 |
|
| 293 |
+
var validPermissionCodes = await _db.Permissions
|
| 294 |
+
.Where(p => !p.IsDeleted)
|
| 295 |
+
.Select(p => p.PermissionCode)
|
| 296 |
.ToListAsync();
|
| 297 |
|
| 298 |
var validCodes = validMenuCodes
|
| 299 |
+
.Concat(validPermissionCodes)
|
| 300 |
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
| 301 |
|
| 302 |
+
var invalidCodes = normalizedCodes
|
|
|
|
| 303 |
.Where(code => !validCodes.Contains(code))
|
| 304 |
.Distinct(StringComparer.OrdinalIgnoreCase)
|
| 305 |
.ToList();
|
TaskTrackingSystem.WebApp/Components/Layout/NavMenu.razor
CHANGED
|
@@ -94,16 +94,55 @@
|
|
| 94 |
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
| 95 |
{
|
| 96 |
currentPath = Navigation.ToBaseRelativePath(e.Location).Trim('/');
|
|
|
|
|
|
|
| 97 |
|
| 98 |
-
|
|
|
|
|
|
|
| 99 |
{
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
| 101 |
{
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
}
|
| 104 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
-
|
| 107 |
}
|
| 108 |
|
| 109 |
private void ToggleMenuGroup(string menuCode)
|
|
|
|
| 94 |
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
| 95 |
{
|
| 96 |
currentPath = Navigation.ToBaseRelativePath(e.Location).Trim('/');
|
| 97 |
+
InvokeAsync(RefreshMenusAsync);
|
| 98 |
+
}
|
| 99 |
|
| 100 |
+
private async Task RefreshMenusAsync()
|
| 101 |
+
{
|
| 102 |
+
try
|
| 103 |
{
|
| 104 |
+
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
| 105 |
+
var user = authState.User;
|
| 106 |
+
|
| 107 |
+
if (user.Identity?.IsAuthenticated == true)
|
| 108 |
{
|
| 109 |
+
// GetUserMenusAsync will automatically bust the cache if an admin changed permissions.
|
| 110 |
+
var freshMenus = await MenuAuthorization.GetUserMenusAsync(user);
|
| 111 |
+
|
| 112 |
+
// Only update and re-render if the menu list actually changed.
|
| 113 |
+
if (freshMenus.Count != menus.Count ||
|
| 114 |
+
freshMenus.Select(m => m.MenuCode).SequenceEqual(menus.Select(m => m.MenuCode)) == false)
|
| 115 |
+
{
|
| 116 |
+
menus = freshMenus;
|
| 117 |
+
BuildUrlHierarchy(menus);
|
| 118 |
+
expandedMenuGroups.Clear();
|
| 119 |
+
foreach (var menu in menus)
|
| 120 |
+
{
|
| 121 |
+
if (menu.SubMenus != null && menu.SubMenus.Count > 0 && IsMenuExpanded(menu.MenuCode, menu.SubMenus))
|
| 122 |
+
{
|
| 123 |
+
expandedMenuGroups.Add(menu.MenuCode);
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
else
|
| 128 |
+
{
|
| 129 |
+
// Menus are the same — just refresh expanded state for the new route.
|
| 130 |
+
foreach (var menu in menus)
|
| 131 |
+
{
|
| 132 |
+
if (menu.SubMenus != null && menu.SubMenus.Count > 0 && IsMenuExpanded(menu.MenuCode, menu.SubMenus))
|
| 133 |
+
{
|
| 134 |
+
expandedMenuGroups.Add(menu.MenuCode);
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
}
|
| 139 |
}
|
| 140 |
+
catch (Exception ex)
|
| 141 |
+
{
|
| 142 |
+
Console.WriteLine($"Error refreshing menus on navigation: {ex.Message}");
|
| 143 |
+
}
|
| 144 |
|
| 145 |
+
StateHasChanged();
|
| 146 |
}
|
| 147 |
|
| 148 |
private void ToggleMenuGroup(string menuCode)
|
TaskTrackingSystem.WebApp/MenuAuthorizationService.cs
CHANGED
|
@@ -102,7 +102,8 @@ public class MenuAuthorizationService
|
|
| 102 |
|
| 103 |
if (_sessionState.CachedMenuRoleId == roleKey && _sessionState.CachedMenus != null)
|
| 104 |
{
|
| 105 |
-
|
|
|
|
| 106 |
}
|
| 107 |
|
| 108 |
return LoadMenusAsync(user, roleKey);
|
|
@@ -124,6 +125,43 @@ public class MenuAuthorizationService
|
|
| 124 |
_ = LoadMenusAsync(user, roleKey);
|
| 125 |
}
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
private Task<List<MenuDto>> LoadMenusAsync(ClaimsPrincipal user, string roleKey)
|
| 128 |
{
|
| 129 |
if (_loadingMenus is { IsCompleted: false })
|
|
@@ -141,14 +179,21 @@ public class MenuAuthorizationService
|
|
| 141 |
{
|
| 142 |
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(8));
|
| 143 |
var client = _apiClient.CreateClient(user);
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
{
|
| 147 |
-
Console.WriteLine($"Menu API failed: {(int)
|
| 148 |
return new List<MenuDto>();
|
| 149 |
}
|
| 150 |
|
| 151 |
-
var menus = await
|
| 152 |
?? new List<MenuDto>();
|
| 153 |
|
| 154 |
if (menus.Count == 0)
|
|
@@ -156,6 +201,14 @@ public class MenuAuthorizationService
|
|
| 156 |
menus = BuildFallbackMenus(user);
|
| 157 |
}
|
| 158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
_sessionState.CachedMenus = menus;
|
| 160 |
_sessionState.CachedMenuRoleId = roleKey;
|
| 161 |
return menus;
|
|
|
|
| 102 |
|
| 103 |
if (_sessionState.CachedMenuRoleId == roleKey && _sessionState.CachedMenus != null)
|
| 104 |
{
|
| 105 |
+
// Cache hit — but verify the permissions haven't been changed by an admin.
|
| 106 |
+
return LoadMenusWithVersionCheckAsync(user, roleKey);
|
| 107 |
}
|
| 108 |
|
| 109 |
return LoadMenusAsync(user, roleKey);
|
|
|
|
| 125 |
_ = LoadMenusAsync(user, roleKey);
|
| 126 |
}
|
| 127 |
|
| 128 |
+
/// <summary>
|
| 129 |
+
/// When menus are cached, quickly checks the server-side version to see if an admin changed permissions.
|
| 130 |
+
/// If the version changed, busts the cache and re-fetches the full menu list.
|
| 131 |
+
/// Falls back to the cached menus if the version check itself fails (e.g. network error).
|
| 132 |
+
/// </summary>
|
| 133 |
+
private async Task<List<MenuDto>> LoadMenusWithVersionCheckAsync(ClaimsPrincipal user, string roleKey)
|
| 134 |
+
{
|
| 135 |
+
try
|
| 136 |
+
{
|
| 137 |
+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
| 138 |
+
var client = _apiClient.CreateClient(user);
|
| 139 |
+
var response = await client.GetAsync("Menu/version", cts.Token);
|
| 140 |
+
|
| 141 |
+
if (response.IsSuccessStatusCode)
|
| 142 |
+
{
|
| 143 |
+
var serverVersion = await response.Content.ReadAsStringAsync(cts.Token);
|
| 144 |
+
// Strip surrounding quotes that JSON serialisation may add
|
| 145 |
+
serverVersion = serverVersion.Trim('"');
|
| 146 |
+
|
| 147 |
+
if (serverVersion != _sessionState.CachedMenuVersion)
|
| 148 |
+
{
|
| 149 |
+
// Permissions have changed — bust cache and reload full menus.
|
| 150 |
+
Console.WriteLine($"[MenuAuth] Permission version changed ({_sessionState.CachedMenuVersion} -> {serverVersion}). Reloading menus.");
|
| 151 |
+
_sessionState.ClearMenuCache();
|
| 152 |
+
return await LoadMenusAsync(user, roleKey);
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
catch (Exception ex)
|
| 157 |
+
{
|
| 158 |
+
// If the version check fails, just use the cached menus to avoid breaking the UI.
|
| 159 |
+
Console.WriteLine($"[MenuAuth] Version check failed (using cache): {ex.Message}");
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
return _sessionState.CachedMenus!;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
private Task<List<MenuDto>> LoadMenusAsync(ClaimsPrincipal user, string roleKey)
|
| 166 |
{
|
| 167 |
if (_loadingMenus is { IsCompleted: false })
|
|
|
|
| 179 |
{
|
| 180 |
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(8));
|
| 181 |
var client = _apiClient.CreateClient(user);
|
| 182 |
+
|
| 183 |
+
// Fetch menus and version in parallel for efficiency.
|
| 184 |
+
var menuTask = client.GetAsync("Menu", cts.Token);
|
| 185 |
+
var versionTask = client.GetAsync("Menu/version", cts.Token);
|
| 186 |
+
|
| 187 |
+
await Task.WhenAll(menuTask, versionTask);
|
| 188 |
+
|
| 189 |
+
var menuResponse = await menuTask;
|
| 190 |
+
if (!menuResponse.IsSuccessStatusCode)
|
| 191 |
{
|
| 192 |
+
Console.WriteLine($"Menu API failed: {(int)menuResponse.StatusCode} {menuResponse.ReasonPhrase}");
|
| 193 |
return new List<MenuDto>();
|
| 194 |
}
|
| 195 |
|
| 196 |
+
var menus = await menuResponse.Content.ReadFromJsonAsync<List<MenuDto>>(cancellationToken: cts.Token)
|
| 197 |
?? new List<MenuDto>();
|
| 198 |
|
| 199 |
if (menus.Count == 0)
|
|
|
|
| 201 |
menus = BuildFallbackMenus(user);
|
| 202 |
}
|
| 203 |
|
| 204 |
+
// Store the version so we can detect future permission changes.
|
| 205 |
+
var versionResponse = await versionTask;
|
| 206 |
+
if (versionResponse.IsSuccessStatusCode)
|
| 207 |
+
{
|
| 208 |
+
var version = await versionResponse.Content.ReadAsStringAsync(cts.Token);
|
| 209 |
+
_sessionState.CachedMenuVersion = version.Trim('"');
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
_sessionState.CachedMenus = menus;
|
| 213 |
_sessionState.CachedMenuRoleId = roleKey;
|
| 214 |
return menus;
|
TaskTrackingSystem.WebApp/UserSessionState.cs
CHANGED
|
@@ -11,10 +11,17 @@ public class UserSessionState
|
|
| 11 |
public string? CachedMenuRoleId { get; set; }
|
| 12 |
public List<MenuDto>? CachedMenus { get; set; }
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
public void ClearMenuCache()
|
| 15 |
{
|
| 16 |
CachedMenuRoleId = null;
|
| 17 |
CachedMenus = null;
|
|
|
|
| 18 |
}
|
| 19 |
|
| 20 |
public void ClearSession()
|
|
|
|
| 11 |
public string? CachedMenuRoleId { get; set; }
|
| 12 |
public List<MenuDto>? CachedMenus { get; set; }
|
| 13 |
|
| 14 |
+
/// <summary>
|
| 15 |
+
/// The server-side permissions version (latest RoleMenu timestamp) when the menus were last fetched.
|
| 16 |
+
/// Used to detect when an admin has changed a role's permissions so the cache can be busted.
|
| 17 |
+
/// </summary>
|
| 18 |
+
public string? CachedMenuVersion { get; set; }
|
| 19 |
+
|
| 20 |
public void ClearMenuCache()
|
| 21 |
{
|
| 22 |
CachedMenuRoleId = null;
|
| 23 |
CachedMenus = null;
|
| 24 |
+
CachedMenuVersion = null;
|
| 25 |
}
|
| 26 |
|
| 27 |
public void ClearSession()
|
permission-schema-upgrade.sql
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
Safe upgrade path for permission tables.
|
| 3 |
+
|
| 4 |
+
What this script does:
|
| 5 |
+
1. Renames the old dbo.RoleMenus table to dbo.RoleMenus_Legacy if it still uses MenuCode.
|
| 6 |
+
2. Creates the new normalized tables:
|
| 7 |
+
- dbo.Menus
|
| 8 |
+
- dbo.Permissions
|
| 9 |
+
- dbo.RoleMenus
|
| 10 |
+
- dbo.RolePermissions
|
| 11 |
+
3. Copies data from the old schema into the new schema.
|
| 12 |
+
|
| 13 |
+
Run this once in SQL Server Management Studio after backing up the database.
|
| 14 |
+
*/
|
| 15 |
+
|
| 16 |
+
SET XACT_ABORT ON;
|
| 17 |
+
BEGIN TRY
|
| 18 |
+
BEGIN TRANSACTION;
|
| 19 |
+
|
| 20 |
+
-------------------------------------------------------------------------
|
| 21 |
+
-- Step 1: rename the legacy RoleMenus table if needed
|
| 22 |
+
-------------------------------------------------------------------------
|
| 23 |
+
IF OBJECT_ID('dbo.RoleMenus', 'U') IS NOT NULL
|
| 24 |
+
AND COL_LENGTH('dbo.RoleMenus', 'MenuId') IS NULL
|
| 25 |
+
AND COL_LENGTH('dbo.RoleMenus', 'MenuCode') IS NOT NULL
|
| 26 |
+
AND OBJECT_ID('dbo.RoleMenus_Legacy', 'U') IS NULL
|
| 27 |
+
BEGIN
|
| 28 |
+
EXEC sp_rename 'dbo.RoleMenus', 'RoleMenus_Legacy';
|
| 29 |
+
END
|
| 30 |
+
|
| 31 |
+
-------------------------------------------------------------------------
|
| 32 |
+
-- Step 2: create the new tables if they do not already exist
|
| 33 |
+
-------------------------------------------------------------------------
|
| 34 |
+
IF OBJECT_ID('dbo.Menus', 'U') IS NULL
|
| 35 |
+
BEGIN
|
| 36 |
+
CREATE TABLE dbo.Menus (
|
| 37 |
+
MenuId bigint IDENTITY(1,1) NOT NULL CONSTRAINT PK_Menus PRIMARY KEY,
|
| 38 |
+
MenuCode nvarchar(50) NOT NULL,
|
| 39 |
+
ParentMenuId bigint NULL,
|
| 40 |
+
MenuName nvarchar(100) NOT NULL,
|
| 41 |
+
MenuUrl nvarchar(200) NULL,
|
| 42 |
+
Icon nvarchar(50) NULL,
|
| 43 |
+
Visible bit NOT NULL CONSTRAINT DF_Menus_Visible DEFAULT (1),
|
| 44 |
+
OrderNo int NOT NULL CONSTRAINT DF_Menus_OrderNo DEFAULT (0),
|
| 45 |
+
IsDeleted bit NOT NULL CONSTRAINT DF_Menus_IsDeleted DEFAULT (0),
|
| 46 |
+
CreatedById bigint NULL,
|
| 47 |
+
CreatedAt datetime2(0) NOT NULL CONSTRAINT DF_Menus_CreatedAt DEFAULT (SYSUTCDATETIME()),
|
| 48 |
+
UpdatedById bigint NULL,
|
| 49 |
+
UpdatedAt datetime2(0) NULL,
|
| 50 |
+
CONSTRAINT UQ_Menus_MenuCode UNIQUE (MenuCode)
|
| 51 |
+
);
|
| 52 |
+
|
| 53 |
+
ALTER TABLE dbo.Menus
|
| 54 |
+
ADD CONSTRAINT FK_Menus_ParentMenu
|
| 55 |
+
FOREIGN KEY (ParentMenuId) REFERENCES dbo.Menus(MenuId);
|
| 56 |
+
|
| 57 |
+
CREATE INDEX IX_Menus_ParentMenuId ON dbo.Menus(ParentMenuId);
|
| 58 |
+
END
|
| 59 |
+
|
| 60 |
+
IF OBJECT_ID('dbo.Permissions', 'U') IS NULL
|
| 61 |
+
BEGIN
|
| 62 |
+
CREATE TABLE dbo.Permissions (
|
| 63 |
+
PermissionId bigint IDENTITY(1,1) NOT NULL CONSTRAINT PK_Permissions PRIMARY KEY,
|
| 64 |
+
PermissionCode nvarchar(50) NOT NULL,
|
| 65 |
+
MenuId bigint NOT NULL,
|
| 66 |
+
ActionName nvarchar(100) NOT NULL,
|
| 67 |
+
ApiName nvarchar(100) NOT NULL,
|
| 68 |
+
HttpMethod nvarchar(20) NULL,
|
| 69 |
+
Visible bit NOT NULL CONSTRAINT DF_Permissions_Visible DEFAULT (1),
|
| 70 |
+
OrderNo int NOT NULL CONSTRAINT DF_Permissions_OrderNo DEFAULT (0),
|
| 71 |
+
IsDeleted bit NOT NULL CONSTRAINT DF_Permissions_IsDeleted DEFAULT (0),
|
| 72 |
+
CreatedById bigint NULL,
|
| 73 |
+
CreatedAt datetime2(0) NOT NULL CONSTRAINT DF_Permissions_CreatedAt DEFAULT (SYSUTCDATETIME()),
|
| 74 |
+
UpdatedById bigint NULL,
|
| 75 |
+
UpdatedAt datetime2(0) NULL,
|
| 76 |
+
CONSTRAINT UQ_Permissions_PermissionCode UNIQUE (PermissionCode)
|
| 77 |
+
);
|
| 78 |
+
|
| 79 |
+
ALTER TABLE dbo.Permissions
|
| 80 |
+
ADD CONSTRAINT FK_Permissions_Menu
|
| 81 |
+
FOREIGN KEY (MenuId) REFERENCES dbo.Menus(MenuId);
|
| 82 |
+
|
| 83 |
+
CREATE INDEX IX_Permissions_MenuId ON dbo.Permissions(MenuId);
|
| 84 |
+
END
|
| 85 |
+
|
| 86 |
+
IF OBJECT_ID('dbo.RoleMenus', 'U') IS NULL
|
| 87 |
+
BEGIN
|
| 88 |
+
CREATE TABLE dbo.RoleMenus (
|
| 89 |
+
RoleMenuId bigint IDENTITY(1,1) NOT NULL CONSTRAINT PK_RoleMenus PRIMARY KEY,
|
| 90 |
+
RoleId bigint NOT NULL,
|
| 91 |
+
MenuId bigint NOT NULL,
|
| 92 |
+
IsDeleted bit NOT NULL CONSTRAINT DF_RoleMenus_IsDeleted DEFAULT (0),
|
| 93 |
+
CreatedById bigint NULL,
|
| 94 |
+
CreatedAt datetime2(0) NOT NULL CONSTRAINT DF_RoleMenus_CreatedAt DEFAULT (SYSUTCDATETIME()),
|
| 95 |
+
UpdatedById bigint NULL,
|
| 96 |
+
UpdatedAt datetime2(0) NULL,
|
| 97 |
+
CONSTRAINT UQ_RoleMenus_Role_Menu UNIQUE (RoleId, MenuId)
|
| 98 |
+
);
|
| 99 |
+
|
| 100 |
+
ALTER TABLE dbo.RoleMenus
|
| 101 |
+
ADD CONSTRAINT FK_RoleMenus_Roles
|
| 102 |
+
FOREIGN KEY (RoleId) REFERENCES dbo.Roles(Id);
|
| 103 |
+
|
| 104 |
+
ALTER TABLE dbo.RoleMenus
|
| 105 |
+
ADD CONSTRAINT FK_RoleMenus_Menus
|
| 106 |
+
FOREIGN KEY (MenuId) REFERENCES dbo.Menus(MenuId);
|
| 107 |
+
|
| 108 |
+
CREATE INDEX IX_RoleMenus_RoleId ON dbo.RoleMenus(RoleId);
|
| 109 |
+
CREATE INDEX IX_RoleMenus_MenuId ON dbo.RoleMenus(MenuId);
|
| 110 |
+
END
|
| 111 |
+
|
| 112 |
+
IF OBJECT_ID('dbo.RolePermissions', 'U') IS NULL
|
| 113 |
+
BEGIN
|
| 114 |
+
CREATE TABLE dbo.RolePermissions (
|
| 115 |
+
RolePermissionId bigint IDENTITY(1,1) NOT NULL CONSTRAINT PK_RolePermissions PRIMARY KEY,
|
| 116 |
+
RoleId bigint NOT NULL,
|
| 117 |
+
PermissionId bigint NOT NULL,
|
| 118 |
+
IsDeleted bit NOT NULL CONSTRAINT DF_RolePermissions_IsDeleted DEFAULT (0),
|
| 119 |
+
CreatedById bigint NULL,
|
| 120 |
+
CreatedAt datetime2(0) NOT NULL CONSTRAINT DF_RolePermissions_CreatedAt DEFAULT (SYSUTCDATETIME()),
|
| 121 |
+
UpdatedById bigint NULL,
|
| 122 |
+
UpdatedAt datetime2(0) NULL,
|
| 123 |
+
CONSTRAINT UQ_RolePermissions_Role_Permission UNIQUE (RoleId, PermissionId)
|
| 124 |
+
);
|
| 125 |
+
|
| 126 |
+
ALTER TABLE dbo.RolePermissions
|
| 127 |
+
ADD CONSTRAINT FK_RolePermissions_Roles
|
| 128 |
+
FOREIGN KEY (RoleId) REFERENCES dbo.Roles(Id);
|
| 129 |
+
|
| 130 |
+
ALTER TABLE dbo.RolePermissions
|
| 131 |
+
ADD CONSTRAINT FK_RolePermissions_Permissions
|
| 132 |
+
FOREIGN KEY (PermissionId) REFERENCES dbo.Permissions(PermissionId);
|
| 133 |
+
|
| 134 |
+
CREATE INDEX IX_RolePermissions_RoleId ON dbo.RolePermissions(RoleId);
|
| 135 |
+
CREATE INDEX IX_RolePermissions_PermissionId ON dbo.RolePermissions(PermissionId);
|
| 136 |
+
END
|
| 137 |
+
|
| 138 |
+
-------------------------------------------------------------------------
|
| 139 |
+
-- Step 3: build mapping tables from the old source tables
|
| 140 |
+
-------------------------------------------------------------------------
|
| 141 |
+
IF OBJECT_ID('tempdb..#MenuStage') IS NOT NULL DROP TABLE #MenuStage;
|
| 142 |
+
IF OBJECT_ID('tempdb..#PermissionStage') IS NOT NULL DROP TABLE #PermissionStage;
|
| 143 |
+
|
| 144 |
+
CREATE TABLE #MenuStage (
|
| 145 |
+
OldMenuCode nvarchar(50) NOT NULL PRIMARY KEY,
|
| 146 |
+
OldParentCode nvarchar(50) NULL,
|
| 147 |
+
NewMenuId bigint NOT NULL
|
| 148 |
+
);
|
| 149 |
+
|
| 150 |
+
CREATE TABLE #PermissionStage (
|
| 151 |
+
OldPermissionCode nvarchar(50) NOT NULL PRIMARY KEY,
|
| 152 |
+
OldParentCode nvarchar(50) NOT NULL,
|
| 153 |
+
NewPermissionId bigint NOT NULL
|
| 154 |
+
);
|
| 155 |
+
|
| 156 |
+
-------------------------------------------------------------------------
|
| 157 |
+
-- Step 4: copy menus
|
| 158 |
+
-------------------------------------------------------------------------
|
| 159 |
+
IF OBJECT_ID('dbo.Menus', 'U') IS NOT NULL AND NOT EXISTS (SELECT 1 FROM dbo.Menus)
|
| 160 |
+
BEGIN
|
| 161 |
+
INSERT INTO dbo.Menus (
|
| 162 |
+
MenuCode, ParentMenuId, MenuName, MenuUrl, Icon,
|
| 163 |
+
Visible, OrderNo, IsDeleted,
|
| 164 |
+
CreatedById, CreatedAt, UpdatedById, UpdatedAt
|
| 165 |
+
)
|
| 166 |
+
OUTPUT
|
| 167 |
+
inserted.MenuCode,
|
| 168 |
+
inserted.MenuId
|
| 169 |
+
INTO #MenuStage (OldMenuCode, NewMenuId)
|
| 170 |
+
SELECT
|
| 171 |
+
src.MenuCode,
|
| 172 |
+
NULL,
|
| 173 |
+
src.MenuName,
|
| 174 |
+
src.MenuUrl,
|
| 175 |
+
src.Icon,
|
| 176 |
+
src.Visible,
|
| 177 |
+
src.OrderNo,
|
| 178 |
+
CASE WHEN src.DelFlag = 0 THEN 0 ELSE 1 END,
|
| 179 |
+
TRY_CONVERT(bigint, src.CreatedUserId),
|
| 180 |
+
src.CreatedDateTime,
|
| 181 |
+
TRY_CONVERT(bigint, src.ModifiedUserId),
|
| 182 |
+
src.ModifiedDateTime
|
| 183 |
+
FROM dbo.MenuAdmins src
|
| 184 |
+
WHERE src.DelFlag = 0;
|
| 185 |
+
|
| 186 |
+
UPDATE stage
|
| 187 |
+
SET stage.OldParentCode = src.ParentCode
|
| 188 |
+
FROM #MenuStage stage
|
| 189 |
+
INNER JOIN dbo.MenuAdmins src
|
| 190 |
+
ON src.MenuCode = stage.OldMenuCode;
|
| 191 |
+
|
| 192 |
+
UPDATE m
|
| 193 |
+
SET ParentMenuId = parent.NewMenuId
|
| 194 |
+
FROM dbo.Menus m
|
| 195 |
+
INNER JOIN #MenuStage child
|
| 196 |
+
ON child.NewMenuId = m.MenuId
|
| 197 |
+
LEFT JOIN #MenuStage parent
|
| 198 |
+
ON parent.OldMenuCode = NULLIF(LTRIM(RTRIM(child.OldParentCode)), '')
|
| 199 |
+
WHERE NULLIF(LTRIM(RTRIM(child.OldParentCode)), '') IS NOT NULL
|
| 200 |
+
AND LTRIM(RTRIM(child.OldParentCode)) <> '0';
|
| 201 |
+
END
|
| 202 |
+
|
| 203 |
+
-------------------------------------------------------------------------
|
| 204 |
+
-- Step 5: copy permissions
|
| 205 |
+
-------------------------------------------------------------------------
|
| 206 |
+
IF OBJECT_ID('dbo.Permissions', 'U') IS NOT NULL AND NOT EXISTS (SELECT 1 FROM dbo.Permissions)
|
| 207 |
+
BEGIN
|
| 208 |
+
INSERT INTO dbo.Permissions (
|
| 209 |
+
PermissionCode, MenuId, ActionName, ApiName, HttpMethod,
|
| 210 |
+
Visible, OrderNo, IsDeleted,
|
| 211 |
+
CreatedById, CreatedAt, UpdatedById, UpdatedAt
|
| 212 |
+
)
|
| 213 |
+
OUTPUT
|
| 214 |
+
inserted.PermissionCode,
|
| 215 |
+
inserted.PermissionId
|
| 216 |
+
INTO #PermissionStage (OldPermissionCode, NewPermissionId)
|
| 217 |
+
SELECT
|
| 218 |
+
src.MenuDetailCode,
|
| 219 |
+
m.NewMenuId,
|
| 220 |
+
src.ActionName,
|
| 221 |
+
src.ApiName,
|
| 222 |
+
NULL,
|
| 223 |
+
src.Visible,
|
| 224 |
+
src.OrderNo,
|
| 225 |
+
CASE WHEN src.DelFlag = 0 THEN 0 ELSE 1 END,
|
| 226 |
+
TRY_CONVERT(bigint, src.CreatedUserId),
|
| 227 |
+
src.CreatedDateTime,
|
| 228 |
+
TRY_CONVERT(bigint, src.ModifiedUserId),
|
| 229 |
+
src.ModifiedDateTime
|
| 230 |
+
FROM dbo.MenuAdminDetails src
|
| 231 |
+
INNER JOIN #MenuStage m
|
| 232 |
+
ON m.OldMenuCode = src.ParentMenuCode
|
| 233 |
+
WHERE src.DelFlag = 0;
|
| 234 |
+
|
| 235 |
+
UPDATE stage
|
| 236 |
+
SET stage.OldParentCode = src.ParentMenuCode
|
| 237 |
+
FROM #PermissionStage stage
|
| 238 |
+
INNER JOIN dbo.MenuAdminDetails src
|
| 239 |
+
ON src.MenuDetailCode = stage.OldPermissionCode;
|
| 240 |
+
END
|
| 241 |
+
|
| 242 |
+
-------------------------------------------------------------------------
|
| 243 |
+
-- Step 6: copy role assignments from the legacy table if it exists
|
| 244 |
+
-------------------------------------------------------------------------
|
| 245 |
+
IF OBJECT_ID('dbo.RoleMenus_Legacy', 'U') IS NOT NULL
|
| 246 |
+
BEGIN
|
| 247 |
+
INSERT INTO dbo.RoleMenus (
|
| 248 |
+
RoleId, MenuId, IsDeleted,
|
| 249 |
+
CreatedById, CreatedAt, UpdatedById, UpdatedAt
|
| 250 |
+
)
|
| 251 |
+
SELECT DISTINCT
|
| 252 |
+
COALESCE(roleById.Id, roleByCode.Id) AS RoleId,
|
| 253 |
+
menuStage.NewMenuId,
|
| 254 |
+
0,
|
| 255 |
+
TRY_CONVERT(bigint, src.CreatedUserId),
|
| 256 |
+
src.CreatedDateTime,
|
| 257 |
+
TRY_CONVERT(bigint, src.ModifiedUserId),
|
| 258 |
+
src.ModifiedDateTime
|
| 259 |
+
FROM dbo.RoleMenus_Legacy src
|
| 260 |
+
LEFT JOIN dbo.Roles roleById
|
| 261 |
+
ON roleById.Id = src.RoleId
|
| 262 |
+
AND roleById.IsDeleted <> 1
|
| 263 |
+
LEFT JOIN dbo.Roles roleByCode
|
| 264 |
+
ON roleByCode.Name = src.RoleCode
|
| 265 |
+
AND roleByCode.IsDeleted <> 1
|
| 266 |
+
INNER JOIN #MenuStage menuStage
|
| 267 |
+
ON menuStage.OldMenuCode = src.MenuCode
|
| 268 |
+
WHERE src.DelFlag = 0
|
| 269 |
+
AND COALESCE(roleById.Id, roleByCode.Id) IS NOT NULL;
|
| 270 |
+
|
| 271 |
+
INSERT INTO dbo.RolePermissions (
|
| 272 |
+
RoleId, PermissionId, IsDeleted,
|
| 273 |
+
CreatedById, CreatedAt, UpdatedById, UpdatedAt
|
| 274 |
+
)
|
| 275 |
+
SELECT DISTINCT
|
| 276 |
+
COALESCE(roleById.Id, roleByCode.Id) AS RoleId,
|
| 277 |
+
permStage.NewPermissionId,
|
| 278 |
+
0,
|
| 279 |
+
TRY_CONVERT(bigint, src.CreatedUserId),
|
| 280 |
+
src.CreatedDateTime,
|
| 281 |
+
TRY_CONVERT(bigint, src.ModifiedUserId),
|
| 282 |
+
src.ModifiedDateTime
|
| 283 |
+
FROM dbo.RoleMenus_Legacy src
|
| 284 |
+
LEFT JOIN dbo.Roles roleById
|
| 285 |
+
ON roleById.Id = src.RoleId
|
| 286 |
+
AND roleById.IsDeleted <> 1
|
| 287 |
+
LEFT JOIN dbo.Roles roleByCode
|
| 288 |
+
ON roleByCode.Name = src.RoleCode
|
| 289 |
+
AND roleByCode.IsDeleted <> 1
|
| 290 |
+
INNER JOIN #PermissionStage permStage
|
| 291 |
+
ON permStage.OldPermissionCode = src.MenuCode
|
| 292 |
+
WHERE src.DelFlag = 0
|
| 293 |
+
AND COALESCE(roleById.Id, roleByCode.Id) IS NOT NULL;
|
| 294 |
+
|
| 295 |
+
INSERT INTO dbo.RoleMenus (
|
| 296 |
+
RoleId, MenuId, IsDeleted,
|
| 297 |
+
CreatedById, CreatedAt, UpdatedById, UpdatedAt
|
| 298 |
+
)
|
| 299 |
+
SELECT DISTINCT
|
| 300 |
+
COALESCE(roleById.Id, roleByCode.Id) AS RoleId,
|
| 301 |
+
parentMenu.NewMenuId,
|
| 302 |
+
0,
|
| 303 |
+
TRY_CONVERT(bigint, src.CreatedUserId),
|
| 304 |
+
src.CreatedDateTime,
|
| 305 |
+
TRY_CONVERT(bigint, src.ModifiedUserId),
|
| 306 |
+
src.ModifiedDateTime
|
| 307 |
+
FROM dbo.RoleMenus_Legacy src
|
| 308 |
+
LEFT JOIN dbo.Roles roleById
|
| 309 |
+
ON roleById.Id = src.RoleId
|
| 310 |
+
AND roleById.IsDeleted <> 1
|
| 311 |
+
LEFT JOIN dbo.Roles roleByCode
|
| 312 |
+
ON roleByCode.Name = src.RoleCode
|
| 313 |
+
AND roleByCode.IsDeleted <> 1
|
| 314 |
+
INNER JOIN #PermissionStage permStage
|
| 315 |
+
ON permStage.OldPermissionCode = src.MenuCode
|
| 316 |
+
INNER JOIN #MenuStage parentMenu
|
| 317 |
+
ON parentMenu.OldMenuCode = permStage.OldParentCode
|
| 318 |
+
WHERE src.DelFlag = 0
|
| 319 |
+
AND COALESCE(roleById.Id, roleByCode.Id) IS NOT NULL;
|
| 320 |
+
END
|
| 321 |
+
|
| 322 |
+
COMMIT TRANSACTION;
|
| 323 |
+
END TRY
|
| 324 |
+
BEGIN CATCH
|
| 325 |
+
IF @@TRANCOUNT > 0
|
| 326 |
+
ROLLBACK TRANSACTION;
|
| 327 |
+
|
| 328 |
+
THROW;
|
| 329 |
+
END CATCH;
|