diff --git a/BusinessLogic/BusinessLogic.csproj b/BusinessLogic/BusinessLogic.csproj
index d0efbf0..c105ec7 100644
--- a/BusinessLogic/BusinessLogic.csproj
+++ b/BusinessLogic/BusinessLogic.csproj
@@ -6,8 +6,17 @@
enable
+
+
+
+
-
+
+
+
+
+
+
diff --git a/BusinessLogic/BusinessLogic/RoleLogic.cs b/BusinessLogic/BusinessLogic/RoleLogic.cs
new file mode 100644
index 0000000..9be0c6c
--- /dev/null
+++ b/BusinessLogic/BusinessLogic/RoleLogic.cs
@@ -0,0 +1,96 @@
+using Contracts.BindingModels;
+using Contracts.BusinessLogicContracts;
+using Contracts.Converters;
+using Contracts.Exceptions;
+using Contracts.SearchModels;
+using Contracts.StorageContracts;
+using Contracts.ViewModels;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BusinessLogic.BusinessLogic
+{
+ public class RoleLogic : IRoleLogic
+ {
+ private readonly ILogger _logger;
+ private readonly IRoleStorage _roleStorage;
+
+ public RoleLogic(ILogger logger, IRoleStorage roleStorage)
+ {
+ _logger = logger;
+ _roleStorage = roleStorage;
+ }
+
+ public RoleViewModel Create(RoleBindingModel model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+
+ var role = _roleStorage.Insert(model);
+ if (role is null)
+ {
+ throw new Exception("Insert operation failed.");
+ }
+
+ return RoleConverter.ToView(role);
+ }
+
+ public RoleViewModel Delete(RoleSearchModel model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+
+ _logger.LogInformation("Delete role. Id: {0}", model.Id);
+ var role = _roleStorage.Delete(model);
+ if (role is null)
+ {
+ throw new Exception("Delete operation failed.");
+ }
+
+ return RoleConverter.ToView(role);
+ }
+
+ public RoleViewModel ReadElement(RoleSearchModel model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+
+ _logger.LogInformation("ReadElement. Id: {0}", model.Id);
+ var role = _roleStorage.GetElement(model);
+ if (role is null)
+ {
+ throw new ElementNotFoundException();
+ }
+ _logger.LogInformation("ReadElement find. Id: {0}", role.Id);
+
+ return RoleConverter.ToView(role);
+ }
+
+ public IEnumerable ReadElements(RoleSearchModel? model)
+ {
+ _logger.LogInformation("ReadList. Id: {Id}", model?.Id);
+ var list = _roleStorage.GetList(model);
+ if (list is null || list.Count() == 0)
+ {
+ _logger.LogWarning("ReadList return null list");
+ return [];
+ }
+ _logger.LogInformation("ReadList. Count: {Count}", list.Count());
+
+ return list.Select(RoleConverter.ToView);
+ }
+
+ public RoleViewModel Update(RoleBindingModel model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+
+ var role = _roleStorage.Update(model);
+ if (role is null)
+ {
+ throw new Exception("Update operation failed.");
+ }
+ return RoleConverter.ToView(role);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BusinessLogic/BusinessLogic/UserLogic.cs b/BusinessLogic/BusinessLogic/UserLogic.cs
new file mode 100644
index 0000000..b028660
--- /dev/null
+++ b/BusinessLogic/BusinessLogic/UserLogic.cs
@@ -0,0 +1,134 @@
+using BusinessLogic.Tools;
+using Contracts.BindingModels;
+using Contracts.BusinessLogicContracts;
+using Contracts.Converters;
+using Contracts.Exceptions;
+using Contracts.SearchModels;
+using Contracts.StorageContracts;
+using Contracts.ViewModels;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BusinessLogic.BusinessLogic
+{
+ public class UserLogic : IUserLogic
+ {
+ private readonly ILogger _logger;
+ private readonly IUserStorage _userStorage;
+
+ public UserLogic(ILogger logger, IUserStorage userStorage)
+ {
+ _logger = logger;
+ _userStorage = userStorage;
+ }
+
+ public UserViewModel Create(UserBindingModel model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+ // Проверяем пароль
+ _validatePassword(model.Password);
+ // Хешируем пароль
+ model.PasswordHash = PasswordHasher.Hash(model.Password);
+ var user = _userStorage.Insert(model);
+ if (user is null)
+ {
+ throw new Exception("Insert operation failed.");
+ }
+
+ return UserConverter.ToView(user);
+ }
+
+ public UserViewModel Delete(UserSearchModel model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+
+ _logger.LogInformation("Delete user. Id: {0}", model.Id);
+ var user = _userStorage.Delete(model);
+ if (user is null)
+ {
+ throw new ElementNotFoundException();
+ }
+
+ return UserConverter.ToView(user);
+ }
+
+ public IEnumerable ReadElements(UserSearchModel? model)
+ {
+ _logger.LogInformation("ReadList. Id: {Id}", model?.Id);
+ var list = _userStorage.GetList(model);
+ if (list is null || list.Count() == 0)
+ {
+ _logger.LogWarning("ReadList return null list");
+ return [];
+ }
+ _logger.LogInformation("ReadList. Count: {Count}", list.Count());
+
+ return list.Select(UserConverter.ToView);
+ }
+
+ public UserViewModel ReadElement(UserSearchModel model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+
+ _logger.LogInformation("ReadElement. Id: {0}", model.Id);
+ var user = _userStorage.GetElement(model);
+ if (user is null)
+ {
+ throw new ElementNotFoundException();
+ }
+ _logger.LogInformation("ReadElement find. Id: {0}", user.Id);
+
+ return UserConverter.ToView(user);
+ }
+
+ public UserViewModel Update(UserBindingModel model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+
+ if (model.Password is not null)
+ {
+ _validatePassword(model.Password);
+ model.PasswordHash = PasswordHasher.Hash(model.Password);
+ }
+ var user = _userStorage.Update(model);
+ if (user is null)
+ {
+ throw new Exception("Update operation failed.");
+ }
+ return UserConverter.ToView(user);
+ }
+
+ public UserViewModel Login(string email, string password)
+ {
+ if (email is null)
+ {
+ throw new AccountException("Email is null");
+ }
+ var user = _userStorage.GetElement(new() { Email = email });
+
+ if (user is null)
+ {
+ throw new ElementNotFoundException();
+ }
+ // Проверяем пароль
+ _validatePassword(password);
+ if (!PasswordHasher.Verify(password, user.PasswordHash))
+ {
+ throw new AccountException("The passwords don't match.");
+ }
+ return UserConverter.ToView(user);
+ }
+
+ public void _validatePassword(string? password)
+ {
+ if (string.IsNullOrWhiteSpace(password))
+ {
+ throw new AccountException("The password is null.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BusinessLogic/Tools/PasswordHasher.cs b/BusinessLogic/Tools/PasswordHasher.cs
new file mode 100644
index 0000000..f52c90f
--- /dev/null
+++ b/BusinessLogic/Tools/PasswordHasher.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BusinessLogic.Tools
+{
+ internal class PasswordHasher
+ {
+ ///
+ /// Хеширует с использование SHA256
+ ///
+ /// Пароль
+ /// Хеш пароля
+ public static string Hash(string password)
+ {
+ return BCrypt.Net.BCrypt.HashPassword(password);
+ }
+
+ ///
+ /// Проверяет на соответствие пароля и его хеша
+ ///
+ /// Пароль
+ /// Хеш пароля
+ ///
+ public static bool Verify(string password, string passHash)
+ {
+ return BCrypt.Net.BCrypt.Verify(password, passHash);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Contracts/BindingModels/UserBindingModel.cs b/Contracts/BindingModels/UserBindingModel.cs
index 7b6c7c6..d5e19ae 100644
--- a/Contracts/BindingModels/UserBindingModel.cs
+++ b/Contracts/BindingModels/UserBindingModel.cs
@@ -13,6 +13,7 @@ namespace Contracts.BindingModels
public string SecondName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
+ public string? Password { get; set; }
public DateTime Birthday { get; set; }
public RoleBindingModel Role { get; set; } = null!;
}
diff --git a/Contracts/BusinessLogicContracts/IRoleLogic.cs b/Contracts/BusinessLogicContracts/IRoleLogic.cs
new file mode 100644
index 0000000..99e24dc
--- /dev/null
+++ b/Contracts/BusinessLogicContracts/IRoleLogic.cs
@@ -0,0 +1,24 @@
+using Contracts.BindingModels;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Contracts.BusinessLogicContracts
+{
+ public interface IRoleLogic
+ {
+ RoleViewModel Create(RoleBindingModel model);
+
+ RoleViewModel Update(RoleBindingModel model);
+
+ RoleViewModel ReadElement(RoleSearchModel model);
+
+ IEnumerable ReadElements(RoleSearchModel? model);
+
+ RoleViewModel Delete(RoleSearchModel model);
+ }
+}
\ No newline at end of file
diff --git a/Contracts/BusinessLogicContracts/IUserLogic.cs b/Contracts/BusinessLogicContracts/IUserLogic.cs
new file mode 100644
index 0000000..fdcc510
--- /dev/null
+++ b/Contracts/BusinessLogicContracts/IUserLogic.cs
@@ -0,0 +1,27 @@
+using Contracts.BindingModels;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Contracts.BusinessLogicContracts
+{
+ public interface IUserLogic
+ {
+ UserViewModel Login(string email, string password);
+
+ UserViewModel Create(UserBindingModel model);
+
+ UserViewModel Update(UserBindingModel model);
+
+ UserViewModel ReadElement(UserSearchModel model);
+
+ IEnumerable ReadElements(UserSearchModel? model);
+
+ UserViewModel Delete(UserSearchModel model);
+ }
+}
\ No newline at end of file
diff --git a/Contracts/Contracts.csproj b/Contracts/Contracts.csproj
index 64eaa86..fa71b7a 100644
--- a/Contracts/Contracts.csproj
+++ b/Contracts/Contracts.csproj
@@ -6,9 +6,4 @@
enable
-
-
-
-
-
diff --git a/Contracts/Converters/RoleConverter.cs b/Contracts/Converters/RoleConverter.cs
new file mode 100644
index 0000000..37aa7e0
--- /dev/null
+++ b/Contracts/Converters/RoleConverter.cs
@@ -0,0 +1,25 @@
+using Contracts.BindingModels;
+using Contracts.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Contracts.Converters
+{
+ public static class RoleConverter
+ {
+ public static RoleViewModel ToView(RoleBindingModel model) => new()
+ {
+ Id = model.Id,
+ Name = model.Name,
+ };
+
+ public static RoleBindingModel ToBinding(RoleViewModel model) => new()
+ {
+ Id = model.Id,
+ Name = model.Name,
+ };
+ }
+}
\ No newline at end of file
diff --git a/Contracts/Converters/UserConverter.cs b/Contracts/Converters/UserConverter.cs
new file mode 100644
index 0000000..b951d11
--- /dev/null
+++ b/Contracts/Converters/UserConverter.cs
@@ -0,0 +1,33 @@
+using Contracts.BindingModels;
+using Contracts.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Contracts.Converters
+{
+ public static class UserConverter
+ {
+ public static UserViewModel ToView(UserBindingModel model) => new()
+ {
+ Id = model.Id,
+ FirstName = model.FirstName,
+ SecondName = model.SecondName,
+ Email = model.Email,
+ Birthday = model.Birthday,
+ Role = RoleConverter.ToView(model.Role),
+ };
+
+ public static UserBindingModel ToBinding(UserViewModel model) => new()
+ {
+ Id = model.Id,
+ FirstName = model.FirstName,
+ SecondName = model.SecondName,
+ Email = model.Email,
+ Birthday = model.Birthday,
+ Role = RoleConverter.ToBinding(model.Role),
+ };
+ }
+}
\ No newline at end of file
diff --git a/Contracts/Exceptions/AccountException.cs b/Contracts/Exceptions/AccountException.cs
new file mode 100644
index 0000000..1678eda
--- /dev/null
+++ b/Contracts/Exceptions/AccountException.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Contracts.Exceptions
+{
+ public class AccountException : Exception
+ {
+ public AccountException()
+ {
+ }
+
+ public AccountException(string message) : base(message)
+ {
+ }
+
+ public AccountException(string message, Exception inner) : base(message, inner)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Contracts/Exceptions/ElementNotFoundException.cs b/Contracts/Exceptions/ElementNotFoundException.cs
new file mode 100644
index 0000000..7fad056
--- /dev/null
+++ b/Contracts/Exceptions/ElementNotFoundException.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Contracts.Exceptions
+{
+ public class ElementNotFoundException : Exception
+ {
+ public ElementNotFoundException() : base("Element not founded.")
+ {
+ }
+
+ public ElementNotFoundException(string message) : base(message)
+ {
+ }
+
+ public ElementNotFoundException(string message, Exception inner) : base(message, inner)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Contracts/StorageContracts/IRoleStorage.cs b/Contracts/StorageContracts/IRoleStorage.cs
new file mode 100644
index 0000000..d8b7f15
--- /dev/null
+++ b/Contracts/StorageContracts/IRoleStorage.cs
@@ -0,0 +1,24 @@
+using Contracts.BindingModels;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Contracts.StorageContracts
+{
+ public interface IRoleStorage
+ {
+ RoleBindingModel? Insert(RoleBindingModel model);
+
+ IEnumerable GetList(RoleSearchModel? model);
+
+ RoleBindingModel? GetElement(RoleSearchModel model);
+
+ RoleBindingModel? Update(RoleBindingModel model);
+
+ RoleBindingModel? Delete(RoleSearchModel model);
+ }
+}
\ No newline at end of file
diff --git a/Contracts/StorageContracts/IUserStorage.cs b/Contracts/StorageContracts/IUserStorage.cs
new file mode 100644
index 0000000..3a38799
--- /dev/null
+++ b/Contracts/StorageContracts/IUserStorage.cs
@@ -0,0 +1,23 @@
+using Contracts.BindingModels;
+using Contracts.SearchModels;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Contracts.StorageContracts
+{
+ public interface IUserStorage
+ {
+ UserBindingModel? Insert(UserBindingModel model);
+
+ IEnumerable GetList(UserSearchModel? model);
+
+ UserBindingModel? GetElement(UserSearchModel model);
+
+ UserBindingModel? Update(UserBindingModel model);
+
+ UserBindingModel? Delete(UserSearchModel model);
+ }
+}
\ No newline at end of file
diff --git a/Contracts/ViewModels/UserViewModel.cs b/Contracts/ViewModels/UserViewModel.cs
index 4f15600..6174d56 100644
--- a/Contracts/ViewModels/UserViewModel.cs
+++ b/Contracts/ViewModels/UserViewModel.cs
@@ -12,7 +12,6 @@ namespace Contracts.ViewModels
public string FirstName { get; set; } = string.Empty;
public string SecondName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
- public string PasswordHash { get; set; } = string.Empty;
public DateTime Birthday { get; set; }
public RoleViewModel Role { get; set; } = null!;
}
diff --git a/DataModels/Models/IUser.cs b/DataModels/Models/IUser.cs
index 7098f65..9eb2ef6 100644
--- a/DataModels/Models/IUser.cs
+++ b/DataModels/Models/IUser.cs
@@ -13,6 +13,5 @@ namespace DataModels.Models
string PasswordHash { get; }
string Email { get; }
DateTime Birthday { get; }
- Guid RoleId { get; }
}
}
\ No newline at end of file
diff --git a/DatabaseImplement/Database.cs b/DatabaseImplement/Database.cs
new file mode 100644
index 0000000..7bb94c7
--- /dev/null
+++ b/DatabaseImplement/Database.cs
@@ -0,0 +1,25 @@
+using DatabaseImplement.Models;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DatabaseImplement
+{
+ public class Database : DbContext
+ {
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ if (optionsBuilder.IsConfigured == false)
+ {
+ optionsBuilder.UseNpgsql("Server=192.168.191.42:32768;Database=gun_market;Username=postgres;Password=7355608;");
+ }
+ base.OnConfiguring(optionsBuilder);
+ }
+
+ public virtual DbSet Roles { get; set; } = null!;
+ public virtual DbSet Users { get; set; } = null!;
+ }
+}
\ No newline at end of file
diff --git a/DatabaseImplement/DatabaseImplement.csproj b/DatabaseImplement/DatabaseImplement.csproj
index 85b7ca3..2929944 100644
--- a/DatabaseImplement/DatabaseImplement.csproj
+++ b/DatabaseImplement/DatabaseImplement.csproj
@@ -7,8 +7,16 @@
-
-
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
diff --git a/DatabaseImplement/Implements/RoleStorage.cs b/DatabaseImplement/Implements/RoleStorage.cs
new file mode 100644
index 0000000..4394a59
--- /dev/null
+++ b/DatabaseImplement/Implements/RoleStorage.cs
@@ -0,0 +1,88 @@
+using Contracts.BindingModels;
+using Contracts.SearchModels;
+using Contracts.StorageContracts;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DatabaseImplement.Implements
+{
+ public class RoleStorage : IRoleStorage
+ {
+ public RoleBindingModel? Delete(RoleSearchModel model)
+ {
+ if (model.Id is null)
+ {
+ return null;
+ }
+
+ var context = new Database();
+ var role = context.Roles.FirstOrDefault(r => r.Id == model.Id);
+ if (role is null)
+ {
+ return null;
+ }
+
+ context.Remove(role);
+ context.SaveChanges();
+ return role.GetBindingModel();
+ }
+
+ public RoleBindingModel? GetElement(RoleSearchModel model)
+ {
+ if (model.Id is null)
+ {
+ return null;
+ }
+ var context = new Database();
+ return context.Roles
+ .FirstOrDefault(r => r.Id == model.Id)
+ ?.GetBindingModel();
+ }
+
+ public IEnumerable GetList(RoleSearchModel? model)
+ {
+ var context = new Database();
+ if (model is null)
+ {
+ return context.Roles.Select(r => r.GetBindingModel());
+ }
+ if (model.Id is null)
+ {
+ return [];
+ }
+ return context.Roles
+ .Where(r => r.Id == model.Id)
+ .Select(r => r.GetBindingModel());
+ }
+
+ public RoleBindingModel? Insert(RoleBindingModel model)
+ {
+ var context = new Database();
+ var newRole = Models.Role.ToRoleFromBinding(model);
+
+ context.Roles.Add(newRole);
+ context.SaveChanges();
+
+ return newRole.GetBindingModel();
+ }
+
+ public RoleBindingModel? Update(RoleBindingModel model)
+ {
+ var context = new Database();
+ var role = context.Roles.FirstOrDefault(r => r.Id == model.Id);
+
+ if (role is null)
+ {
+ return null;
+ }
+
+ role.Update(model);
+
+ context.SaveChanges();
+ return role.GetBindingModel();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DatabaseImplement/Implements/UserStorage.cs b/DatabaseImplement/Implements/UserStorage.cs
new file mode 100644
index 0000000..fdbdf0a
--- /dev/null
+++ b/DatabaseImplement/Implements/UserStorage.cs
@@ -0,0 +1,109 @@
+using Contracts.BindingModels;
+using Contracts.SearchModels;
+using Contracts.StorageContracts;
+using DatabaseImplement.Models;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DatabaseImplement.Implements
+{
+ public class UserStorage : IUserStorage
+ {
+ public UserBindingModel? Delete(UserSearchModel model)
+ {
+ if (model.Id is null && model.Email is null)
+ {
+ return null;
+ }
+
+ var context = new Database();
+ var user = context.Users.FirstOrDefault(u =>
+ (model.Id.HasValue && u.Id == model.Id)
+ || (!string.IsNullOrEmpty(u.Email) && u.Email.Contains(model.Email)));
+
+ if (user is null)
+ {
+ return null;
+ }
+ context.Remove(user);
+ context.SaveChanges();
+
+ return user.GetBindingModel();
+ }
+
+ public UserBindingModel? GetElement(UserSearchModel model)
+ {
+ if (model.Id is null && model.Email is null)
+ {
+ return null;
+ }
+ var context = new Database();
+ return context.Users
+ .Include(u => u.Role)
+ .FirstOrDefault(u =>
+ (model.Id.HasValue && u.Id == model.Id)
+ || (!string.IsNullOrEmpty(u.Email) && u.Email.Contains(model.Email)))
+ ?.GetBindingModel();
+ }
+
+ public IEnumerable GetList(UserSearchModel? model)
+ {
+ var context = new Database();
+ if (model is null)
+ {
+ return context.Users
+ .Include(u => u.Role)
+ .Select(r => r.GetBindingModel());
+ }
+ if (model.Id is null && model.Email is null)
+ {
+ return [];
+ }
+ return context.Users
+ .Where(u =>
+ (model.Id.HasValue && u.Id == model.Id)
+ || (!string.IsNullOrEmpty(u.Email) && u.Email.Contains(model.Email)))
+ .Include(u => u.Role)
+ .Select(r => r.GetBindingModel());
+ }
+
+ public UserBindingModel? Insert(UserBindingModel model)
+ {
+ var context = new Database();
+ var role = context.Roles.FirstOrDefault(r => r.Id == model.Role.Id);
+ if (role is null)
+ {
+ return null;
+ }
+ var newUser = Models.User.ToUserFromBinding(model, role);
+
+ context.Users.Add(newUser);
+ context.SaveChanges();
+
+ return newUser.GetBindingModel();
+ }
+
+ public UserBindingModel? Update(UserBindingModel model)
+ {
+ var context = new Database();
+ var user = context.Users
+ .FirstOrDefault(u => u.Id == model.Id);
+ var role = context.Roles.FirstOrDefault(r => r.Id == model.Role.Id);
+
+ if (user is null || role is null)
+ {
+ return null;
+ }
+
+ user.Update(model, role);
+
+ context.SaveChanges();
+ return user.GetBindingModel();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DatabaseImplement/Migrations/20240604184007_registration_init.Designer.cs b/DatabaseImplement/Migrations/20240604184007_registration_init.Designer.cs
new file mode 100644
index 0000000..3dbe69b
--- /dev/null
+++ b/DatabaseImplement/Migrations/20240604184007_registration_init.Designer.cs
@@ -0,0 +1,89 @@
+//
+using System;
+using DatabaseImplement;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DatabaseImplement.Migrations
+{
+ [DbContext(typeof(Database))]
+ [Migration("20240604184007_registration_init")]
+ partial class registration_init
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.6")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DatabaseImplement.Models.Role", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Roles");
+ });
+
+ modelBuilder.Entity("DatabaseImplement.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Birthday")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("FirstName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .HasColumnType("uuid");
+
+ b.Property("SecondName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("DatabaseImplement.Models.User", b =>
+ {
+ b.HasOne("DatabaseImplement.Models.Role", "Role")
+ .WithMany()
+ .HasForeignKey("RoleId");
+
+ b.Navigation("Role");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/DatabaseImplement/Migrations/20240604184007_registration_init.cs b/DatabaseImplement/Migrations/20240604184007_registration_init.cs
new file mode 100644
index 0000000..8f81df4
--- /dev/null
+++ b/DatabaseImplement/Migrations/20240604184007_registration_init.cs
@@ -0,0 +1,64 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DatabaseImplement.Migrations
+{
+ ///
+ public partial class registration_init : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Roles",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Name = table.Column(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Roles", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Users",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ FirstName = table.Column(type: "text", nullable: false),
+ SecondName = table.Column(type: "text", nullable: false),
+ PasswordHash = table.Column(type: "text", nullable: false),
+ Email = table.Column(type: "text", nullable: false),
+ Birthday = table.Column(type: "timestamp with time zone", nullable: false),
+ RoleId = table.Column(type: "uuid", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Users", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Users_Roles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "Roles",
+ principalColumn: "Id");
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Users_RoleId",
+ table: "Users",
+ column: "RoleId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Users");
+
+ migrationBuilder.DropTable(
+ name: "Roles");
+ }
+ }
+}
diff --git a/DatabaseImplement/Migrations/DatabaseModelSnapshot.cs b/DatabaseImplement/Migrations/DatabaseModelSnapshot.cs
new file mode 100644
index 0000000..875158d
--- /dev/null
+++ b/DatabaseImplement/Migrations/DatabaseModelSnapshot.cs
@@ -0,0 +1,86 @@
+//
+using System;
+using DatabaseImplement;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DatabaseImplement.Migrations
+{
+ [DbContext(typeof(Database))]
+ partial class DatabaseModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.6")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DatabaseImplement.Models.Role", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Roles");
+ });
+
+ modelBuilder.Entity("DatabaseImplement.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Birthday")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("FirstName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .HasColumnType("uuid");
+
+ b.Property("SecondName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("DatabaseImplement.Models.User", b =>
+ {
+ b.HasOne("DatabaseImplement.Models.Role", "Role")
+ .WithMany()
+ .HasForeignKey("RoleId");
+
+ b.Navigation("Role");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/DatabaseImplement/Models/Role.cs b/DatabaseImplement/Models/Role.cs
new file mode 100644
index 0000000..f5d7970
--- /dev/null
+++ b/DatabaseImplement/Models/Role.cs
@@ -0,0 +1,47 @@
+using Contracts.BindingModels;
+using Contracts.ViewModels;
+using DataModels.Models;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DatabaseImplement.Models
+{
+ public class Role : IRole
+ {
+ public Guid Id { get; set; }
+
+ [Required]
+ public string Name { get; set; } = string.Empty;
+
+ public RoleBindingModel GetBindingModel() => new()
+ {
+ Id = Id,
+ Name = Name,
+ };
+
+ public static Role ToRoleFromView(RoleViewModel model) => new()
+ {
+ Id = model.Id,
+ Name = model.Name,
+ };
+
+ public static Role ToRoleFromBinding(RoleBindingModel model) => new()
+ {
+ Id = model.Id,
+ Name = model.Name,
+ };
+
+ public void Update(RoleBindingModel model)
+ {
+ if (model is null)
+ {
+ throw new ArgumentNullException("Update role: bindinng model is null");
+ }
+ Name = model.Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/DatabaseImplement/Models/User.cs b/DatabaseImplement/Models/User.cs
new file mode 100644
index 0000000..a2f2ecf
--- /dev/null
+++ b/DatabaseImplement/Models/User.cs
@@ -0,0 +1,82 @@
+using Contracts.BindingModels;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+using DataModels.Models;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DatabaseImplement.Models
+{
+ public class User : IUser
+ {
+ public Guid Id { get; set; }
+
+ [Required]
+ public string FirstName { get; set; } = string.Empty;
+
+ [Required]
+ public string SecondName { get; set; } = string.Empty;
+
+ [Required]
+ public string PasswordHash { get; set; } = string.Empty;
+
+ [Required]
+ public string Email { get; set; } = string.Empty;
+
+ [Required]
+ public DateTime Birthday { get; set; }
+
+ public Role? Role { get; set; }
+
+ public UserBindingModel GetBindingModel() => new()
+ {
+ Id = Id,
+ FirstName = FirstName,
+ SecondName = SecondName,
+ Email = Email,
+ PasswordHash = PasswordHash,
+ Birthday = Birthday,
+ Role = Role?.GetBindingModel() ?? new()
+ };
+
+ public static User ToUserFromView(UserViewModel model, Role role) => new()
+ {
+ Id = model.Id,
+ FirstName = model.FirstName,
+ SecondName = model.SecondName,
+ Email = model.Email,
+ Birthday = model.Birthday,
+ Role = role
+ };
+
+ public static User ToUserFromBinding(UserBindingModel model, Role role) => new()
+ {
+ Id = model.Id,
+ FirstName = model.FirstName,
+ SecondName = model.SecondName,
+ Email = model.Email,
+ PasswordHash = model.PasswordHash,
+ Birthday = model.Birthday,
+ Role = role
+ };
+
+ public void Update(UserBindingModel model, Role role)
+ {
+ if (model is null)
+ {
+ throw new ArgumentNullException("Update user: binding model is null");
+ }
+
+ Email = model.Email;
+ FirstName = model.FirstName;
+ SecondName = model.SecondName;
+ PasswordHash = model.PasswordHash;
+ Birthday = model.Birthday;
+ Role = role;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RestAPI/Controllers/RoleController.cs b/RestAPI/Controllers/RoleController.cs
new file mode 100644
index 0000000..9f03335
--- /dev/null
+++ b/RestAPI/Controllers/RoleController.cs
@@ -0,0 +1,120 @@
+using BusinessLogic.BusinessLogic;
+using Contracts.BindingModels;
+using Contracts.BusinessLogicContracts;
+using Contracts.Exceptions;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.AspNetCore.Mvc;
+
+namespace RestAPI.Controllers
+{
+ [Route("[controller]/[action]")]
+ [ApiController]
+ public class RoleController
+ {
+ private readonly ILogger _logger;
+ private readonly IRoleLogic _roleLogic;
+
+ public RoleController(ILogger logger, IRoleLogic roleLogic)
+ {
+ _logger = logger;
+ _roleLogic = roleLogic;
+ }
+
+ [HttpGet]
+ public IResult Get([FromQuery] RoleSearchModel model)
+ {
+ try
+ {
+ var res = _roleLogic.ReadElement(model);
+ return Results.Ok(res);
+ }
+ catch (ElementNotFoundException ex)
+ {
+ _logger.LogInformation(ex, "Role not found");
+ return Results.NoContent();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error get role");
+ return Results.Problem(ex.Message);
+ }
+ }
+
+ [HttpGet]
+ public IResult GeFilteredtList([FromQuery] RoleSearchModel model)
+ {
+ try
+ {
+ var res = _roleLogic.ReadElements(model).ToList();
+ return Results.Ok(res);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error get list role");
+ return Results.Problem(ex.Message);
+ }
+ }
+
+ [HttpGet]
+ public IResult GetList()
+ {
+ try
+ {
+ var res = _roleLogic.ReadElements(null).ToList();
+ return Results.Ok(res);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error get list role");
+ return Results.Problem(ex.Message);
+ }
+ }
+
+ [HttpPost]
+ public IResult Create([FromBody] RoleBindingModel model)
+ {
+ try
+ {
+ var res = _roleLogic.Create(model);
+ return Results.Created(string.Empty, res);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error create role");
+ return Results.Problem(ex.Message);
+ }
+ }
+
+ [HttpPatch]
+ public IResult Update([FromBody] RoleBindingModel model)
+ {
+ try
+ {
+ var res = _roleLogic.Update(model);
+ return Results.Ok(res);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error update role");
+ return Results.Problem(ex.Message);
+ }
+ }
+
+ [HttpDelete]
+ public IResult Delete([FromQuery] RoleSearchModel model)
+ {
+ try
+ {
+ var res = _roleLogic.Delete(model); ;
+ return Results.Ok(res);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error delete role");
+ return Results.Problem(ex.Message);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/RestAPI/Controllers/UserController.cs b/RestAPI/Controllers/UserController.cs
new file mode 100644
index 0000000..bbeb162
--- /dev/null
+++ b/RestAPI/Controllers/UserController.cs
@@ -0,0 +1,130 @@
+using BusinessLogic.BusinessLogic;
+using Contracts.BindingModels;
+using Contracts.BusinessLogicContracts;
+using Contracts.Exceptions;
+using Contracts.SearchModels;
+using Microsoft.AspNetCore.Mvc;
+
+namespace RestAPI.Controllers
+{
+ [Route("[controller]/[action]")]
+ [ApiController]
+ public class UserController
+ {
+ private readonly ILogger _logger;
+ private readonly IUserLogic _userLogic;
+
+ public UserController(ILogger logger, IUserLogic userLogic)
+ {
+ _userLogic = userLogic;
+ _logger = logger;
+ }
+
+ [HttpPost]
+ public IResult Login(string email, string password)
+ {
+ try
+ {
+ var res = _userLogic.Login(email, password);
+ return Results.Ok(res);
+ }
+ catch (ElementNotFoundException ex)
+ {
+ _logger.LogInformation(ex, "User not found");
+ return Results.NoContent();
+ }
+ catch (AccountException ex)
+ {
+ _logger.LogWarning(ex, "Wrong login data");
+ return Results.BadRequest(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error get user");
+ return Results.Problem(ex.Message);
+ }
+ }
+
+ [HttpPost]
+ public IResult Registration([FromBody] UserBindingModel model)
+ {
+ try
+ {
+ var res = _userLogic.Create(model);
+ return Results.Ok(res);
+ }
+ catch (AccountException ex)
+ {
+ _logger.LogWarning(ex, "Wrong registration data");
+ throw;
+ return Results.BadRequest(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error create user");
+ throw;
+ return Results.Problem(ex.Message);
+ }
+ }
+
+ [HttpGet]
+ public IResult Get([FromQuery] UserSearchModel model)
+ {
+ try
+ {
+ var res = _userLogic.ReadElement(model);
+ return Results.Ok(res);
+ }
+ catch (ElementNotFoundException ex)
+ {
+ _logger.LogInformation(ex, "User not found");
+ return Results.NoContent();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error get user");
+ return Results.Problem(ex.Message);
+ }
+ }
+
+ [HttpPatch]
+ public IResult Update([FromBody] UserBindingModel model)
+ {
+ try
+ {
+ var res = _userLogic.Update(model);
+ return Results.Ok(res);
+ }
+ catch (AccountException ex)
+ {
+ _logger.LogWarning(ex, "Wrong update data");
+ return Results.BadRequest(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error update user");
+ return Results.Problem(ex.Message);
+ }
+ }
+
+ [HttpDelete]
+ public IResult Delete(Guid id)
+ {
+ try
+ {
+ var res = _userLogic.Delete(new() { Id = id });
+ return Results.Ok(res);
+ }
+ catch (ElementNotFoundException ex)
+ {
+ _logger.LogInformation(ex, "User not found");
+ return Results.NoContent();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error delete user");
+ return Results.Problem(ex.Message);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/RestAPI/Controllers/WeatherForecastController.cs b/RestAPI/Controllers/WeatherForecastController.cs
deleted file mode 100644
index 40741f2..0000000
--- a/RestAPI/Controllers/WeatherForecastController.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-
-namespace RestAPI.Controllers
-{
- [ApiController]
- [Route("[controller]")]
- public class WeatherForecastController : ControllerBase
- {
- private static readonly string[] Summaries = new[]
- {
- "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
- };
-
- private readonly ILogger _logger;
-
- public WeatherForecastController(ILogger logger)
- {
- _logger = logger;
- }
-
- [HttpGet(Name = "GetWeatherForecast")]
- public IEnumerable Get()
- {
- return Enumerable.Range(1, 5).Select(index => new WeatherForecast
- {
- Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
- TemperatureC = Random.Shared.Next(-20, 55),
- Summary = Summaries[Random.Shared.Next(Summaries.Length)]
- })
- .ToArray();
- }
- }
-}
diff --git a/RestAPI/Program.cs b/RestAPI/Program.cs
index 48863a6..6fbbd7c 100644
--- a/RestAPI/Program.cs
+++ b/RestAPI/Program.cs
@@ -1,19 +1,43 @@
+using BusinessLogic.BusinessLogic;
+using Contracts.BusinessLogicContracts;
+using Contracts.StorageContracts;
+using DatabaseImplement.Implements;
+using Microsoft.OpenApi.Models;
+using System;
+
+const string VERSION = "v1";
+const string TITLE = "21GunsRestAPI";
+
var builder = WebApplication.CreateBuilder(args);
-// Add services to the container.
+builder.Logging.SetMinimumLevel(LogLevel.Trace);
+builder.Logging.AddLog4Net("log4net.config");
+
+#region DI
+
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+
+#endregion DI
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
+builder.Services.AddSwaggerGen(c =>
+{
+ c.SwaggerDoc(VERSION, new OpenApiInfo { Title = TITLE, Version = VERSION });
+});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
- app.UseSwagger();
- app.UseSwaggerUI();
+ app.UseSwagger();
+ app.UseSwaggerUI(c => c.SwaggerEndpoint($"/swagger/{VERSION}/swagger.json", $"{TITLE} {VERSION}"));
}
app.UseHttpsRedirection();
@@ -22,4 +46,4 @@ app.UseAuthorization();
app.MapControllers();
-app.Run();
+app.Run();
\ No newline at end of file
diff --git a/RestAPI/RestAPI.csproj b/RestAPI/RestAPI.csproj
index 9daa180..ee6f069 100644
--- a/RestAPI/RestAPI.csproj
+++ b/RestAPI/RestAPI.csproj
@@ -7,7 +7,24 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
diff --git a/RestAPI/RestAPI.http b/RestAPI/RestAPI.http
index ae5b1fd..cb9c1f5 100644
--- a/RestAPI/RestAPI.http
+++ b/RestAPI/RestAPI.http
@@ -1,6 +1,6 @@
@RestAPI_HostAddress = http://localhost:5168
-GET {{RestAPI_HostAddress}}/weatherforecast/
+GET {{RestAPI_HostAddress}}/api/
Accept: application/json
###
diff --git a/RestAPI/WeatherForecast.cs b/RestAPI/WeatherForecast.cs
deleted file mode 100644
index cf5ac94..0000000
--- a/RestAPI/WeatherForecast.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace RestAPI
-{
- public class WeatherForecast
- {
- public DateOnly Date { get; set; }
-
- public int TemperatureC { get; set; }
-
- public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
-
- public string? Summary { get; set; }
- }
-}
diff --git a/RestAPI/log4net.config b/RestAPI/log4net.config
new file mode 100644
index 0000000..0da4e4c
--- /dev/null
+++ b/RestAPI/log4net.config
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file