diff --git a/src/ProjectManagementSystem.Api/Controllers/Admin/MembersController.cs b/src/ProjectManagementSystem.Api/Controllers/Admin/MembersController.cs
new file mode 100644
index 0000000..8b72277
--- /dev/null
+++ b/src/ProjectManagementSystem.Api/Controllers/Admin/MembersController.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using ProjectManagementSystem.Api.Exceptions;
+using ProjectManagementSystem.Api.Extensions;
+using ProjectManagementSystem.Api.Models.Admin.Members;
+using ProjectManagementSystem.Domain.Admin.Members;
+
+namespace ProjectManagementSystem.Api.Controllers.Admin
+{
+ [Authorize(Roles = "Admin")]
+ [ApiController]
+ [ProducesResponseType(401)]
+ public sealed class MembersController : ControllerBase
+ {
+ ///
+ /// Create member
+ ///
+ ///
+ /// Input model
+ [HttpPost("admin/users/{id}/members")]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(typeof(ProblemDetails), 409)]
+ public async Task Create(
+ CancellationToken cancellationToken,
+ [FromRoute] Guid id,
+ [FromBody] CreateMemberBinding binding,
+ [FromServices] IUserRepository userRepository)
+ {
+ var user = await userRepository.Get(id, cancellationToken);
+
+ user.AddMember(new Member(binding.Id, id, binding.ProjectId, binding.RoleId));
+
+ await userRepository.Save(user);
+
+ return CreatedAtRoute("GetMemberAdminRoute", new {id = binding.Id}, null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Api/Controllers/Admin/RolesController.cs b/src/ProjectManagementSystem.Api/Controllers/Admin/RolesController.cs
new file mode 100644
index 0000000..bb57ccf
--- /dev/null
+++ b/src/ProjectManagementSystem.Api/Controllers/Admin/RolesController.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using ProjectManagementSystem.Api.Exceptions;
+using ProjectManagementSystem.Api.Models.Admin.Roles;
+using ProjectManagementSystem.Domain.Admin.Roles;
+using ProjectManagementSystem.Queries.Admin.Roles;
+
+namespace ProjectManagementSystem.Api.Controllers.Admin
+{
+ [Authorize(Roles = "Admin")]
+ [ApiController]
+ [ProducesResponseType(401)]
+ public sealed class RolesController : ControllerBase
+ {
+ ///
+ /// Create project
+ ///
+ /// Input model
+ [HttpPost("admin/roles")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(typeof(ProblemDetails), 409)]
+ public async Task Create(
+ CancellationToken cancellationToken,
+ [FromBody] CreateRoleBinding binding,
+ [FromServices] IRoleRepository roleRepository,
+ [FromServices] IPermissionRepository permissionRepository)
+ {
+ var role = await roleRepository.Get(binding.Id, cancellationToken);
+
+ if (role != null)
+ if (!role.Name.Equals(binding.Name))
+ throw new ApiException(HttpStatusCode.Conflict, ErrorCode.RoleAlreadyExists,
+ "Role already exists with other parameters");
+
+ role = new Role(binding.Id, binding.Name);
+
+ foreach (var permissionId in binding.Permissions)
+ {
+ var permission = await permissionRepository.Get(permissionId, cancellationToken);
+
+ if (permission == null)
+ throw new ApiException(HttpStatusCode.NotFound, ErrorCode.PermissionNotFound, "Permission not found");
+
+ var rolePermission = new RolePermission(binding.Id, permission.Id);
+
+ role.AddRolePermission(rolePermission);
+ }
+
+ await roleRepository.Save(role);
+
+ return CreatedAtRoute("GetRoleAdminRoute", new {id = role.Id}, null);
+ }
+
+ ///
+ /// Find roles
+ ///
+ /// Input model
+ [HttpGet("admin/roles", Name = "FindRolesAdminRoute")]
+ [ProducesResponseType(typeof(RoleListItemView), 200)]
+ public async Task Find(
+ CancellationToken cancellationToken,
+ [FromQuery] FindRolesBinding binding,
+ [FromServices] IMediator mediator)
+ {
+ return Ok(await mediator.Send(new RoleListQuery(binding.Offset, binding.Limit), cancellationToken));
+ }
+
+ ///
+ /// Get the role
+ ///
+ /// Role identifier
+ [HttpGet("admin/roles/{id}", Name = "GetRoleAdminRoute")]
+ [ProducesResponseType(typeof(RoleView), 200)]
+ [ProducesResponseType(typeof(ProblemDetails), 404)]
+ public async Task Get(
+ CancellationToken cancellationToken,
+ [FromRoute] Guid id,
+ [FromServices] IMediator mediator)
+ {
+ var role = await mediator.Send(new RoleQuery(id), cancellationToken);
+
+ if (role == null)
+ throw new ApiException(HttpStatusCode.NotFound, ErrorCode.RoleNotFound, "Role not found");
+
+ return Ok(role);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Api/Exceptions/ErrorCode.cs b/src/ProjectManagementSystem.Api/Exceptions/ErrorCode.cs
index 9521683..02b168f 100644
--- a/src/ProjectManagementSystem.Api/Exceptions/ErrorCode.cs
+++ b/src/ProjectManagementSystem.Api/Exceptions/ErrorCode.cs
@@ -28,5 +28,8 @@ public sealed class ErrorCode
public const string TimeEntryActivityAlreadyExists = "time_entry_activity_already_exists";
public const string TimeEntryNotFound = "time_entry_not_found";
public const string TimeEntryAlreadyExists = "time_entry_already_exists";
+ public const string PermissionNotFound = "permission_not_found";
+ public const string RoleNotFound = "role_not_found";
+ public const string RoleAlreadyExists = "role_activity_already_exists";
}
}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Api/Models/Admin/Members/CreateMemberBinding.cs b/src/ProjectManagementSystem.Api/Models/Admin/Members/CreateMemberBinding.cs
new file mode 100644
index 0000000..5286e6e
--- /dev/null
+++ b/src/ProjectManagementSystem.Api/Models/Admin/Members/CreateMemberBinding.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using FluentValidation;
+
+namespace ProjectManagementSystem.Api.Models.Admin.Members
+{
+ public sealed class CreateMembersBinding
+ {
+ ///
+ ///
+ ///
+ public IEnumerable Members { get; set; }
+ }
+
+ public sealed class CreateMembersBindingValidator : AbstractValidator
+ {
+ public CreateMembersBindingValidator()
+ {
+ RuleForEach(b => b.Members)
+ .NotEmpty();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Api/Models/Admin/Members/CreateProjectRoleBinding.cs b/src/ProjectManagementSystem.Api/Models/Admin/Members/CreateProjectRoleBinding.cs
new file mode 100644
index 0000000..4d4fad5
--- /dev/null
+++ b/src/ProjectManagementSystem.Api/Models/Admin/Members/CreateProjectRoleBinding.cs
@@ -0,0 +1,36 @@
+using System;
+using FluentValidation;
+
+namespace ProjectManagementSystem.Api.Models.Admin.Members
+{
+ public sealed class CreateMemberBinding
+ {
+ ///
+ /// Member identifier
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// Project identifier
+ ///
+ public Guid ProjectId { get; set; }
+
+ ///
+ /// Role identifier
+ ///
+ public Guid RoleId { get; set; }
+ }
+
+ public sealed class CreateMemberBindingValidator : AbstractValidator
+ {
+ public CreateMemberBindingValidator()
+ {
+ RuleFor(b => b.Id)
+ .NotEmpty();
+ RuleFor(b => b.ProjectId)
+ .NotEmpty();
+ RuleFor(b => b.RoleId)
+ .NotEmpty();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Api/Models/Admin/Roles/CreateRoleBinding.cs b/src/ProjectManagementSystem.Api/Models/Admin/Roles/CreateRoleBinding.cs
new file mode 100644
index 0000000..f1106ed
--- /dev/null
+++ b/src/ProjectManagementSystem.Api/Models/Admin/Roles/CreateRoleBinding.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using FluentValidation;
+
+namespace ProjectManagementSystem.Api.Models.Admin.Roles
+{
+ public sealed class CreateRoleBinding
+ {
+ ///
+ ///
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ ///
+ ///
+ public string Name { get; set; }
+
+ ///
+ ///
+ ///
+ public IEnumerable Permissions { get; set; }
+ }
+
+ public sealed class CreateRoleBindingValidator : AbstractValidator
+ {
+ public CreateRoleBindingValidator()
+ {
+ RuleFor(b => b.Id)
+ .NotEmpty();
+ RuleFor(b => b.Name)
+ .NotEmpty();
+ RuleForEach(b => b.Permissions)
+ .NotEmpty();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Api/Models/Admin/Roles/FindProjectsBinding.cs b/src/ProjectManagementSystem.Api/Models/Admin/Roles/FindProjectsBinding.cs
new file mode 100644
index 0000000..420bf43
--- /dev/null
+++ b/src/ProjectManagementSystem.Api/Models/Admin/Roles/FindProjectsBinding.cs
@@ -0,0 +1,29 @@
+using FluentValidation;
+using ProjectManagementSystem.Api.Models.Admin.Projects;
+
+namespace ProjectManagementSystem.Api.Models.Admin.Roles
+{
+ public sealed class FindRolesBinding
+ {
+ ///
+ /// Offset
+ ///
+ public int Offset { get; set; } = 0;
+
+ ///
+ /// Limit
+ ///
+ public int Limit { get; set; } = 10;
+ }
+
+ public sealed class FindRolesBindingValidator : AbstractValidator
+ {
+ public FindRolesBindingValidator()
+ {
+ RuleFor(b => b.Offset)
+ .GreaterThanOrEqualTo(0);
+ RuleFor(b => b.Limit)
+ .InclusiveBetween(2, 1000);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Api/Startup.cs b/src/ProjectManagementSystem.Api/Startup.cs
index 30d82d0..8f8a2b0 100644
--- a/src/ProjectManagementSystem.Api/Startup.cs
+++ b/src/ProjectManagementSystem.Api/Startup.cs
@@ -41,6 +41,7 @@ public void ConfigureServices(IServiceCollection services)
services
.AddMvc(options =>
{
+ options.Filters.Add(typeof(PermissionAuthorizationHandler));
options.Filters.Add(typeof(ErrorHandlingFilter));
options.EnableEndpointRouting = false;
})
diff --git a/src/ProjectManagementSystem.DatabaseMigrations/Entities/Member.cs b/src/ProjectManagementSystem.DatabaseMigrations/Entities/Member.cs
new file mode 100644
index 0000000..0d09b05
--- /dev/null
+++ b/src/ProjectManagementSystem.DatabaseMigrations/Entities/Member.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace ProjectManagementSystem.DatabaseMigrations.Entities
+{
+ public sealed class Member
+ {
+ public Guid UserId { get; set; }
+ public User User { get; set; }
+ public Guid ProjectId { get; set; }
+ public Project Project { get; set; }
+ public Guid RoleId { get; set; }
+ public Role Role { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.DatabaseMigrations/Entities/Permission.cs b/src/ProjectManagementSystem.DatabaseMigrations/Entities/Permission.cs
new file mode 100644
index 0000000..107b50d
--- /dev/null
+++ b/src/ProjectManagementSystem.DatabaseMigrations/Entities/Permission.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.DatabaseMigrations.Entities
+{
+ public sealed class Permission
+ {
+ public string PermissionId { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.DatabaseMigrations/Entities/Role.cs b/src/ProjectManagementSystem.DatabaseMigrations/Entities/Role.cs
new file mode 100644
index 0000000..4f38ce0
--- /dev/null
+++ b/src/ProjectManagementSystem.DatabaseMigrations/Entities/Role.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace ProjectManagementSystem.DatabaseMigrations.Entities
+{
+ public sealed class Role
+ {
+ public Guid RoleId { get; set; }
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.DatabaseMigrations/Entities/RolePermission.cs b/src/ProjectManagementSystem.DatabaseMigrations/Entities/RolePermission.cs
new file mode 100644
index 0000000..6aeaab9
--- /dev/null
+++ b/src/ProjectManagementSystem.DatabaseMigrations/Entities/RolePermission.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace ProjectManagementSystem.DatabaseMigrations.Entities
+{
+ public sealed class RolePermission
+ {
+ public Guid RoleId { get; set; }
+ public Role Role { get; set; }
+ public Guid PermissionId { get; set; }
+ public Permission Permission { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.DatabaseMigrations/ProjectManagementSystemDbContext.cs b/src/ProjectManagementSystem.DatabaseMigrations/ProjectManagementSystemDbContext.cs
index c541e7b..ebe508f 100644
--- a/src/ProjectManagementSystem.DatabaseMigrations/ProjectManagementSystemDbContext.cs
+++ b/src/ProjectManagementSystem.DatabaseMigrations/ProjectManagementSystemDbContext.cs
@@ -45,7 +45,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.IsUnique();
builder.HasIndex(u => u.Email)
.IsUnique();
-
+
builder.HasData(new User
{
UserId = new Guid("0ae12bbd-58ef-4c2e-87a6-2c2cb3f9592d"),
@@ -60,7 +60,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
ConcurrencyStamp = Guid.NewGuid()
});
});
-
+
modelBuilder.Entity(builder =>
{
builder.ToTable("RefreshToken");
@@ -72,7 +72,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
builder.Property(rt => rt.UserId)
.IsRequired();
});
-
+
modelBuilder.Entity(builder =>
{
builder.ToTable("IssuePriority");
@@ -84,7 +84,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
builder.Property(ip => ip.IsActive)
.IsRequired();
});
-
+
modelBuilder.Entity(builder =>
{
builder.ToTable("IssueStatus");
@@ -96,7 +96,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
builder.Property(@is => @is.IsActive)
.IsRequired();
});
-
+
modelBuilder.Entity(builder =>
{
builder.ToTable("Project");
@@ -116,7 +116,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
builder.Property(u => u.ConcurrencyStamp)
.IsConcurrencyToken();
});
-
+
modelBuilder.Entity(builder =>
{
builder.ToTable("Tracker");
@@ -128,11 +128,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
builder.Property(t => t.ConcurrencyStamp)
.IsConcurrencyToken();
});
-
+
modelBuilder.Entity(builder =>
{
builder.ToTable("ProjectTracker");
- builder.HasKey(pt => new { pt.ProjectId, pt.TrackerId });
+ builder.HasKey(pt => new {pt.ProjectId, pt.TrackerId});
builder.HasOne(pt => pt.Project)
.WithMany()
.HasForeignKey(pt => pt.ProjectId)
@@ -142,7 +142,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasForeignKey(pt => pt.TrackerId)
.HasPrincipalKey(t => t.TrackerId);
});
-
+
modelBuilder.Entity(builder =>
{
builder.ToTable("Issue");
@@ -196,7 +196,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasForeignKey(i => i.AssigneeId)
.HasPrincipalKey(p => p.UserId);
});
-
+
modelBuilder.Entity(builder =>
{
builder.ToTable("TimeEntryActivity");
@@ -210,7 +210,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
builder.Property(tea => tea.ConcurrencyStamp)
.IsConcurrencyToken();
});
-
+
modelBuilder.Entity(builder =>
{
builder.ToTable("TimeEntry");
@@ -253,6 +253,56 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasForeignKey(te => te.ActivityId)
.HasPrincipalKey(p => p.TimeEntryActivityId);
});
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Role");
+ builder.HasKey(r => r.RoleId);
+ builder.Property(r => r.RoleId)
+ .ValueGeneratedNever();
+ builder.Property(r => r.Name)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Permission");
+ builder.HasKey(p => p.PermissionId);
+ builder.Property(p => p.PermissionId)
+ .ValueGeneratedNever();
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("RolePermission");
+ builder.HasKey(rp => new {rp.RoleId, rp.PermissionId});
+ builder.HasOne(rp => rp.Role)
+ .WithMany()
+ .HasForeignKey(rp => rp.RoleId)
+ .HasPrincipalKey(r => r.RoleId);
+ builder.HasOne(rp => rp.Permission)
+ .WithMany()
+ .HasForeignKey(rp => rp.PermissionId)
+ .HasPrincipalKey(p => p.PermissionId);
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Member");
+ builder.HasKey(m => new {m.UserId, m.ProjectId, m.RoleId});
+ builder.HasOne(m => m.User)
+ .WithMany()
+ .HasForeignKey(m => m.UserId)
+ .HasPrincipalKey(u => u.UserId);
+ builder.HasOne(m => m.Project)
+ .WithMany()
+ .HasForeignKey(m => m.ProjectId)
+ .HasPrincipalKey(p => p.ProjectId);
+ builder.HasOne(m => m.Role)
+ .WithMany()
+ .HasForeignKey(m => m.RoleId)
+ .HasPrincipalKey(r => r.RoleId);
+ });
}
}
}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Members/IMemberRepository.cs b/src/ProjectManagementSystem.Domain/Admin/Members/IMemberRepository.cs
new file mode 100644
index 0000000..3200b3f
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Members/IMemberRepository.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.Admin.Members
+{
+ public interface IMemberRepository
+ {
+ Task Get(Guid id, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Members/IProjectRepository.cs b/src/ProjectManagementSystem.Domain/Admin/Members/IProjectRepository.cs
new file mode 100644
index 0000000..076788a
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Members/IProjectRepository.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.Admin.Members
+{
+ public interface IProjectRepository
+ {
+ Task Get(Guid id, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Members/IRoleRepository.cs b/src/ProjectManagementSystem.Domain/Admin/Members/IRoleRepository.cs
new file mode 100644
index 0000000..3758276
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Members/IRoleRepository.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.Admin.Members
+{
+ public interface IRoleRepository
+ {
+ Task Get(Guid id, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Members/IUserRepository.cs b/src/ProjectManagementSystem.Domain/Admin/Members/IUserRepository.cs
new file mode 100644
index 0000000..ce46016
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Members/IUserRepository.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.Admin.Members
+{
+ public interface IUserRepository
+ {
+ Task Get(Guid id, CancellationToken cancellationToken);
+ Task Save(User user);
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Members/Member.cs b/src/ProjectManagementSystem.Domain/Admin/Members/Member.cs
new file mode 100644
index 0000000..370a557
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Members/Member.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace ProjectManagementSystem.Domain.Admin.Members
+{
+ public sealed class Member
+ {
+ public Guid MemberId { get; }
+ public Guid UserId { get; }
+ public Guid ProjectId { get; }
+ public Guid RoleId { get; }
+
+ public Member(Guid memberId, Guid userId, Guid projectId, Guid roleId)
+ {
+ MemberId = memberId;
+ UserId = userId;
+ ProjectId = projectId;
+ RoleId = roleId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Members/Project.cs b/src/ProjectManagementSystem.Domain/Admin/Members/Project.cs
new file mode 100644
index 0000000..a10fc8e
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Members/Project.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.Domain.Admin.Members
+{
+ public sealed class Project
+ {
+ public Guid Id { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Members/Role.cs b/src/ProjectManagementSystem.Domain/Admin/Members/Role.cs
new file mode 100644
index 0000000..56078a8
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Members/Role.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.Domain.Admin.Members
+{
+ public sealed class Role
+ {
+ public Guid Id { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Members/User.cs b/src/ProjectManagementSystem.Domain/Admin/Members/User.cs
new file mode 100644
index 0000000..1cd5f89
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Members/User.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+
+namespace ProjectManagementSystem.Domain.Admin.Members
+{
+ public sealed class User
+ {
+ public Guid Id { get; }
+ private List _members = new List();
+ public IEnumerable Members => _members;
+ private Guid _concurrencyStamp;
+
+ public void AddMember(Member member)
+ {
+ _members.Add(member);
+ _concurrencyStamp = Guid.NewGuid();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Roles/IPermissionRepository.cs b/src/ProjectManagementSystem.Domain/Admin/Roles/IPermissionRepository.cs
new file mode 100644
index 0000000..d46a793
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Roles/IPermissionRepository.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.Admin.Roles
+{
+ public interface IPermissionRepository
+ {
+ Task Get(string id, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Roles/IRoleRepository.cs b/src/ProjectManagementSystem.Domain/Admin/Roles/IRoleRepository.cs
new file mode 100644
index 0000000..bb586ff
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Roles/IRoleRepository.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.Admin.Roles
+{
+ public interface IRoleRepository
+ {
+ Task Get(Guid id, CancellationToken cancellationToken);
+ Task Get(string name, CancellationToken cancellationToken);
+ Task Save(Role role);
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Roles/Permission.cs b/src/ProjectManagementSystem.Domain/Admin/Roles/Permission.cs
new file mode 100644
index 0000000..b47b986
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Roles/Permission.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.Domain.Admin.Roles
+{
+ public sealed class Permission
+ {
+ public string Id { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Roles/Role.cs b/src/ProjectManagementSystem.Domain/Admin/Roles/Role.cs
new file mode 100644
index 0000000..6e7d765
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Roles/Role.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+
+namespace ProjectManagementSystem.Domain.Admin.Roles
+{
+ public sealed class Role
+ {
+ public Guid Id { get; }
+ public string Name { get; private set; }
+ private List _rolePermissions = new List();
+ public IEnumerable RolePermissions => _rolePermissions;
+ private Guid _concurrencyStamp;
+
+ public Role(Guid id, string name)
+ {
+ Id = id;
+ Name = name;
+ _concurrencyStamp = Guid.NewGuid();
+ }
+
+ public void AddRolePermission(RolePermission rolePermission)
+ {
+ _rolePermissions.Add(rolePermission);
+ _concurrencyStamp = Guid.NewGuid();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Roles/RoleCreationService.cs b/src/ProjectManagementSystem.Domain/Admin/Roles/RoleCreationService.cs
new file mode 100644
index 0000000..0ae6a8a
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Roles/RoleCreationService.cs
@@ -0,0 +1,7 @@
+namespace ProjectManagementSystem.Domain.Admin.Roles
+{
+ public sealed class RoleCreationService
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/Admin/Roles/RolePermission.cs b/src/ProjectManagementSystem.Domain/Admin/Roles/RolePermission.cs
new file mode 100644
index 0000000..4ea5e9a
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/Admin/Roles/RolePermission.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace ProjectManagementSystem.Domain.Admin.Roles
+{
+ public sealed class RolePermission
+ {
+ public Guid RoleId { get; }
+ public string PermissionId { get; }
+
+ public RolePermission(Guid roleId, string permissionId)
+ {
+ RoleId = roleId;
+ PermissionId = permissionId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/IMemberRepository.cs b/src/ProjectManagementSystem.Domain/User/Members/IMemberRepository.cs
new file mode 100644
index 0000000..1007151
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/IMemberRepository.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public interface IMemberRepository
+ {
+ Task Get(Guid userId, Guid projectId, Guid roleId, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/IPermissionRepository.cs b/src/ProjectManagementSystem.Domain/User/Members/IPermissionRepository.cs
new file mode 100644
index 0000000..e72aebc
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/IPermissionRepository.cs
@@ -0,0 +1,10 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public interface IPermissionRepository
+ {
+ Task Get(string id, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/IProjectRepository.cs b/src/ProjectManagementSystem.Domain/User/Members/IProjectRepository.cs
new file mode 100644
index 0000000..d2f5987
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/IProjectRepository.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public interface IProjectRepository
+ {
+ Task Get(Guid id, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/IUserRepository.cs b/src/ProjectManagementSystem.Domain/User/Members/IUserRepository.cs
new file mode 100644
index 0000000..189bc25
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/IUserRepository.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public interface IUserRepository
+ {
+ Task Get(Guid id, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/Member.cs b/src/ProjectManagementSystem.Domain/User/Members/Member.cs
new file mode 100644
index 0000000..6d98694
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/Member.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public sealed class Member
+ {
+ public Guid UserId { get; set; }
+ public User User { get; set; }
+ public Guid ProjectId { get; set; }
+ public Project Project { get; set; }
+ public Guid RoleId { get; set; }
+ public Role Role { get; set; }
+
+ public Member(Guid userId, Guid projectId, Guid roleId)
+ {
+ UserId = userId;
+ ProjectId = projectId;
+ RoleId = roleId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/Permission.cs b/src/ProjectManagementSystem.Domain/User/Members/Permission.cs
new file mode 100644
index 0000000..2855b0c
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/Permission.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public sealed class Permission
+ {
+ public string Id { get; }
+ private List _rolePermissions = new List();
+ public IEnumerable RolePermissions => _rolePermissions;
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/Project.cs b/src/ProjectManagementSystem.Domain/User/Members/Project.cs
new file mode 100644
index 0000000..cbe96a9
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/Project.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public sealed class Project
+ {
+ public Guid Id { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/Role.cs b/src/ProjectManagementSystem.Domain/User/Members/Role.cs
new file mode 100644
index 0000000..9bc9126
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/Role.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public sealed class Role
+ {
+ public Guid Id { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/RolePermission.cs b/src/ProjectManagementSystem.Domain/User/Members/RolePermission.cs
new file mode 100644
index 0000000..65a8356
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/RolePermission.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public sealed class RolePermission
+ {
+ public Guid RoleId { get; }
+ public string PermissionId { get; }
+
+ public RolePermission(Guid roleId, string permissionId)
+ {
+ RoleId = roleId;
+ PermissionId = permissionId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/User.cs b/src/ProjectManagementSystem.Domain/User/Members/User.cs
new file mode 100644
index 0000000..5ee0bbb
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/User.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public sealed class User
+ {
+ public Guid Id { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Domain/User/Members/UserAuthorizationService.cs b/src/ProjectManagementSystem.Domain/User/Members/UserAuthorizationService.cs
new file mode 100644
index 0000000..a20ae4f
--- /dev/null
+++ b/src/ProjectManagementSystem.Domain/User/Members/UserAuthorizationService.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProjectManagementSystem.Domain.User.Members
+{
+ public class UserAuthorizationService
+ {
+ private readonly IUserRepository _userRepository;
+ private readonly IProjectRepository _projectRepository;
+ private readonly IPermissionRepository _permissionRepository;
+ private readonly IMemberRepository _memberRepository;
+
+ public UserAuthorizationService(IUserRepository userRepository, IProjectRepository projectRepository,
+ IPermissionRepository permissionRepository, IMemberRepository memberRepository)
+ {
+ _userRepository = userRepository;
+ _projectRepository = projectRepository;
+ _permissionRepository = permissionRepository;
+ _memberRepository = memberRepository;
+ }
+
+ public async Task Authorization(Guid userId, Guid projectId, string permissionName,
+ CancellationToken cancellationToken)
+ {
+ var user = await _userRepository.Get(userId, cancellationToken);
+
+ if (user == null)
+ throw new Exception();
+
+ var project = await _projectRepository.Get(projectId, cancellationToken);
+
+ if (project == null)
+ throw new Exception();
+
+ var permission = await _permissionRepository.Get(permissionName, cancellationToken);
+
+ if (permission == null)
+ throw new Exception();
+
+ foreach (var rolePermission in permission.RolePermissions)
+ {
+ if (await _memberRepository.Get(userId, projectId, rolePermission.RoleId, cancellationToken) != null)
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Infrastructure/Admin/Members/UserDbContext.cs b/src/ProjectManagementSystem.Infrastructure/Admin/Members/UserDbContext.cs
new file mode 100644
index 0000000..ddc4e6b
--- /dev/null
+++ b/src/ProjectManagementSystem.Infrastructure/Admin/Members/UserDbContext.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace ProjectManagementSystem.Infrastructure.Admin.Members
+{
+ public sealed class UserDbContext : DbContext
+ {
+ public UserDbContext(DbContextOptions options) : base(options) { }
+
+ internal DbSet Users { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("User");
+ builder.HasKey(u => u.Id);
+ builder.Property(u => u.Id)
+ .HasColumnName("UserId")
+ .ValueGeneratedNever();
+ builder.Property("_concurrencyStamp")
+ .HasColumnName("ConcurrencyStamp")
+ .IsConcurrencyToken();
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Infrastructure/Admin/Members/UserRepository.cs b/src/ProjectManagementSystem.Infrastructure/Admin/Members/UserRepository.cs
new file mode 100644
index 0000000..4844189
--- /dev/null
+++ b/src/ProjectManagementSystem.Infrastructure/Admin/Members/UserRepository.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using ProjectManagementSystem.Domain.Admin.Members;
+
+namespace ProjectManagementSystem.Infrastructure.Admin.Members
+{
+ public sealed class UserRepository : IUserRepository
+ {
+ private readonly UserDbContext _context;
+
+ public UserRepository(UserDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Get(Guid userId, CancellationToken cancellationToken)
+ {
+ return await _context.Users
+ .SingleOrDefaultAsync(u => u.Id == userId, cancellationToken);
+ }
+
+ public async Task Save(Domain.Admin.Members.User user)
+ {
+ if (_context.Entry(user).State == EntityState.Detached)
+ await _context.Users.AddAsync(user);
+
+ await _context.SaveChangesAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Infrastructure/User/Members/MemberDbContext.cs b/src/ProjectManagementSystem.Infrastructure/User/Members/MemberDbContext.cs
new file mode 100644
index 0000000..f07988b
--- /dev/null
+++ b/src/ProjectManagementSystem.Infrastructure/User/Members/MemberDbContext.cs
@@ -0,0 +1,70 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace ProjectManagementSystem.Infrastructure.User.Members
+{
+ public sealed class MemberDbContext : DbContext
+ {
+ public MemberDbContext(DbContextOptions options) : base(options) { }
+
+ internal DbSet Users { get; set; }
+ internal DbSet Projects { get; set; }
+ internal DbSet Permissions { get; set; }
+ internal DbSet Members { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("User");
+ builder.HasKey(u => u.Id);
+ builder.Property(u => u.Id)
+ .HasColumnName("UserId");
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Project");
+ builder.HasKey(p => p.Id);
+ builder.Property(p => p.Id)
+ .HasColumnName("ProjectId");
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("RolePermission");
+ builder.HasKey(rp => new {rp.RoleId, rp.PermissionId});
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Permission");
+ builder.HasKey(p => p.Id);
+ builder.Property(p => p.Id)
+ .HasColumnName("PermissionId");
+ builder.HasMany(p => p.RolePermissions)
+ .WithOne()
+ .HasForeignKey(rp => rp.PermissionId)
+ .HasPrincipalKey(p => p.Id);
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Member");
+ builder.HasOne(m => m.User)
+ .WithMany()
+ .HasForeignKey(m => m.UserId)
+ .HasPrincipalKey(u => u.Id);
+ builder.HasOne(m => m.Project)
+ .WithMany()
+ .HasForeignKey(m => m.ProjectId)
+ .HasPrincipalKey(p => p.Id);
+ builder.HasOne(m => m.Role)
+ .WithMany()
+ .HasForeignKey(m => m.RoleId)
+ .HasPrincipalKey(r => r.Id);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Infrastructure/User/Members/MemberRepository.cs b/src/ProjectManagementSystem.Infrastructure/User/Members/MemberRepository.cs
new file mode 100644
index 0000000..eda9759
--- /dev/null
+++ b/src/ProjectManagementSystem.Infrastructure/User/Members/MemberRepository.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using ProjectManagementSystem.Domain.User.Members;
+
+namespace ProjectManagementSystem.Infrastructure.User.Members
+{
+ public sealed class MemberRepository : IMemberRepository
+ {
+ private readonly MemberDbContext _context;
+
+ public MemberRepository(MemberDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Get(Guid userId, Guid projectId, Guid roleId, CancellationToken cancellationToken)
+ {
+ return await _context.Members.SingleOrDefaultAsync(m =>
+ m.UserId == userId &&
+ m.ProjectId == projectId &&
+ m.RoleId == roleId, cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Infrastructure/User/Members/PermissionRepository.cs b/src/ProjectManagementSystem.Infrastructure/User/Members/PermissionRepository.cs
new file mode 100644
index 0000000..a1c5d1b
--- /dev/null
+++ b/src/ProjectManagementSystem.Infrastructure/User/Members/PermissionRepository.cs
@@ -0,0 +1,22 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using ProjectManagementSystem.Domain.User.Members;
+
+namespace ProjectManagementSystem.Infrastructure.User.Members
+{
+ public sealed class PermissionRepository : IPermissionRepository
+ {
+ private readonly MemberDbContext _context;
+
+ public PermissionRepository(MemberDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Get(string id, CancellationToken cancellationToken)
+ {
+ return await _context.Permissions.SingleOrDefaultAsync(p => p.Id == id, cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Infrastructure/User/Members/ProjectRepository.cs b/src/ProjectManagementSystem.Infrastructure/User/Members/ProjectRepository.cs
new file mode 100644
index 0000000..f5cbea0
--- /dev/null
+++ b/src/ProjectManagementSystem.Infrastructure/User/Members/ProjectRepository.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using ProjectManagementSystem.Domain.User.Members;
+
+namespace ProjectManagementSystem.Infrastructure.User.Members
+{
+ public sealed class ProjectRepository : IProjectRepository
+ {
+ private readonly MemberDbContext _context;
+
+ public ProjectRepository(MemberDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Get(Guid id, CancellationToken cancellationToken)
+ {
+ return await _context.Projects.SingleOrDefaultAsync(u => u.Id == id, cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Infrastructure/User/Members/UserRepository.cs b/src/ProjectManagementSystem.Infrastructure/User/Members/UserRepository.cs
new file mode 100644
index 0000000..12dbd27
--- /dev/null
+++ b/src/ProjectManagementSystem.Infrastructure/User/Members/UserRepository.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using ProjectManagementSystem.Domain.User.Members;
+
+namespace ProjectManagementSystem.Infrastructure.User.Members
+{
+ public sealed class UserRepository : IUserRepository
+ {
+ private readonly MemberDbContext _context;
+
+ public UserRepository(MemberDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Get(Guid id, CancellationToken cancellationToken)
+ {
+ return await _context.Users.SingleOrDefaultAsync(u => u.Id == id, cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/Issue.cs b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/Issue.cs
new file mode 100644
index 0000000..b749646
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/Issue.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace ProjectManagementSystem.Queries.Infrastructure.User.Issues
+{
+ internal sealed class Issue
+ {
+ public Guid Id { get; }
+ public long Number { get; }
+ public string Title { get; }
+ public string Description { get; }
+ public DateTime CreateDate { get; }
+ public DateTime? UpdateDate { get; }
+ public DateTime? StartDate { get; }
+ public DateTime? DueDate { get; }
+ public Guid ProjectId { get; }
+ public Project Project { get; }
+ public Guid TrackerId { get; }
+ public Tracker Tracker { get; }
+ public Guid StatusId { get; }
+ public IssueStatus Status { get; }
+ public Guid PriorityId { get; }
+ public IssuePriority Priority { get; }
+ public Guid AuthorId { get; }
+ public User Author { get; }
+ public Guid? AssigneeId { get; }
+ public User? Assignee { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueDbContext.cs b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueDbContext.cs
new file mode 100644
index 0000000..36afde0
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueDbContext.cs
@@ -0,0 +1,103 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace ProjectManagementSystem.Queries.Infrastructure.User.Issues
+{
+ public sealed class IssueDbContext : DbContext
+ {
+ public IssueDbContext(DbContextOptions options) : base(options) { }
+
+ internal DbSet Issues { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Project");
+ builder.HasKey(p => p.Id);
+ builder.Property(p => p.Id)
+ .HasColumnName("ProjectId");
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Tracker");
+ builder.HasKey(t => t.Id);
+ builder.Property(t => t.Id)
+ .HasColumnName("TrackerId");
+ builder.Property(t => t.Name);
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("IssueStatus");
+ builder.HasKey(@is => @is.Id);
+ builder.Property(@is => @is.Id)
+ .HasColumnName("IssueStatusId");
+ builder.Property(@is => @is.Name);
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("IssuePriority");
+ builder.HasKey(ip => ip.Id);
+ builder.Property(ip => ip.Id)
+ .HasColumnName("IssuePriorityId");
+ builder.Property(ip => ip.Name);
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("User");
+ builder.HasKey(u => u.Id);
+ builder.Property(u => u.Id)
+ .HasColumnName("UserId");
+ builder.Property(u => u.Name);
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Issue");
+ builder.HasKey(i => i.Id);
+ builder.Property(i => i.Id)
+ .HasColumnName("IssueId");
+ builder.Property(i => i.Number);
+ builder.Property(i => i.Title);
+ builder.Property(i => i.Description);
+ builder.Property(i => i.CreateDate);
+ builder.Property(i => i.StartDate);
+ builder.Property(i => i.DueDate);
+ builder.Property(i => i.TrackerId);
+ builder.Property(i => i.StatusId);
+ builder.Property(i => i.PriorityId);
+ builder.Property(i => i.AuthorId);
+ builder.Property(i => i.AssigneeId);
+ builder.HasOne(i => i.Project)
+ .WithMany()
+ .HasForeignKey(i => i.ProjectId)
+ .HasPrincipalKey(p => p.Id);
+ builder.HasOne(i => i.Tracker)
+ .WithMany()
+ .HasForeignKey(i => i.TrackerId)
+ .HasPrincipalKey(t => t.Id);
+ builder.HasOne(i => i.Status)
+ .WithMany()
+ .HasForeignKey(i => i.StatusId)
+ .HasPrincipalKey(@is => @is.Id);
+ builder.HasOne(i => i.Priority)
+ .WithMany()
+ .HasForeignKey(i => i.PriorityId)
+ .HasPrincipalKey(ip => ip.Id);
+ builder.HasOne(i => i.Author)
+ .WithMany()
+ .HasForeignKey(i => i.AuthorId)
+ .HasPrincipalKey(a => a.Id);
+ builder.HasOne(i => i.Assignee)
+ .WithMany()
+ .HasForeignKey(i => i.AssigneeId)
+ .HasPrincipalKey(p => p.Id);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueListQueryHandler.cs b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueListQueryHandler.cs
new file mode 100644
index 0000000..4b982fc
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueListQueryHandler.cs
@@ -0,0 +1,45 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+using ProjectManagementSystem.Queries.User.Issues;
+
+namespace ProjectManagementSystem.Queries.Infrastructure.User.Issues
+{
+ public sealed class IssueListQueryHandler : IRequestHandler>
+ {
+ private readonly IssueDbContext _context;
+
+ public IssueListQueryHandler(IssueDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task> Handle(IssueListQuery query, CancellationToken cancellationToken)
+ {
+ var sql = _context.Issues.AsNoTracking()
+ .OrderBy(p => p.CreateDate)
+ .Select(i => new IssueListItemView
+ {
+ Id = i.Id,
+ Number = i.Number,
+ Title = i.Title,
+ TrackerName = i.Tracker.Name,
+ StatusName = i.Status.Name,
+ PriorityName = i.Priority.Name,
+ AssigneeName = i.Assignee.Name,
+ UpdateDate = i.UpdateDate ?? i.CreateDate,
+ })
+ .AsQueryable();
+
+ return new Page
+ {
+ Limit = query.Limit,
+ Offset = query.Offset,
+ Total = await sql.CountAsync(cancellationToken),
+ Items = await sql.Skip(query.Offset).Take(query.Limit).ToListAsync(cancellationToken)
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssuePriority.cs b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssuePriority.cs
new file mode 100644
index 0000000..13cb65c
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssuePriority.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace ProjectManagementSystem.Queries.Infrastructure.User.Issues
+{
+ internal sealed class IssuePriority
+ {
+ public Guid Id { get; }
+ public string Name { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueQueryHandler.cs b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueQueryHandler.cs
new file mode 100644
index 0000000..a4cfc2c
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueQueryHandler.cs
@@ -0,0 +1,49 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+using ProjectManagementSystem.Queries.User.Issues;
+
+namespace ProjectManagementSystem.Queries.Infrastructure.User.Issues
+{
+ public sealed class IssueQueryHandler : IRequestHandler
+ {
+ private readonly IssueDbContext _context;
+
+ public IssueQueryHandler(IssueDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Handle(IssueQuery query, CancellationToken cancellationToken)
+ {
+ return await _context.Issues
+ .Include(i => i.Project)
+ .Include(i => i.Tracker)
+ .Include(i => i.Status)
+ .Include(i => i.Priority)
+ .Include(i => i.Author)
+ .Include(i => i.Assignee)
+ .AsNoTracking()
+ .Where(i => i.Id == query.Id)
+ .Select(i => new IssueView
+ {
+ Id = i.Id,
+ Number = i.Number,
+ Title = i.Title,
+ Description = i.Description,
+ CreateDate = i.CreateDate,
+ UpdateDate = i.UpdateDate,
+ StartDate = i.StartDate,
+ DueDate = i.DueDate,
+ TrackerName = i.Tracker.Name,
+ StatusName = i.Status.Name,
+ PriorityName = i.Priority.Name,
+ AuthorName = i.Author.Name,
+ AssigneeName = i.Assignee.Name
+ })
+ .SingleOrDefaultAsync(cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueStatus.cs b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueStatus.cs
new file mode 100644
index 0000000..8136434
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/IssueStatus.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace ProjectManagementSystem.Queries.Infrastructure.User.Issues
+{
+ internal sealed class IssueStatus
+ {
+ public Guid Id { get; }
+ public string Name { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/Project.cs b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/Project.cs
new file mode 100644
index 0000000..0fbbfeb
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/Project.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.Queries.Infrastructure.User.Issues
+{
+ internal sealed class Project
+ {
+ public Guid Id { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/ProjectDbContext.cs b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/ProjectDbContext.cs
new file mode 100644
index 0000000..59272ee
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/ProjectDbContext.cs
@@ -0,0 +1,126 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace ProjectManagementSystem.Queries.Infrastructure.User.Issues
+{
+ public sealed class ProjectDbContext : DbContext
+ {
+ public ProjectDbContext(DbContextOptions options) : base(options) { }
+
+ internal DbSet Projects { get; set; }
+ internal DbSet Trackers { get; set; }
+ internal DbSet IssueStatuses { get; set; }
+ internal DbSet IssuePriorities { get; set; }
+ internal DbSet Users { get; set; }
+ internal DbSet Issues { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Project");
+ builder.HasKey(p => p.Id);
+ builder.Property(p => p.Id)
+ .HasColumnName("Id")
+ .ValueGeneratedNever();
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Tracker");
+ builder.HasKey(t => t.Id);
+ builder.Property(t => t.Id)
+ .HasColumnName("Id")
+ .ValueGeneratedNever();
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("IssueStatus");
+ builder.HasKey(@is => @is.Id);
+ builder.Property(@is => @is.Id)
+ .HasColumnName("Id")
+ .ValueGeneratedNever();
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("IssuePriority");
+ builder.HasKey(ip => ip.Id);
+ builder.Property(ip => ip.Id)
+ .HasColumnName("Id")
+ .ValueGeneratedNever();
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("User");
+ builder.HasKey(u => u.Id);
+ builder.Property(u => u.Id)
+ .HasColumnName("Id")
+ .ValueGeneratedNever();
+ });
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.ToTable("Issue");
+ builder.HasKey(i => i.Id);
+ builder.Property(i => i.Id)
+ .HasColumnName("Id")
+ .ValueGeneratedNever();
+ builder.Property(i => i.Title)
+ .HasColumnName("Title")
+ .IsRequired();
+ builder.Property(i => i.Description)
+ .HasColumnName("Description")
+ .IsRequired();
+ builder.Property(i => i.CreateDate)
+ .HasColumnName("CreateDate")
+ .IsRequired();
+ builder.Property(i => i.StartDate)
+ .HasColumnName("StartDate");
+ builder.Property(i => i.DueDate)
+ .HasColumnName("DueDate");
+ builder.Property(i => i.TrackerId)
+ .HasColumnName("TrackerId")
+ .IsRequired();
+ builder.Property(i => i.StatusId)
+ .HasColumnName("StatusId")
+ .IsRequired();
+ builder.Property(i => i.PriorityId)
+ .HasColumnName("PriorityId")
+ .IsRequired();
+ builder.Property(i => i.AuthorId)
+ .HasColumnName("AuthorId")
+ .IsRequired();
+ builder.Property(i => i.AssigneeId)
+ .HasColumnName("AssigneeId")
+ .IsRequired();
+ builder.Property("_concurrencyStamp")
+ .HasColumnName("ConcurrencyStamp")
+ .IsConcurrencyToken();
+ builder.HasOne(i => i.Tracker)
+ .WithMany()
+ .HasForeignKey(i => i.TrackerId)
+ .HasPrincipalKey(t => t.Id);
+ builder.HasOne(i => i.Status)
+ .WithMany()
+ .HasForeignKey(i => i.StatusId)
+ .HasPrincipalKey(@is => @is.Id);
+ builder.HasOne(i => i.Priority)
+ .WithMany()
+ .HasForeignKey(i => i.PriorityId)
+ .HasPrincipalKey(ip => ip.Id);
+ builder.HasOne(i => i.Author)
+ .WithMany()
+ .HasForeignKey(i => i.AuthorId)
+ .HasPrincipalKey(a => a.Id);
+ builder.HasOne(i => i.Assignee)
+ .WithMany()
+ .HasForeignKey(i => i.AssigneeId)
+ .HasPrincipalKey(p => p.Id);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/Tracker.cs b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/Tracker.cs
new file mode 100644
index 0000000..e4c0cd2
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/Tracker.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace ProjectManagementSystem.Queries.Infrastructure.User.Issues
+{
+ internal sealed class Tracker
+ {
+ public Guid Id { get; }
+ public string Name { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/User.cs b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/User.cs
new file mode 100644
index 0000000..984508f
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries.Infrastructure/User/Issues/User.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace ProjectManagementSystem.Queries.Infrastructure.User.Issues
+{
+ internal sealed class User
+ {
+ public Guid Id { get; }
+ public string Name { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries/Admin/Permissions/PermissionView.cs b/src/ProjectManagementSystem.Queries/Admin/Permissions/PermissionView.cs
new file mode 100644
index 0000000..f5361ae
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries/Admin/Permissions/PermissionView.cs
@@ -0,0 +1,7 @@
+namespace ProjectManagementSystem.Queries.Admin.Permissions
+{
+ public sealed class PermissionView
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries/Admin/Roles/RoleListItemView.cs b/src/ProjectManagementSystem.Queries/Admin/Roles/RoleListItemView.cs
new file mode 100644
index 0000000..175ad30
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries/Admin/Roles/RoleListItemView.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.Queries.Admin.Roles
+{
+ public sealed class RoleListItemView
+ {
+ public Guid Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries/Admin/Roles/RoleListQuery.cs b/src/ProjectManagementSystem.Queries/Admin/Roles/RoleListQuery.cs
new file mode 100644
index 0000000..b934245
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries/Admin/Roles/RoleListQuery.cs
@@ -0,0 +1,7 @@
+namespace ProjectManagementSystem.Queries.Admin.Roles
+{
+ public sealed class RoleListQuery : PageQuery
+ {
+ public RoleListQuery(int offset, int limit) : base(offset, limit) { }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries/Admin/Roles/RoleQuery.cs b/src/ProjectManagementSystem.Queries/Admin/Roles/RoleQuery.cs
new file mode 100644
index 0000000..672fc79
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries/Admin/Roles/RoleQuery.cs
@@ -0,0 +1,15 @@
+using System;
+using MediatR;
+
+namespace ProjectManagementSystem.Queries.Admin.Roles
+{
+ public sealed class RoleQuery : IRequest
+ {
+ public Guid Id { get; }
+
+ public RoleQuery(Guid id)
+ {
+ Id = id;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.Queries/Admin/Roles/RoleView.cs b/src/ProjectManagementSystem.Queries/Admin/Roles/RoleView.cs
new file mode 100644
index 0000000..e929240
--- /dev/null
+++ b/src/ProjectManagementSystem.Queries/Admin/Roles/RoleView.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.Queries.Admin.Roles
+{
+ public sealed class RoleView
+ {
+ public Guid Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.WebApi/Authorization/PermissionAuthorizationHandler.cs b/src/ProjectManagementSystem.WebApi/Authorization/PermissionAuthorizationHandler.cs
new file mode 100644
index 0000000..d2a492c
--- /dev/null
+++ b/src/ProjectManagementSystem.WebApi/Authorization/PermissionAuthorizationHandler.cs
@@ -0,0 +1,15 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+
+namespace ProjectManagementSystem.WebApi.Authorization
+{
+ public class PermissionAuthorizationHandler : AuthorizationHandler
+ {
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
+ {
+ //var a = context.HttpContext.Request.RouteValues.;
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.WebApi/Authorization/PermissionRequirement.cs b/src/ProjectManagementSystem.WebApi/Authorization/PermissionRequirement.cs
new file mode 100644
index 0000000..09d888b
--- /dev/null
+++ b/src/ProjectManagementSystem.WebApi/Authorization/PermissionRequirement.cs
@@ -0,0 +1,14 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace ProjectManagementSystem.WebApi.Authorization
+{
+ public sealed class PermissionRequirement : IAuthorizationRequirement
+ {
+ public string Permission { get; }
+
+ public PermissionRequirement(string permission)
+ {
+ Permission = permission;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.WebApi/Controllers/Admin/PermissionsController.cs b/src/ProjectManagementSystem.WebApi/Controllers/Admin/PermissionsController.cs
new file mode 100644
index 0000000..cd8dc13
--- /dev/null
+++ b/src/ProjectManagementSystem.WebApi/Controllers/Admin/PermissionsController.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace ProjectManagementSystem.WebApi.Controllers.Admin
+{
+ [ApiController]
+ public sealed class PermissionsController : ControllerBase
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.WebApi/Controllers/Admin/RolesController.cs b/src/ProjectManagementSystem.WebApi/Controllers/Admin/RolesController.cs
new file mode 100644
index 0000000..58d1e0d
--- /dev/null
+++ b/src/ProjectManagementSystem.WebApi/Controllers/Admin/RolesController.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using ProjectManagementSystem.Domain.Admin.CreateRoles;
+using ProjectManagementSystem.Queries.Admin.Roles;
+using ProjectManagementSystem.WebApi.Exceptions;
+using ProjectManagementSystem.WebApi.Models.Admin.Roles;
+
+namespace ProjectManagementSystem.WebApi.Controllers.Admin
+{
+ [Authorize(Roles = "Admin")]
+ [ApiController]
+ public class RolesController : ControllerBase
+ {
+ [HttpPost("admin/roles")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(typeof(ProblemDetails), 409)]
+ public async Task Create(
+ CancellationToken cancellationToken,
+ [FromBody] CreateRoleBindModel model,
+ [FromServices] IRoleRepository roleRepository,
+ [FromServices] IPermissionRepository permissionRepository)
+ {
+ var role = await roleRepository.Get(model.Id, cancellationToken);
+
+ if (role != null)
+ throw new ApiException(HttpStatusCode.Conflict, ErrorCode.RoleAlreadyExists,
+ "Role already exists with other parameters");
+
+ role = new Role(model.Id, model.Name);
+
+ foreach (var permissionModel in model.Permissions)
+ {
+ var permission = await permissionRepository.Get(permissionModel.Id, cancellationToken);
+
+ if (permission == null)
+ throw new ApiException(HttpStatusCode.NotFound, ErrorCode.TrackerNotFound, "Tracker not found");
+
+ var rolePermission = new RolePermission(model.Id, permission.Id);
+
+ role.AddRolePermission(rolePermission);
+ }
+
+ await roleRepository.Save(role);
+
+ return CreatedAtRoute("GetRoleAdminRoute", new {id = role.Id}, null);
+ }
+
+ [HttpGet("admin/roles")]
+ [ProducesResponseType(typeof(RoleListViewModel), 200)]
+ public async Task Find(
+ CancellationToken cancellationToken,
+ [FromQuery] QueryRoleBindModel model,
+ [FromServices] IMediator mediator)
+ {
+ return Ok(await mediator.Send(new RoleListQuery(model.Offset, model.Limit), cancellationToken));
+ }
+
+ [HttpGet("admin/roles/{id}", Name = "GetRoleAdminRoute")]
+ [ProducesResponseType(typeof(RoleViewModel), 200)]
+ [ProducesResponseType(typeof(ProblemDetails), 404)]
+ public async Task Get(
+ CancellationToken cancellationToken,
+ [FromRoute] Guid id,
+ [FromServices] IMediator mediator)
+ {
+ var role = await mediator.Send(new RoleQuery(id), cancellationToken);
+
+ if (role == null)
+ throw new ApiException(HttpStatusCode.NotFound, ErrorCode.IssueStatusNotFound,
+ "Issue status not found");
+
+ return Ok(role);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.WebApi/Models/Admin/Roles/AddPermissionBindModel.cs b/src/ProjectManagementSystem.WebApi/Models/Admin/Roles/AddPermissionBindModel.cs
new file mode 100644
index 0000000..017ccc7
--- /dev/null
+++ b/src/ProjectManagementSystem.WebApi/Models/Admin/Roles/AddPermissionBindModel.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ProjectManagementSystem.WebApi.Models.Admin.Roles
+{
+ public sealed class AddPermissionBindModel
+ {
+ public Guid Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.WebApi/Models/Admin/Roles/CreateRoleBindModel.cs b/src/ProjectManagementSystem.WebApi/Models/Admin/Roles/CreateRoleBindModel.cs
new file mode 100644
index 0000000..d98f122
--- /dev/null
+++ b/src/ProjectManagementSystem.WebApi/Models/Admin/Roles/CreateRoleBindModel.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+
+namespace ProjectManagementSystem.WebApi.Models.Admin.Roles
+{
+ public sealed class CreateRoleBindModel
+ {
+ public Guid Id { get; private set; }
+ public string Name { get; private set; }
+ public IEnumerable Permissions { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectManagementSystem.WebApi/Models/Admin/Roles/QueryRoleBindModel.cs b/src/ProjectManagementSystem.WebApi/Models/Admin/Roles/QueryRoleBindModel.cs
new file mode 100644
index 0000000..e7203f6
--- /dev/null
+++ b/src/ProjectManagementSystem.WebApi/Models/Admin/Roles/QueryRoleBindModel.cs
@@ -0,0 +1,7 @@
+namespace ProjectManagementSystem.WebApi.Models.Admin.Roles
+{
+ public sealed class QueryRoleBindModel : QueryPageBindModel
+ {
+
+ }
+}
\ No newline at end of file