diff --git a/Directory.Build.props b/Directory.Build.props index b4b2f3d..f2b90fb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 2.3.0 + 2.4.0 Tudor Stanciu STA Tuitio diff --git a/ReleaseNotes.xml b/ReleaseNotes.xml index 2cf75b9..3122d04 100644 --- a/ReleaseNotes.xml +++ b/ReleaseNotes.xml @@ -86,4 +86,13 @@ ◾ Published new versions of Tuitio's nuget packages + + 2.4.0 + 2023-04-03 01:14 + + Added user groups and roles + ◾ From this version, any user can be assigned to groups and can have roles. + ◾ Each user group can have roles that will be applied to all users who are part of the group. + + \ No newline at end of file diff --git a/src/Tuitio.Domain.Data/DbContexts/TuitioDbContext.cs b/src/Tuitio.Domain.Data/DbContexts/TuitioDbContext.cs index 26b4da0..84d58df 100644 --- a/src/Tuitio.Domain.Data/DbContexts/TuitioDbContext.cs +++ b/src/Tuitio.Domain.Data/DbContexts/TuitioDbContext.cs @@ -29,6 +29,11 @@ namespace Tuitio.Domain.Data.DbContexts modelBuilder.ApplyConfiguration(new UserTokenConfiguration()); modelBuilder.ApplyConfiguration(new ContactTypeConfiguration()); modelBuilder.ApplyConfiguration(new ContactOptionConfiguration()); + modelBuilder.ApplyConfiguration(new UserGroupConfiguration()); + modelBuilder.ApplyConfiguration(new UserRoleConfiguration()); + modelBuilder.ApplyConfiguration(new UserXUserGroupConfiguration()); + modelBuilder.ApplyConfiguration(new UserGroupXUserRoleConfiguration()); + modelBuilder.ApplyConfiguration(new UserXUserRoleConfiguration()); } } } diff --git a/src/Tuitio.Domain.Data/EntityTypeConfiguration/AppUserConfiguration.cs b/src/Tuitio.Domain.Data/EntityTypeConfiguration/AppUserConfiguration.cs index 771d0d0..b6e5ea6 100644 --- a/src/Tuitio.Domain.Data/EntityTypeConfiguration/AppUserConfiguration.cs +++ b/src/Tuitio.Domain.Data/EntityTypeConfiguration/AppUserConfiguration.cs @@ -15,6 +15,8 @@ namespace Tuitio.Domain.Data.EntityTypeConfiguration builder.HasOne(z => z.Status).WithMany().HasForeignKey(z => z.StatusId); builder.HasMany(z => z.Claims).WithOne().HasForeignKey(z => z.UserId); builder.HasMany(z => z.ContactOptions).WithOne().HasForeignKey(z => z.UserId); + builder.HasMany(z => z.UserGroups).WithOne().HasForeignKey(z => z.UserId); + builder.HasMany(z => z.UserRoles).WithOne().HasForeignKey(z => z.UserId); } } } diff --git a/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserGroupConfiguration.cs b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserGroupConfiguration.cs new file mode 100644 index 0000000..d0d8092 --- /dev/null +++ b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserGroupConfiguration.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2020 Tudor Stanciu + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Tuitio.Domain.Entities; + +namespace Tuitio.Domain.Data.EntityTypeConfiguration +{ + class UserGroupConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("UserGroup").HasKey(z => z.UserGroupId); + builder.Property(z => z.UserGroupId).ValueGeneratedOnAdd(); + builder.HasMany(z => z.GroupRoles).WithOne().HasForeignKey(z => z.UserGroupId); + } + } +} diff --git a/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserGroupXUserRoleConfiguration.cs b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserGroupXUserRoleConfiguration.cs new file mode 100644 index 0000000..a37d700 --- /dev/null +++ b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserGroupXUserRoleConfiguration.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2020 Tudor Stanciu + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Tuitio.Domain.Entities; + +namespace Tuitio.Domain.Data.EntityTypeConfiguration +{ + class UserGroupXUserRoleConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("UserGroupXUserRole").HasKey(z => new { z.UserGroupId, z.UserRoleId }); + builder.HasOne(z => z.UserRole).WithMany().HasForeignKey(z => z.UserRoleId); + } + } +} diff --git a/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserRoleConfiguration.cs b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserRoleConfiguration.cs new file mode 100644 index 0000000..db4ae95 --- /dev/null +++ b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserRoleConfiguration.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2020 Tudor Stanciu + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Tuitio.Domain.Entities; + +namespace Tuitio.Domain.Data.EntityTypeConfiguration +{ + class UserRoleConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("UserRole").HasKey(z => z.UserRoleId); + builder.Property(z => z.UserRoleId).ValueGeneratedOnAdd(); + } + } +} diff --git a/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserXUserGroupConfiguration.cs b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserXUserGroupConfiguration.cs new file mode 100644 index 0000000..b37bca8 --- /dev/null +++ b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserXUserGroupConfiguration.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2020 Tudor Stanciu + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Tuitio.Domain.Entities; + +namespace Tuitio.Domain.Data.EntityTypeConfiguration +{ + class UserXUserGroupConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("UserXUserGroup").HasKey(z => new { z.UserId, z.UserGroupId }); + builder.HasOne(z => z.UserGroup).WithMany().HasForeignKey(z => z.UserGroupId); + } + } +} diff --git a/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserXUserRoleConfiguration.cs b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserXUserRoleConfiguration.cs new file mode 100644 index 0000000..a3b8762 --- /dev/null +++ b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserXUserRoleConfiguration.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2020 Tudor Stanciu + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Tuitio.Domain.Entities; + +namespace Tuitio.Domain.Data.EntityTypeConfiguration +{ + class UserXUserRoleConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("UserXUserRole").HasKey(z => new { z.UserId, z.UserRoleId }); + builder.HasOne(z => z.UserRole).WithMany().HasForeignKey(z => z.UserRoleId); + } + } +} diff --git a/src/Tuitio.Domain.Data/Scripts/2.4.0/01.UserGroup table.sql b/src/Tuitio.Domain.Data/Scripts/2.4.0/01.UserGroup table.sql new file mode 100644 index 0000000..3b63d01 --- /dev/null +++ b/src/Tuitio.Domain.Data/Scripts/2.4.0/01.UserGroup table.sql @@ -0,0 +1,19 @@ +if not exists (select top 1 1 from sys.objects where name = 'UserGroup' and type = 'U') +begin + create table UserGroup + ( + UserGroupId int identity(1, 1) constraint PK_UserGroup primary key, + UserGroupCode varchar(30) not null, + UserGroupName varchar(50) not null + ) +end + +if not exists (select top 1 1 from UserGroup) +begin + insert into UserGroup(UserGroupCode, UserGroupName) + values ('ADMINISTRATORS', 'Administrators'), + ('USERS', 'Users'), + ('DEVELOPERS', 'Developers'), + ('VIEWERS', 'Viewers'), + ('GUESTS', 'Guests') +end \ No newline at end of file diff --git a/src/Tuitio.Domain.Data/Scripts/2.4.0/02.UserRole table.sql b/src/Tuitio.Domain.Data/Scripts/2.4.0/02.UserRole table.sql new file mode 100644 index 0000000..d179656 --- /dev/null +++ b/src/Tuitio.Domain.Data/Scripts/2.4.0/02.UserRole table.sql @@ -0,0 +1,22 @@ +if not exists (select top 1 1 from sys.objects where name = 'UserRole' and type = 'U') +begin + create table UserRole + ( + UserRoleId int identity(1, 1) constraint PK_UserRole primary key, + UserRoleCode varchar(30) not null, + UserRoleName varchar(50) not null + ) +end + +if not exists (select top 1 1 from UserRole) +begin + insert into UserRole(UserRoleCode, UserRoleName) + values ('SYSTEM_ADMINISTRATOR', 'System administrator'), + ('FULLSTACK_DEVELOPER', 'Fullstack developer'), + ('POWER_USER', 'Power user'), + ('REGULAR_USER', 'Regular user'), + ('READONLY_USER', 'Readonly user'), + ('ANONYMOUS_USER', 'Anonymous user'), + ('DEMO_USER', 'Demo user') +end + diff --git a/src/Tuitio.Domain.Data/Scripts/2.4.0/03.New tables UserXUserGroup UserGroupXUserRole UserXUserRole.sql b/src/Tuitio.Domain.Data/Scripts/2.4.0/03.New tables UserXUserGroup UserGroupXUserRole UserXUserRole.sql new file mode 100644 index 0000000..0068d32 --- /dev/null +++ b/src/Tuitio.Domain.Data/Scripts/2.4.0/03.New tables UserXUserGroup UserGroupXUserRole UserXUserRole.sql @@ -0,0 +1,29 @@ +if not exists (select top 1 1 from sys.objects where name = 'UserXUserGroup' and type = 'U') +begin + create table UserXUserGroup + ( + UserId int not null constraint FK_UserXUserGroup_AppUser foreign key references AppUser(UserId), + UserGroupId int not null constraint FK_UserXUserGroup_UserGroup foreign key references UserGroup(UserGroupId), + constraint PK_UserXUserGroup primary key (UserId, UserGroupId) + ) +end + +if not exists (select top 1 1 from sys.objects where name = 'UserGroupXUserRole' and type = 'U') +begin + create table UserGroupXUserRole + ( + UserGroupId int not null constraint FK_UserGroupXUserRole_UserGroup references UserGroup(UserGroupId), + UserRoleId int not null constraint FK_UserGroupXUserRole_UserRole references UserRole(UserRoleId), + constraint PK_UserGroupXUserRole primary key (UserGroupId, UserRoleId) + ) +end + +if not exists (select top 1 1 from sys.objects where name = 'UserXUserRole' and type = 'U') +begin + create table UserXUserRole + ( + UserId int not null constraint FK_UserXUserRole_AppUser references AppUser(UserId), + UserRoleId int not null constraint FK_UserXUserRole_UserRole references UserRole(UserRoleId), + constraint PK_UserXUserRole primary key (UserId, UserRoleId) + ) +end \ No newline at end of file diff --git a/src/Tuitio.Domain.Data/Scripts/2.4.0/04.Link user groups and roles.sql b/src/Tuitio.Domain.Data/Scripts/2.4.0/04.Link user groups and roles.sql new file mode 100644 index 0000000..e1a91c8 --- /dev/null +++ b/src/Tuitio.Domain.Data/Scripts/2.4.0/04.Link user groups and roles.sql @@ -0,0 +1,58 @@ + +declare @admin_group_id int, @admin_role_id int + +select @admin_group_id = UserGroupId from UserGroup where UserGroupCode = 'ADMINISTRATORS' +select @admin_role_id = UserRoleId from UserRole where UserRoleCode = 'SYSTEM_ADMINISTRATOR' + +if not exists (select top 1 1 from UserGroupXUserRole where UserGroupId = @admin_group_id and UserRoleId = @admin_role_id) +begin + insert into UserGroupXUserRole (UserGroupId, UserRoleId) + values (@admin_group_id, @admin_role_id) +end + + +declare @developer_group_id int, @developer_role_id int + +select @developer_group_id = UserGroupId from UserGroup where UserGroupCode = 'DEVELOPERS' +select @developer_role_id = UserRoleId from UserRole where UserRoleCode = 'FULLSTACK_DEVELOPER' + +if not exists (select top 1 1 from UserGroupXUserRole where UserGroupId = @developer_group_id and UserRoleId = @developer_role_id) +begin + insert into UserGroupXUserRole (UserGroupId, UserRoleId) + values (@developer_group_id, @developer_role_id) +end + + +declare @user_group_id int, @user_role_id int + +select @user_group_id = UserGroupId from UserGroup where UserGroupCode = 'USERS' +select @user_role_id = UserRoleId from UserRole where UserRoleCode = 'REGULAR_USER' + +if not exists (select top 1 1 from UserGroupXUserRole where UserGroupId = @user_group_id and UserRoleId = @user_role_id) +begin + insert into UserGroupXUserRole (UserGroupId, UserRoleId) + values (@user_group_id, @user_role_id) +end + + +declare @viewer_group_id int, @viewer_role_id int + +select @viewer_group_id = UserGroupId from UserGroup where UserGroupCode = 'VIEWERS' +select @viewer_role_id = UserRoleId from UserRole where UserRoleCode = 'READONLY_USER' + +if not exists (select top 1 1 from UserGroupXUserRole where UserGroupId = @viewer_group_id and UserRoleId = @viewer_role_id) +begin + insert into UserGroupXUserRole (UserGroupId, UserRoleId) + values (@viewer_group_id, @viewer_role_id) +end + +declare @guest_group_id int, @guest_role_id int + +select @guest_group_id = UserGroupId from UserGroup where UserGroupCode = 'GUESTS' +select @guest_role_id = UserRoleId from UserRole where UserRoleCode = 'ANONYMOUS_USER' + +if not exists (select top 1 1 from UserGroupXUserRole where UserGroupId = @guest_group_id and UserRoleId = @guest_role_id) +begin + insert into UserGroupXUserRole (UserGroupId, UserRoleId) + values (@guest_group_id, @guest_role_id) +end \ No newline at end of file diff --git a/src/Tuitio.Domain.Data/Tuitio.Domain.Data.csproj b/src/Tuitio.Domain.Data/Tuitio.Domain.Data.csproj index cd5c1bf..48067a6 100644 --- a/src/Tuitio.Domain.Data/Tuitio.Domain.Data.csproj +++ b/src/Tuitio.Domain.Data/Tuitio.Domain.Data.csproj @@ -34,6 +34,18 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + diff --git a/src/Tuitio.Domain/Entities/AppUser.cs b/src/Tuitio.Domain/Entities/AppUser.cs index f7933bf..49714eb 100644 --- a/src/Tuitio.Domain/Entities/AppUser.cs +++ b/src/Tuitio.Domain/Entities/AppUser.cs @@ -23,5 +23,7 @@ namespace Tuitio.Domain.Entities public UserStatus Status { get; set; } public ICollection Claims { get; set; } public ICollection ContactOptions { get; set; } + public ICollection UserGroups { get; set; } + public ICollection UserRoles { get; set; } } } diff --git a/src/Tuitio.Domain/Entities/UserGroup.cs b/src/Tuitio.Domain/Entities/UserGroup.cs new file mode 100644 index 0000000..20f7da4 --- /dev/null +++ b/src/Tuitio.Domain/Entities/UserGroup.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2020 Tudor Stanciu + +using System.Collections.Generic; + +namespace Tuitio.Domain.Entities +{ + public class UserGroup + { + public int UserGroupId { get; set; } + public string UserGroupCode { get; set; } + public string UserGroupName { get; set; } + public ICollection GroupRoles { get; set; } + } +} diff --git a/src/Tuitio.Domain/Entities/UserGroupXUserRole.cs b/src/Tuitio.Domain/Entities/UserGroupXUserRole.cs new file mode 100644 index 0000000..7dc9d55 --- /dev/null +++ b/src/Tuitio.Domain/Entities/UserGroupXUserRole.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2020 Tudor Stanciu + +namespace Tuitio.Domain.Entities +{ + public class UserGroupXUserRole + { + public int UserGroupId { get; set; } + public int UserRoleId { get; set; } + public UserRole UserRole { get; set; } + } +} diff --git a/src/Tuitio.Domain/Entities/UserRole.cs b/src/Tuitio.Domain/Entities/UserRole.cs new file mode 100644 index 0000000..e81894f --- /dev/null +++ b/src/Tuitio.Domain/Entities/UserRole.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2020 Tudor Stanciu + +namespace Tuitio.Domain.Entities +{ + public class UserRole + { + public int UserRoleId { get; set; } + public string UserRoleCode { get; set; } + public string UserRoleName { get; set; } + } +} diff --git a/src/Tuitio.Domain/Entities/UserXUserGroup.cs b/src/Tuitio.Domain/Entities/UserXUserGroup.cs new file mode 100644 index 0000000..e17a7ee --- /dev/null +++ b/src/Tuitio.Domain/Entities/UserXUserGroup.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2020 Tudor Stanciu + +namespace Tuitio.Domain.Entities +{ + public class UserXUserGroup + { + public int UserId { get; set; } + public int UserGroupId { get; set; } + public UserGroup UserGroup { get; set; } + } +} diff --git a/src/Tuitio.Domain/Entities/UserXUserRole.cs b/src/Tuitio.Domain/Entities/UserXUserRole.cs new file mode 100644 index 0000000..a5274c6 --- /dev/null +++ b/src/Tuitio.Domain/Entities/UserXUserRole.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2020 Tudor Stanciu + +namespace Tuitio.Domain.Entities +{ + public class UserXUserRole + { + public int UserId { get; set; } + public int UserRoleId { get; set; } + public UserRole UserRole { get; set; } + } +} \ No newline at end of file