diff --git a/BusinessLogic/BusinessLogic/UserLogic.cs b/BusinessLogic/BusinessLogic/UserLogic.cs index 7e22df0..ee6b806 100644 --- a/BusinessLogic/BusinessLogic/UserLogic.cs +++ b/BusinessLogic/BusinessLogic/UserLogic.cs @@ -21,147 +21,150 @@ using System.Threading.Tasks; namespace BusinessLogic.BusinessLogic { - public class UserLogic : IUserLogic - { - private readonly ILogger _logger; - private readonly IUserStorage _userStorage; + public class UserLogic : IUserLogic + { + private readonly ILogger _logger; + private readonly IUserStorage _userStorage; - public UserLogic(ILogger logger, IUserStorage userStorage) - { - _logger = logger; - _userStorage = userStorage; - } + public UserLogic(ILogger logger, IUserStorage userStorage) + { + _logger = logger; + _userStorage = userStorage; + } - public string Create(UserBindingModel model) - { - // Проверяем модель - _validate(model); - var usr = _userStorage.GetElement(new() { Email = model.Email }); - if (usr is not null) - { - throw new AccountException("An account with that email already exists."); - } - // Хешируем пароль - model.PasswordHash = PasswordHasher.Hash(model.Password!); - var user = _userStorage.Insert(model); - if (user is null) - { - throw new Exception("Insert operation failed."); - } + public string Create(UserBindingModel model) + { + // Проверяем модель + _validate(model); + var usr = _userStorage.GetElement(new() { Email = model.Email }); + if (usr is not null) + { + throw new AccountException("An account with that email already exists."); + } + // Хешируем пароль + model.PasswordHash = PasswordHasher.Hash(model.Password!); + model.Birthday = model.Birthday.ToUniversalTime(); - MailSender.Send(new MailRegistration(user)); + var user = _userStorage.Insert(model); + if (user is null) + { + throw new Exception("Insert operation failed."); + } - return JwtProvider.Generate(user); - } + MailSender.Send(new MailRegistration(user)); - public UserViewModel Delete(UserSearchModel model) - { - ArgumentNullException.ThrowIfNull(model); + return JwtProvider.Generate(user); + } - _logger.LogInformation("Delete user. Id: {0}", model.Id); - var user = _userStorage.Delete(model); - if (user is null) - { - throw new ElementNotFoundException(); - } - MailSender.Send(new MailDeleteUser(user)); + public UserViewModel Delete(UserSearchModel model) + { + ArgumentNullException.ThrowIfNull(model); - return UserConverter.ToView(user); - } + _logger.LogInformation("Delete user. Id: {0}", model.Id); + var user = _userStorage.Delete(model); + if (user is null) + { + throw new ElementNotFoundException(); + } + MailSender.Send(new MailDeleteUser(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 UserConverter.ToView(user); + } - return list.Select(UserConverter.ToView); - } + 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()); - public UserViewModel ReadElement(UserSearchModel model) - { - ArgumentNullException.ThrowIfNull(model); + return list.Select(UserConverter.ToView); + } - _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); + public UserViewModel ReadElement(UserSearchModel model) + { + ArgumentNullException.ThrowIfNull(model); - return UserConverter.ToView(user); - } + _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); - public UserViewModel Update(UserBindingModel model) - { - _validate(model); + return UserConverter.ToView(user); + } - model.PasswordHash = PasswordHasher.Hash(model.Password!); - var user = _userStorage.Update(model); - if (user is null) - { - throw new Exception("Update operation failed."); - } + public UserViewModel Update(UserBindingModel model) + { + _validate(model); - MailSender.Send(new MailUpdateUserData(user)); + model.PasswordHash = PasswordHasher.Hash(model.Password!); + model.Birthday = model.Birthday.ToUniversalTime(); + var user = _userStorage.Update(model); + if (user is null) + { + throw new Exception("Update operation failed."); + } - return UserConverter.ToView(user); - } + MailSender.Send(new MailUpdateUserData(user)); - public string Login(string email, string password) - { - _isValidEmail(email); - var user = _userStorage.GetElement(new() { Email = email }); + return UserConverter.ToView(user); + } - if (user is null) - { - throw new ElementNotFoundException(); - } - // Проверяем пароль - _isValidPassword(password); - if (!PasswordHasher.Verify(password, user.PasswordHash)) - { - throw new AccountException("The passwords don't match."); - } - return JwtProvider.Generate(user); - } + public string Login(string email, string password) + { + _isValidEmail(email); + var user = _userStorage.GetElement(new() { Email = email }); - private void _validate(UserBindingModel model) - { - ArgumentNullException.ThrowIfNull(model); - _isValidPassword(model.Password); - _isValidEmail(model.Email); - } + if (user is null) + { + throw new ElementNotFoundException(); + } + // Проверяем пароль + _isValidPassword(password); + if (!PasswordHasher.Verify(password, user.PasswordHash)) + { + throw new AccountException("The passwords don't match."); + } + return JwtProvider.Generate(user); + } - private void _isValidPassword(string? password) - { - if (string.IsNullOrWhiteSpace(password)) - { - throw new AccountException("The password is null."); - } - var hasMin8Max15Chars = new Regex(@".{8,15}"); - if (!hasMin8Max15Chars.IsMatch(password)) - { - throw new AccountException("The password must not be less than 8 or more than 15 characters long."); - } - } + private void _validate(UserBindingModel model) + { + ArgumentNullException.ThrowIfNull(model); + _isValidPassword(model.Password); + _isValidEmail(model.Email); + } - private void _isValidEmail(string? email) - { - if (string.IsNullOrWhiteSpace(email)) - { - throw new AccountException("The email is null."); - } - if (!MailAddress.TryCreate(email, out _)) - { - throw new AccountException("The email is not valid."); - } - } - } + private void _isValidPassword(string? password) + { + if (string.IsNullOrWhiteSpace(password)) + { + throw new AccountException("The password is null."); + } + var hasMin8Max15Chars = new Regex(@".{8,15}"); + if (!hasMin8Max15Chars.IsMatch(password)) + { + throw new AccountException("The password must not be less than 8 or more than 15 characters long."); + } + } + + private void _isValidEmail(string? email) + { + if (string.IsNullOrWhiteSpace(email)) + { + throw new AccountException("The email is null."); + } + if (!MailAddress.TryCreate(email, out _)) + { + throw new AccountException("The email is not valid."); + } + } + } } \ No newline at end of file diff --git a/Contracts/SearchModels/RoleSearchModel.cs b/Contracts/SearchModels/RoleSearchModel.cs index 058eda2..f0df1b8 100644 --- a/Contracts/SearchModels/RoleSearchModel.cs +++ b/Contracts/SearchModels/RoleSearchModel.cs @@ -6,8 +6,9 @@ using System.Threading.Tasks; namespace Contracts.SearchModels { - public class RoleSearchModel - { - public Guid? Id { get; set; } - } + public class RoleSearchModel + { + public Guid? Id { get; set; } + public string? Name { get; set; } + } } \ No newline at end of file diff --git a/DatabaseImplement/Implements/RoleStorage.cs b/DatabaseImplement/Implements/RoleStorage.cs index 4394a59..a1d8e63 100644 --- a/DatabaseImplement/Implements/RoleStorage.cs +++ b/DatabaseImplement/Implements/RoleStorage.cs @@ -9,80 +9,82 @@ using System.Threading.Tasks; namespace DatabaseImplement.Implements { - public class RoleStorage : IRoleStorage - { - public RoleBindingModel? Delete(RoleSearchModel model) - { - if (model.Id is null) - { - return null; - } + 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; - } + 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(); - } + 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 RoleBindingModel? GetElement(RoleSearchModel model) + { + if (model.Id is null && string.IsNullOrWhiteSpace(model.Name)) + { + return null; + } + var context = new Database(); + return context.Roles + .FirstOrDefault(r => (model.Id.HasValue && r.Id == model.Id) + || (!string.IsNullOrWhiteSpace(model.Name) && r.Name.Contains(model.Name))) + ?.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 IEnumerable GetList(RoleSearchModel? model) + { + var context = new Database(); + if (model is null && string.IsNullOrWhiteSpace(model.Name)) + { + return context.Roles.Select(r => r.GetBindingModel()); + } + if (model.Id is null) + { + return []; + } + return context.Roles + .Where(r => (model.Id.HasValue && r.Id == model.Id) + || (!string.IsNullOrWhiteSpace(model.Name) && r.Name.Contains(model.Name))) + .Select(r => r.GetBindingModel()); + } - public RoleBindingModel? Insert(RoleBindingModel model) - { - var context = new Database(); - var newRole = Models.Role.ToRoleFromBinding(model); + public RoleBindingModel? Insert(RoleBindingModel model) + { + var context = new Database(); + var newRole = Models.Role.ToRoleFromBinding(model); - context.Roles.Add(newRole); - context.SaveChanges(); + context.Roles.Add(newRole); + context.SaveChanges(); - return newRole.GetBindingModel(); - } + return newRole.GetBindingModel(); + } - public RoleBindingModel? Update(RoleBindingModel model) - { - var context = new Database(); - var role = context.Roles.FirstOrDefault(r => r.Id == model.Id); + 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; - } + if (role is null) + { + return null; + } - role.Update(model); + role.Update(model); - context.SaveChanges(); - return role.GetBindingModel(); - } - } + context.SaveChanges(); + return role.GetBindingModel(); + } + } } \ No newline at end of file diff --git a/RestAPI/Controllers/UserController.cs b/RestAPI/Controllers/UserController.cs index 57d6eee..adae152 100644 --- a/RestAPI/Controllers/UserController.cs +++ b/RestAPI/Controllers/UserController.cs @@ -1,4 +1,5 @@ using BusinessLogic.BusinessLogic; +using BusinessLogic.Tools.Mail.MailTemplates; using Contracts.BindingModels; using Contracts.BusinessLogicContracts; using Contracts.Exceptions; @@ -7,122 +8,124 @@ using Microsoft.AspNetCore.Mvc; namespace RestAPI.Controllers { - [Route("[controller]/[action]")] - [ApiController] - public class UserController - { - private readonly ILogger _logger; - private readonly IUserLogic _userLogic; + [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; - } + 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 Login([FromBody] UserData data) + { + try + { + var res = _userLogic.Login(data.email, data.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"); - return Results.BadRequest(ex.Message); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error create 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"); + return Results.BadRequest(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error create user"); + 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); - } - } + [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); - } - } + [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); - } - } - } + [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); + } + } + } + + public record class UserData(string email, string password); } \ No newline at end of file diff --git a/WebApp/APIClient.cs b/WebApp/APIClient.cs new file mode 100644 index 0000000..3257bd9 --- /dev/null +++ b/WebApp/APIClient.cs @@ -0,0 +1,70 @@ +using Newtonsoft.Json; +using System.Net.Http.Headers; +using System.Text; + +namespace WebApp; + +public class APIClient +{ + private static readonly HttpClient _client = new(); + + public static void Connect(IConfiguration configuration) + { + _client.BaseAddress = new Uri(configuration["API"]); + _client.DefaultRequestHeaders.Accept.Clear(); + _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + } + + public static T? GetRequest(string requestUrl) + { + var response = _client.GetAsync(requestUrl); + var result = response.Result.Content.ReadAsStringAsync().Result; + if (!response.Result.IsSuccessStatusCode) + { + throw new Exception(response.Result.ReasonPhrase); + } + return JsonConvert.DeserializeObject(result); + } + + public static object? PostRequest(string requestUrl, T model) + { + var json = JsonConvert.SerializeObject(model); + var data = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = _client.PostAsync(requestUrl, data); + + var result = response.Result.Content.ReadAsStringAsync().Result; + if (!response.Result.IsSuccessStatusCode) + { + throw new Exception(response.Result.ReasonPhrase); + } + return result; + } + + public static object? DeleteRequest(string requestUrl) + { + var response = _client.DeleteAsync(requestUrl); + + var result = response.Result.Content.ReadAsStringAsync().Result; + if (!response.Result.IsSuccessStatusCode) + { + throw new Exception(response.Result.ReasonPhrase); + } + return result; + } + + public static object? PatchRequest(string requestUrl, T model) + { + var json = JsonConvert.SerializeObject(model); + var data = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = _client.PatchAsync(requestUrl, data); + + var result = response.Result.Content.ReadAsStringAsync().Result; + if (!response.Result.IsSuccessStatusCode) + { + throw new Exception(response.Result.ReasonPhrase); + } + return result; + } +} \ No newline at end of file diff --git a/WebApp/Helpers/Roles.cs b/WebApp/Helpers/Roles.cs new file mode 100644 index 0000000..084cfe6 --- /dev/null +++ b/WebApp/Helpers/Roles.cs @@ -0,0 +1,10 @@ +namespace WebApp.Helpers; + +public static class Roles +{ + public const string User = "Обычный пользователь"; + public const string Admin = "Админ"; + + // TODO: Добавить нужные роли + public const string Worker = "Сотрудник"; +} \ No newline at end of file diff --git a/WebApp/Pages/Login.cshtml b/WebApp/Pages/Login.cshtml new file mode 100644 index 0000000..d914310 --- /dev/null +++ b/WebApp/Pages/Login.cshtml @@ -0,0 +1,57 @@ +@page +@model WebApp.Pages.LoginModel +@{ + ViewData["Title"] = "Log In"; +} +
+ +
+
+
+
+ +
+ +

Log in

+ +
+ + +
+ +
+ + +
+ +
+ +
+ +

Don't have an account? Register here

+
+ +
+ +
+
+ Login image +
+
+
+
\ No newline at end of file diff --git a/WebApp/Pages/Login.cshtml.cs b/WebApp/Pages/Login.cshtml.cs new file mode 100644 index 0000000..4a53a79 --- /dev/null +++ b/WebApp/Pages/Login.cshtml.cs @@ -0,0 +1,28 @@ +using Contracts.BindingModels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; + +namespace WebApp.Pages +{ + public class LoginModel : PageModel + { + public void OnGet() + { + } + + public IActionResult OnPostAsync(string email, string password) + { + var response = APIClient.PostRequest("user/login", new { email, password }); + + if (response is null || response is not string) + { + throw new Exception("Something wrong LOL!"); + } + + this.SetJWT((string)response); + + return RedirectToPage("Index"); + } + } +} \ No newline at end of file diff --git a/WebApp/Pages/PageModelExtension.cs b/WebApp/Pages/PageModelExtension.cs new file mode 100644 index 0000000..8d8969d --- /dev/null +++ b/WebApp/Pages/PageModelExtension.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; + +namespace WebApp.Pages +{ + public static class PageModelExtension + { + public static string? GetUserId(this PageModel pageModel) + { + if (pageModel.User.Identity.IsAuthenticated) + { + var userIdClaim = pageModel.User.Claims.FirstOrDefault(c => c.Type == "userId"); + return userIdClaim?.Value; + } + return null; + } + + public static void SetJWT(this PageModel pageModel, string jwt) + { + string token = (string)JsonConvert.DeserializeObject(jwt); + // Сохраняем в кукис токен + pageModel.Response.Cookies.Append("21gunsthebest", token); + } + + public static void DeleteJWT(this PageModel pageModel) + { + pageModel.Response.Cookies.Delete("21gunsthebest"); + } + } +} \ No newline at end of file diff --git a/WebApp/Pages/Shared/_Layout.cshtml b/WebApp/Pages/Shared/_Layout.cshtml index b103339..e7560e9 100644 --- a/WebApp/Pages/Shared/_Layout.cshtml +++ b/WebApp/Pages/Shared/_Layout.cshtml @@ -3,7 +3,7 @@ - @ViewData["Title"] - WebApp + @ViewData["Title"] - 21 GUNS @@ -12,7 +12,7 @@