From e18de8db1da96df55c5cc59904081b22e5b8da6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B0=D1=82=D1=8C=D1=8F=D0=BD=D0=B0=20=D0=90=D1=80?= =?UTF-8?q?=D1=82=D0=B0=D0=BC=D0=BE=D0=BD=D0=BE=D0=B2=D0=B0?= Date: Mon, 9 Dec 2024 02:10:45 +0400 Subject: [PATCH] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B4=D0=BE=D0=BB=D0=B6?= =?UTF-8?q?=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=D0=B4=20=D0=BE=D1=86=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=D0=B9=20=D1=80=D0=B5=D0=B7=D1=8E=D0=BC=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BusinessLogic/AssessmentLogic.cs | 10 -- .../BusinessLogic/CompanyLogic.cs | 7 +- .../BusinessLogic/CriterionLogic.cs | 10 -- .../BusinessLogic/ResumeLogic.cs | 37 +++- .../BusinessLogic/UserLogic.cs | 69 +++++--- .../BusinessLogic/VacancyLogic.cs | 42 ++++- CandidateReviewClientApp/APIClient.cs | 77 +++++++- .../Controllers/AssessmentController.cs | 79 +++++++++ .../Controllers/CompanyController.cs | 80 ++++++--- .../Controllers/CriterionController.cs | 79 +++++++++ .../Controllers/HomeController.cs | 94 ++++++---- .../Controllers/ResumeController.cs | 159 +++++++++++++++++ .../Controllers/UserController.cs | 104 +++++------ .../Controllers/VacancyController.cs | 105 ++++++----- .../Models/ErrorViewModel.cs | 2 +- .../Views/Company/CompanyProfile.cshtml | 106 ++++------- .../Views/Company/EditCompanyProfile.cshtml | 18 +- .../Views/Criterion/ManageCriterions.cshtml | 73 ++++++++ .../Views/Home/Enter.cshtml | 12 ++ .../Views/Home/Index.cshtml | 47 ++++- .../Views/Resume/EditResume.cshtml | 81 +++++++++ .../Views/Resume/ResumeDetails.cshtml | 166 ++++++++++++++++++ .../Views/Shared/Error.cshtml | 58 +++--- .../Views/Shared/_Layout.cshtml | 3 +- .../Views/Shared/_VacanciesTable.cshtml | 33 ++++ .../Views/User/UserProfile.cshtml | 58 +++++- .../Views/User/UserProfileEdit.cshtml | 101 +++++------ .../Views/Vacancy/EditVacancy.cshtml | 17 +- .../Views/Vacancy/SearchVacancies.cshtml | 9 +- .../Views/Vacancy/VacancyDetails.cshtml | 62 ++++++- .../images/default-company-logo.png | Bin 14369 -> 0 bytes .../BindingModels/AssessmentBindingModel.cs | 9 +- .../BindingModels/CriterionBindingModel.cs | 9 +- .../BusinessLogicsContracts/ICompanyLogic.cs | 2 +- .../SearchModels/AssessmentSearchModel.cs | 3 +- .../SearchModels/VacancySearchModel.cs | 6 +- .../StoragesContracts/ICompanyStorage.cs | 2 +- .../ViewModels/AssessmentViewModel.cs | 12 +- .../ViewModels/CriterionViewModel.cs | 9 +- .../ViewModels/ResumeViewModel.cs | 5 + .../ViewModels/UserViewModel.cs | 2 + .../ViewModels/VacancyViewModel.cs | 4 + .../Enums/CriterionTypeEnum.cs | 10 -- .../Enums/JobTypeEnum.cs | 12 +- .../Enums/ResumeStatusEnum.cs | 11 +- .../Models/IAssessmentModel.cs | 6 +- .../Models/ICriterionModel.cs | 7 +- .../Implements/CompanyStorage.cs | 18 +- .../Implements/ResumeStorage.cs | 31 ++-- .../Implements/UserStorage.cs | 15 +- .../Implements/VacancyStorage.cs | 14 +- ... 20241203082827_InitialCreate.Designer.cs} | 79 ++------- ...ate.cs => 20241203082827_InitialCreate.cs} | 113 ++++++------ .../CandidateReviewDatabaseModelSnapshot.cs | 77 ++------ .../Models/Assessment.cs | 15 +- .../Models/Company.cs | 6 - .../Models/Criterion.cs | 25 +-- .../Models/Resume.cs | 2 - .../Models/User.cs | 6 - .../Models/Vacancy.cs | 3 - .../Controllers/AssessmentController.cs | 80 +++++++++ .../Controllers/CompanyController.cs | 28 ++- .../Controllers/CriterionController.cs | 95 ++++++++++ .../Controllers/ResumeController.cs | 98 +++++++++++ .../Controllers/VacancyController.cs | 3 +- 65 files changed, 1856 insertions(+), 759 deletions(-) create mode 100644 CandidateReviewClientApp/Controllers/AssessmentController.cs create mode 100644 CandidateReviewClientApp/Controllers/CriterionController.cs create mode 100644 CandidateReviewClientApp/Controllers/ResumeController.cs create mode 100644 CandidateReviewClientApp/Views/Criterion/ManageCriterions.cshtml create mode 100644 CandidateReviewClientApp/Views/Resume/EditResume.cshtml create mode 100644 CandidateReviewClientApp/Views/Resume/ResumeDetails.cshtml create mode 100644 CandidateReviewClientApp/Views/Shared/_VacanciesTable.cshtml delete mode 100644 CandidateReviewClientApp/images/default-company-logo.png delete mode 100644 CandidateReviewDataModels/Enums/CriterionTypeEnum.cs rename CandidateReviewDatabaseImplement/Migrations/{20241105152221_InitialCreate.Designer.cs => 20241203082827_InitialCreate.Designer.cs} (84%) rename CandidateReviewDatabaseImplement/Migrations/{20241105152221_InitialCreate.cs => 20241203082827_InitialCreate.cs} (91%) create mode 100644 CandidateReviewRestApi/Controllers/AssessmentController.cs create mode 100644 CandidateReviewRestApi/Controllers/CriterionController.cs create mode 100644 CandidateReviewRestApi/Controllers/ResumeController.cs diff --git a/CandidateReviewBusinessLogic/BusinessLogic/AssessmentLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/AssessmentLogic.cs index f0df265..fe5efbb 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/AssessmentLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/AssessmentLogic.cs @@ -91,16 +91,6 @@ namespace CandidateReviewBusinessLogic.BusinessLogic return; } - if (model.ResumeId <= 0) - { - throw new ArgumentException("Нет идентификатора резюме", nameof(model.ResumeId)); - } - - if (model.UserId <= 0) - { - throw new ArgumentException("Нет идентификатора пользователя", nameof(model.UserId)); - } - var element = _assessmentStorage.GetElement(new AssessmentSearchModel { ResumeId = model.ResumeId, diff --git a/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs index 9038cf2..909a826 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs @@ -21,15 +21,16 @@ namespace CandidateReviewBusinessLogic.BusinessLogic _vacancyStorage = vacancyStorage; _userStorage = userStorage; } - public int Create(CompanyBindingModel model) + public int? Create(CompanyBindingModel model) { CheckModel(model); - if (_сompanyStorage.Insert(model) == null) + var companyId = _сompanyStorage.Insert(model); + if (companyId == null) { _logger.LogWarning("Insert operation failed"); return 0; } - return model.Id; + return companyId; } public bool Delete(CompanyBindingModel model) diff --git a/CandidateReviewBusinessLogic/BusinessLogic/CriterionLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/CriterionLogic.cs index f4ec282..fa65ebb 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/CriterionLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/CriterionLogic.cs @@ -95,16 +95,6 @@ namespace CandidateReviewBusinessLogic.BusinessLogic { throw new ArgumentNullException("Нет названия критерия оценивания", nameof(model.Name)); } - - if (model.Weight <= 0) - { - throw new ArgumentException("Некорректный вес критерия оценивания", nameof(model.Weight)); - } - - if (string.IsNullOrEmpty(model.Type.ToString())) - { - throw new ArgumentNullException("Нет типа критерия оценивания", nameof(model.Type)); - } } } } diff --git a/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs index a65dbea..e4c36a2 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs @@ -11,10 +11,16 @@ namespace CandidateReviewBusinessLogic.BusinessLogic { private readonly ILogger _logger; private readonly IResumeStorage _resumeStorage; - public ResumeLogic(ILogger logger, IResumeStorage resumeStorage) + private readonly IVacancyStorage _vacancyStorage; + private readonly IUserStorage _userStorage; + private readonly IAssessmentStorage _assessmentStorage; + public ResumeLogic(ILogger logger, IResumeStorage resumeStorage, IUserStorage userStorage, IVacancyStorage vacancyStorage, IAssessmentStorage assessmentStorage) { _logger = logger; _resumeStorage = resumeStorage; + _userStorage = userStorage; + _vacancyStorage = vacancyStorage; + _assessmentStorage = assessmentStorage; } public bool Create(ResumeBindingModel model) { @@ -51,8 +57,35 @@ namespace CandidateReviewBusinessLogic.BusinessLogic _logger.LogWarning("ReadElement element not found"); return null; } + + var assessments = _assessmentStorage.GetFilteredList(new AssessmentSearchModel { UserId = element.Id }); + var assessmentViewModels = assessments?.Select(a => new AssessmentViewModel + { + Id = a.Id, + ResumeId = a.ResumeId, + UserId = a.UserId, + Comment = a.Comment, + AssessmentCriterions = a.AssessmentCriterions + }).ToList() ?? new List(); + + var resumeViewModel = new ResumeViewModel + { + Id = element.Id, + VacancyId = element.VacancyId, + VacancyName = _vacancyStorage.GetElement(new VacancySearchModel { Id = element.VacancyId }).JobTitle, + UserId = element.UserId, + UserName = _userStorage.GetElement(new UserSearchModel { Id = element.UserId }).Name + " " + _userStorage.GetElement(new UserSearchModel { Id = element.UserId }).Surname, + Title = element.Title, + Experience = element.Experience, + Education = element.Education, + PhotoFilePath = element.PhotoFilePath, + Description = element.Description, + Skills = element.Skills, + Status = element.Status, + PreviousAssessments = assessmentViewModels + }; _logger.LogInformation("ReadElement find. Id: {Id}", element.Id); - return element; + return resumeViewModel; } public List? ReadList(ResumeSearchModel? model) diff --git a/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs index b515749..f4656d5 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs @@ -14,10 +14,14 @@ namespace CandidateReviewBusinessLogic.BusinessLogic { private readonly ILogger _logger; private readonly IUserStorage _userStorage; - public UserLogic(ILogger logger, IUserStorage userStorage) + private readonly IResumeStorage _resumeStorage; + private readonly IVacancyStorage _vacancyStorage; + public UserLogic(ILogger logger, IUserStorage userStorage, IResumeStorage resumeStorage, IVacancyStorage vacancyStorage) { _logger = logger; _userStorage = userStorage; + _resumeStorage = resumeStorage; + _vacancyStorage = vacancyStorage; } private string EncryptPassword(string password) @@ -57,28 +61,50 @@ namespace CandidateReviewBusinessLogic.BusinessLogic throw new ArgumentNullException(nameof(model)); } var element = _userStorage.GetElement(model); - if (element != null) + if (element == 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.LogWarning("ReadElement: User not found for Id: {Id}", model.Id); + return null; } - return null; + var resumes = _resumeStorage.GetFilteredList(new ResumeSearchModel { UserId = element.Id }); + var resumeViewModels = resumes?.Select(r => new ResumeViewModel + { + Id = r.Id, + VacancyId = r.VacancyId, + VacancyName = _vacancyStorage.GetElement(new VacancySearchModel { Id = r.VacancyId }).JobTitle, + UserId = r.UserId, + Title = r.Title, + Experience = r.Experience, + Education = r.Education, + PhotoFilePath = r.PhotoFilePath, + Description = r.Description, + Skills = r.Skills, + Status = r.Status + }).ToList() ?? new List(); + + string hashedPassword = element.Password; + if (model.Password != element.Password && model.Password != null) + { + hashedPassword = EncryptPassword(model.Password); + } + + var userViewModel = new UserViewModel + { + Id = element.Id, + Surname = element.Surname, + Name = element.Name, + LastName = element.LastName, + Email = element.Email, + Password = hashedPassword, + EmailConfirmed = element.EmailConfirmed, + AvatarFilePath = element.AvatarFilePath, + CompanyId = element.CompanyId, + PhoneNumber = element.PhoneNumber, + Role = element.Role, + Resumes = resumeViewModels + }; + _logger.LogInformation("ReadElement: User found. Id: {Id}", element.Id); + return userViewModel; } public List? ReadList(UserSearchModel? model) @@ -104,7 +130,6 @@ namespace CandidateReviewBusinessLogic.BusinessLogic { 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); diff --git a/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs index 93e2da7..88f2d2c 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs @@ -12,10 +12,15 @@ namespace CandidateReviewBusinessLogic.BusinessLogic { private readonly ILogger _logger; private readonly IVacancyStorage _vacancyStorage; - public VacancyLogic(ILogger logger, IVacancyStorage vacancyStorage) + private readonly IResumeStorage _resumeStorage; + private readonly ICompanyStorage _companyStorage; + + public VacancyLogic(ILogger logger, IVacancyStorage vacancyStorage, IResumeStorage resumeStorage, ICompanyStorage companyStorage) { _logger = logger; _vacancyStorage = vacancyStorage; + _resumeStorage = resumeStorage; + _companyStorage = companyStorage; } public bool Create(VacancyBindingModel model) { @@ -52,8 +57,41 @@ namespace CandidateReviewBusinessLogic.BusinessLogic _logger.LogWarning("ReadElement element not found"); return null; } + var resumes = _resumeStorage.GetFilteredList(new ResumeSearchModel { VacancyId = element.Id }); + var resumeViewModels = resumes?.Select(r => new ResumeViewModel + { + Id = r.Id, + VacancyId = r.VacancyId, + VacancyName = _vacancyStorage.GetElement(new VacancySearchModel { Id = r.VacancyId }).JobTitle, + UserName = r.UserName, + UserId = r.UserId, + Title = r.Title, + Experience = r.Experience, + Education = r.Education, + PhotoFilePath = r.PhotoFilePath, + Description = r.Description, + Skills = r.Skills, + Status = r.Status + }).ToList() ?? new List(); + + var vacancyViewModel = new VacancyViewModel + { + Id = element.Id, + CompanyId = element.CompanyId, + CompanyName = _companyStorage.GetElement(new CompanySearchModel { Id = element.CompanyId}).Name, + CreatedAt = element.CreatedAt, + Description = element.Description, + JobTitle = element.JobTitle, + JobType = element.JobType, + Requirements = element.Requirements, + Responsibilities = element.Responsibilities, + Salary = element.Salary, + Status = element.Status, + Tags = element.Tags, + Resumes = resumeViewModels + }; _logger.LogInformation("ReadElement find. Id: {Id}", element.Id); - return element; + return vacancyViewModel; } public List? ReadList(VacancySearchModel? model) diff --git a/CandidateReviewClientApp/APIClient.cs b/CandidateReviewClientApp/APIClient.cs index 5ecd33c..970a44f 100644 --- a/CandidateReviewClientApp/APIClient.cs +++ b/CandidateReviewClientApp/APIClient.cs @@ -1,4 +1,5 @@ -using CandidateReviewContracts.ViewModels; +using CandidateReviewContracts.BindingModels; +using CandidateReviewContracts.ViewModels; using Newtonsoft.Json; using System.Net.Http.Headers; using System.Text; @@ -10,7 +11,6 @@ namespace CandidateReviewClientApp private static readonly HttpClient _user = new(); public static UserViewModel? User { get; set; } = null; public static CompanyViewModel? Company { get; set; } = null; - public static VacancyViewModel? Vacancy { get; set; } = null; public static void Connect(IConfiguration configuration) { _user.BaseAddress = new Uri(configuration["IPAddress"]); @@ -30,6 +30,36 @@ namespace CandidateReviewClientApp throw new Exception(result); } } + + public static async Task GetRequestAsync(string requestUrl) + { + try + { + // Асинхронный запрос + var response = await _user.GetAsync(requestUrl); + + // Чтение содержимого ответа + var result = await response.Content.ReadAsStringAsync(); + + // Проверка статуса ответа + if (response.IsSuccessStatusCode) + { + // Десериализация результата + return JsonConvert.DeserializeObject(result); + } + else + { + throw new Exception(result); + } + } + catch (Exception ex) + { + // Логирование или дополнительная обработка исключений, если требуется + throw new Exception($"Ошибка при выполнении запроса к {requestUrl}: {ex.Message}", ex); + } + } + + public static void PostRequest(string requestUrl, T model) { var json = JsonConvert.SerializeObject(model); @@ -41,5 +71,48 @@ namespace CandidateReviewClientApp throw new Exception(result); } } + + public static async Task PostRequestAsync(string requestUrl, object model) + { + var json = JsonConvert.SerializeObject(model); + var data = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await _user.PostAsync(requestUrl, data); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new Exception($"HTTP Error {response.StatusCode}: {errorContent}"); + } + + var responseJson = await response.Content.ReadAsStringAsync(); + dynamic responseObject = JsonConvert.DeserializeObject(responseJson); + + try + { + return (int)responseObject.id; + } + catch (Exception ex) + { + throw new Exception($"Could not parse ID from response: {responseJson}, Error: {ex.Message}"); + } + } + + public static async Task PostRequestAsynchron(string requestUrl, object model) + { + var json = JsonConvert.SerializeObject(model); + var data = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await _user.PostAsync(requestUrl, data); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new Exception($"HTTP Error {response.StatusCode}: {errorContent}"); + } + + var responseJson = await response.Content.ReadAsStringAsync(); + dynamic responseObject = JsonConvert.DeserializeObject(responseJson); + } } } diff --git a/CandidateReviewClientApp/Controllers/AssessmentController.cs b/CandidateReviewClientApp/Controllers/AssessmentController.cs new file mode 100644 index 0000000..d7b9c31 --- /dev/null +++ b/CandidateReviewClientApp/Controllers/AssessmentController.cs @@ -0,0 +1,79 @@ +using CandidateReviewClientApp.Models; +using CandidateReviewContracts.BindingModels; +using CandidateReviewContracts.ViewModels; +using CandidateReviewDataModels.Models; +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; + +namespace CandidateReviewClientApp.Controllers +{ + public class AssessmentController : Controller + { + private readonly ILogger _logger; + + public AssessmentController(ILogger logger) + { + _logger = logger; + } + + [HttpPost] + public IActionResult AddAssessment(int resumeId, string comment, Dictionary criteriaValues) + { + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try + { + var userId = APIClient.User?.Id; + var allCriterions = APIClient.GetRequest>("api/criterion/list"); + if (allCriterions == null || !allCriterions.Any()) + { + throw new Exception("Критерии не найдены"); + } + + criteriaValues = criteriaValues.Where(kvp => kvp.Key != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + var assessmentCriterions = criteriaValues + .ToDictionary( + kvp => kvp.Key, + kvp => + { + var criterionViewModel = allCriterions.FirstOrDefault(c => c.Id == kvp.Key); + if (criterionViewModel == null) + { + throw new InvalidOperationException($"Критерий с ID {kvp.Key} не найден."); + } + + var criterionModel = new CriterionViewModel + { + Id = criterionViewModel.Id, + Name = criterionViewModel.Name + }; + + return ((ICriterionModel)criterionModel, kvp.Value); + } + ); + + APIClient.PostRequest("api/assessment/create", new AssessmentBindingModel + { + ResumeId = resumeId, + UserId = userId, + Comment = comment, + AssessmentCriterions = assessmentCriterions + }); + + return Redirect($"~/Resume/ResumeDetails/{resumeId}"); + } + catch (Exception ex) + { + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); + } + } + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error(string errorMessage, string returnUrl) + { + ViewBag.ErrorMessage = errorMessage ?? "Произошла непредвиденная ошибка."; + ViewBag.ReturnUrl = returnUrl; + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } + } +} diff --git a/CandidateReviewClientApp/Controllers/CompanyController.cs b/CandidateReviewClientApp/Controllers/CompanyController.cs index ea28f6a..abf83e8 100644 --- a/CandidateReviewClientApp/Controllers/CompanyController.cs +++ b/CandidateReviewClientApp/Controllers/CompanyController.cs @@ -1,6 +1,9 @@ -using CandidateReviewContracts.BindingModels; +using CandidateReviewClientApp.Models; +using CandidateReviewContracts.BindingModels; using CandidateReviewContracts.ViewModels; using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; +using System.Xml.Linq; namespace CandidateReviewClientApp.Controllers { @@ -38,42 +41,53 @@ namespace CandidateReviewClientApp.Controllers { 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, - Surname = APIClient.User.Surname, - Name = APIClient.User.Name, - LastName = APIClient.User.LastName, - Email = APIClient.User.Email, - Password = APIClient.User.Password, - EmailConfirmed = APIClient.User.EmailConfirmed, - Role = APIClient.User.Role, - AvatarFilePath = APIClient.User.AvatarFilePath, - PhoneNumber = APIClient.User.PhoneNumber - }); - } + var model = APIClient.GetRequest($"api/company/profile?id={id}"); return View(model); } [HttpPost] - public void EditCompanyProfile(CompanyBindingModel model) + public async Task EditCompanyProfile(CompanyBindingModel model) { - if (APIClient.User == null) + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try { - throw new Exception("Доступно только авторизованным пользователям"); + if (APIClient.User == null) + { + throw new Exception("Доступно только авторизованным пользователям"); + } + if (model.Id != 0) + { + APIClient.PostRequest("api/company/update", model); + } + else + { + var companyId = await APIClient.PostRequestAsync("api/company/create", model); + APIClient.PostRequest("api/user/update", new UserBindingModel + { + Id = APIClient.User.Id, + Surname = APIClient.User.Surname, + Name = APIClient.User.Name, + LastName = APIClient.User.LastName, + CompanyId = companyId, + Email = APIClient.User.Email, + Password = APIClient.User.Password, + EmailConfirmed = APIClient.User.EmailConfirmed, + Role = APIClient.User.Role, + AvatarFilePath = APIClient.User.AvatarFilePath, + PhoneNumber = APIClient.User.PhoneNumber + }); + APIClient.Company = APIClient.GetRequest($"api/company/profile?id={companyId}"); + } + if (APIClient.Company == null) + { + throw new Exception("Компания не определена"); + } + return Redirect($"~/Company/CompanyProfile/{APIClient.Company.Id}"); } - if (model.Id != 0) + catch (Exception ex) { - APIClient.PostRequest("api/company/update", model); - } - else - { - APIClient.PostRequest("api/company/create", model); - } - Response.Redirect($"/Company/CompanyProfile/{model.Id}"); + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); + } } [HttpPost] @@ -87,5 +101,13 @@ namespace CandidateReviewClientApp.Controllers APIClient.PostRequest($"api/company/delete", new CompanyBindingModel { Id = id }); Response.Redirect("/Home/Index"); } + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error(string errorMessage, string returnUrl) + { + ViewBag.ErrorMessage = errorMessage ?? "Произошла непредвиденная ошибка."; + ViewBag.ReturnUrl = returnUrl; + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } } } diff --git a/CandidateReviewClientApp/Controllers/CriterionController.cs b/CandidateReviewClientApp/Controllers/CriterionController.cs new file mode 100644 index 0000000..f6d141b --- /dev/null +++ b/CandidateReviewClientApp/Controllers/CriterionController.cs @@ -0,0 +1,79 @@ +using CandidateReviewClientApp.Models; +using CandidateReviewContracts.BindingModels; +using CandidateReviewContracts.ViewModels; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using System.Diagnostics; + +namespace CandidateReviewClientApp.Controllers +{ + public class CriterionController : Controller + { + private readonly ILogger _logger; + + public CriterionController(ILogger logger) + { + _logger = logger; + } + + [HttpPost] + public async Task AddCriterion(string name) + { + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try + { + if (string.IsNullOrEmpty(name)) + { + throw new Exception("Название критерия не может быть пустым"); + } + await APIClient.PostRequestAsynchron("api/criterion/create", new CriterionBindingModel + { + Name = name + }); + + return Redirect("/Criterion/ManageCriterions"); + } + catch (Exception ex) + { + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); + } + } + + [HttpGet] + public async Task ManageCriterions() + { + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try + { + var criterions = await APIClient.GetRequestAsync>("api/criterion/list"); + return View(criterions); + } + catch (Exception ex) + { + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); + } + } + + public IActionResult Delete(int id) + { + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try + { + APIClient.PostRequest($"api/criterion/delete", new CriterionBindingModel { Id = id }); + return Redirect("~/Criterion/ManageCriterions"); + } + catch (Exception ex) + { + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); + } + } + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error(string errorMessage, string returnUrl) + { + ViewBag.ErrorMessage = errorMessage ?? "Произошла непредвиденная ошибка."; + ViewBag.ReturnUrl = returnUrl; + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } + } +} diff --git a/CandidateReviewClientApp/Controllers/HomeController.cs b/CandidateReviewClientApp/Controllers/HomeController.cs index 9c4c68d..66ec28b 100644 --- a/CandidateReviewClientApp/Controllers/HomeController.cs +++ b/CandidateReviewClientApp/Controllers/HomeController.cs @@ -29,23 +29,31 @@ namespace CandidateReviewUserApp.Controllers } [HttpPost] - public void Enter(string login, string password) + public async Task Enter(string login, string password) { - if (string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password)) + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try { - throw new Exception(" "); + if (string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password)) + { + throw new Exception(" "); + } + APIClient.User = await APIClient.GetRequestAsync($"api/user/login?login={login}&password={password}"); + if (APIClient.User == null) + { + throw new Exception(" /"); + } + if (APIClient.User?.CompanyId != null) + { + APIClient.Company = await APIClient.GetRequestAsync($"api/company/profile?id={APIClient.User?.CompanyId}"); + } + + return RedirectToAction("Index"); } - APIClient.User = APIClient.GetRequest($"api/user/login?login={login}&password={password}"); - if (APIClient.User == null) + catch (Exception ex) { - throw new Exception(" /"); + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); } - if (APIClient.User?.CompanyId != null) - { - APIClient.Company = APIClient.GetRequest($"api/company/profile?id={APIClient.User?.CompanyId}"); - } - - Response.Redirect("/Home/Index"); } [HttpGet] @@ -55,42 +63,50 @@ namespace CandidateReviewUserApp.Controllers } [HttpPost] - public void Register(string login, string password, string surname, string name, string lastname) + public IActionResult Register(string login, string password, string surname, string name, string lastname) { - if (string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(surname) || string.IsNullOrEmpty(name)) + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try { - throw new Exception(" , "); + if (string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(surname) || string.IsNullOrEmpty(name)) + { + throw new Exception(" , "); + } + + RoleEnum role = RoleEnum.; + + if (login.Equals("tania.art03@gmail.com", StringComparison.OrdinalIgnoreCase)) + { + role = RoleEnum.; + } + else + { + role = RoleEnum.; + } + APIClient.PostRequest("api/user/register", new UserBindingModel + { + Surname = surname, + Name = name, + LastName = lastname ?? null, + Email = login, + Password = password, + EmailConfirmed = false, + Role = role + }); + + return RedirectToAction("Enter"); } - - RoleEnum role = RoleEnum.; - - if (login.Equals("tania.art03@gmail.com", StringComparison.OrdinalIgnoreCase)) + catch (Exception ex) { - role = RoleEnum.; + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); } - else if (login.Equals("t.artamonova73@icloud.com", StringComparison.OrdinalIgnoreCase)) - { - role = RoleEnum.; - } - - APIClient.PostRequest("api/user/register", new UserBindingModel - { - Surname = surname, - Name = name, - LastName = lastname ?? null, - Email = login, - Password = password, - EmailConfirmed = false, - Role = role - }); - - Response.Redirect("/Home/Enter"); - return; } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() + public IActionResult Error(string errorMessage, string returnUrl) { + ViewBag.ErrorMessage = errorMessage ?? " ."; + ViewBag.ReturnUrl = returnUrl; return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } diff --git a/CandidateReviewClientApp/Controllers/ResumeController.cs b/CandidateReviewClientApp/Controllers/ResumeController.cs new file mode 100644 index 0000000..0380062 --- /dev/null +++ b/CandidateReviewClientApp/Controllers/ResumeController.cs @@ -0,0 +1,159 @@ +using CandidateReviewClientApp.Models; +using CandidateReviewContracts.BindingModels; +using CandidateReviewContracts.ViewModels; +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; + +namespace CandidateReviewClientApp.Controllers +{ + public class ResumeController : Controller + { + private readonly ILogger _logger; + + public ResumeController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public async Task ResumeDetails(int? id) + { + if (APIClient.User == null) + { + return Redirect("~/Home/Enter"); + } + + var resume = await APIClient.GetRequestAsync($"api/resume/details?id={id}"); + if (resume == null || !id.HasValue) + { + return View(); + } + + var criterions = APIClient.GetRequest>($"api/criterion/list"); + + ViewBag.Criterions = criterions; + return View(resume); + } + + [HttpGet] + public async Task EditResume(int? id, int? vacancyId) + { + if (APIClient.User == null) + { + return RedirectToAction("Enter", "Home"); + } + + ResumeViewModel model; + if (id.HasValue) + { + model = await APIClient.GetRequestAsync($"api/resume/details?id={id}"); + if (model == null) + { + return NotFound(); + } + } + else + { + model = new ResumeViewModel { UserId = APIClient.User.Id, VacancyId = vacancyId ?? 0 }; + } + return View(model); + } + + [HttpPost] + public IActionResult EditResume(ResumeBindingModel model, bool isDraft) + { + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try + { + if (APIClient.User == null) + { + throw new Exception("Доступно только авторизованным пользователям"); + } + if (model.Id != 0) + { + if (isDraft) + { + model.Status = CandidateReviewDataModels.Enums.ResumeStatusEnum.Черновик; + } + else + { + model.Status = CandidateReviewDataModels.Enums.ResumeStatusEnum.Обрабатывается; + } + APIClient.PostRequest("api/resume/update", model); + } + else + { + model.UserId = APIClient.User.Id; + if (isDraft) + { + model.Status = CandidateReviewDataModels.Enums.ResumeStatusEnum.Черновик; + } + else + { + model.Status = CandidateReviewDataModels.Enums.ResumeStatusEnum.Обрабатывается; + } + var vacancy = APIClient.GetRequest($"api/vacancy/details?id={model.VacancyId}"); + var resume = APIClient.GetRequest($"api/resume/check?userId={model.UserId}&vacancyId={model.VacancyId}"); + if (resume == null) + { + APIClient.PostRequest("api/resume/create", model); + if (APIClient.User != null) + { + APIClient.User?.Resumes.Add(new ResumeViewModel + { + Id = model.Id + }); + } + else + { + throw new Exception("Пользователь не найден"); + } + if (vacancy != null) + { + vacancy.Resumes.Add(new ResumeViewModel + { + Id = model.Id + }); + } + else + { + throw new Exception("Вакансия не найдена"); + } + } + else + { + throw new Exception("Вы уже создавали резюме на эту вакансию!"); + } + } + return Redirect($"~/User/UserProfile/{model.UserId}"); + } + catch (Exception ex) + { + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); + } + } + + public IActionResult Delete(int id) + { + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try + { + APIClient.PostRequest($"api/resume/delete", new ResumeBindingModel { Id = id }); + return Redirect("~/User/UserProfile"); + } + catch (Exception ex) + { + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); + } + } + + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error(string errorMessage, string returnUrl) + { + ViewBag.ErrorMessage = errorMessage ?? "Произошла непредвиденная ошибка."; + ViewBag.ReturnUrl = returnUrl; + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } + } +} diff --git a/CandidateReviewClientApp/Controllers/UserController.cs b/CandidateReviewClientApp/Controllers/UserController.cs index 9f5cdff..3c2383c 100644 --- a/CandidateReviewClientApp/Controllers/UserController.cs +++ b/CandidateReviewClientApp/Controllers/UserController.cs @@ -18,13 +18,11 @@ namespace CandidateReviewClientApp.Controllers [HttpGet] public IActionResult UserProfile(int? id) { - var userId = id ?? APIClient.User?.Id; - - var model = APIClient.GetRequest($"api/user/profile?id={userId}"); + var model = APIClient.GetRequest($"api/user/profile?id={id}"); if (model == null) { - return RedirectToAction("/Home/Index"); + return Redirect("/Home/Index"); } return View(model); @@ -60,54 +58,69 @@ namespace CandidateReviewClientApp.Controllers } [HttpPost] - public void UserProfileEdit(UserBindingModel model) + public IActionResult UserProfileEdit(UserBindingModel model) { - if (model.Id != 0) + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try { - APIClient.PostRequest("api/user/update", model); - } - else - { - APIClient.PostRequest("api/user/register", model); - if (APIClient.Company != null) + if (model.Id != 0) { - APIClient.Company?.Employees.Add(new UserViewModel - { - Id = model.Id, - Surname = model.Surname, - Name = model.Name, - LastName = model.LastName, - CompanyId = model.CompanyId, - Email = model.Email, - Password = model.Password, - EmailConfirmed = model.EmailConfirmed, - Role = CandidateReviewDataModels.Enums.RoleEnum.Сотрудник, - AvatarFilePath = model.AvatarFilePath, - PhoneNumber = model.PhoneNumber - }); + APIClient.PostRequest("api/user/update", model); } - Response.Redirect($"/Company/CompanyProfile/{model.CompanyId}"); - return; - } - - Response.Redirect($"/User/UserProfile/{model.Id}"); + else + { + APIClient.PostRequest("api/user/register", model); + if (APIClient.Company != null) + { + APIClient.Company?.Employees.Add(new UserViewModel + { + Id = model.Id, + Surname = model.Surname, + Name = model.Name, + LastName = model.LastName, + CompanyId = APIClient.Company.Id, + Email = model.Email, + Password = model.Password, + EmailConfirmed = model.EmailConfirmed, + Role = CandidateReviewDataModels.Enums.RoleEnum.Сотрудник, + AvatarFilePath = model.AvatarFilePath, + PhoneNumber = model.PhoneNumber + }); + } + return Redirect($"~/Company/CompanyProfile/{model.CompanyId}"); + } + return Redirect($"/User/UserProfile/{model.Id}"); + } + catch (Exception ex) + { + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); + } } public IActionResult DeleteEmployee(int id) { - APIClient.PostRequest("api/user/delete", new UserBindingModel + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try { - Id = id - }); - APIClient.Company = APIClient.GetRequest($"api/company/profile?id={APIClient.User?.CompanyId}"); + APIClient.PostRequest("api/user/delete", new UserBindingModel + { + Id = id + }); + APIClient.Company = APIClient.GetRequest($"api/company/profile?id={APIClient.User?.CompanyId}"); - return Redirect($"~/Company/CompanyProfile"); + return Redirect($"~/Company/CompanyProfile"); + } + catch (Exception ex) + { + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); + } } [HttpGet] public void Logout() { APIClient.User = null; + APIClient.Company = null; Response.Redirect("/Home/Enter"); } @@ -119,26 +132,15 @@ namespace CandidateReviewClientApp.Controllers throw new Exception("Доступно только авторизованным пользователям"); } - APIClient.PostRequest($"api/user/delete", new UserBindingModel { - Id = model.Id, - Surname = model.Surname, - Name = model.Name, - LastName = model.LastName, - CompanyId = model.CompanyId, - Email = model.Email, - Password = model.Password, - EmailConfirmed = model.EmailConfirmed, - Role = model.Role, - AvatarFilePath = model.AvatarFilePath, - PhoneNumber = model.PhoneNumber - }); - + APIClient.PostRequest($"api/user/delete", model); Response.Redirect("/Home/Enter"); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() + public IActionResult Error(string errorMessage, string returnUrl) { + ViewBag.ErrorMessage = errorMessage ?? "Произошла непредвиденная ошибка."; + ViewBag.ReturnUrl = returnUrl; return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } diff --git a/CandidateReviewClientApp/Controllers/VacancyController.cs b/CandidateReviewClientApp/Controllers/VacancyController.cs index 5d16d07..922e9a9 100644 --- a/CandidateReviewClientApp/Controllers/VacancyController.cs +++ b/CandidateReviewClientApp/Controllers/VacancyController.cs @@ -1,7 +1,8 @@ -using CandidateReviewContracts.BindingModels; +using CandidateReviewClientApp.Models; +using CandidateReviewContracts.BindingModels; using CandidateReviewContracts.ViewModels; using Microsoft.AspNetCore.Mvc; -using System.Reflection; +using System.Diagnostics; namespace CandidateReviewClientApp.Controllers { @@ -20,12 +21,13 @@ namespace CandidateReviewClientApp.Controllers { return Redirect("~/Home/Enter"); } + VacancyViewModel vacancy; if (id.HasValue) { - APIClient.Vacancy = APIClient.GetRequest($"api/vacancy/details?id={id}"); + vacancy = APIClient.GetRequest($"api/vacancy/details?id={id}"); + return View(vacancy); } - var model = APIClient.Vacancy; - return View(model); + return View(); } [HttpGet] @@ -44,50 +46,57 @@ namespace CandidateReviewClientApp.Controllers } [HttpPost] - [HttpPost] - public void EditVacancy(VacancyBindingModel model) + public IActionResult EditVacancy(VacancyBindingModel model) { - if (APIClient.User == null) + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + try { - throw new Exception("Доступно только авторизованным пользователям"); - } - - if (!string.IsNullOrEmpty(model.Tags)) - { - model.Tags = model.Tags.ToLowerInvariant(); - } - - if (model.Id != 0) - { - APIClient.PostRequest("api/vacancy/update", model); - } - else - { - APIClient.PostRequest("api/vacancy/create", model); - if (APIClient.Company != null) + if (APIClient.User == null) { - if (!string.IsNullOrEmpty(model.Tags)) - { - model.Tags = model.Tags.ToLowerInvariant(); - } - - APIClient.Company?.Vacancies.Add(new VacancyViewModel - { - Id = model.Id, - CompanyId = model.CompanyId, - CreatedAt = DateTime.Now.ToUniversalTime(), - Description = model.Description, - JobTitle = model.JobTitle, - JobType = model.JobType, - Requirements = model.Requirements, - Responsibilities = model.Responsibilities, - Salary = model.Salary, - Status = model.Status, - Tags = model.Tags - }); + throw new Exception("Доступно только авторизованным пользователям"); } + + if (!string.IsNullOrEmpty(model.Tags)) + { + model.Tags = model.Tags.ToLowerInvariant(); + } + if (model.Id != 0) + { + APIClient.PostRequest("api/vacancy/update", model); + } + else + { + model.CompanyId = APIClient.Company.Id; + APIClient.PostRequest("api/vacancy/create", model); + if (APIClient.Company != null) + { + if (!string.IsNullOrEmpty(model.Tags)) + { + model.Tags = model.Tags.ToLowerInvariant(); + } + + APIClient.Company?.Vacancies.Add(new VacancyViewModel + { + Id = model.Id, + CompanyId = model.CompanyId, + CreatedAt = DateTime.Now.ToUniversalTime(), + Description = model.Description, + JobTitle = model.JobTitle, + JobType = model.JobType, + Requirements = model.Requirements, + Responsibilities = model.Responsibilities, + Salary = model.Salary, + Status = model.Status, + Tags = model.Tags + }); + } + } + return Redirect($"~/Company/CompanyProfile/{model.CompanyId}"); + } + catch (Exception ex) + { + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); } - Response.Redirect($"/Company/CompanyProfile/{model.CompanyId}"); } public IActionResult Delete(int id) @@ -119,5 +128,13 @@ namespace CandidateReviewClientApp.Controllers var results = APIClient.GetRequest>($"api/vacancy/search?tags={tags}"); return View(results); } + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error(string errorMessage, string returnUrl) + { + ViewBag.ErrorMessage = errorMessage ?? "Произошла непредвиденная ошибка."; + ViewBag.ReturnUrl = returnUrl; + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } } } diff --git a/CandidateReviewClientApp/Models/ErrorViewModel.cs b/CandidateReviewClientApp/Models/ErrorViewModel.cs index b398a8d..63fe605 100644 --- a/CandidateReviewClientApp/Models/ErrorViewModel.cs +++ b/CandidateReviewClientApp/Models/ErrorViewModel.cs @@ -3,7 +3,7 @@ namespace CandidateReviewClientApp.Models public class ErrorViewModel { public string? RequestId { get; set; } - + public string? ErrorMessage { get; set; } public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); } } diff --git a/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml b/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml index c31516c..19f96dd 100644 --- a/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml +++ b/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml @@ -3,7 +3,6 @@ @{ ViewData["Title"] = "Профиль компании"; - var userRole = (APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Сотрудник || APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Администратор) ? true : false; } @@ -22,10 +22,7 @@

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

- @if (userRole) - { - Редактировать - } + Редактировать
@@ -41,48 +38,20 @@

Вакансии компании

- @if (userRole) - { - Добавить вакансию - } + Добавить вакансию
@if (Model.Vacancies != null && Model.Vacancies.Any()) { - - - - - - - - - - - @foreach (var vacancy in Model.Vacancies) - { - - - - - - - } - -
НазваниеТип занятостиСтатусДействия
@vacancy.JobTitle@vacancy.JobType@vacancy.Status - - - - @if (userRole) - { - - - - - - - } -
+ @await Html.PartialAsync("_VacanciesTable", Model.Vacancies.Take(5)) + @if (Model.Vacancies.Count() > 5) + { + + } + + } else { @@ -94,10 +63,7 @@

Сотрудники компании

- @if (userRole) - { - Добавить сотрудника - } + Добавить сотрудника
@if (Model.Employees != null && Model.Employees.Any()) @@ -122,32 +88,17 @@ - @if (userRole) - { - - - - - - - } + + + + + + } - @* *@ } else { @@ -157,3 +108,20 @@
+ +@section Scripts { + +} diff --git a/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml b/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml index 84062ce..4823d99 100644 --- a/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml +++ b/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml @@ -5,6 +5,18 @@ var title = @Model.Id <= 0 ? "Создать профиль компании" : "Редактировать профиль компании"; } +@if (!ViewData.ModelState.IsValid) +{ +
+
    + @foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors)) + { +
  • @error.ErrorMessage
  • + } +
+
+} +

@title

@@ -37,8 +49,12 @@
-
+
+ @if (Model.Id > 0) + { + Отмена + }
diff --git a/CandidateReviewClientApp/Views/Criterion/ManageCriterions.cshtml b/CandidateReviewClientApp/Views/Criterion/ManageCriterions.cshtml new file mode 100644 index 0000000..c9027dd --- /dev/null +++ b/CandidateReviewClientApp/Views/Criterion/ManageCriterions.cshtml @@ -0,0 +1,73 @@ +@using CandidateReviewContracts.ViewModels +@model List + +@{ + ViewData["Title"] = "Управление критериями"; +} + +
+
+

Управление критериями

+ +
+ + + + + + + + + + + @if (Model != null && Model.Count > 0) + { + int index = 1; + foreach (var criterion in Model) + { + + + + + + index++; + } + } + else + { + + + + } + +
#Название критерияДействия
@index@criterion.Name + Удалить +
Нет доступных критериев.
+ + +
+ + \ No newline at end of file diff --git a/CandidateReviewClientApp/Views/Home/Enter.cshtml b/CandidateReviewClientApp/Views/Home/Enter.cshtml index 5e29842..e2d229c 100644 --- a/CandidateReviewClientApp/Views/Home/Enter.cshtml +++ b/CandidateReviewClientApp/Views/Home/Enter.cshtml @@ -3,6 +3,18 @@ Layout = "_LoginLayout"; } +@if (!ViewData.ModelState.IsValid) +{ +
+
    + @foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors)) + { +
  • @error.ErrorMessage
  • + } +
+
+} +
diff --git a/CandidateReviewClientApp/Views/Home/Index.cshtml b/CandidateReviewClientApp/Views/Home/Index.cshtml index bcfd79a..9b1f853 100644 --- a/CandidateReviewClientApp/Views/Home/Index.cshtml +++ b/CandidateReviewClientApp/Views/Home/Index.cshtml @@ -3,6 +3,49 @@ }
-

Welcome

-

Learn about building Web apps with ASP.NET Core.

+

Добро пожаловать в CandidateReview

+

Здесь вы можете:

+ +
+
+

Создать профиль своей компании

+

Здесь вы можете создать и настроить профиль вашей компании, указать основные данные. Профиль вашей компании будет виден кандидатам, что поможет вам привлечь их внимание к вакансиям, которые вы предлагаете.

+
+
+

Подобрать кандидатов на вакансию

+

Используйте наш удобный инструмент для подбора кандидатов на вакансию. Вы можете просмотреть параметры возможных кандидатов. Это упростит процесс поиска подходящих кандидатов для ваших вакансий и даст вам больше шансов найти именно того, кто вам нужен.

+
+
+

Найти подходящую вакансию

+

Если вы ищете работу, здесь вы можете просмотреть все доступные вакансии. Мы стремимся помочь вам найти именно то рабочее место, которое соответствует вашим ожиданиям и интересам.

+
+
+

Оценить кандидатов на вакансию

+

После просмотра резюме у вас есть возможность оценить кандидатов и оставить свои комментарии. Вы можете выставить оценки по различным критериям и добавить свои заметки. Это поможет в дальнейшем процессе выбора и предоставляет общую картину по каждому кандидату, что значительно упрощает принятие решения.

+
+ +
+ + diff --git a/CandidateReviewClientApp/Views/Resume/EditResume.cshtml b/CandidateReviewClientApp/Views/Resume/EditResume.cshtml new file mode 100644 index 0000000..d69af52 --- /dev/null +++ b/CandidateReviewClientApp/Views/Resume/EditResume.cshtml @@ -0,0 +1,81 @@ +@using CandidateReviewContracts.ViewModels +@using CandidateReviewDataModels.Enums +@model ResumeViewModel + +@{ + var title = Model.Id <= 0 ? "Создать резюме" : "Редактировать резюме"; +} + + +
+

@title

+
+ + + + +
+ + +
Пожалуйста, введите название резюме.
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + Предварительный просмотр фото +
Выберите файл изображения.
+
+
+ @if (Model.Status == ResumeStatusEnum.Черновик) + { + + } + @if (Model.Id <= 0) + { + + } + +
+
+
+ + \ No newline at end of file diff --git a/CandidateReviewClientApp/Views/Resume/ResumeDetails.cshtml b/CandidateReviewClientApp/Views/Resume/ResumeDetails.cshtml new file mode 100644 index 0000000..7853c52 --- /dev/null +++ b/CandidateReviewClientApp/Views/Resume/ResumeDetails.cshtml @@ -0,0 +1,166 @@ +@using CandidateReviewDataModels.Models +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using CandidateReviewContracts.ViewModels +@model ResumeViewModel + +@{ + ViewData["Title"] = "Резюме"; + bool userRole = APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Сотрудник || APIClient.User.Role == CandidateReviewDataModels.Enums.RoleEnum.Администратор; +} + +
+
+
+

@Model.Title

+ Аватар пользователя + +
+ +
+ +
+
+
+
+
+
Вакансия:
+
@Model.VacancyName
+ +
Навыки:
+
@Model.Skills
+ +
Статус:
+
@Model.Status
+
+
+
+
+
Опыт работы:
+
@Model.Experience
+ +
Образование:
+
@Model.Education
+ +
Описание:
+
@Html.Raw(Model.Description)
+
+
+
+ + @if (Model.Status == CandidateReviewDataModels.Enums.ResumeStatusEnum.Черновик) + { + + } +
+
+ + @if (userRole) + { +

Оценка резюме

+ +
+
+ @if (ViewBag.Criterions != null) + { + + @foreach (var criterion in ViewBag.Criterions) + { + var previousAssessment = Model.PreviousAssessments?.FirstOrDefault(a => a.Id == criterion.Id); + var isSelected = previousAssessment?.Id == criterion.Id ? "selected" : string.Empty; + +
+
+ +
+
+ +
+
+ } + } + else + { +

Нет доступных критериев.

+ } +
+ +
+
+ + +
+
+ + + + +
+ + + } +
+ + diff --git a/CandidateReviewClientApp/Views/Shared/Error.cshtml b/CandidateReviewClientApp/Views/Shared/Error.cshtml index a1e0478..971bbbc 100644 --- a/CandidateReviewClientApp/Views/Shared/Error.cshtml +++ b/CandidateReviewClientApp/Views/Shared/Error.cshtml @@ -1,25 +1,39 @@ @model ErrorViewModel -@{ - ViewData["Title"] = "Error"; -} -

Error.

-

An error occurred while processing your request.

+ + + + + + + + + -@if (Model.ShowRequestId) -{ -

- Request ID: @Model.RequestId -

-} - -

Development Mode

-

- Swapping to Development environment will display more detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

+ + + \ No newline at end of file diff --git a/CandidateReviewClientApp/Views/Shared/_Layout.cshtml b/CandidateReviewClientApp/Views/Shared/_Layout.cshtml index 9d6217e..6e90a02 100644 --- a/CandidateReviewClientApp/Views/Shared/_Layout.cshtml +++ b/CandidateReviewClientApp/Views/Shared/_Layout.cshtml @@ -25,7 +25,7 @@ @if (APIClient.User?.Role == RoleEnum.Сотрудник || APIClient.User?.Role == RoleEnum.Администратор) { }
+
© 2024 - Ревью кандидатов на вакансию diff --git a/CandidateReviewClientApp/Views/Shared/_VacanciesTable.cshtml b/CandidateReviewClientApp/Views/Shared/_VacanciesTable.cshtml new file mode 100644 index 0000000..49eba73 --- /dev/null +++ b/CandidateReviewClientApp/Views/Shared/_VacanciesTable.cshtml @@ -0,0 +1,33 @@ +@model IEnumerable + + + + + + + + + + + + @foreach (var vacancy in Model) + { + + + + + + + } + +
НазваниеТип занятостиСтатусДействия
@vacancy.JobTitle@vacancy.JobType@vacancy.Status + + + + + + + + + +
diff --git a/CandidateReviewClientApp/Views/User/UserProfile.cshtml b/CandidateReviewClientApp/Views/User/UserProfile.cshtml index e00da5f..154b5fd 100644 --- a/CandidateReviewClientApp/Views/User/UserProfile.cshtml +++ b/CandidateReviewClientApp/Views/User/UserProfile.cshtml @@ -3,6 +3,7 @@ @{ ViewData["Title"] = "Профиль пользователя"; + var userRole = APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Пользователь ? true : false; }
@@ -12,7 +13,7 @@ Аватар пользователя
- @Model?.Name @(string.IsNullOrEmpty(@Model?.Surname) ? "" : @Model?.Surname) @(string.IsNullOrEmpty(@Model?.LastName) ? "" : @Model?.LastName) + @(string.IsNullOrEmpty(@Model?.Surname) ? "" : @Model?.Surname) @Model?.Name @(string.IsNullOrEmpty(@Model?.LastName) ? "" : @Model?.LastName)
Email:
@@ -37,15 +38,56 @@
-
-
-
-

Мои резюме

-
-
+ @if (userRole) + { +
+
+
+

Мои резюме

+
+
+ @if (Model.Resumes != null && Model.Resumes.Any()) + { + + + + + + + + + + + @foreach (var resume in Model.Resumes) + { + + + + + + + } + +
НазваниеВакансияСтатусДействия
@resume.Title@resume.VacancyName@resume.Status + + + + + + + + + +
+ } + else + { +

Нет резюме.

+ } +
-
+ }
diff --git a/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml b/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml index 277f1e9..d3a065c 100644 --- a/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml +++ b/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml @@ -2,7 +2,7 @@ @model UserViewModel @{ - ViewData["Title"] = "Редактирование профиля"; + var title = @Model.Id <= 0 ? "Добавить сотрудника" : "Редактировать профиль"; var userRole = APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Администратор ? true : false; } @@ -11,69 +11,60 @@

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

- - - @if (userRole) + + + +
+ + +
Пожалуйста, введите фамилию.
+
+ +
+ + +
Пожалуйста, введите имя.
+
+ +
+ + +
Пожалуйста, введите отчество.
+
+ +
+ + +
Пожалуйста, введите электронную почту.
+
+ + @if (Model.Id <= 0) { - - -
- - -
Пожалуйста, введите имя.
-
- -
- - -
Пожалуйста, введите фамилию.
-
-
- - -
Пожалуйста, введите электронную почту.
-
-
Пожалуйста, введите пароль.
- -
- - Отмена -
} - else - { - - -
- - -
Пожалуйста, введите отчество.
-
-
- - -
Пожалуйста, введите телефон.
-
- - +
+ + +
Пожалуйста, введите телефон.
+
+ + -
- - - Предварительный просмотр аватара -
Выберите файл изображения.
-
-
- - Отмена -
- } +
+ + + Предварительный просмотр аватара +
Выберите файл изображения.
+
+
+ + +
diff --git a/CandidateReviewClientApp/Views/Vacancy/EditVacancy.cshtml b/CandidateReviewClientApp/Views/Vacancy/EditVacancy.cshtml index 6564e3a..afa36e5 100644 --- a/CandidateReviewClientApp/Views/Vacancy/EditVacancy.cshtml +++ b/CandidateReviewClientApp/Views/Vacancy/EditVacancy.cshtml @@ -10,7 +10,7 @@

@title

- +
@@ -28,6 +28,11 @@
+
+ + +
+
@@ -36,12 +41,7 @@
-
- -
- - -
+
@@ -52,8 +52,9 @@
-
+
+
diff --git a/CandidateReviewClientApp/Views/Vacancy/SearchVacancies.cshtml b/CandidateReviewClientApp/Views/Vacancy/SearchVacancies.cshtml index dab799a..a9eea30 100644 --- a/CandidateReviewClientApp/Views/Vacancy/SearchVacancies.cshtml +++ b/CandidateReviewClientApp/Views/Vacancy/SearchVacancies.cshtml @@ -14,10 +14,6 @@
- @if (ViewBag.Message != null) - { -

@ViewBag.Message

- } @if (Model != null) @@ -32,6 +28,7 @@ Тип работы Зарплата Тэги + Действия @@ -42,6 +39,10 @@ @vacancy.JobType @vacancy.Salary @vacancy.Tags + + Просмотр + Составить резюме + } diff --git a/CandidateReviewClientApp/Views/Vacancy/VacancyDetails.cshtml b/CandidateReviewClientApp/Views/Vacancy/VacancyDetails.cshtml index 9d77635..e48b09a 100644 --- a/CandidateReviewClientApp/Views/Vacancy/VacancyDetails.cshtml +++ b/CandidateReviewClientApp/Views/Vacancy/VacancyDetails.cshtml @@ -4,25 +4,26 @@ @{ ViewData["Title"] = "Информация о вакансии"; - var companyName = APIClient.Company?.Name ?? "Компания не определена"; + bool userRole = APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Сотрудник || APIClient.User.Role == CandidateReviewDataModels.Enums.RoleEnum.Администратор; }
-
+

@Model.JobTitle

-
- Назад +
-
+
Компания:
-
@companyName
+
@Model.CompanyName
Тип занятости:
@Model.JobType
@@ -55,4 +56,51 @@
-
\ No newline at end of file + + @if (userRole) + { +
+
+
+

Резюме на вакансию

+
+
+ @if (Model.Resumes != null && Model.Resumes.Any()) + { + + + + + + + + + + + @foreach (var resume in Model.Resumes) + { + + + + + + + } + +
НазваниеКандидатСтатусДействия
@resume.Title@resume.UserName@resume.Status + + Посмотреть и оценить + +
+ } + else + { +

Нет резюме для данной вакансии.

+ } +
+
+
+ } +
+ + diff --git a/CandidateReviewClientApp/images/default-company-logo.png b/CandidateReviewClientApp/images/default-company-logo.png deleted file mode 100644 index 1f71f2508e6fdf62bd151d66a3780e8e8b0beafd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14369 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 CSAi7( diff --git a/CandidateReviewContracts/BindingModels/AssessmentBindingModel.cs b/CandidateReviewContracts/BindingModels/AssessmentBindingModel.cs index 1b924a1..4e415cd 100644 --- a/CandidateReviewContracts/BindingModels/AssessmentBindingModel.cs +++ b/CandidateReviewContracts/BindingModels/AssessmentBindingModel.cs @@ -4,17 +4,14 @@ namespace CandidateReviewContracts.BindingModels { public class AssessmentBindingModel : IAssessmentModel { - public int ResumeId { get; set; } - public int UserId { get; set; } - - public int? Rating { get; set; } - - public DateTime CreatedAt { get; set; } = DateTime.Now; + public int? UserId { get; set; } public string? Comment { get; set; } public int Id { get; set; } public Dictionary AssessmentCriterions { get; set; } = new(); + + public int? ResumeId { get; set; } } } diff --git a/CandidateReviewContracts/BindingModels/CriterionBindingModel.cs b/CandidateReviewContracts/BindingModels/CriterionBindingModel.cs index 7a250a7..2a1c9b5 100644 --- a/CandidateReviewContracts/BindingModels/CriterionBindingModel.cs +++ b/CandidateReviewContracts/BindingModels/CriterionBindingModel.cs @@ -1,5 +1,4 @@ -using CandidateReviewDataModels.Enums; -using CandidateReviewDataModels.Models; +using CandidateReviewDataModels.Models; namespace CandidateReviewContracts.BindingModels { @@ -7,12 +6,6 @@ namespace CandidateReviewContracts.BindingModels { public string Name { get; set; } = string.Empty; - public CriterionTypeEnum Type { get; set; } - - public string? Description { get; set; } - - public int Weight { get; set; } - public int Id { get; set; } } } diff --git a/CandidateReviewContracts/BusinessLogicsContracts/ICompanyLogic.cs b/CandidateReviewContracts/BusinessLogicsContracts/ICompanyLogic.cs index 0fb5439..0aa495c 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); - int Create(CompanyBindingModel model); + int? Create(CompanyBindingModel model); bool Update(CompanyBindingModel model); bool Delete(CompanyBindingModel model); } diff --git a/CandidateReviewContracts/SearchModels/AssessmentSearchModel.cs b/CandidateReviewContracts/SearchModels/AssessmentSearchModel.cs index 5e3e699..fbf0963 100644 --- a/CandidateReviewContracts/SearchModels/AssessmentSearchModel.cs +++ b/CandidateReviewContracts/SearchModels/AssessmentSearchModel.cs @@ -2,10 +2,11 @@ { public class AssessmentSearchModel { - public int? ResumeId { get; set; } public int? UserId { get; set; } + public int? ResumeId { get; set; } + public int? Id { get; set; } } } diff --git a/CandidateReviewContracts/SearchModels/VacancySearchModel.cs b/CandidateReviewContracts/SearchModels/VacancySearchModel.cs index d246789..d2bb19f 100644 --- a/CandidateReviewContracts/SearchModels/VacancySearchModel.cs +++ b/CandidateReviewContracts/SearchModels/VacancySearchModel.cs @@ -1,4 +1,6 @@ -namespace CandidateReviewContracts.SearchModels +using CandidateReviewDataModels.Enums; + +namespace CandidateReviewContracts.SearchModels { public class VacancySearchModel { @@ -11,5 +13,7 @@ public string? Tags { get; set; } public int? Id { get; set; } + + public VacancyStatusEnum? Status { get; set; } } } diff --git a/CandidateReviewContracts/StoragesContracts/ICompanyStorage.cs b/CandidateReviewContracts/StoragesContracts/ICompanyStorage.cs index 8b76e99..39c94a3 100644 --- a/CandidateReviewContracts/StoragesContracts/ICompanyStorage.cs +++ b/CandidateReviewContracts/StoragesContracts/ICompanyStorage.cs @@ -9,7 +9,7 @@ namespace CandidateReviewContracts.StoragesContracts List GetFullList(); List GetFilteredList(CompanySearchModel model); CompanyViewModel? GetElement(CompanySearchModel model); - CompanyViewModel? Insert(CompanyBindingModel model); + int? Insert(CompanyBindingModel model); CompanyViewModel? Update(CompanyBindingModel model); CompanyViewModel? Delete(CompanyBindingModel model); } diff --git a/CandidateReviewContracts/ViewModels/AssessmentViewModel.cs b/CandidateReviewContracts/ViewModels/AssessmentViewModel.cs index e0e1da1..627d58e 100644 --- a/CandidateReviewContracts/ViewModels/AssessmentViewModel.cs +++ b/CandidateReviewContracts/ViewModels/AssessmentViewModel.cs @@ -4,16 +4,14 @@ namespace CandidateReviewContracts.ViewModels { public class AssessmentViewModel : IAssessmentModel { - public int ResumeId { get; set; } - - public int UserId { get; set; } - - public int? Rating { get; set; } - - public DateTime CreatedAt { get; set; } + public int? UserId { get; set; } public string? Comment { get; set; } public int Id { get; set; } + + public int? ResumeId { get; set; } + + public Dictionary AssessmentCriterions { get; set; } = new(); } } diff --git a/CandidateReviewContracts/ViewModels/CriterionViewModel.cs b/CandidateReviewContracts/ViewModels/CriterionViewModel.cs index f58c32b..1566833 100644 --- a/CandidateReviewContracts/ViewModels/CriterionViewModel.cs +++ b/CandidateReviewContracts/ViewModels/CriterionViewModel.cs @@ -1,5 +1,4 @@ -using CandidateReviewDataModels.Enums; -using CandidateReviewDataModels.Models; +using CandidateReviewDataModels.Models; namespace CandidateReviewContracts.ViewModels { @@ -7,12 +6,6 @@ namespace CandidateReviewContracts.ViewModels { public string Name { get; set; } = string.Empty; - public CriterionTypeEnum Type { get; set; } - - public string? Description { get; set; } - - public int Weight { get; set; } - public int Id { get; set; } } } diff --git a/CandidateReviewContracts/ViewModels/ResumeViewModel.cs b/CandidateReviewContracts/ViewModels/ResumeViewModel.cs index 0ada276..505ca85 100644 --- a/CandidateReviewContracts/ViewModels/ResumeViewModel.cs +++ b/CandidateReviewContracts/ViewModels/ResumeViewModel.cs @@ -7,8 +7,12 @@ namespace CandidateReviewContracts.ViewModels { public int VacancyId { get; set; } + public string VacancyName { get; set; } = string.Empty; + public int UserId { get; set; } + public string UserName { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; public string Experience { get; set; } = string.Empty; @@ -24,5 +28,6 @@ namespace CandidateReviewContracts.ViewModels public ResumeStatusEnum Status { get; set; } public int Id { get; set; } + public List PreviousAssessments { get; set; } = new(); } } diff --git a/CandidateReviewContracts/ViewModels/UserViewModel.cs b/CandidateReviewContracts/ViewModels/UserViewModel.cs index ea88474..fc5ac1c 100644 --- a/CandidateReviewContracts/ViewModels/UserViewModel.cs +++ b/CandidateReviewContracts/ViewModels/UserViewModel.cs @@ -26,5 +26,7 @@ namespace CandidateReviewContracts.ViewModels public RoleEnum Role { get; set; } public int Id { get; set; } + + public List Resumes { get; set; } = new(); } } diff --git a/CandidateReviewContracts/ViewModels/VacancyViewModel.cs b/CandidateReviewContracts/ViewModels/VacancyViewModel.cs index a5a853d..c534d0a 100644 --- a/CandidateReviewContracts/ViewModels/VacancyViewModel.cs +++ b/CandidateReviewContracts/ViewModels/VacancyViewModel.cs @@ -7,6 +7,8 @@ namespace CandidateReviewContracts.ViewModels { public int CompanyId { get; set; } + public string CompanyName { get; set; } = string.Empty; + public string JobTitle { get; set; } = string.Empty; public string Requirements { get; set; } = string.Empty; @@ -26,5 +28,7 @@ namespace CandidateReviewContracts.ViewModels public string? Tags { get; set; } public int Id { get; set; } + + public List Resumes { get; set; } = new(); } } diff --git a/CandidateReviewDataModels/Enums/CriterionTypeEnum.cs b/CandidateReviewDataModels/Enums/CriterionTypeEnum.cs deleted file mode 100644 index 13dda8e..0000000 --- a/CandidateReviewDataModels/Enums/CriterionTypeEnum.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace CandidateReviewDataModels.Enums -{ - public enum CriterionTypeEnum - { - Опыт = 0, - Навыки = 1, - Образование = 2, - Впечатление = 3 - } -} diff --git a/CandidateReviewDataModels/Enums/JobTypeEnum.cs b/CandidateReviewDataModels/Enums/JobTypeEnum.cs index 774df4d..6a3aab7 100644 --- a/CandidateReviewDataModels/Enums/JobTypeEnum.cs +++ b/CandidateReviewDataModels/Enums/JobTypeEnum.cs @@ -2,17 +2,15 @@ { public enum JobTypeEnum { - РаботаВОфисе = 0, - УдаленнаяРабота = 1, + Офис = 0, + Удаленно = 1, Гибрид = 2, Фриланс = 3, Подработка = 4, Сменная = 5, Контракт = 6, - ПолныйРабочийДень = 7, - НеполныйРабочийДень = 8, - Проектная = 9, - Сезонная = 10, - Волонтерская = 11 + Проектная = 7, + Сезонная = 8, + Волонтерская = 9 } } diff --git a/CandidateReviewDataModels/Enums/ResumeStatusEnum.cs b/CandidateReviewDataModels/Enums/ResumeStatusEnum.cs index 7469c13..5f42233 100644 --- a/CandidateReviewDataModels/Enums/ResumeStatusEnum.cs +++ b/CandidateReviewDataModels/Enums/ResumeStatusEnum.cs @@ -2,11 +2,10 @@ { public enum ResumeStatusEnum { - Создано = 0, - Отправлено = 1, - Обрабатывается = 2, - Принято = 3, - Отклонено = 4, - Архив = 5 + Черновик = 0, + Обрабатывается = 1, + Принято = 2, + Отклонено = 3, + Архив = 4 } } diff --git a/CandidateReviewDataModels/Models/IAssessmentModel.cs b/CandidateReviewDataModels/Models/IAssessmentModel.cs index a3a1d57..b34517f 100644 --- a/CandidateReviewDataModels/Models/IAssessmentModel.cs +++ b/CandidateReviewDataModels/Models/IAssessmentModel.cs @@ -2,10 +2,8 @@ { public interface IAssessmentModel : IId { - int ResumeId { get; } - int UserId { get; } - int? Rating { get; } - DateTime CreatedAt { get; } + int? UserId { get; } + int? ResumeId { get; } string? Comment { get; } } } diff --git a/CandidateReviewDataModels/Models/ICriterionModel.cs b/CandidateReviewDataModels/Models/ICriterionModel.cs index 1f23f94..41f7785 100644 --- a/CandidateReviewDataModels/Models/ICriterionModel.cs +++ b/CandidateReviewDataModels/Models/ICriterionModel.cs @@ -1,12 +1,7 @@ -using CandidateReviewDataModels.Enums; - -namespace CandidateReviewDataModels.Models +namespace CandidateReviewDataModels.Models { public interface ICriterionModel : IId { string Name { get; } - CriterionTypeEnum Type { get; } - string? Description { get; } - int Weight { get; } } } diff --git a/CandidateReviewDatabaseImplement/Implements/CompanyStorage.cs b/CandidateReviewDatabaseImplement/Implements/CompanyStorage.cs index 9e07fb3..2c7c549 100644 --- a/CandidateReviewDatabaseImplement/Implements/CompanyStorage.cs +++ b/CandidateReviewDatabaseImplement/Implements/CompanyStorage.cs @@ -12,7 +12,7 @@ namespace CandidateReviewDatabaseImplement.Implements public CompanyViewModel? Delete(CompanyBindingModel model) { using var context = new CandidateReviewDatabase(); - var element = context.Companies.Include(x => x.Users).Include(x => x.Vacancies).FirstOrDefault(rec => rec.Id == model.Id); + var element = context.Companies.FirstOrDefault(rec => rec.Id == model.Id); if (element != null) { context.Companies.Remove(element); @@ -30,8 +30,6 @@ namespace CandidateReviewDatabaseImplement.Implements } using var context = new CandidateReviewDatabase(); return context.Companies - .Include(x => x.Users) - .Include(x => x.Vacancies) .FirstOrDefault(x => (!string.IsNullOrEmpty(model.Name) && x.Name == model.Name) || (model.Id.HasValue && x.Id == model.Id))? .GetViewModel; } @@ -44,8 +42,6 @@ namespace CandidateReviewDatabaseImplement.Implements } using var context = new CandidateReviewDatabase(); return context.Companies - .Include(x => x.Users) - .Include(x => x.Vacancies) .Where(x => x.Name.Equals(model.Name)) .Select(x => x.GetViewModel) .ToList(); @@ -55,13 +51,11 @@ namespace CandidateReviewDatabaseImplement.Implements { using var context = new CandidateReviewDatabase(); return context.Companies - .Include(x => x.Users) - .Include(x => x.Vacancies) .Select(x => x.GetViewModel) .ToList(); } - public CompanyViewModel? Insert(CompanyBindingModel model) + public int? Insert(CompanyBindingModel model) { var newCompany = Company.Create(model); if (newCompany == null) @@ -71,19 +65,13 @@ namespace CandidateReviewDatabaseImplement.Implements using var context = new CandidateReviewDatabase(); context.Companies.Add(newCompany); context.SaveChanges(); - return context.Companies - .Include(x => x.Users) - .Include(x => x.Vacancies) - .FirstOrDefault(x => x.Id == model.Id)? - .GetViewModel; + return newCompany.Id; } public CompanyViewModel? Update(CompanyBindingModel model) { using var context = new CandidateReviewDatabase(); var company = context.Companies - .Include(x => x.Users) - .Include(x => x.Vacancies) .FirstOrDefault(x => x.Id == model.Id); if (company == null) { diff --git a/CandidateReviewDatabaseImplement/Implements/ResumeStorage.cs b/CandidateReviewDatabaseImplement/Implements/ResumeStorage.cs index bda1b78..b0ee136 100644 --- a/CandidateReviewDatabaseImplement/Implements/ResumeStorage.cs +++ b/CandidateReviewDatabaseImplement/Implements/ResumeStorage.cs @@ -16,7 +16,6 @@ namespace CandidateReviewDatabaseImplement.Implements var element = context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .FirstOrDefault(rec => rec.Id == model.Id); if (element != null) @@ -31,18 +30,22 @@ namespace CandidateReviewDatabaseImplement.Implements } public ResumeViewModel? GetElement(ResumeSearchModel model) - { - if (!model.Id.HasValue) - { - return null; - } + { using var context = new CandidateReviewDatabase(); + if (model.VacancyId.HasValue && model.UserId.HasValue) + { + return context.Resumes + .Include(x => x.Vacancy) + .Include(x => x.User) + .FirstOrDefault(x => x.VacancyId == model.VacancyId && x.UserId == model.UserId) + ?.GetViewModel; + } + if (model.VacancyId.HasValue) { return context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .FirstOrDefault(x => x.VacancyId == model.VacancyId) ?.GetViewModel; } @@ -52,17 +55,19 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .FirstOrDefault(x => x.UserId == model.UserId) ?.GetViewModel; } + if (!model.Id.HasValue) + { + return null; + } if (!string.IsNullOrEmpty(model.Title)) { return context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .FirstOrDefault(x => x.Title == model.Title) ?.GetViewModel; } @@ -70,7 +75,6 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .FirstOrDefault(x => model.Id.HasValue && x.Id == model.Id) ?.GetViewModel; } @@ -87,7 +91,6 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .Where(x => x.VacancyId == model.VacancyId) .ToList() .Select(x => x.GetViewModel) @@ -99,7 +102,6 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .Where(x => x.UserId == model.UserId) .ToList() .Select(x => x.GetViewModel) @@ -111,7 +113,6 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .Where(x => x.Title == model.Title) .ToList() .Select(x => x.GetViewModel) @@ -120,7 +121,6 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .ToList() .Select(x => x.GetViewModel) .ToList(); @@ -133,7 +133,6 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .Select(x => x.GetViewModel).ToList(); } @@ -154,7 +153,6 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .FirstOrDefault(x => x.Id == newResume.Id) ?.GetViewModel; } @@ -166,7 +164,6 @@ namespace CandidateReviewDatabaseImplement.Implements var resume = context.Resumes .Include(x => x.Vacancy) .Include(x => x.User) - .Include(x => x.Assessment) .FirstOrDefault(x => x.Id == model.Id); if (resume == null) diff --git a/CandidateReviewDatabaseImplement/Implements/UserStorage.cs b/CandidateReviewDatabaseImplement/Implements/UserStorage.cs index 4f6d14f..a2f46e9 100644 --- a/CandidateReviewDatabaseImplement/Implements/UserStorage.cs +++ b/CandidateReviewDatabaseImplement/Implements/UserStorage.cs @@ -13,7 +13,7 @@ namespace CandidateReviewDatabaseImplement.Implements public UserViewModel? Delete(UserBindingModel model) { using var context = new CandidateReviewDatabase(); - var element = context.Users.Include(x => x.Company).Include(x => x.Resumes).Include(x => x.Assessments).FirstOrDefault(rec => rec.Id == model.Id); + var element = context.Users.Include(x => x.Company).FirstOrDefault(rec => rec.Id == model.Id); if (element != null) { context.Users.Remove(element); @@ -32,8 +32,6 @@ namespace CandidateReviewDatabaseImplement.Implements using var context = new CandidateReviewDatabase(); return context.Users .Include(x => x.Company) - .Include(x => x.Resumes) - .Include(x => x.Assessments) .FirstOrDefault(x => (!string.IsNullOrEmpty(model.Email) && x.Email == model.Email) || (model.Id.HasValue && x.Id == model.Id))? .GetViewModel; } @@ -45,9 +43,6 @@ namespace CandidateReviewDatabaseImplement.Implements if (model.CompanyId.HasValue && model.Role != null) { return context.Users - .Include(x => x.Company) - .Include(x => x.Resumes) - .Include(x => x.Assessments) .Where(x => x.CompanyId.Equals(model.CompanyId) && x.Role.Equals(model.Role)) .Select(x => x.GetViewModel) .ToList(); @@ -59,8 +54,6 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Users .Include(x => x.Company) - .Include(x => x.Resumes) - .Include(x => x.Assessments) .Where(x => x.Email.Equals(model.Email) && x.Password.Equals(model.Password)) .Select(x => x.GetViewModel) .ToList(); @@ -71,8 +64,6 @@ namespace CandidateReviewDatabaseImplement.Implements using var context = new CandidateReviewDatabase(); return context.Users .Include(x => x.Company) - .Include(x => x.Resumes) - .Include(x => x.Assessments) .ToList() .Select(x => x.GetViewModel) .ToList(); @@ -90,8 +81,6 @@ namespace CandidateReviewDatabaseImplement.Implements context.SaveChanges(); return context.Users .Include(x => x.Company) - .Include(x => x.Resumes) - .Include(x => x.Assessments) .FirstOrDefault(x => x.Id == newUser.Id)? .GetViewModel; } @@ -101,8 +90,6 @@ namespace CandidateReviewDatabaseImplement.Implements using var context = new CandidateReviewDatabase(); var user = context.Users .Include(x => x.Company) - .Include(x => x.Resumes) - .Include(x => x.Assessments) .FirstOrDefault(x => x.Id == model.Id); if (user == null) diff --git a/CandidateReviewDatabaseImplement/Implements/VacancyStorage.cs b/CandidateReviewDatabaseImplement/Implements/VacancyStorage.cs index 1cbaf4b..9d6a173 100644 --- a/CandidateReviewDatabaseImplement/Implements/VacancyStorage.cs +++ b/CandidateReviewDatabaseImplement/Implements/VacancyStorage.cs @@ -15,7 +15,6 @@ namespace CandidateReviewDatabaseImplement.Implements var element = context.Vacancies .Include(x => x.Company) - .Include(x => x.Resumes) .FirstOrDefault(rec => rec.Id == model.Id); if (element != null) @@ -40,7 +39,6 @@ namespace CandidateReviewDatabaseImplement.Implements { return context.Vacancies .Include(x => x.Company) - .Include(x => x.Resumes) .FirstOrDefault(x => x.CompanyId == model.CompanyId && x.JobTitle.Equals(model.JobTitle)) ?.GetViewModel; } @@ -48,13 +46,11 @@ namespace CandidateReviewDatabaseImplement.Implements { return context.Vacancies .Include(x => x.Company) - .Include(x => x.Resumes) .FirstOrDefault(x => x.Tags.Contains(model.Tags)) ?.GetViewModel; } return context.Vacancies .Include(x => x.Company) - .Include(x => x.Resumes) .FirstOrDefault(x => model.Id.HasValue && x.Id == model.Id) ?.GetViewModel; } @@ -70,26 +66,23 @@ namespace CandidateReviewDatabaseImplement.Implements { return context.Vacancies .Include(x => x.Company) - .Include(x => x.Resumes) .Where(x => x.CompanyId == model.CompanyId && x.JobTitle.Equals(model.JobTitle)) .ToList() .Select(x => x.GetViewModel) .ToList(); } - if (!string.IsNullOrEmpty(model.Tags)) + if (!string.IsNullOrEmpty(model.Tags) && model.Status.HasValue) { var tags = model.Tags.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(t => t.ToLowerInvariant()).ToArray(); return context.Vacancies .Include(x => x.Company) - .Include(x => x.Resumes) - .Where(x => tags.Any(tag => x.Tags.Contains(tag))) + .Where(x => tags.Any(tag => x.Tags.Contains(tag)) && x.Status == model.Status) .ToList() .Select(x => x.GetViewModel) .ToList(); } return context.Vacancies .Include(x => x.Company) - .Include(x => x.Resumes) .ToList() .Select(x => x.GetViewModel) .ToList(); @@ -101,7 +94,6 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Vacancies .Include(x => x.Company) - .Include(x => x.Resumes) .Select(x => x.GetViewModel).ToList(); } @@ -121,7 +113,6 @@ namespace CandidateReviewDatabaseImplement.Implements return context.Vacancies .Include(x => x.Company) - .Include(x => x.Resumes) .FirstOrDefault(x => x.Id == newVacancy.Id) ?.GetViewModel; } @@ -132,7 +123,6 @@ namespace CandidateReviewDatabaseImplement.Implements var vacancy = context.Vacancies .Include(x => x.Company) - .Include(x => x.Resumes) .FirstOrDefault(x => x.Id == model.Id); if (vacancy == null) diff --git a/CandidateReviewDatabaseImplement/Migrations/20241105152221_InitialCreate.Designer.cs b/CandidateReviewDatabaseImplement/Migrations/20241203082827_InitialCreate.Designer.cs similarity index 84% rename from CandidateReviewDatabaseImplement/Migrations/20241105152221_InitialCreate.Designer.cs rename to CandidateReviewDatabaseImplement/Migrations/20241203082827_InitialCreate.Designer.cs index d889240..f6d1ebe 100644 --- a/CandidateReviewDatabaseImplement/Migrations/20241105152221_InitialCreate.Designer.cs +++ b/CandidateReviewDatabaseImplement/Migrations/20241203082827_InitialCreate.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace CandidateReviewDatabaseImplement.Migrations { [DbContext(typeof(CandidateReviewDatabase))] - [Migration("20241105152221_InitialCreate")] + [Migration("20241203082827_InitialCreate")] partial class InitialCreate { /// @@ -36,20 +36,16 @@ namespace CandidateReviewDatabaseImplement.Migrations b.Property("Comment") .HasColumnType("text"); - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Rating") + b.Property("ResumeId") .HasColumnType("integer"); - b.Property("ResumeId") - .HasColumnType("integer"); - - b.Property("UserId") + b.Property("UserId") .HasColumnType("integer"); b.HasKey("Id"); + b.HasIndex("ResumeId"); + b.HasIndex("UserId"); b.ToTable("Assessments"); @@ -121,19 +117,10 @@ namespace CandidateReviewDatabaseImplement.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("Description") - .HasColumnType("text"); - b.Property("Name") .IsRequired() .HasColumnType("text"); - b.Property("Type") - .HasColumnType("integer"); - - b.Property("Weight") - .HasColumnType("integer"); - b.HasKey("Id"); b.ToTable("Criterions"); @@ -161,9 +148,6 @@ namespace CandidateReviewDatabaseImplement.Migrations b.Property("PhotoFilePath") .HasColumnType("text"); - b.Property("ResumeId") - .HasColumnType("integer"); - b.Property("Skills") .IsRequired() .HasColumnType("text"); @@ -183,9 +167,6 @@ namespace CandidateReviewDatabaseImplement.Migrations b.HasKey("Id"); - b.HasIndex("ResumeId") - .IsUnique(); - b.HasIndex("UserId"); b.HasIndex("VacancyId"); @@ -292,11 +273,15 @@ namespace CandidateReviewDatabaseImplement.Migrations modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Assessment", b => { + b.HasOne("CandidateReviewDatabaseImplement.Models.Resume", "Resume") + .WithMany() + .HasForeignKey("ResumeId"); + b.HasOne("CandidateReviewDatabaseImplement.Models.User", "User") - .WithMany("Assessments") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Resume"); b.Navigation("User"); }); @@ -322,26 +307,18 @@ namespace CandidateReviewDatabaseImplement.Migrations modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Resume", b => { - b.HasOne("CandidateReviewDatabaseImplement.Models.Assessment", "Assessment") - .WithOne("Resume") - .HasForeignKey("CandidateReviewDatabaseImplement.Models.Resume", "ResumeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - b.HasOne("CandidateReviewDatabaseImplement.Models.User", "User") - .WithMany("Resumes") + .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("CandidateReviewDatabaseImplement.Models.Vacancy", "Vacancy") - .WithMany("Resumes") + .WithMany() .HasForeignKey("VacancyId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Assessment"); - b.Navigation("User"); b.Navigation("Vacancy"); @@ -350,7 +327,7 @@ namespace CandidateReviewDatabaseImplement.Migrations modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.User", b => { b.HasOne("CandidateReviewDatabaseImplement.Models.Company", "Company") - .WithMany("Users") + .WithMany() .HasForeignKey("CompanyId"); b.Navigation("Company"); @@ -359,7 +336,7 @@ namespace CandidateReviewDatabaseImplement.Migrations modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Vacancy", b => { b.HasOne("CandidateReviewDatabaseImplement.Models.Company", "Company") - .WithMany("Vacancies") + .WithMany() .HasForeignKey("CompanyId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -370,34 +347,12 @@ namespace CandidateReviewDatabaseImplement.Migrations modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Assessment", b => { b.Navigation("Criterions"); - - b.Navigation("Resume") - .IsRequired(); - }); - - modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Company", b => - { - b.Navigation("Users"); - - b.Navigation("Vacancies"); }); modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Criterion", b => { b.Navigation("AssessmentCriterions"); }); - - modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.User", b => - { - b.Navigation("Assessments"); - - b.Navigation("Resumes"); - }); - - modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Vacancy", b => - { - b.Navigation("Resumes"); - }); #pragma warning restore 612, 618 } } diff --git a/CandidateReviewDatabaseImplement/Migrations/20241105152221_InitialCreate.cs b/CandidateReviewDatabaseImplement/Migrations/20241203082827_InitialCreate.cs similarity index 91% rename from CandidateReviewDatabaseImplement/Migrations/20241105152221_InitialCreate.cs rename to CandidateReviewDatabaseImplement/Migrations/20241203082827_InitialCreate.cs index 2b5e7ff..08851e3 100644 --- a/CandidateReviewDatabaseImplement/Migrations/20241105152221_InitialCreate.cs +++ b/CandidateReviewDatabaseImplement/Migrations/20241203082827_InitialCreate.cs @@ -36,10 +36,7 @@ namespace CandidateReviewDatabaseImplement.Migrations { Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Name = table.Column(type: "text", nullable: false), - Type = table.Column(type: "integer", nullable: false), - Description = table.Column(type: "text", nullable: true), - Weight = table.Column(type: "integer", nullable: false) + Name = table.Column(type: "text", nullable: false) }, constraints: table => { @@ -101,27 +98,62 @@ namespace CandidateReviewDatabaseImplement.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "Resumes", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + VacancyId = table.Column(type: "integer", nullable: false), + UserId = table.Column(type: "integer", nullable: false), + Title = table.Column(type: "text", nullable: false), + Experience = table.Column(type: "text", nullable: false), + Education = table.Column(type: "text", nullable: false), + PhotoFilePath = table.Column(type: "text", nullable: true), + Description = table.Column(type: "text", nullable: true), + Skills = table.Column(type: "text", nullable: false), + Status = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Resumes", x => x.Id); + table.ForeignKey( + name: "FK_Resumes_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Resumes_Vacancies_VacancyId", + column: x => x.VacancyId, + principalTable: "Vacancies", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "Assessments", columns: table => new { Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ResumeId = table.Column(type: "integer", nullable: false), - UserId = table.Column(type: "integer", nullable: false), - Rating = table.Column(type: "integer", nullable: true), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UserId = table.Column(type: "integer", nullable: true), + ResumeId = table.Column(type: "integer", nullable: true), Comment = table.Column(type: "text", nullable: true) }, constraints: table => { table.PrimaryKey("PK_Assessments", x => x.Id); + table.ForeignKey( + name: "FK_Assessments_Resumes_ResumeId", + column: x => x.ResumeId, + principalTable: "Resumes", + principalColumn: "Id"); table.ForeignKey( name: "FK_Assessments_Users_UserId", column: x => x.UserId, principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + principalColumn: "Id"); }); migrationBuilder.CreateTable( @@ -151,46 +183,6 @@ namespace CandidateReviewDatabaseImplement.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "Resumes", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - VacancyId = table.Column(type: "integer", nullable: false), - UserId = table.Column(type: "integer", nullable: false), - Title = table.Column(type: "text", nullable: false), - Experience = table.Column(type: "text", nullable: false), - Education = table.Column(type: "text", nullable: false), - PhotoFilePath = table.Column(type: "text", nullable: true), - Description = table.Column(type: "text", nullable: true), - Skills = table.Column(type: "text", nullable: false), - Status = table.Column(type: "integer", nullable: false), - ResumeId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Resumes", x => x.Id); - table.ForeignKey( - name: "FK_Resumes_Assessments_ResumeId", - column: x => x.ResumeId, - principalTable: "Assessments", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Resumes_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Resumes_Vacancies_VacancyId", - column: x => x.VacancyId, - principalTable: "Vacancies", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateIndex( name: "IX_AssessmentCriterions_AssessmentId", table: "AssessmentCriterions", @@ -201,17 +193,16 @@ namespace CandidateReviewDatabaseImplement.Migrations table: "AssessmentCriterions", column: "CriterionId"); + migrationBuilder.CreateIndex( + name: "IX_Assessments_ResumeId", + table: "Assessments", + column: "ResumeId"); + migrationBuilder.CreateIndex( name: "IX_Assessments_UserId", table: "Assessments", column: "UserId"); - migrationBuilder.CreateIndex( - name: "IX_Resumes_ResumeId", - table: "Resumes", - column: "ResumeId", - unique: true); - migrationBuilder.CreateIndex( name: "IX_Resumes_UserId", table: "Resumes", @@ -240,20 +231,20 @@ namespace CandidateReviewDatabaseImplement.Migrations name: "AssessmentCriterions"); migrationBuilder.DropTable( - name: "Resumes"); + name: "Assessments"); migrationBuilder.DropTable( name: "Criterions"); migrationBuilder.DropTable( - name: "Assessments"); - - migrationBuilder.DropTable( - name: "Vacancies"); + name: "Resumes"); migrationBuilder.DropTable( name: "Users"); + migrationBuilder.DropTable( + name: "Vacancies"); + migrationBuilder.DropTable( name: "Companies"); } diff --git a/CandidateReviewDatabaseImplement/Migrations/CandidateReviewDatabaseModelSnapshot.cs b/CandidateReviewDatabaseImplement/Migrations/CandidateReviewDatabaseModelSnapshot.cs index c5fc887..4c1e3bb 100644 --- a/CandidateReviewDatabaseImplement/Migrations/CandidateReviewDatabaseModelSnapshot.cs +++ b/CandidateReviewDatabaseImplement/Migrations/CandidateReviewDatabaseModelSnapshot.cs @@ -33,20 +33,16 @@ namespace CandidateReviewDatabaseImplement.Migrations b.Property("Comment") .HasColumnType("text"); - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Rating") + b.Property("ResumeId") .HasColumnType("integer"); - b.Property("ResumeId") - .HasColumnType("integer"); - - b.Property("UserId") + b.Property("UserId") .HasColumnType("integer"); b.HasKey("Id"); + b.HasIndex("ResumeId"); + b.HasIndex("UserId"); b.ToTable("Assessments"); @@ -118,19 +114,10 @@ namespace CandidateReviewDatabaseImplement.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("Description") - .HasColumnType("text"); - b.Property("Name") .IsRequired() .HasColumnType("text"); - b.Property("Type") - .HasColumnType("integer"); - - b.Property("Weight") - .HasColumnType("integer"); - b.HasKey("Id"); b.ToTable("Criterions"); @@ -158,9 +145,6 @@ namespace CandidateReviewDatabaseImplement.Migrations b.Property("PhotoFilePath") .HasColumnType("text"); - b.Property("ResumeId") - .HasColumnType("integer"); - b.Property("Skills") .IsRequired() .HasColumnType("text"); @@ -180,9 +164,6 @@ namespace CandidateReviewDatabaseImplement.Migrations b.HasKey("Id"); - b.HasIndex("ResumeId") - .IsUnique(); - b.HasIndex("UserId"); b.HasIndex("VacancyId"); @@ -289,11 +270,15 @@ namespace CandidateReviewDatabaseImplement.Migrations modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Assessment", b => { + b.HasOne("CandidateReviewDatabaseImplement.Models.Resume", "Resume") + .WithMany() + .HasForeignKey("ResumeId"); + b.HasOne("CandidateReviewDatabaseImplement.Models.User", "User") - .WithMany("Assessments") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Resume"); b.Navigation("User"); }); @@ -319,26 +304,18 @@ namespace CandidateReviewDatabaseImplement.Migrations modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Resume", b => { - b.HasOne("CandidateReviewDatabaseImplement.Models.Assessment", "Assessment") - .WithOne("Resume") - .HasForeignKey("CandidateReviewDatabaseImplement.Models.Resume", "ResumeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - b.HasOne("CandidateReviewDatabaseImplement.Models.User", "User") - .WithMany("Resumes") + .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("CandidateReviewDatabaseImplement.Models.Vacancy", "Vacancy") - .WithMany("Resumes") + .WithMany() .HasForeignKey("VacancyId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Assessment"); - b.Navigation("User"); b.Navigation("Vacancy"); @@ -347,7 +324,7 @@ namespace CandidateReviewDatabaseImplement.Migrations modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.User", b => { b.HasOne("CandidateReviewDatabaseImplement.Models.Company", "Company") - .WithMany("Users") + .WithMany() .HasForeignKey("CompanyId"); b.Navigation("Company"); @@ -356,7 +333,7 @@ namespace CandidateReviewDatabaseImplement.Migrations modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Vacancy", b => { b.HasOne("CandidateReviewDatabaseImplement.Models.Company", "Company") - .WithMany("Vacancies") + .WithMany() .HasForeignKey("CompanyId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -367,34 +344,12 @@ namespace CandidateReviewDatabaseImplement.Migrations modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Assessment", b => { b.Navigation("Criterions"); - - b.Navigation("Resume") - .IsRequired(); - }); - - modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Company", b => - { - b.Navigation("Users"); - - b.Navigation("Vacancies"); }); modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Criterion", b => { b.Navigation("AssessmentCriterions"); }); - - modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.User", b => - { - b.Navigation("Assessments"); - - b.Navigation("Resumes"); - }); - - modelBuilder.Entity("CandidateReviewDatabaseImplement.Models.Vacancy", b => - { - b.Navigation("Resumes"); - }); #pragma warning restore 612, 618 } } diff --git a/CandidateReviewDatabaseImplement/Models/Assessment.cs b/CandidateReviewDatabaseImplement/Models/Assessment.cs index a694911..8795938 100644 --- a/CandidateReviewDatabaseImplement/Models/Assessment.cs +++ b/CandidateReviewDatabaseImplement/Models/Assessment.cs @@ -8,14 +8,9 @@ namespace CandidateReviewDatabaseImplement.Models { public class Assessment : IAssessmentModel { - [Required] - public int ResumeId { get; set; } - [Required] - public int UserId { get; set; } + public int? UserId { get; set; } - public int? Rating { get; set; } - [Required] - public DateTime CreatedAt { get; set; } + public int? ResumeId { get; set; } public string? Comment { get; set; } @@ -53,8 +48,6 @@ namespace CandidateReviewDatabaseImplement.Models Id = model.Id, ResumeId = model.ResumeId, UserId = model.UserId, - Rating = model.Rating, - CreatedAt = model.CreatedAt, Comment = model.Comment, Criterions = model.AssessmentCriterions.Select(x => new AssessmentCriterion() { @@ -71,8 +64,6 @@ namespace CandidateReviewDatabaseImplement.Models } ResumeId = model.ResumeId; UserId = model.UserId; - Rating = model.Rating; - CreatedAt = model.CreatedAt; Comment = model.Comment; } public AssessmentViewModel GetViewModel => new() @@ -80,8 +71,6 @@ namespace CandidateReviewDatabaseImplement.Models Id = Id, ResumeId = ResumeId, UserId = UserId, - Rating = Rating, - CreatedAt = CreatedAt, Comment = Comment }; diff --git a/CandidateReviewDatabaseImplement/Models/Company.cs b/CandidateReviewDatabaseImplement/Models/Company.cs index 014a207..526c654 100644 --- a/CandidateReviewDatabaseImplement/Models/Company.cs +++ b/CandidateReviewDatabaseImplement/Models/Company.cs @@ -22,12 +22,6 @@ namespace CandidateReviewDatabaseImplement.Models public int Id { get; set; } - [ForeignKey("CompanyId")] - public virtual List Users { get; set; } = new(); - - [ForeignKey("CompanyId")] - public virtual List Vacancies { get; set; } = new(); - public static Company? Create(CompanyBindingModel model) { if (model == null) diff --git a/CandidateReviewDatabaseImplement/Models/Criterion.cs b/CandidateReviewDatabaseImplement/Models/Criterion.cs index 2eeb876..01f978d 100644 --- a/CandidateReviewDatabaseImplement/Models/Criterion.cs +++ b/CandidateReviewDatabaseImplement/Models/Criterion.cs @@ -1,6 +1,5 @@ using CandidateReviewContracts.BindingModels; using CandidateReviewContracts.ViewModels; -using CandidateReviewDataModels.Enums; using CandidateReviewDataModels.Models; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -11,12 +10,6 @@ namespace CandidateReviewDatabaseImplement.Models { [Required] public string Name { get; set; } = string.Empty; - [Required] - public CriterionTypeEnum Type { get; set; } - - public string? Description { get; set; } - [Required] - public int Weight { get; set; } public int Id { get; set; } [ForeignKey("CriterionId")] @@ -31,10 +24,7 @@ namespace CandidateReviewDatabaseImplement.Models return new Criterion() { Id = model.Id, - Name = model.Name, - Type = model.Type, - Description = model.Description, - Weight = model.Weight + Name = model.Name }; } public static Criterion Create(CriterionViewModel model) @@ -42,10 +32,7 @@ namespace CandidateReviewDatabaseImplement.Models return new Criterion { Id = model.Id, - Name = model.Name, - Type = model.Type, - Description = model.Description, - Weight = model.Weight + Name = model.Name }; } public void Update(CriterionBindingModel model) @@ -55,17 +42,11 @@ namespace CandidateReviewDatabaseImplement.Models return; } Name = model.Name; - Type = model.Type; - Description = model.Description; - Weight = model.Weight; } public CriterionViewModel GetViewModel => new() { Id = Id, - Name = Name, - Type = Type, - Description = Description, - Weight = Weight + Name = Name }; } } diff --git a/CandidateReviewDatabaseImplement/Models/Resume.cs b/CandidateReviewDatabaseImplement/Models/Resume.cs index 2108a4d..e4d78a0 100644 --- a/CandidateReviewDatabaseImplement/Models/Resume.cs +++ b/CandidateReviewDatabaseImplement/Models/Resume.cs @@ -30,8 +30,6 @@ namespace CandidateReviewDatabaseImplement.Models public int Id { get; set; } - [ForeignKey("ResumeId")] - public virtual Assessment Assessment { get; set; } = new(); public virtual Vacancy Vacancy { get; set; } public virtual User User { get; set; } diff --git a/CandidateReviewDatabaseImplement/Models/User.cs b/CandidateReviewDatabaseImplement/Models/User.cs index 1055c04..57123e8 100644 --- a/CandidateReviewDatabaseImplement/Models/User.cs +++ b/CandidateReviewDatabaseImplement/Models/User.cs @@ -31,12 +31,6 @@ namespace CandidateReviewDatabaseImplement.Models public int Id { get; set; } public virtual Company Company { get; set; } - [ForeignKey("UserId")] - public virtual List Resumes { get; set; } = new(); - - [ForeignKey("UserId")] - public virtual List Assessments { get; set; } = new(); - public static User? Create(UserBindingModel model) { if (model == null) diff --git a/CandidateReviewDatabaseImplement/Models/Vacancy.cs b/CandidateReviewDatabaseImplement/Models/Vacancy.cs index 8fc6e63..db31c8e 100644 --- a/CandidateReviewDatabaseImplement/Models/Vacancy.cs +++ b/CandidateReviewDatabaseImplement/Models/Vacancy.cs @@ -33,9 +33,6 @@ namespace CandidateReviewDatabaseImplement.Models public int Id { get; set; } public virtual Company Company { get; set; } - [ForeignKey("VacancyId")] - public virtual List Resumes { get; set; } = new(); - public static Vacancy? Create(VacancyBindingModel model) { if (model == null) diff --git a/CandidateReviewRestApi/Controllers/AssessmentController.cs b/CandidateReviewRestApi/Controllers/AssessmentController.cs new file mode 100644 index 0000000..74f7028 --- /dev/null +++ b/CandidateReviewRestApi/Controllers/AssessmentController.cs @@ -0,0 +1,80 @@ +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 AssessmentController : Controller + { + private readonly ILogger _logger; + private readonly IAssessmentLogic _logic; + public AssessmentController(IAssessmentLogic logic, ILogger logger) + { + _logger = logger; + _logic = logic; + } + + [HttpGet] + public AssessmentViewModel? Details(int id) + { + try + { + return _logic.ReadElement(new AssessmentSearchModel + { + Id = id + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка получения оценки"); + throw; + } + } + + [HttpPost] + public void Create(AssessmentBindingModel model) + { + try + { + _logic.Create(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка создания оценки"); + throw; + } + } + + [HttpPost] + public void Update(AssessmentBindingModel model) + { + try + { + _logic.Update(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка обновления оценки"); + throw; + } + } + + [HttpPost] + public void Delete(AssessmentBindingModel model) + { + try + { + _logic.Delete(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка удаления оценки"); + throw; + } + } + } +} diff --git a/CandidateReviewRestApi/Controllers/CompanyController.cs b/CandidateReviewRestApi/Controllers/CompanyController.cs index 716a34f..0fa004b 100644 --- a/CandidateReviewRestApi/Controllers/CompanyController.cs +++ b/CandidateReviewRestApi/Controllers/CompanyController.cs @@ -2,7 +2,6 @@ using CandidateReviewContracts.BusinessLogicsContracts; using CandidateReviewContracts.SearchModels; using CandidateReviewContracts.ViewModels; -using CandidateReviewDataModels.Models; using Microsoft.AspNetCore.Mvc; using System.Linq; @@ -39,17 +38,34 @@ namespace CandidateReviewRestApi.Controllers } } - [HttpPost] - public int Create(CompanyBindingModel model) + [HttpGet] + public CompanyViewModel? DefineByName(string name) { try { - int newCompanyId = _logic.Create(model); - return newCompanyId; + return _logic.ReadElement(new CompanySearchModel + { + Name = name + }); } catch (Exception ex) { - _logger.LogError(ex, "Ошибка создания профиля компании"); + _logger.LogError(ex, "Ошибка определения компании"); + throw; + } + } + + [HttpPost] + public IActionResult Create(CompanyBindingModel model) + { + try + { + int? id = _logic.Create(model); + return Ok(new CompanyBindingModel { Id = (int)id }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка создания компании"); throw; } } diff --git a/CandidateReviewRestApi/Controllers/CriterionController.cs b/CandidateReviewRestApi/Controllers/CriterionController.cs new file mode 100644 index 0000000..f0f8b23 --- /dev/null +++ b/CandidateReviewRestApi/Controllers/CriterionController.cs @@ -0,0 +1,95 @@ +using CandidateReviewContracts.BindingModels; +using CandidateReviewContracts.BusinessLogicsContracts; +using CandidateReviewContracts.SearchModels; +using CandidateReviewContracts.ViewModels; +using CandidateReviewDataModels.Models; +using Microsoft.AspNetCore.Mvc; + +namespace CandidateReviewRestApi.Controllers +{ + [Route("api/[controller]/[action]")] + [ApiController] + public class CriterionController : Controller + { + private readonly ILogger _logger; + private readonly ICriterionLogic _logic; + public CriterionController(ICriterionLogic logic, ILogger logger) + { + _logger = logger; + _logic = logic; + } + + [HttpGet] + public CriterionViewModel? Details(int id) + { + try + { + return _logic.ReadElement(new CriterionSearchModel + { + Id = id + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка получения критерия"); + throw; + } + } + + [HttpGet] + public List? List() + { + try + { + return _logic.ReadList(null); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка получения списка критериев"); + throw; + } + } + + [HttpPost] + public void Create(CriterionBindingModel model) + { + try + { + _logic.Create(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка создания критерия"); + throw; + } + } + + [HttpPost] + public void Update(CriterionBindingModel model) + { + try + { + _logic.Update(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка обновления критерия"); + throw; + } + } + + [HttpPost] + public void Delete(CriterionBindingModel model) + { + try + { + _logic.Delete(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка удаления критерия"); + throw; + } + } + } +} diff --git a/CandidateReviewRestApi/Controllers/ResumeController.cs b/CandidateReviewRestApi/Controllers/ResumeController.cs new file mode 100644 index 0000000..47265eb --- /dev/null +++ b/CandidateReviewRestApi/Controllers/ResumeController.cs @@ -0,0 +1,98 @@ +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 ResumeController : Controller + { + private readonly ILogger _logger; + private readonly IResumeLogic _logic; + public ResumeController(IResumeLogic logic, ILogger logger) + { + _logger = logger; + _logic = logic; + } + + [HttpGet] + public ResumeViewModel? Details(int id) + { + try + { + return _logic.ReadElement(new ResumeSearchModel + { + Id = id + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка получения резюме"); + throw; + } + } + + [HttpGet] + public ResumeViewModel? Check(int userId, int vacancyId) + { + try + { + return _logic.ReadElement(new ResumeSearchModel + { + UserId = userId, + VacancyId = vacancyId + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка получения резюме"); + throw; + } + } + + [HttpPost] + public void Create(ResumeBindingModel model) + { + try + { + _logic.Create(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка создания резюме"); + throw; + } + } + + [HttpPost] + public void Update(ResumeBindingModel model) + { + try + { + _logic.Update(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка обновления резюме"); + throw; + } + } + + [HttpPost] + public void Delete(ResumeBindingModel model) + { + try + { + _logic.Delete(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка удаления резюме"); + throw; + } + } + } +} diff --git a/CandidateReviewRestApi/Controllers/VacancyController.cs b/CandidateReviewRestApi/Controllers/VacancyController.cs index 2e1f8e8..6e50368 100644 --- a/CandidateReviewRestApi/Controllers/VacancyController.cs +++ b/CandidateReviewRestApi/Controllers/VacancyController.cs @@ -44,7 +44,8 @@ namespace CandidateReviewRestApi.Controllers { return _logic.ReadList(new VacancySearchModel { - Tags = tags + Tags = tags, + Status = CandidateReviewDataModels.Enums.VacancyStatusEnum.Открыта }); } return new List();