diff --git a/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs index 1652bf5..335ae50 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs @@ -17,15 +17,15 @@ namespace CandidateReviewBusinessLogic.BusinessLogic _logger = logger; _сompanyStorage = сompanyStorage; } - public bool Create(CompanyBindingModel model) + public int Create(CompanyBindingModel model) { CheckModel(model); if (_сompanyStorage.Insert(model) == null) { _logger.LogWarning("Insert operation failed"); - return false; + return 0; } - return true; + return model.Id; } public bool Delete(CompanyBindingModel model) diff --git a/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs index ec51487..6e0c09f 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs @@ -5,6 +5,8 @@ using CandidateReviewContracts.ViewModels; using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using CandidateReviewContracts.StoragesContracts; +using System.Text; +using System.Security.Cryptography; namespace CandidateReviewBusinessLogic.BusinessLogic { @@ -17,9 +19,17 @@ namespace CandidateReviewBusinessLogic.BusinessLogic _logger = logger; _userStorage = userStorage; } + + private string EncryptPassword(string password) + { + byte[] hashedBytes = SHA256.HashData(Encoding.UTF8.GetBytes(password)); + return Convert.ToBase64String(hashedBytes); + } public bool Create(UserBindingModel model) { CheckModel(model); + CheckPassword(model); + model.Password = EncryptPassword(model.Password); if (_userStorage.Insert(model) == null) { _logger.LogWarning("Insert operation failed"); @@ -31,6 +41,7 @@ namespace CandidateReviewBusinessLogic.BusinessLogic public bool Delete(UserBindingModel model) { CheckModel(model, false); + CheckPassword(model); _logger.LogInformation("Delete. Id: {Id}", model.Id); if (_userStorage.Delete(model) == null) { @@ -47,13 +58,28 @@ namespace CandidateReviewBusinessLogic.BusinessLogic throw new ArgumentNullException(nameof(model)); } var element = _userStorage.GetElement(model); - if (element == null) + if (element != null) { - _logger.LogWarning("ReadElement element not found"); - return null; + string hashedPassword = element.Password; + if (element != null && model.Password != element.Password && model.Password != null) + { + hashedPassword = EncryptPassword(model.Password); + } + if (element == null) + { + _logger.LogWarning("ReadElement element not found"); + return null; + } + else + { + if (element.Password == hashedPassword) + { + _logger.LogInformation("ReadElement find. Id: {Id}", element.Id); + return element; + } + } } - _logger.LogInformation("ReadElement find. Id: {Id}", element.Id); - return element; + return null; } public List? ReadList(UserSearchModel? model) @@ -71,6 +97,19 @@ namespace CandidateReviewBusinessLogic.BusinessLogic public bool Update(UserBindingModel model) { CheckModel(model); + var elem = _userStorage.GetElement(new UserSearchModel + { + Id = model.Id + }); + if (elem != null && model.Password != elem.Password) + { + if (!Regex.IsMatch(model.Password, @"^^((\w+\d+\W+)|(\w+\W+\d+)|(\d+\w+\W+)|(\d+\W+\w+)|(\W+\w+\d+)|(\W+\d+\w+))[\w\d\W]*$", RegexOptions.IgnoreCase)) + { + return false; + throw new ArgumentException("Неправильно введенный пароль", nameof(model.Password)); + } + model.Password = EncryptPassword(model.Password); + } if (_userStorage.Update(model) == null) { _logger.LogWarning("Update operation failed"); @@ -99,21 +138,11 @@ namespace CandidateReviewBusinessLogic.BusinessLogic if (string.IsNullOrEmpty(model.Name)) { throw new ArgumentNullException("Нет имени пользователя", nameof(model.Name)); - } - - if (string.IsNullOrEmpty(model.Password)) - { - throw new ArgumentNullException("Нет пароля пользователя", nameof(model.LastName)); - } + } if (string.IsNullOrEmpty(model.Email)) { - throw new ArgumentNullException("Нет почты пользователя", nameof(model.LastName)); - } - - if (string.IsNullOrEmpty(model.Password)) - { - throw new ArgumentNullException("Нет пароля пользователя", nameof(model.LastName)); + throw new ArgumentNullException("Нет почты пользователя", nameof(model.Email)); } if (!Regex.IsMatch(model.Email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.IgnoreCase)) @@ -121,11 +150,6 @@ namespace CandidateReviewBusinessLogic.BusinessLogic throw new ArgumentException("Неправильно введенный email", nameof(model.Email)); } - if (!Regex.IsMatch(model.Password, @"^^((\w+\d+\W+)|(\w+\W+\d+)|(\d+\w+\W+)|(\d+\W+\w+)|(\W+\w+\d+)|(\W+\d+\w+))[\w\d\W]*$", RegexOptions.IgnoreCase)) - { - throw new ArgumentException("Неправильно введенный пароль", nameof(model.Password)); - } - var element = _userStorage.GetElement(new UserSearchModel { Email = model.Email @@ -136,5 +160,18 @@ namespace CandidateReviewBusinessLogic.BusinessLogic throw new InvalidOperationException("Пользователь с такой почтой уже есть"); } } + + private void CheckPassword(UserBindingModel model) + { + if (string.IsNullOrEmpty(model.Password)) + { + throw new ArgumentNullException("Нет пароля пользователя", nameof(model.Password)); + } + + if (!Regex.IsMatch(model.Password, @"^^((\w+\d+\W+)|(\w+\W+\d+)|(\d+\w+\W+)|(\d+\W+\w+)|(\W+\w+\d+)|(\W+\d+\w+))[\w\d\W]*$", RegexOptions.IgnoreCase)) + { + throw new ArgumentException("Неправильно введенный пароль", nameof(model.Password)); + } + } } } diff --git a/CandidateReviewClientApp/APIClient.cs b/CandidateReviewClientApp/APIClient.cs index 7df0dbe..43edc7f 100644 --- a/CandidateReviewClientApp/APIClient.cs +++ b/CandidateReviewClientApp/APIClient.cs @@ -9,6 +9,7 @@ namespace CandidateReviewClientApp { private static readonly HttpClient _user = new(); public static UserViewModel? User { get; set; } = null; + public static CompanyViewModel? Company { get; set; } = null; public static void Connect(IConfiguration configuration) { _user.BaseAddress = new Uri(configuration["IPAddress"]); diff --git a/CandidateReviewClientApp/Controllers/CompanyController.cs b/CandidateReviewClientApp/Controllers/CompanyController.cs new file mode 100644 index 0000000..19e8a4d --- /dev/null +++ b/CandidateReviewClientApp/Controllers/CompanyController.cs @@ -0,0 +1,79 @@ +using CandidateReviewContracts.BindingModels; +using CandidateReviewContracts.ViewModels; +using Microsoft.AspNetCore.Mvc; + +namespace CandidateReviewClientApp.Controllers +{ + public class CompanyController : Controller + { + private readonly ILogger _logger; + public CompanyController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IActionResult CompanyProfile(int? id) + { + if (APIClient.User == null) + { + return Redirect("~/Home/Enter"); + } + if (id.HasValue) + { + APIClient.Company = APIClient.GetRequest($"api/company/profile?id={id}"); + } + var model = APIClient.Company; + return View(model); + } + + [HttpGet] + public IActionResult EditCompanyProfile(int? id) + { + if (APIClient.User == null) + { + return Redirect("~/Home/Enter"); + } + if (!id.HasValue) + { + return View(new CompanyViewModel()); + } + var model = APIClient.GetRequest($"api/company/profile?id={id}"); + if (model != null) + { + APIClient.PostRequest($"api/user/update", new UserBindingModel { Id = APIClient.User.Id, CompanyId = model.Id }); + } + return View(model); + } + + [HttpPost] + public void EditCompanyProfile(CompanyBindingModel model) + { + if (APIClient.User == null) + { + throw new Exception("Доступно только авторизованным пользователям"); + } + if (model.Id != 0) + { + APIClient.PostRequest("api/company/update", model); + } + else + { + APIClient.PostRequest("api/company/create", model); + } + Response.Redirect("/Home/Index"); + } + + [HttpPost] + public void Delete(int id) + { + if (APIClient.User == null) + { + throw new Exception("Доступно только авторизованным пользователям"); + } + + APIClient.PostRequest($"api/company/delete", new CompanyBindingModel { Id = id }); + Response.Redirect("/Home/Index"); + } + } +} diff --git a/CandidateReviewClientApp/Controllers/HomeController.cs b/CandidateReviewClientApp/Controllers/HomeController.cs index 666f85c..bcdabe1 100644 --- a/CandidateReviewClientApp/Controllers/HomeController.cs +++ b/CandidateReviewClientApp/Controllers/HomeController.cs @@ -2,6 +2,7 @@ using CandidateReviewClientApp; using CandidateReviewClientApp.Models; using CandidateReviewContracts.BindingModels; using CandidateReviewContracts.ViewModels; +using CandidateReviewDataModels.Enums; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; @@ -44,7 +45,7 @@ namespace CandidateReviewUserApp.Controllers { throw new Exception(" /"); } - Response.Redirect("Index"); + Response.Redirect("/Home/Index"); } [HttpGet] @@ -52,6 +53,7 @@ namespace CandidateReviewUserApp.Controllers { return View(); } + [HttpPost] public void Register(string login, string password, string surname, string name, string lastname) { @@ -59,6 +61,18 @@ namespace CandidateReviewUserApp.Controllers { throw new Exception(" , "); } + + RoleEnum role = RoleEnum.; + + if (login.Equals("tania.art03@gmail.com", StringComparison.OrdinalIgnoreCase)) + { + role = RoleEnum.; + } + else if (login.Equals("t.artamonova73@icloud.com", StringComparison.OrdinalIgnoreCase)) + { + role = RoleEnum.; + } + APIClient.PostRequest("api/user/register", new UserBindingModel { Surname = surname, @@ -67,9 +81,10 @@ namespace CandidateReviewUserApp.Controllers Email = login, Password = password, EmailConfirmed = false, - Role = CandidateReviewDataModels.Enums.RoleEnum. + Role = role }); - Response.Redirect("Enter"); + + Response.Redirect("/Home/Enter"); return; } diff --git a/CandidateReviewClientApp/Controllers/UserController.cs b/CandidateReviewClientApp/Controllers/UserController.cs new file mode 100644 index 0000000..1cbaf14 --- /dev/null +++ b/CandidateReviewClientApp/Controllers/UserController.cs @@ -0,0 +1,77 @@ +using CandidateReviewClientApp.Models; +using CandidateReviewContracts.BindingModels; +using CandidateReviewContracts.ViewModels; +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; + +namespace CandidateReviewClientApp.Controllers +{ + public class UserController : Controller + { + private readonly ILogger _logger; + + public UserController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IActionResult UserProfile(int? id) + { + var userId = id ?? APIClient.User.Id; + + var model = APIClient.GetRequest($"api/user/profile?id={userId}"); + + if (model == null) + { + return RedirectToAction("Index"); + } + + return View(model); + } + + [HttpGet] + public IActionResult UserProfileEdit() + { + if (APIClient.User == null) + { + return Redirect("/Home/Enter"); + } + return View(APIClient.User); + } + + [HttpPost] + public void UserProfileEdit(UserBindingModel model) + { + APIClient.PostRequest("api/user/update", new UserBindingModel + { + Id = model.Id, + Surname = model.Surname, + Name = model.Name, + LastName = model.LastName ?? null, + CompanyId = model.CompanyId ?? null, + Email = model.Email, + Password = model.Password, + EmailConfirmed = model.EmailConfirmed, + Role = model.Role, + AvatarFilePath = model.AvatarFilePath ?? null, + PhoneNumber = model.PhoneNumber ?? null + }); + + Response.Redirect("/User/UserProfile"); + } + + [HttpGet] + public void Logout() + { + APIClient.User = null; + Response.Redirect("/Home/Enter"); + } + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() + { + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } + } +} diff --git a/CandidateReviewClientApp/Program.cs b/CandidateReviewClientApp/Program.cs index 848fa9c..73524e5 100644 --- a/CandidateReviewClientApp/Program.cs +++ b/CandidateReviewClientApp/Program.cs @@ -25,6 +25,6 @@ app.UseAuthorization(); app.MapControllerRoute( name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); + pattern: "{controller=Home}/{action=Enter}/{id?}"); app.Run(); diff --git a/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml b/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml new file mode 100644 index 0000000..63f898f --- /dev/null +++ b/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml @@ -0,0 +1,58 @@ +@using CandidateReviewContracts.ViewModels +@model CompanyViewModel + +@{ + ViewData["Title"] = "Профиль компании"; +} + +
+
+
+
+ @if (!string.IsNullOrEmpty(Model.LogoFilePath)) + { + Логотип компании + } +
+
@Model.Name
+

@Model.Description

+ @Model.Website +
+
+
+
+
+
+

Информация о компании

+ Редактировать +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Название@Model.Name
Описание@Model.Description
Официальный сайт@Model.Website
Адрес@Model.Address
Контакты@Model.Contacts
+
+
+
+
+
diff --git a/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml b/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml new file mode 100644 index 0000000..84062ce --- /dev/null +++ b/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml @@ -0,0 +1,65 @@ +@using CandidateReviewContracts.ViewModels +@model CompanyViewModel + +@{ + var title = @Model.Id <= 0 ? "Создать профиль компании" : "Редактировать профиль компании"; +} + +
+

@title

+
+ + +
+ + +
Пожалуйста, введите название компании.
+
+ +
+ + +
+ +
+ + +
Пожалуйста, введите валидный URL (например, https://example.com).
+
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+ + + diff --git a/CandidateReviewClientApp/Views/Home/Enter.cshtml b/CandidateReviewClientApp/Views/Home/Enter.cshtml index da6af5c..5e29842 100644 --- a/CandidateReviewClientApp/Views/Home/Enter.cshtml +++ b/CandidateReviewClientApp/Views/Home/Enter.cshtml @@ -1,5 +1,6 @@ @{ ViewData["Title"] = "Вход"; + Layout = "_LoginLayout"; } diff --git a/CandidateReviewClientApp/Views/Home/Register.cshtml b/CandidateReviewClientApp/Views/Home/Register.cshtml index 99623c0..67a4af1 100644 --- a/CandidateReviewClientApp/Views/Home/Register.cshtml +++ b/CandidateReviewClientApp/Views/Home/Register.cshtml @@ -1,5 +1,6 @@ @{ ViewData["Title"] = "Регистрация"; + Layout = "_LoginLayout"; }
@@ -34,10 +35,12 @@
+
- - + \ No newline at end of file diff --git a/CandidateReviewClientApp/Views/Shared/_Layout.cshtml b/CandidateReviewClientApp/Views/Shared/_Layout.cshtml index 373c3a8..0357106 100644 --- a/CandidateReviewClientApp/Views/Shared/_Layout.cshtml +++ b/CandidateReviewClientApp/Views/Shared/_Layout.cshtml @@ -1,57 +1,52 @@ - +@using CandidateReviewDataModels.Enums + - - - - @ViewData["Title"] - CandidateReviewClientApp - - - - - - - - -
- -
-
-
- @RenderBody() -
-
- - - @await RenderSectionAsync("Scripts", required: false) - - \ No newline at end of file + + +
+
+ @RenderBody() +
+
+ +
+
+ © 2024 - Ревью кандидатов на вакансию +
+
+ @await RenderSectionAsync("Scripts", required: false) + + diff --git a/CandidateReviewClientApp/Views/Shared/_LoginLayout.cshtml b/CandidateReviewClientApp/Views/Shared/_LoginLayout.cshtml new file mode 100644 index 0000000..84acfee --- /dev/null +++ b/CandidateReviewClientApp/Views/Shared/_LoginLayout.cshtml @@ -0,0 +1,48 @@ + + + + + + @ViewData["Title"] - CandidateReviewClientApp + + + + + + + + + +
+
+ @RenderBody() +
+
+ +
+
+ © 2024 - Ревью кандидатов на вакансию +
+
+ @await RenderSectionAsync("Scripts", required: false) + + diff --git a/CandidateReviewClientApp/Views/User/UserProfile.cshtml b/CandidateReviewClientApp/Views/User/UserProfile.cshtml new file mode 100644 index 0000000..a69cdec --- /dev/null +++ b/CandidateReviewClientApp/Views/User/UserProfile.cshtml @@ -0,0 +1,52 @@ +@using CandidateReviewContracts.ViewModels +@model UserViewModel + +@{ + ViewData["Title"] = "Профиль пользователя"; +} + +
+
+
+
+ @if (!string.IsNullOrEmpty(@Model.AvatarFilePath)) + { + Аватар пользователя + } +
+
+ @Model.Name @(string.IsNullOrEmpty(@Model.Surname) ? "" : @Model.Surname) @(string.IsNullOrEmpty(@Model.LastName) ? "" : @Model.LastName) +
+
+
+
+
+
+
+

Контактная информация

+ +
+
+
+
Email
+
@Model.Email
+ + @if (!string.IsNullOrEmpty(@Model.PhoneNumber)) + { +
Телефон
+
@Model.PhoneNumber
+ } + +
Роль
+
@Model.Role
+
+
+
+
+
+
+ diff --git a/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml b/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml new file mode 100644 index 0000000..88dbca9 --- /dev/null +++ b/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml @@ -0,0 +1,99 @@ +@using CandidateReviewContracts.ViewModels +@model UserViewModel + +@{ + ViewData["Title"] = "Редактирование профиля"; +} + +
+
+
+

Редактирование профиля

+
+ + +
+ + +
Пожалуйста, введите имя.
+
+ +
+ + +
Пожалуйста, введите фамилию.
+
+ +
+ + +
Пожалуйста, введите отчество.
+
+ +
+ + +
Пожалуйста, введите телефон.
+
+ + + + + + +
+ + +
Выберите файл изображения.
+
+ +
+ + Отмена +
+
+
+
+
+ +@section Scripts { + + + + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } +} + + diff --git a/CandidateReviewContracts/BusinessLogicsContracts/ICompanyLogic.cs b/CandidateReviewContracts/BusinessLogicsContracts/ICompanyLogic.cs index e98fa94..0fb5439 100644 --- a/CandidateReviewContracts/BusinessLogicsContracts/ICompanyLogic.cs +++ b/CandidateReviewContracts/BusinessLogicsContracts/ICompanyLogic.cs @@ -8,7 +8,7 @@ namespace CandidateReviewContracts.BusinessLogicsContracts { List? ReadList(CompanySearchModel? model); CompanyViewModel? ReadElement(CompanySearchModel model); - bool Create(CompanyBindingModel model); + int Create(CompanyBindingModel model); bool Update(CompanyBindingModel model); bool Delete(CompanyBindingModel model); } diff --git a/CandidateReviewDatabaseImplement/Models/User.cs b/CandidateReviewDatabaseImplement/Models/User.cs index dafaabc..1055c04 100644 --- a/CandidateReviewDatabaseImplement/Models/User.cs +++ b/CandidateReviewDatabaseImplement/Models/User.cs @@ -92,6 +92,7 @@ namespace CandidateReviewDatabaseImplement.Models Role = model.Role; CompanyId = model.CompanyId; } + public UserViewModel GetViewModel => new() { Id = Id, diff --git a/CandidateReviewRestApi/Controllers/CompanyController.cs b/CandidateReviewRestApi/Controllers/CompanyController.cs new file mode 100644 index 0000000..e6fed6c --- /dev/null +++ b/CandidateReviewRestApi/Controllers/CompanyController.cs @@ -0,0 +1,81 @@ +using CandidateReviewContracts.BindingModels; +using CandidateReviewContracts.BusinessLogicsContracts; +using CandidateReviewContracts.SearchModels; +using CandidateReviewContracts.ViewModels; +using Microsoft.AspNetCore.Mvc; + +namespace CandidateReviewRestApi.Controllers +{ + [Route("api/[controller]/[action]")] + [ApiController] + public class CompanyController : Controller + { + private readonly ILogger _logger; + private readonly ICompanyLogic _logic; + public CompanyController(ICompanyLogic logic, ILogger logger) + { + _logger = logger; + _logic = logic; + } + + [HttpGet] + public CompanyViewModel? Profile(int id) + { + try + { + return _logic.ReadElement(new CompanySearchModel + { + Id = id + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка получения профиля компании"); + throw; + } + } + + [HttpPost] + public int Create(CompanyBindingModel model) + { + try + { + int newCompanyId = _logic.Create(model); + return newCompanyId; + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка создания профиля компании"); + throw; + } + } + + [HttpPost] + public void Update(CompanyBindingModel model) + { + try + { + _logic.Update(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка обновления профиля компании"); + throw; + } + } + + [HttpDelete] + public void Delete(CompanyBindingModel model) + { + try + { + _logic.Delete(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка удаления профиля компании"); + throw; + } + } + } +} diff --git a/CandidateReviewRestApi/Controllers/UserController.cs b/CandidateReviewRestApi/Controllers/UserController.cs index ac14e10..29f0ae5 100644 --- a/CandidateReviewRestApi/Controllers/UserController.cs +++ b/CandidateReviewRestApi/Controllers/UserController.cs @@ -36,6 +36,23 @@ namespace CandidateReviewRestApi.Controllers } } + [HttpGet] + public UserViewModel? Profile(int id) + { + try + { + return _logic.ReadElement(new UserSearchModel + { + Id = id + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка получения данных"); + throw; + } + } + [HttpPost] public void Register(UserBindingModel model) { @@ -51,7 +68,7 @@ namespace CandidateReviewRestApi.Controllers } [HttpPost] - public void UpdateData(UserBindingModel model) + public void Update(UserBindingModel model) { try {