diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3d25c9a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CA1822: Member als statisch markieren +dotnet_diagnostic.CA1822.severity = suggestion diff --git a/.idea/.idea.UserService/.idea/indexLayout.xml b/.idea/.idea.UserService/.idea/indexLayout.xml new file mode 100644 index 0000000..27ba142 --- /dev/null +++ b/.idea/.idea.UserService/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.UserService/.idea/modules.xml b/.idea/.idea.UserService/.idea/modules.xml new file mode 100644 index 0000000..9ed9bc0 --- /dev/null +++ b/.idea/.idea.UserService/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.UserService/.idea/projectSettingsUpdater.xml b/.idea/.idea.UserService/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000..4bb9f4d --- /dev/null +++ b/.idea/.idea.UserService/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.UserService/.idea/vcs.xml b/.idea/.idea.UserService/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.UserService/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/.idea.UserService/riderModule.iml b/.idea/.idea.UserService/riderModule.iml new file mode 100644 index 0000000..882628e --- /dev/null +++ b/.idea/.idea.UserService/riderModule.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UserService.DatabaseLayer/DataModels/ModelBuilderExtensions.cs b/UserService.DatabaseLayer/DataModels/ModelBuilderExtensions.cs index d06cd66..242eebd 100644 --- a/UserService.DatabaseLayer/DataModels/ModelBuilderExtensions.cs +++ b/UserService.DatabaseLayer/DataModels/ModelBuilderExtensions.cs @@ -1,5 +1,7 @@ -using Microsoft.EntityFrameworkCore; +using System; +using Microsoft.EntityFrameworkCore; using System.Collections.Generic; +using UserService.Infrastructure.DataModels; namespace UserService.DatabaseLayer.DataModels { @@ -7,6 +9,7 @@ namespace UserService.DatabaseLayer.DataModels { public static void Seed(this ModelBuilder modelBuilder) { + if (modelBuilder == null) throw new ArgumentNullException(nameof(modelBuilder)); var groups = new OrganizationUnit { CommonName = "Groups", Id = -1 }; var users = new OrganizationUnit { CommonName = "Users", Id = -2 }; var germany = new OrganizationUnit{CommonName = "Germany", Id = -6, ParentId = -2}; @@ -14,7 +17,7 @@ namespace UserService.DatabaseLayer.DataModels var arizona = new OrganizationUnit{CommonName = "Arizona" , Id = -4, ParentId = -5 }; var france = new OrganizationUnit{CommonName = "France" , Id = -3, ParentId = -2 }; modelBuilder.Entity().HasData(users, groups, germany, usa, arizona, france); - var user = new User { CommonName = "holger", IsActive = true, Id = -7, ParentId = germany.Id }; + var user = new User { CommonName = Environment.UserName, IsActive = true, Id = -7, ParentId = users.Id }; modelBuilder.Entity().HasData(user); var secGroup = new SecurityGroup { CommonName = "Global Admin", Id = -8, ParentId = groups.Id }; modelBuilder.Entity().HasData(secGroup); @@ -25,6 +28,7 @@ namespace UserService.DatabaseLayer.DataModels public static void CreateRelations(this ModelBuilder modelBuilder) { + if (modelBuilder == null) throw new ArgumentNullException(nameof(modelBuilder)); modelBuilder.Entity() .HasKey(bc => new { bc.MemberId, bc.UserId }); modelBuilder.Entity() @@ -35,7 +39,6 @@ namespace UserService.DatabaseLayer.DataModels .HasOne(bc => bc.Member) .WithMany(c => c!.Members) .HasForeignKey(bc => bc.MemberId); - modelBuilder.Entity() .HasMany(c => c.Children) .WithOne(e => e.Parent!) @@ -47,6 +50,7 @@ namespace UserService.DatabaseLayer.DataModels { public static IEnumerable GetSecurityGroups(this User user) { + if (user == null) throw new ArgumentNullException(nameof(user)); foreach (var userMember in user.MemberOf) { if (userMember.Member is SecurityGroup securityGroup) @@ -62,6 +66,7 @@ namespace UserService.DatabaseLayer.DataModels { public static IEnumerable GetUsers(this SecurityGroup securityGroup) { + if (securityGroup == null) throw new ArgumentNullException(nameof(securityGroup)); foreach (var userMember in securityGroup.Members) { if (userMember.User is null) continue; diff --git a/UserService.DatabaseLayer/DataModels/Node.cs b/UserService.DatabaseLayer/DataModels/Node.cs deleted file mode 100644 index 5484f05..0000000 --- a/UserService.DatabaseLayer/DataModels/Node.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; - -namespace UserService.DatabaseLayer.DataModels -{ - public class OrganizationUnit : Node - { - public Member? Manager { get; set; } - - /// - public override string ToString() - { - var sb = new StringBuilder(); - if (Level != 0) - { - sb.Append("|"); - } - - sb.Append('-', Level * 4); - - return sb + CommonName; - } - - - } - - public class SecurityGroup : Member - { - } - - public class User : Member - { - public string? FirstName { get; set; } - public string? LastName { get; set; } - public bool IsActive { get; set; } - - public string FullName => $"{FirstName} {LastName}"; - - public IEnumerable MemberOf { get; set; } = new List(); - } - - public class UserMember - { - public int MemberId { get; set; } - public Member? Member { get; set; } - - public int UserId { get; set; } - - public User? User { get; set; } - } - - public abstract class Member : Node - { - [EmailAddress] - public string? EMail { get; set; } - - public ICollection Members { get; set; } = new List(); - } - - public abstract class Node : ICloneable - { - public int Id { get; set; } - [Required] public string CommonName { get; set; } = null!; - public string? Description { get; set; } - public ICollection Children { get; set; } = new List(); - public Node? Parent { get; set; } //Parent - public int? ParentId { get; set; } - - public override string ToString() => $"[{GetType().Name}] {Id:D5} {CommonName}"; - - public int Level => Parent?.Level + 1 ?? 0; - - /// - public virtual object Clone() => MemberwiseClone(); - } -} \ No newline at end of file diff --git a/UserService.DatabaseLayer/DataModels/UserServiceDbContext.cs b/UserService.DatabaseLayer/DataModels/UserServiceDbContext.cs index 68c5567..cd9e5d8 100644 --- a/UserService.DatabaseLayer/DataModels/UserServiceDbContext.cs +++ b/UserService.DatabaseLayer/DataModels/UserServiceDbContext.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using UserService.Infrastructure.DataModels; namespace UserService.DatabaseLayer.DataModels { diff --git a/UserService.DatabaseLayer/Migrations/20200725195658_initial.Designer.cs b/UserService.DatabaseLayer/Migrations/20200821193933_InitialCreate.Designer.cs similarity index 76% rename from UserService.DatabaseLayer/Migrations/20200725195658_initial.Designer.cs rename to UserService.DatabaseLayer/Migrations/20200821193933_InitialCreate.Designer.cs index 56a7dcf..315dc82 100644 --- a/UserService.DatabaseLayer/Migrations/20200725195658_initial.Designer.cs +++ b/UserService.DatabaseLayer/Migrations/20200821193933_InitialCreate.Designer.cs @@ -9,16 +9,16 @@ using UserService.DatabaseLayer.DataModels; namespace UserService.DatabaseLayer.Migrations { [DbContext(typeof(UserServiceDbContext))] - [Migration("20200725195658_initial")] - partial class initial + [Migration("20200821193933_InitialCreate")] + partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "3.1.6"); + .HasAnnotation("ProductVersion", "3.1.7"); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.Node", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -47,7 +47,7 @@ namespace UserService.DatabaseLayer.Migrations b.HasDiscriminator("Discriminator").HasValue("Node"); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.UserMember", b => { b.Property("MemberId") .HasColumnType("INTEGER"); @@ -69,9 +69,9 @@ namespace UserService.DatabaseLayer.Migrations }); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Member", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.Member", b => { - b.HasBaseType("UserService.DatabaseLayer.DataModels.Node"); + b.HasBaseType("UserService.Infrastructure.DataModels.Node"); b.Property("EMail") .HasColumnType("TEXT"); @@ -79,9 +79,9 @@ namespace UserService.DatabaseLayer.Migrations b.HasDiscriminator().HasValue("Member"); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.OrganizationUnit", b => { - b.HasBaseType("UserService.DatabaseLayer.DataModels.Node"); + b.HasBaseType("UserService.Infrastructure.DataModels.Node"); b.Property("ManagerId") .HasColumnType("INTEGER"); @@ -127,9 +127,9 @@ namespace UserService.DatabaseLayer.Migrations }); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.SecurityGroup", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.SecurityGroup", b => { - b.HasBaseType("UserService.DatabaseLayer.DataModels.Member"); + b.HasBaseType("UserService.Infrastructure.DataModels.Member"); b.HasDiscriminator().HasValue("SecurityGroup"); @@ -142,9 +142,9 @@ namespace UserService.DatabaseLayer.Migrations }); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.User", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.User", b => { - b.HasBaseType("UserService.DatabaseLayer.DataModels.Member"); + b.HasBaseType("UserService.Infrastructure.DataModels.Member"); b.Property("FirstName") .HasColumnType("TEXT"); @@ -162,36 +162,36 @@ namespace UserService.DatabaseLayer.Migrations { Id = -7, CommonName = "holger", - ParentId = -6, + ParentId = -2, IsActive = true }); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.Node", b => { - b.HasOne("UserService.DatabaseLayer.DataModels.Node", "Parent") + b.HasOne("UserService.Infrastructure.DataModels.Node", "Parent") .WithMany("Children") .HasForeignKey("ParentId"); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.UserMember", b => { - b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Member") + b.HasOne("UserService.Infrastructure.DataModels.Member", "Member") .WithMany("Members") .HasForeignKey("MemberId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("UserService.DatabaseLayer.DataModels.User", "User") + b.HasOne("UserService.Infrastructure.DataModels.User", "User") .WithMany("MemberOf") .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.OrganizationUnit", b => { - b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Manager") + b.HasOne("UserService.Infrastructure.DataModels.Member", "Manager") .WithMany() .HasForeignKey("ManagerId"); }); diff --git a/UserService.DatabaseLayer/Migrations/20200725195658_initial.cs b/UserService.DatabaseLayer/Migrations/20200821193933_InitialCreate.cs similarity index 98% rename from UserService.DatabaseLayer/Migrations/20200725195658_initial.cs rename to UserService.DatabaseLayer/Migrations/20200821193933_InitialCreate.cs index bfb6a17..47c7b27 100644 --- a/UserService.DatabaseLayer/Migrations/20200725195658_initial.cs +++ b/UserService.DatabaseLayer/Migrations/20200821193933_InitialCreate.cs @@ -2,7 +2,7 @@ namespace UserService.DatabaseLayer.Migrations { - public partial class initial : Migration + public partial class InitialCreate : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -90,13 +90,13 @@ namespace UserService.DatabaseLayer.Migrations migrationBuilder.InsertData( table: "Node", - columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "EMail" }, - values: new object[] { -8, "Global Admin", null, "SecurityGroup", -1, null }); + columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "EMail", "FirstName", "IsActive", "LastName" }, + values: new object[] { -7, "holger", null, "User", -2, null, null, true, null }); migrationBuilder.InsertData( table: "Node", - columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "EMail", "FirstName", "IsActive", "LastName" }, - values: new object[] { -7, "holger", null, "User", -6, null, null, true, null }); + columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "EMail" }, + values: new object[] { -8, "Global Admin", null, "SecurityGroup", -1, null }); migrationBuilder.InsertData( table: "Node", diff --git a/UserService.DatabaseLayer/Migrations/UserServiceDbContextModelSnapshot.cs b/UserService.DatabaseLayer/Migrations/UserServiceDbContextModelSnapshot.cs index 4d03a6e..6bd5c44 100644 --- a/UserService.DatabaseLayer/Migrations/UserServiceDbContextModelSnapshot.cs +++ b/UserService.DatabaseLayer/Migrations/UserServiceDbContextModelSnapshot.cs @@ -14,9 +14,9 @@ namespace UserService.DatabaseLayer.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "3.1.6"); + .HasAnnotation("ProductVersion", "3.1.7"); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.Node", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -45,7 +45,7 @@ namespace UserService.DatabaseLayer.Migrations b.HasDiscriminator("Discriminator").HasValue("Node"); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.UserMember", b => { b.Property("MemberId") .HasColumnType("INTEGER"); @@ -67,9 +67,9 @@ namespace UserService.DatabaseLayer.Migrations }); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Member", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.Member", b => { - b.HasBaseType("UserService.DatabaseLayer.DataModels.Node"); + b.HasBaseType("UserService.Infrastructure.DataModels.Node"); b.Property("EMail") .HasColumnType("TEXT"); @@ -77,9 +77,9 @@ namespace UserService.DatabaseLayer.Migrations b.HasDiscriminator().HasValue("Member"); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.OrganizationUnit", b => { - b.HasBaseType("UserService.DatabaseLayer.DataModels.Node"); + b.HasBaseType("UserService.Infrastructure.DataModels.Node"); b.Property("ManagerId") .HasColumnType("INTEGER"); @@ -125,9 +125,9 @@ namespace UserService.DatabaseLayer.Migrations }); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.SecurityGroup", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.SecurityGroup", b => { - b.HasBaseType("UserService.DatabaseLayer.DataModels.Member"); + b.HasBaseType("UserService.Infrastructure.DataModels.Member"); b.HasDiscriminator().HasValue("SecurityGroup"); @@ -140,9 +140,9 @@ namespace UserService.DatabaseLayer.Migrations }); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.User", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.User", b => { - b.HasBaseType("UserService.DatabaseLayer.DataModels.Member"); + b.HasBaseType("UserService.Infrastructure.DataModels.Member"); b.Property("FirstName") .HasColumnType("TEXT"); @@ -160,36 +160,36 @@ namespace UserService.DatabaseLayer.Migrations { Id = -7, CommonName = "holger", - ParentId = -6, + ParentId = -2, IsActive = true }); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.Node", b => { - b.HasOne("UserService.DatabaseLayer.DataModels.Node", "Parent") + b.HasOne("UserService.Infrastructure.DataModels.Node", "Parent") .WithMany("Children") .HasForeignKey("ParentId"); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.UserMember", b => { - b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Member") + b.HasOne("UserService.Infrastructure.DataModels.Member", "Member") .WithMany("Members") .HasForeignKey("MemberId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("UserService.DatabaseLayer.DataModels.User", "User") + b.HasOne("UserService.Infrastructure.DataModels.User", "User") .WithMany("MemberOf") .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); - modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b => + modelBuilder.Entity("UserService.Infrastructure.DataModels.OrganizationUnit", b => { - b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Manager") + b.HasOne("UserService.Infrastructure.DataModels.Member", "Manager") .WithMany() .HasForeignKey("ManagerId"); }); diff --git a/UserService.DatabaseLayer/Queryable.cs b/UserService.DatabaseLayer/Queryable.cs new file mode 100644 index 0000000..bc15ede --- /dev/null +++ b/UserService.DatabaseLayer/Queryable.cs @@ -0,0 +1,17 @@ +using System.Linq.Expressions; + +// ReSharper disable once CheckNamespace +namespace System.Linq +{ + public static class Queryable + { + /// + /// Makes a where filtering, if it is not null. + /// + public static IQueryable WhereOrDefault(this IQueryable source, Expression>? predicate) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + return predicate is null ? source : source.Where(predicate); + } + } +} diff --git a/UserService.DatabaseLayer/Repository/BaseRepository.cs b/UserService.DatabaseLayer/Repository/BaseRepository.cs index aeb0dd7..61db729 100644 --- a/UserService.DatabaseLayer/Repository/BaseRepository.cs +++ b/UserService.DatabaseLayer/Repository/BaseRepository.cs @@ -1,55 +1,57 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using UserService.DatabaseLayer.DataModels; +using UserService.Infrastructure.DataModels; namespace UserService.DatabaseLayer.Repository { public class BaseRepository where T : Node { - protected readonly Func> _context; + protected Func> Context { get; } protected BaseRepository(Func> context) { - _context = context; + Context = context; } - public virtual async Task> GetAllAsync(CancellationToken token = default) + public virtual async Task> GetAllAsync(Expression>? predicate = null, CancellationToken token = default) { await using var db = new UserServiceDbContext(); - return await _context(db).Include(x => x.Parent).ToListAsync(token); + return await Context(db).Include(x => x.Parent).WhereOrDefault(predicate).ToListAsync(token).ConfigureAwait(false); } public async Task GetAsync(Expression> predicate, CancellationToken token = default) { await using var db = new UserServiceDbContext(); - return await _context(db).FirstOrDefaultAsync(predicate, token); + return await Context(db).FirstOrDefaultAsync(predicate, token).ConfigureAwait(false); } public async Task AddAsync(T entity, CancellationToken token = default) { await using var db = new UserServiceDbContext(); - await _context(db).AddAsync(@entity, token); - await db.SaveChangesAsync(token); + await Context(db).AddAsync(@entity, token).ConfigureAwait(false); + await db.SaveChangesAsync(token).ConfigureAwait(false); } - public async Task UpdateAsync(T entity, CancellationToken token = default) + public async Task UpdateAsync(T entity, CancellationToken token = default) { await using var db = new UserServiceDbContext(); - _context(db).Update(entity); - await db.SaveChangesAsync(token); + Context(db).Update(entity); + var items= await db.SaveChangesAsync(token).ConfigureAwait(false); + return items > 0; } public async Task DeleteAsync(T entity, CancellationToken token = default) { await using var db = new UserServiceDbContext(); - _context(db).Remove(entity); - await db.SaveChangesAsync(token); + Context(db).Remove(entity); + await db.SaveChangesAsync(token).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/UserService.DatabaseLayer/Repository/IRepository.cs b/UserService.DatabaseLayer/Repository/IRepository.cs index 19f8598..74058e5 100644 --- a/UserService.DatabaseLayer/Repository/IRepository.cs +++ b/UserService.DatabaseLayer/Repository/IRepository.cs @@ -1,18 +1,19 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; -using UserService.DatabaseLayer.DataModels; +using UserService.Infrastructure.DataModels; namespace UserService.DatabaseLayer.Repository { public interface IRepository where T : Node { - Task> GetAllAsync(CancellationToken token = default); + Task> GetAllAsync(Expression>? predicate = null, CancellationToken token = default); Task GetAsync(Expression> predicate, CancellationToken token = default); Task AddAsync(T entity, CancellationToken token = default); - Task UpdateAsync(T entity, CancellationToken token = default); + Task UpdateAsync(T entity, CancellationToken token = default); Task DeleteAsync(T entity, CancellationToken token = default); } diff --git a/UserService.DatabaseLayer/Repository/OrganizationUnitsRepository.cs b/UserService.DatabaseLayer/Repository/OrganizationUnitsRepository.cs new file mode 100644 index 0000000..fa44e7c --- /dev/null +++ b/UserService.DatabaseLayer/Repository/OrganizationUnitsRepository.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using UserService.DatabaseLayer.DataModels; +using UserService.Infrastructure.DataModels; + +namespace UserService.DatabaseLayer.Repository +{ + public class OrganizationUnitsRepository : BaseRepository, IOrganizationUnitsRepository + { + public OrganizationUnitsRepository() : base(x => x.OrganizationUnits) + { + } + + /// + public override async Task> GetAllAsync( + Expression>? predicate = null, CancellationToken token = default) + { + await using var db = new UserServiceDbContext(); + var result = new List(); + var rootOus = await Context(db) + .Include(x => x.Parent) + .WhereOrDefault(predicate) + .ToListAsync(cancellationToken: token).ConfigureAwait(false); + + IEnumerable Rec(Node node) + { + if (!(node is OrganizationUnit organizationUnit)) yield break; + yield return organizationUnit; + foreach (var ouChild in rootOus.Where(x => x.ParentId != null && x.ParentId == organizationUnit.Id)) + { + foreach (var unit in Rec(ouChild)) + { + yield return unit; + } + } + } + + foreach (var ou in rootOus.Where(x => x.ParentId is null)) + { + result.AddRange(Rec(ou)); + } + + return result; + } + } +} \ No newline at end of file diff --git a/UserService.DatabaseLayer/Repository/Repository.cs b/UserService.DatabaseLayer/Repository/Repository.cs deleted file mode 100644 index 2be88fd..0000000 --- a/UserService.DatabaseLayer/Repository/Repository.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using UserService.DatabaseLayer.DataModels; - -namespace UserService.DatabaseLayer.Repository -{ - public class OrganizationUnitsRepository : BaseRepository, IOrganizationUnitsRepository - { - public OrganizationUnitsRepository() : base(x => x.OrganizationUnits) - { - } - - /// - public override async Task> GetAllAsync(CancellationToken token = default) - { - - - await using var db = new UserServiceDbContext(); - var result = new List(); - var rootOus = await _context(db) - .Include(x => x.Parent) - .ToListAsync(token); - - IEnumerable Rec(Node node) - { - if (!(node is OrganizationUnit organizationUnit)) yield break; - yield return organizationUnit; - foreach (var ouChild in rootOus.Where(x=>x.ParentId != null && x.ParentId == organizationUnit.Id)) - { - foreach (var unit in Rec(ouChild)) - { - yield return unit; - } - } - } - foreach (var ou in rootOus.Where(x=> x.ParentId is null)) - { - result.AddRange(Rec(ou)); - } - return result; - } - } - - public class SecurityGroupsRepository : BaseRepository, ISecurityGroupsRepository - { - public SecurityGroupsRepository() : base(x=> x.SecurityGroups) - { - } - } - - - public class UsersRepository : BaseRepository, IUsersRepository - { - public UsersRepository() : base(x => x.Users) - { - } - } - - public class NodesRepository : INodesRepository - { - public NodesRepository() - { - } - - public static async IAsyncEnumerable GetNodesAsync([EnumeratorCancellation] CancellationToken token = default) - { - await using var db = new UserServiceDbContext(); - await foreach (var note in db.OrganizationUnits.AsAsyncEnumerable().WithCancellation(token)) - { - yield return note; - } - await foreach (var node in db.SecurityGroups.AsAsyncEnumerable().WithCancellation(token)) - { - yield return node; - } - - await foreach (var node in db.Users.AsAsyncEnumerable().WithCancellation(token)) - { - yield return node; - } - } - - public async Task> GetAllAsync(CancellationToken token = default) - { - - var list = new List(); - await foreach (var node in GetNodesAsync(token)) - { - list.Add(node); - } - - return list; - } - - public Task GetAsync(Expression> predicate, CancellationToken token = default) - { - throw new NotImplementedException(); - } - - public Task AddAsync(Node entity, CancellationToken token = default) - { - throw new NotImplementedException(); - } - - public Task UpdateAsync(Node entity, CancellationToken token = default) - { - throw new NotImplementedException(); - } - - public Task DeleteAsync(Node entity, CancellationToken token = default) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/UserService.DatabaseLayer/Repository/SecurityGroupsRepository.cs b/UserService.DatabaseLayer/Repository/SecurityGroupsRepository.cs new file mode 100644 index 0000000..6948ce7 --- /dev/null +++ b/UserService.DatabaseLayer/Repository/SecurityGroupsRepository.cs @@ -0,0 +1,11 @@ +using UserService.Infrastructure.DataModels; + +namespace UserService.DatabaseLayer.Repository +{ + public class SecurityGroupsRepository : BaseRepository, ISecurityGroupsRepository + { + public SecurityGroupsRepository() : base(x => x.SecurityGroups) + { + } + } +} \ No newline at end of file diff --git a/UserService.DatabaseLayer/Repository/UsersRepository.cs b/UserService.DatabaseLayer/Repository/UsersRepository.cs new file mode 100644 index 0000000..cb60251 --- /dev/null +++ b/UserService.DatabaseLayer/Repository/UsersRepository.cs @@ -0,0 +1,11 @@ +using UserService.Infrastructure.DataModels; + +namespace UserService.DatabaseLayer.Repository +{ + public class UsersRepository : BaseRepository, IUsersRepository + { + public UsersRepository() : base(x => x.Users) + { + } + } +} \ No newline at end of file diff --git a/UserService.DatabaseLayer/UserService.DatabaseLayer.csproj b/UserService.DatabaseLayer/UserService.DatabaseLayer.csproj index c6062ce..6bd1a27 100644 --- a/UserService.DatabaseLayer/UserService.DatabaseLayer.csproj +++ b/UserService.DatabaseLayer/UserService.DatabaseLayer.csproj @@ -2,16 +2,24 @@ netcoreapp3.1 - 8 + latest enable - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/UserService.Infrastructure/DataModels/Member.cs b/UserService.Infrastructure/DataModels/Member.cs new file mode 100644 index 0000000..48be61a --- /dev/null +++ b/UserService.Infrastructure/DataModels/Member.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace UserService.Infrastructure.DataModels +{ + public abstract class Member : Node + { + public ICollection Members { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/UserService.Infrastructure/DataModels/Node.cs b/UserService.Infrastructure/DataModels/Node.cs new file mode 100644 index 0000000..6bbb3c0 --- /dev/null +++ b/UserService.Infrastructure/DataModels/Node.cs @@ -0,0 +1,24 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace UserService.Infrastructure.DataModels +{ + public abstract class Node : ICloneable + { + public int Id { get; set; } + [Required] public string CommonName { get; set; } = null!; + public string? Description { get; set; } + public ICollection Children { get; set; } = new List(); + public Node? Parent { get; set; } //Parent + public int? ParentId { get; set; } + + public override string ToString() => CommonName; + + public int Level => Parent?.Level + 1 ?? 0; + + /// + public virtual object Clone() => MemberwiseClone(); + } +} \ No newline at end of file diff --git a/UserService.Infrastructure/DataModels/OrganizationUnit.cs b/UserService.Infrastructure/DataModels/OrganizationUnit.cs new file mode 100644 index 0000000..405278a --- /dev/null +++ b/UserService.Infrastructure/DataModels/OrganizationUnit.cs @@ -0,0 +1,7 @@ +namespace UserService.Infrastructure.DataModels +{ + public class OrganizationUnit : Node + { + public Member? Manager { get; set; } + } +} \ No newline at end of file diff --git a/UserService.Infrastructure/DataModels/SecurityGroup.cs b/UserService.Infrastructure/DataModels/SecurityGroup.cs new file mode 100644 index 0000000..78d3a91 --- /dev/null +++ b/UserService.Infrastructure/DataModels/SecurityGroup.cs @@ -0,0 +1,6 @@ +namespace UserService.Infrastructure.DataModels +{ + public class SecurityGroup : Member + { + } +} \ No newline at end of file diff --git a/UserService.Infrastructure/DataModels/User.cs b/UserService.Infrastructure/DataModels/User.cs new file mode 100644 index 0000000..29307e0 --- /dev/null +++ b/UserService.Infrastructure/DataModels/User.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace UserService.Infrastructure.DataModels +{ + public class User : Member + { + public string? FirstName { get; set; } + public string? LastName { get; set; } + public bool IsActive { get; set; } + + [EmailAddress] + public string? EMail { get; set; } + + public string FullName => $"{FirstName} {LastName}"; + + public IEnumerable MemberOf { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/UserService.Infrastructure/DataModels/UserMember.cs b/UserService.Infrastructure/DataModels/UserMember.cs new file mode 100644 index 0000000..91d4f69 --- /dev/null +++ b/UserService.Infrastructure/DataModels/UserMember.cs @@ -0,0 +1,12 @@ +namespace UserService.Infrastructure.DataModels +{ + public class UserMember + { + public int MemberId { get; set; } + public Member? Member { get; set; } + + public int UserId { get; set; } + + public User? User { get; set; } + } +} \ No newline at end of file diff --git a/UserService.Infrastructure/NodeExtensions.cs b/UserService.Infrastructure/NodeExtensions.cs new file mode 100644 index 0000000..db0a73d --- /dev/null +++ b/UserService.Infrastructure/NodeExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UserService.Infrastructure.DataModels; + +namespace UserService.Infrastructure +{ + public static class NodeExtensions + { + public static void MapFields(this User user, Dictionary values) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + if (values == null) throw new ArgumentNullException(nameof(values)); + var properties = user.GetType().GetProperties(); + foreach (var keyValuePair in values) + { + var propertyInfo = properties.FirstOrDefault(x => x.Name == keyValuePair.Key); + if (propertyInfo == null) continue; + propertyInfo.SetValue(user, keyValuePair.Value); + } + } + } +} \ No newline at end of file diff --git a/UserService.Infrastructure/UserService.Infrastructure.csproj b/UserService.Infrastructure/UserService.Infrastructure.csproj new file mode 100644 index 0000000..9b7b3a1 --- /dev/null +++ b/UserService.Infrastructure/UserService.Infrastructure.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + latest + enable + + + + + + + diff --git a/UserService.Infrastructure/Validators.cs b/UserService.Infrastructure/Validators.cs new file mode 100644 index 0000000..11222cd --- /dev/null +++ b/UserService.Infrastructure/Validators.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using UserService.Infrastructure.DataModels; + +namespace UserService.Infrastructure +{ + public static class Validators + { + public static bool? ValidateEmail(string? mailAddress) + { + if (string.IsNullOrEmpty(mailAddress)) return null; + return new EmailAddressAttribute().IsValid(mailAddress); + } + + public static bool? ValidateCommonName(string? commonName, IEnumerable users) + { + if (string.IsNullOrEmpty(commonName)) return false; + return users.All(x => x.CommonName != commonName); + } + } +} diff --git a/UserService.Test/UnitTest1.cs b/UserService.Test/UnitTest1.cs index 10d9223..8fc8774 100644 --- a/UserService.Test/UnitTest1.cs +++ b/UserService.Test/UnitTest1.cs @@ -1,12 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using UserService.DatabaseLayer.DataModels; +using UserService.Infrastructure.DataModels; namespace UserService.Test { @@ -21,10 +20,10 @@ namespace UserService.Test public async Task Test1() { await using var db = new UserServiceDbContext(); - var user = await db.Users.FirstOrDefaultAsync(); - var secGroup = await db.SecurityGroups.FirstOrDefaultAsync(); - var ous = await db.OrganizationUnits.ToListAsync(); - var mo = await db.UserMembers.ToListAsync(); + var user = await db.Users.FirstOrDefaultAsync().ConfigureAwait(false); + var secGroup = await db.SecurityGroups.FirstOrDefaultAsync().ConfigureAwait(false); + var ous = await db.OrganizationUnits.ToListAsync().ConfigureAwait(false); + var mo = await db.UserMembers.ToListAsync().ConfigureAwait(false); var securityGroupsOfUser = user.GetSecurityGroups(); var usersOfSecurityGroup = secGroup.GetUsers(); @@ -40,12 +39,11 @@ namespace UserService.Test public async Task Test2() { await using var db = new UserServiceDbContext(); - var ous = await db.OrganizationUnits.ToListAsync(); + var ous = await db.OrganizationUnits.ToListAsync().ConfigureAwait(false); var sb = new StringBuilder(); NewMethod(ous, null, 0, ref sb); var result = sb.ToString(); Assert.Pass(); - } private static void NewMethod(IEnumerable ous, Node parent, int level, ref StringBuilder sb) diff --git a/UserService.Test/UserService.Test.csproj b/UserService.Test/UserService.Test.csproj index 022e46a..c20cbd9 100644 --- a/UserService.Test/UserService.Test.csproj +++ b/UserService.Test/UserService.Test.csproj @@ -1,15 +1,21 @@ - + netcoreapp3.1 false + + latest + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + diff --git a/UserService.db b/UserService.db index a58f715..ca2980f 100644 Binary files a/UserService.db and b/UserService.db differ diff --git a/UserService.sln b/UserService.sln index f9488a0..6153a9d 100644 --- a/UserService.sln +++ b/UserService.sln @@ -9,6 +9,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Test", "UserSer EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.DatabaseLayer", "UserService.DatabaseLayer\UserService.DatabaseLayer.csproj", "{4505C991-7E39-416F-94E5-D906DD0D90F9}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A8D411B0-CD71-4448-9D4F-12898D0CCDD6}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserService.Infrastructure", "UserService.Infrastructure\UserService.Infrastructure.csproj", "{586BD023-5C7B-4D9C-A17E-A0D5CEDADD20}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +34,10 @@ Global {4505C991-7E39-416F-94E5-D906DD0D90F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {4505C991-7E39-416F-94E5-D906DD0D90F9}.Release|Any CPU.ActiveCfg = Release|Any CPU {4505C991-7E39-416F-94E5-D906DD0D90F9}.Release|Any CPU.Build.0 = Release|Any CPU + {586BD023-5C7B-4D9C-A17E-A0D5CEDADD20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {586BD023-5C7B-4D9C-A17E-A0D5CEDADD20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {586BD023-5C7B-4D9C-A17E-A0D5CEDADD20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {586BD023-5C7B-4D9C-A17E-A0D5CEDADD20}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/UserService/App.razor b/UserService/App.razor index ece7489..9c5b0e0 100644 --- a/UserService/App.razor +++ b/UserService/App.razor @@ -1,5 +1,4 @@ - - + @@ -10,4 +9,4 @@ - + \ No newline at end of file diff --git a/UserService/Pages/Directory.razor b/UserService/Pages/Directory.razor index 4ba6e41..a7eed1e 100644 --- a/UserService/Pages/Directory.razor +++ b/UserService/Pages/Directory.razor @@ -1,11 +1,12 @@ @page "/directory" -@using UserService.DatabaseLayer.DataModels -@using UserService.DatabaseLayer.Repository -@inject IOrganizationUnitsRepository OuRepository +@inherits DirectoryBase -

TODO

- -@if (_organizationUnits == null) + + +

TODO

+
+
+@if (OrganizationUnits == null) {

Loading... @@ -13,17 +14,20 @@ } else { - - -} -@code { - private IReadOnlyList _organizationUnits; - private OrganizationUnit _selectedOu; - - protected override async Task OnInitializedAsync() - { - _organizationUnits = (await OuRepository.GetAllAsync().ConfigureAwait(false)).Where(x=> x.Parent is null).ToList(); - } - + + + + + + @context?.CommonName + + + + + + } \ No newline at end of file diff --git a/UserService/Pages/Directory.razor.cs b/UserService/Pages/Directory.razor.cs new file mode 100644 index 0000000..d7e6419 --- /dev/null +++ b/UserService/Pages/Directory.razor.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using UserService.DatabaseLayer.Repository; +using UserService.Infrastructure.DataModels; + +namespace UserService.Pages +{ + public class DirectoryBase : ComponentBase + { + private Node? _selectedNode; + public IReadOnlyList? OrganizationUnits { get; set; } + + public Node? SelectedNode + { + get => _selectedNode; + set + { + if(Equals(_selectedNode, value)) return; + _selectedNode = value; + OnSelectedNodeChanged(value); + } + } + + private async void OnSelectedNodeChanged(Node? value) + { + if (value == null) return; + Members = await UsersRepository.GetAllAsync(x => x.ParentId == value.Id).ConfigureAwait(false); + } + + public IReadOnlyList? Members { get; set; } + + [Inject] public IOrganizationUnitsRepository OuRepository { get; set; } = null!; + + [Inject] public IUsersRepository UsersRepository { get; set; } = null!; + + + protected override async Task OnInitializedAsync() + { + OrganizationUnits = (await OuRepository.GetAllAsync().ConfigureAwait(false)) + .Where(x => x.Parent is null) + .ToList(); + } + + } +} \ No newline at end of file diff --git a/UserService/Pages/MembersBase.cs b/UserService/Pages/MembersBase.cs new file mode 100644 index 0000000..fddd685 --- /dev/null +++ b/UserService/Pages/MembersBase.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blazorise; +using Blazorise.DataGrid; +using Microsoft.AspNetCore.Components; +using UserService.Infrastructure; +using UserService.Infrastructure.DataModels; + +namespace UserService.Pages +{ + public abstract class MembersBase : ComponentBase where T : Member + { + protected IReadOnlyList? OrganizationUnits { get; set; } + + protected IReadOnlyList? Members { get; set; } + + protected string? CustomFilterValue { get; set; } + + protected abstract Task RowInsertingCallback(CancellableRowChange> arg); + + protected abstract Task RowInsertedCallback(SavedRowItem> arg); + protected abstract Task RowDeletingCallback(CancellableRowChange arg); + protected abstract Task RowUpdatingCallback(CancellableRowChange> arg); + protected abstract bool OnCustomFilter(T model); + + protected void ValidateCommonName(ValidatorEventArgs e) + { + if (e == null) throw new ArgumentNullException(nameof(e)); + var commonName = e.Value?.ToString(); + var validationResult = Validators.ValidateCommonName(commonName, Members ?? Enumerable.Empty()); + e.Status = validationResult == false ? ValidationStatus.Error : ValidationStatus.Success; + } + } +} \ No newline at end of file diff --git a/UserService/Pages/OrganizationUnits.razor b/UserService/Pages/OrganizationUnits.razor deleted file mode 100644 index c17c79e..0000000 --- a/UserService/Pages/OrganizationUnits.razor +++ /dev/null @@ -1,62 +0,0 @@ -@page "/organizationUnits" -@using UserService.DatabaseLayer.DataModels -@inherits OrganizationUnitsBase - -

List of all organization units

- -@if (OrganizationUnits is null) -{ -

- Loading... -

-} -else -{ - - - Common Name - Description - Parent - Manager - - - - - @context.CommonName - @context.Description - @context.Parent - @context.Manager - - edit - - - delete - - - - - Create organization unit -} - - - @if (OuToEdit != null) - { - @(OuToEdit.Id == 0 ? "New" : "Edit") @OuToEdit.CommonName (@OuToEdit.Id) - - -

- - @**@ -

- - - } - else - { - No securityGroup selected - } - - No Thanks - OK - - \ No newline at end of file diff --git a/UserService/Pages/OrganizationUnits.razor.cs b/UserService/Pages/OrganizationUnits.razor.cs deleted file mode 100644 index 637a122..0000000 --- a/UserService/Pages/OrganizationUnits.razor.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; -using UserService.DatabaseLayer.DataModels; -using UserService.DatabaseLayer.Repository; - -namespace UserService.Pages -{ - public class OrganizationUnitsBase : ComponentBase - { - [Inject] private IOrganizationUnitsRepository OrganizationUnitsRepository { get; set; } = null!; - - protected bool DialogIsOpen { get; set; } - protected OrganizationUnit? OuToEdit { get; private set; } - - protected IReadOnlyList? OrganizationUnits { get; private set; } - - protected override async Task OnInitializedAsync() - { - OrganizationUnits = await OrganizationUnitsRepository.GetAllAsync().ConfigureAwait(false); - } - - protected void Edit(OrganizationUnit organizationUnit) - { - DialogIsOpen = true; - OuToEdit = organizationUnit; - } - - protected async Task OkClick() - { - if (OuToEdit is null) return; - await OrganizationUnitsRepository.UpdateAsync(OuToEdit).ConfigureAwait(false); - DialogIsOpen = false; - } - - protected async Task Delete(OrganizationUnit organizationUnit) - { - await OrganizationUnitsRepository.DeleteAsync(organizationUnit).ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/UserService/Pages/SecurityGroups.razor b/UserService/Pages/SecurityGroups.razor index fdbfba0..e02635f 100644 --- a/UserService/Pages/SecurityGroups.razor +++ b/UserService/Pages/SecurityGroups.razor @@ -1,59 +1,87 @@ @page "/securitygroups" -@using UserService.DatabaseLayer.DataModels -@using UserService.DatabaseLayer.Repository +@using UserService.Infrastructure.DataModels @inherits SecurityGroupsBase

Table of all security groups

-@if (SecurityGroups is null) +@if (Members is null) { -

- Loading... -

+

+ Loading... +

} else { - - - Common Name - Description - E-Mail - Parent - - - - - @context.CommonName - @context.Description - @context.EMail - @context.Parent - edit - delete + - - - Create new group -} + + + + + + + + + + + + + + + + + + + - - @if (SecurityGroupToEdit != null) - { - @(SecurityGroupToEdit.Id == 0 ? "New" : "Edit") @SecurityGroupToEdit.CommonName (@SecurityGroupToEdit.Id) - - -

- - -

- - - } - else - { - No securityGroup selected - } - - No Thanks - OK - - \ No newline at end of file + + + + + + + Please enter a valid common name! + + + + + + + + @{ + var name = ((SecurityGroup) context ).Parent?.CommonName ?? "-"; + @name + } + + + + + + +} \ No newline at end of file diff --git a/UserService/Pages/SecurityGroups.razor.cs b/UserService/Pages/SecurityGroups.razor.cs index b1269ba..4145a00 100644 --- a/UserService/Pages/SecurityGroups.razor.cs +++ b/UserService/Pages/SecurityGroups.razor.cs @@ -1,47 +1,49 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Blazorise.DataGrid; using Microsoft.AspNetCore.Components; -using UserService.DatabaseLayer.DataModels; using UserService.DatabaseLayer.Repository; +using UserService.Infrastructure.DataModels; namespace UserService.Pages { - public class SecurityGroupsBase : ComponentBase + public class SecurityGroupsBase : MembersBase { - [Inject] private ISecurityGroupsRepository? SecurityGroupsRepository { get; set; } - [Inject] private IOrganizationUnitsRepository? OrganizationUnitsRepository { get; set; } - - protected bool DialogIsOpen; - protected SecurityGroup? SecurityGroupToEdit; - - protected IReadOnlyList? SecurityGroups; - protected IReadOnlyList? OrganizationUnits; + [Inject] private ISecurityGroupsRepository SecurityGroupsRepository { get; set; } = null!; + [Inject] private IOrganizationUnitsRepository OrganizationUnitsRepository { get; set; } = null!; protected override async Task OnInitializedAsync() { - SecurityGroups = await SecurityGroupsRepository.GetAllAsync().ConfigureAwait(false); + Members = await SecurityGroupsRepository.GetAllAsync().ConfigureAwait(false); OrganizationUnits = await OrganizationUnitsRepository.GetAllAsync().ConfigureAwait(false); } - protected void EditSecurityGroup(SecurityGroup securityGroup) + /// + protected override async Task RowInsertingCallback(CancellableRowChange> arg) { - SecurityGroupToEdit = securityGroup; - DialogIsOpen = true; } - protected async Task OkClick() + /// + protected override async Task RowInsertedCallback(SavedRowItem> arg) { - if (!(SecurityGroupToEdit is null)) - { - await SecurityGroupsRepository.UpdateAsync(SecurityGroupToEdit).ConfigureAwait(false); - } - - DialogIsOpen = false; } - protected async Task DeleteSecurityGroup(SecurityGroup securityGroup) + /// + protected override async Task RowDeletingCallback(CancellableRowChange arg) { - await SecurityGroupsRepository.DeleteAsync(securityGroup).ConfigureAwait(false); + + + } + + /// + protected override async Task RowUpdatingCallback(CancellableRowChange> arg) + { + } + + /// + protected override bool OnCustomFilter(SecurityGroup model) + { + return true; } } } \ No newline at end of file diff --git a/UserService/Pages/Users.razor b/UserService/Pages/Users.razor index 6bc2fd3..625b638 100644 --- a/UserService/Pages/Users.razor +++ b/UserService/Pages/Users.razor @@ -1,10 +1,10 @@ @page "/users" -@using UserService.DatabaseLayer.DataModels +@using UserService.Infrastructure.DataModels @inherits UsersBase

List of all users

-@if (Users == null) +@if (Members == null) {

Loading... @@ -12,55 +12,92 @@ } else { - - - Common Name - Full name - Description - E-Mail - Parent - Is active - - - - - @context.CommonName - @context.FullName - @context.Description - @context.EMail - @context.Parent - @context.IsActive - edit - delete - - + - Create user -} + + + + + + + + + + + + + + + + + + + - - @if (UserToEdit != null) - { - @(UserToEdit.Id == 0 ? "New" : "Edit") @UserToEdit.CommonName (@UserToEdit.Id) - - - -

- - -

- - -

- - - } - else - { - No securityGroup selected - } - - No Thanks - OK - - \ No newline at end of file + + + + + + + Please enter a valid common name! + + + + + + + + + + + + + Please enter the email. + Email is ok. + Enter valid email! + + + + + + + + @{ + var name = ((User) context ).Parent?.CommonName ?? "-"; + @name + } + + + + + + + +} \ No newline at end of file diff --git a/UserService/Pages/Users.razor.cs b/UserService/Pages/Users.razor.cs index 8aac50e..ac0916b 100644 --- a/UserService/Pages/Users.razor.cs +++ b/UserService/Pages/Users.razor.cs @@ -1,44 +1,99 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +using System; using Microsoft.AspNetCore.Components; -using UserService.DatabaseLayer.DataModels; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blazorise; +using Blazorise.DataGrid; +using Microsoft.JSInterop; using UserService.DatabaseLayer.Repository; +using UserService.Infrastructure; +using UserService.Infrastructure.DataModels; namespace UserService.Pages { - public class UsersBase : ComponentBase + public class UsersBase : MembersBase { [Inject] private IUsersRepository UsersRepository { get; set; } = null!; [Inject] private IOrganizationUnitsRepository OrganizationUnitsRepository { get; set; } = null!; - protected bool DialogIsOpen { get; set; } - protected User? UserToEdit { get; private set; } - - protected IReadOnlyList? Users { get; private set; } - protected IReadOnlyList? OrganizationUnits { get; private set; } + [Inject] private IJSRuntime JsRuntime { get; set; } = null!; protected override async Task OnInitializedAsync() { - Users = await UsersRepository.GetAllAsync(); + Members = await UsersRepository.GetAllAsync().ConfigureAwait(false); OrganizationUnits = await OrganizationUnitsRepository.GetAllAsync().ConfigureAwait(false); } - protected void EditUser(User user) + protected override async Task RowInsertingCallback(CancellableRowChange> arg) { - DialogIsOpen = true; - UserToEdit = user; + if (arg is null) throw new ArgumentNullException(nameof(arg)); + var mailValidation = Validators.ValidateEmail(arg.Values[nameof(User.EMail)]?.ToString()); + var commonNameValidation = Validators.ValidateCommonName(arg.Values[nameof(User.CommonName)]?.ToString(), + Members ?? Enumerable.Empty()); + if (mailValidation == true && commonNameValidation == true) return; + + await JsRuntime.InvokeVoidAsync("alert", "User could not be added").ConfigureAwait(false); + arg.Cancel = true; } - protected async Task OkClick() + protected override async Task RowInsertedCallback(SavedRowItem> arg) { - if (UserToEdit is null) return; - await UsersRepository.UpdateAsync(UserToEdit).ConfigureAwait(false); - DialogIsOpen = false; + if (arg is null) throw new ArgumentNullException(nameof(arg)); + var user = arg.Item; + await UsersRepository.AddAsync(user).ConfigureAwait(false); + user.Parent = OrganizationUnits?.FirstOrDefault(x => x.Id == user.ParentId); } - protected async Task DeleteUser(User user) + protected override async Task RowDeletingCallback(CancellableRowChange arg) { - await UsersRepository.DeleteAsync(user).ConfigureAwait(false); + if (arg == null) throw new ArgumentNullException(nameof(arg)); + var confirmed = await JsRuntime.InvokeAsync("confirm", + $"You are about to delete the user {arg.Item.FullName}. Are you sure?").ConfigureAwait(false); + if (confirmed) + { + await UsersRepository.DeleteAsync(arg.Item).ConfigureAwait(false); + return; + } + + arg.Cancel = true; + } + + protected static void ValidateEmail(ValidatorEventArgs e) + { + if (e == null) throw new ArgumentNullException(nameof(e)); + var email = e.Value?.ToString(); + var validationResult = Validators.ValidateEmail(email); + + e.Status = validationResult switch + { + null => ValidationStatus.None, + false => ValidationStatus.Error, + _ => ValidationStatus.Success + }; + } + + protected override async Task RowUpdatingCallback(CancellableRowChange> arg) + { + if (arg == null) throw new ArgumentNullException(nameof(arg)); + var user = arg.Item; + user.MapFields(arg.Values); + user.Parent = OrganizationUnits?.FirstOrDefault(x => x.Id == (int?) arg.Values[nameof(Node.ParentId)]); + var result = await UsersRepository.UpdateAsync(user).ConfigureAwait(false); + arg.Cancel = !result; + } + + protected override bool OnCustomFilter(User model) + { + if (model == null) throw new ArgumentNullException(nameof(model)); + // We want to accept empty value as valid or otherwise + // datagrid will not show anything. + if (string.IsNullOrEmpty(CustomFilterValue) || CustomFilterValue.Length < 3) return true; + + return + model.FirstName?.Contains(CustomFilterValue, StringComparison.OrdinalIgnoreCase) == true + || model.LastName?.Contains(CustomFilterValue, StringComparison.OrdinalIgnoreCase) == true + || model.CommonName.Contains(CustomFilterValue, StringComparison.OrdinalIgnoreCase); } } } \ No newline at end of file diff --git a/UserService/Pages/_Host.cshtml b/UserService/Pages/_Host.cshtml index 8b3f80a..b5f61a9 100644 --- a/UserService/Pages/_Host.cshtml +++ b/UserService/Pages/_Host.cshtml @@ -8,33 +8,41 @@ - - + + UserService - + + - - - + + + + + + + - - - + + + -

- - An error has occurred. This application may no longer respond until reloaded. - - - An unhandled exception has occurred. See browser dev tools for details. - - Reload - 🗙 -
- - - - +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + + + + \ No newline at end of file diff --git a/UserService/Shared/NavMenu.razor b/UserService/Shared/NavMenu.razor index d1a68c0..3fb8567 100644 --- a/UserService/Shared/NavMenu.razor +++ b/UserService/Shared/NavMenu.razor @@ -1,49 +1,38 @@ - - - -
- -
+ @code { - private bool _collapseNavMenu = true; + private SidebarInfo _sidebarInfo; - private string NavMenuCssClass => _collapseNavMenu ? "collapse" : null; - - private void ToggleNavMenu() + protected override void OnInitialized() { - _collapseNavMenu = !_collapseNavMenu; + _sidebarInfo = new SidebarInfo + { + Brand = new SidebarBrandInfo + { + Text = "User service", + To = "/" + }, + Items = new List + { + new SidebarItemInfo + { + Text = "Directory", + Icon = IconName.Folder, + To = "/directory" + }, + new SidebarItemInfo + { + Text = "Users", + Icon = IconName.Users, + To = "/users" + }, + new SidebarItemInfo + { + Text = "Security groups", + Icon = IconName.Book, + To = "/securitygroups" + } + } + }; } - } \ No newline at end of file diff --git a/UserService/Shared/SurveyPrompt.razor b/UserService/Shared/SurveyPrompt.razor deleted file mode 100644 index e9706c8..0000000 --- a/UserService/Shared/SurveyPrompt.razor +++ /dev/null @@ -1,16 +0,0 @@ - - -@code { - // Demonstrates how a parent component can supply parameters - [Parameter] - public string Title { get; set; } -} diff --git a/UserService/Startup.cs b/UserService/Startup.cs index e43811c..8827107 100644 --- a/UserService/Startup.cs +++ b/UserService/Startup.cs @@ -1,4 +1,7 @@ -using System.Net.Http; +using System; +using Blazorise; +using Blazorise.Bootstrap; +using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -26,12 +29,19 @@ namespace UserService services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddScoped(); + services + .AddBlazorise(options => + { + options.ChangeTextOnKeyPress = true; // optional + }) + .AddBootstrapProviders() + .AddFontAwesomeIcons(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + if (app == null) throw new ArgumentNullException(nameof(app)); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -48,6 +58,10 @@ namespace UserService app.UseRouting(); + app.ApplicationServices + .UseBootstrapProviders() + .UseFontAwesomeIcons(); + app.UseEndpoints(endpoints => { endpoints.MapBlazorHub(); @@ -55,4 +69,4 @@ namespace UserService }); } } -} +} \ No newline at end of file diff --git a/UserService/UserService.csproj b/UserService/UserService.csproj index 1217d5d..c104d48 100644 --- a/UserService/UserService.csproj +++ b/UserService/UserService.csproj @@ -3,14 +3,28 @@ netcoreapp3.1 enable + latest - + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive +
diff --git a/UserService/_Imports.razor b/UserService/_Imports.razor index 2e8ba2a..8a87504 100644 --- a/UserService/_Imports.razor +++ b/UserService/_Imports.razor @@ -7,4 +7,7 @@ @using Microsoft.JSInterop @using UserService @using UserService.Shared -@using MatBlazor \ No newline at end of file +@using Blazorise +@using Blazorise.Sidebar +@using Blazorise.TreeView +@using Blazorise.DataGrid