профиль компании и пользователя + редактрование (работает не все)

This commit is contained in:
Татьяна Артамонова 2024-11-18 23:37:50 +04:00
parent 3ccdcf3e76
commit 5528e3db15
19 changed files with 719 additions and 87 deletions

View File

@ -17,15 +17,15 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
_logger = logger;
_сompanyStorage = сompanyStorage;
}
public bool Create(CompanyBindingModel model)
public int Create(CompanyBindingModel model)
{
CheckModel(model);
if (_сompanyStorage.Insert(model) == null)
{
_logger.LogWarning("Insert operation failed");
return false;
return 0;
}
return true;
return model.Id;
}
public bool Delete(CompanyBindingModel model)

View File

@ -5,6 +5,8 @@ using CandidateReviewContracts.ViewModels;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using CandidateReviewContracts.StoragesContracts;
using System.Text;
using System.Security.Cryptography;
namespace CandidateReviewBusinessLogic.BusinessLogic
{
@ -17,9 +19,17 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
_logger = logger;
_userStorage = userStorage;
}
private string EncryptPassword(string password)
{
byte[] hashedBytes = SHA256.HashData(Encoding.UTF8.GetBytes(password));
return Convert.ToBase64String(hashedBytes);
}
public bool Create(UserBindingModel model)
{
CheckModel(model);
CheckPassword(model);
model.Password = EncryptPassword(model.Password);
if (_userStorage.Insert(model) == null)
{
_logger.LogWarning("Insert operation failed");
@ -31,6 +41,7 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
public bool Delete(UserBindingModel model)
{
CheckModel(model, false);
CheckPassword(model);
_logger.LogInformation("Delete. Id: {Id}", model.Id);
if (_userStorage.Delete(model) == null)
{
@ -47,13 +58,28 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
throw new ArgumentNullException(nameof(model));
}
var element = _userStorage.GetElement(model);
if (element == null)
if (element != null)
{
_logger.LogWarning("ReadElement element not found");
return null;
string hashedPassword = element.Password;
if (element != null && model.Password != element.Password && model.Password != null)
{
hashedPassword = EncryptPassword(model.Password);
}
if (element == null)
{
_logger.LogWarning("ReadElement element not found");
return null;
}
else
{
if (element.Password == hashedPassword)
{
_logger.LogInformation("ReadElement find. Id: {Id}", element.Id);
return element;
}
}
}
_logger.LogInformation("ReadElement find. Id: {Id}", element.Id);
return element;
return null;
}
public List<UserViewModel>? ReadList(UserSearchModel? model)
@ -71,6 +97,19 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
public bool Update(UserBindingModel model)
{
CheckModel(model);
var elem = _userStorage.GetElement(new UserSearchModel
{
Id = model.Id
});
if (elem != null && model.Password != elem.Password)
{
if (!Regex.IsMatch(model.Password, @"^^((\w+\d+\W+)|(\w+\W+\d+)|(\d+\w+\W+)|(\d+\W+\w+)|(\W+\w+\d+)|(\W+\d+\w+))[\w\d\W]*$", RegexOptions.IgnoreCase))
{
return false;
throw new ArgumentException("Неправильно введенный пароль", nameof(model.Password));
}
model.Password = EncryptPassword(model.Password);
}
if (_userStorage.Update(model) == null)
{
_logger.LogWarning("Update operation failed");
@ -99,21 +138,11 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
if (string.IsNullOrEmpty(model.Name))
{
throw new ArgumentNullException("Нет имени пользователя", nameof(model.Name));
}
if (string.IsNullOrEmpty(model.Password))
{
throw new ArgumentNullException("Нет пароля пользователя", nameof(model.LastName));
}
}
if (string.IsNullOrEmpty(model.Email))
{
throw new ArgumentNullException("Нет почты пользователя", nameof(model.LastName));
}
if (string.IsNullOrEmpty(model.Password))
{
throw new ArgumentNullException("Нет пароля пользователя", nameof(model.LastName));
throw new ArgumentNullException("Нет почты пользователя", nameof(model.Email));
}
if (!Regex.IsMatch(model.Email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.IgnoreCase))
@ -121,11 +150,6 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
throw new ArgumentException("Неправильно введенный email", nameof(model.Email));
}
if (!Regex.IsMatch(model.Password, @"^^((\w+\d+\W+)|(\w+\W+\d+)|(\d+\w+\W+)|(\d+\W+\w+)|(\W+\w+\d+)|(\W+\d+\w+))[\w\d\W]*$", RegexOptions.IgnoreCase))
{
throw new ArgumentException("Неправильно введенный пароль", nameof(model.Password));
}
var element = _userStorage.GetElement(new UserSearchModel
{
Email = model.Email
@ -136,5 +160,18 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
throw new InvalidOperationException("Пользователь с такой почтой уже есть");
}
}
private void CheckPassword(UserBindingModel model)
{
if (string.IsNullOrEmpty(model.Password))
{
throw new ArgumentNullException("Нет пароля пользователя", nameof(model.Password));
}
if (!Regex.IsMatch(model.Password, @"^^((\w+\d+\W+)|(\w+\W+\d+)|(\d+\w+\W+)|(\d+\W+\w+)|(\W+\w+\d+)|(\W+\d+\w+))[\w\d\W]*$", RegexOptions.IgnoreCase))
{
throw new ArgumentException("Неправильно введенный пароль", nameof(model.Password));
}
}
}
}

View File

@ -9,6 +9,7 @@ namespace CandidateReviewClientApp
{
private static readonly HttpClient _user = new();
public static UserViewModel? User { get; set; } = null;
public static CompanyViewModel? Company { get; set; } = null;
public static void Connect(IConfiguration configuration)
{
_user.BaseAddress = new Uri(configuration["IPAddress"]);

View File

@ -0,0 +1,79 @@
using CandidateReviewContracts.BindingModels;
using CandidateReviewContracts.ViewModels;
using Microsoft.AspNetCore.Mvc;
namespace CandidateReviewClientApp.Controllers
{
public class CompanyController : Controller
{
private readonly ILogger<CompanyController> _logger;
public CompanyController(ILogger<CompanyController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult CompanyProfile(int? id)
{
if (APIClient.User == null)
{
return Redirect("~/Home/Enter");
}
if (id.HasValue)
{
APIClient.Company = APIClient.GetRequest<CompanyViewModel?>($"api/company/profile?id={id}");
}
var model = APIClient.Company;
return View(model);
}
[HttpGet]
public IActionResult EditCompanyProfile(int? id)
{
if (APIClient.User == null)
{
return Redirect("~/Home/Enter");
}
if (!id.HasValue)
{
return View(new CompanyViewModel());
}
var model = APIClient.GetRequest<CompanyViewModel?>($"api/company/profile?id={id}");
if (model != null)
{
APIClient.PostRequest($"api/user/update", new UserBindingModel { Id = APIClient.User.Id, CompanyId = model.Id });
}
return View(model);
}
[HttpPost]
public void EditCompanyProfile(CompanyBindingModel model)
{
if (APIClient.User == null)
{
throw new Exception("Доступно только авторизованным пользователям");
}
if (model.Id != 0)
{
APIClient.PostRequest("api/company/update", model);
}
else
{
APIClient.PostRequest("api/company/create", model);
}
Response.Redirect("/Home/Index");
}
[HttpPost]
public void Delete(int id)
{
if (APIClient.User == null)
{
throw new Exception("Доступно только авторизованным пользователям");
}
APIClient.PostRequest($"api/company/delete", new CompanyBindingModel { Id = id });
Response.Redirect("/Home/Index");
}
}
}

View File

@ -2,6 +2,7 @@ using CandidateReviewClientApp;
using CandidateReviewClientApp.Models;
using CandidateReviewContracts.BindingModels;
using CandidateReviewContracts.ViewModels;
using CandidateReviewDataModels.Enums;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
@ -44,7 +45,7 @@ namespace CandidateReviewUserApp.Controllers
{
throw new Exception("Íåâåðíûé ëîãèí/ïàðîëü");
}
Response.Redirect("Index");
Response.Redirect("/Home/Index");
}
[HttpGet]
@ -52,6 +53,7 @@ namespace CandidateReviewUserApp.Controllers
{
return View();
}
[HttpPost]
public void Register(string login, string password, string surname, string name, string lastname)
{
@ -59,6 +61,18 @@ namespace CandidateReviewUserApp.Controllers
{
throw new Exception("Ââåäèòå ëîãèí, ïàðîëü è ÔÈÎ");
}
RoleEnum role = RoleEnum.Íåèçâåñòåí;
if (login.Equals("tania.art03@gmail.com", StringComparison.OrdinalIgnoreCase))
{
role = RoleEnum.Ñîòðóäíèê;
}
else if (login.Equals("t.artamonova73@icloud.com", StringComparison.OrdinalIgnoreCase))
{
role = RoleEnum.Ïîëüçîâàòåëü;
}
APIClient.PostRequest("api/user/register", new UserBindingModel
{
Surname = surname,
@ -67,9 +81,10 @@ namespace CandidateReviewUserApp.Controllers
Email = login,
Password = password,
EmailConfirmed = false,
Role = CandidateReviewDataModels.Enums.RoleEnum.Íåèçâåñòåí
Role = role
});
Response.Redirect("Enter");
Response.Redirect("/Home/Enter");
return;
}

View File

@ -0,0 +1,77 @@
using CandidateReviewClientApp.Models;
using CandidateReviewContracts.BindingModels;
using CandidateReviewContracts.ViewModels;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace CandidateReviewClientApp.Controllers
{
public class UserController : Controller
{
private readonly ILogger<UserController> _logger;
public UserController(ILogger<UserController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult UserProfile(int? id)
{
var userId = id ?? APIClient.User.Id;
var model = APIClient.GetRequest<UserViewModel>($"api/user/profile?id={userId}");
if (model == null)
{
return RedirectToAction("Index");
}
return View(model);
}
[HttpGet]
public IActionResult UserProfileEdit()
{
if (APIClient.User == null)
{
return Redirect("/Home/Enter");
}
return View(APIClient.User);
}
[HttpPost]
public void UserProfileEdit(UserBindingModel model)
{
APIClient.PostRequest("api/user/update", new UserBindingModel
{
Id = model.Id,
Surname = model.Surname,
Name = model.Name,
LastName = model.LastName ?? null,
CompanyId = model.CompanyId ?? null,
Email = model.Email,
Password = model.Password,
EmailConfirmed = model.EmailConfirmed,
Role = model.Role,
AvatarFilePath = model.AvatarFilePath ?? null,
PhoneNumber = model.PhoneNumber ?? null
});
Response.Redirect("/User/UserProfile");
}
[HttpGet]
public void Logout()
{
APIClient.User = null;
Response.Redirect("/Home/Enter");
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

View File

@ -25,6 +25,6 @@ app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
pattern: "{controller=Home}/{action=Enter}/{id?}");
app.Run();

View File

@ -0,0 +1,58 @@
@using CandidateReviewContracts.ViewModels
@model CompanyViewModel
@{
ViewData["Title"] = "Профиль компании";
}
<div class="container mt-5">
<div class="row g-4">
<div class="col-md-4">
<div class="card">
@if (!string.IsNullOrEmpty(Model.LogoFilePath))
{
<img src="@Model.LogoFilePath" class="card-img-top img-fluid" alt="Логотип компании">
}
<div class="card-body">
<h5 class="card-title">@Model.Name</h5>
<p class="card-text text-truncate" style="max-height: 100px; overflow: hidden;">@Model.Description</p>
<a href="@Model.Website" target="_blank" class="btn btn-primary mt-2">@Model.Website</a>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h2>Информация о компании</h2>
<a asp-action="EditCompanyProfile" asp-controller="Company" asp-route-id="@Model.Id" class="btn btn-primary float-end">Редактировать</a>
</div>
<div class="card-body">
<table class="table table-borderless">
<tbody>
<tr>
<th>Название</th>
<td>@Model.Name</td>
</tr>
<tr>
<th>Описание</th>
<td>@Model.Description</td>
</tr>
<tr>
<th>Официальный сайт</th>
<td><a href="@Model.Website" target="_blank">@Model.Website</a></td>
</tr>
<tr>
<th>Адрес</th>
<td>@Model.Address</td>
</tr>
<tr>
<th>Контакты</th>
<td>@Model.Contacts</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,65 @@
@using CandidateReviewContracts.ViewModels
@model CompanyViewModel
@{
var title = @Model.Id <= 0 ? "Создать профиль компании" : "Редактировать профиль компании";
}
<div class="container">
<h1>@title</h1>
<form method="post" class="row g-3 needs-validation" novalidate>
<input type="hidden" name="id" value="@Model?.Id" />
<div class="col-md-6">
<label for="name" class="form-label">Название <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="name" name="name" value="@Model?.Name" required placeholder="Введите название компании">
<div class="invalid-feedback">Пожалуйста, введите название компании.</div>
</div>
<div class="col-md-6">
<label for="description" class="form-label">Описание</label>
<textarea class="form-control" id="description" name="description" rows="3" placeholder="Введите описание компании">@Model?.Description</textarea>
</div>
<div class="col-md-6">
<label for="website" class="form-label">Официальный сайт</label>
<input type="url" class="form-control" id="website" name="website" value="@Model?.Website" placeholder="Введите URL сайта">
<div class="invalid-feedback">Пожалуйста, введите валидный URL (например, https://example.com).</div>
</div>
<div class="col-md-6">
<label for="address" class="form-label">Адрес</label>
<input type="text" class="form-control" id="address" name="address" value="@Model?.Address" placeholder="Введите адрес компании">
</div>
<div class="col-12">
<label for="contacts" class="form-label">Контакты</label>
<input type="text" class="form-control" id="contacts" name="contacts" value="@Model?.Contacts" placeholder="Введите контактную информацию">
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</form>
</div>
<script>
(function () {
'use strict'
var forms = document.querySelectorAll('.needs-validation')
Array.prototype.slice.call(forms)
.forEach(function (form) {
form.addEventListener('submit', function (event) {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add('was-validated')
}, false)
})
})()
</script>

View File

@ -1,5 +1,6 @@
@{
ViewData["Title"] = "Вход";
Layout = "_LoginLayout";
}
<div class="container mt-5">
@ -22,6 +23,9 @@
<div class="form-group text-center mt-3">
<button type="submit" class="btn btn-primary">Войти</button>
</div>
<div class="form-group text-center mt-2">
<a asp-area="" asp-controller="Home" asp-action="Register" class="text-muted">Ещё не зарегистрированы? Регистрация</a>
</div>
</form>
</div>
</div>

View File

@ -1,5 +1,6 @@
@{
ViewData["Title"] = "Регистрация";
Layout = "_LoginLayout";
}
<div class="container mt-5">
@ -34,10 +35,12 @@
<div class="form-group text-center mt-3">
<button type="submit" class="btn btn-primary">Зарегистрироваться</button>
</div>
<div class="form-group text-center mt-2">
<a asp-area="" asp-controller="Home" asp-action="Enter" class="text-muted">Уже зарегистрированы? Войти</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,57 +1,52 @@
<!DOCTYPE html>
@using CandidateReviewDataModels.Enums
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - CandidateReviewClientApp</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/CandidateReviewClientApp.styles.css" asp-append-version="true" />
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Ревью кандидатов на вакансию</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Заказы</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Личные данные</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Mails">Письма</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Enter">Вход</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Register">Регистрация</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - CandidateReviewClientApp</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/CandidateReviewClientApp.styles.css" asp-append-version="true" />
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</head>
<body>
<header>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
&copy; 2024 - Ревью кандидатов на вакансию - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Candidate Review</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav ml-auto">
@if (APIClient.User?.Role == RoleEnum.Сотрудник)
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Company" asp-action="@(APIClient.User?.CompanyId == null ? "EditCompanyProfile" : "CompanyProfile")">Профиль компании</a>
</li>
}
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="UserProfile" asp-route-id="@(APIClient.User?.Id)">Профиль</a>
</li>
</ul>
</div>
</div>
</footer>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
</nav>
</header>
<div class="container mt-4">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted mt-5">
<div class="container">
&copy; 2024 - Ревью кандидатов на вакансию
</div>
</footer>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - CandidateReviewClientApp</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/CandidateReviewClientApp.styles.css" asp-append-version="true" />
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<style>
html, body {
height: 100%;
margin: 0;
}
body {
display: flex;
flex-direction: column;
}
.container {
flex: 1;
}
footer {
background-color: #f8f9fa;
padding: 20px 0;
}
</style>
</head>
<body>
<div class="container mt-4">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted mt-5">
<div class="container">
&copy; 2024 - Ревью кандидатов на вакансию
</div>
</footer>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@ -0,0 +1,52 @@
@using CandidateReviewContracts.ViewModels
@model UserViewModel
@{
ViewData["Title"] = "Профиль пользователя";
}
<div class="container mt-5">
<div class="row">
<div class="col-md-4 mb-4">
<div class="card">
@if (!string.IsNullOrEmpty(@Model.AvatarFilePath))
{
<img src="@Model.AvatarFilePath" class="card-img-top img-fluid rounded-circle mx-auto d-block" style="max-width: 150px; max-height: 150px;" alt="Аватар пользователя">
}
<div class="card-body text-center">
<h5 class="card-title mb-0">
@Model.Name @(string.IsNullOrEmpty(@Model.Surname) ? "" : @Model.Surname) @(string.IsNullOrEmpty(@Model.LastName) ? "" : @Model.LastName)
</h5>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h2 class="mb-0">Контактная информация</h2>
<div class="btn-group float-end mt-2" role="group" aria-label="Действия">
<a asp-action="UserProfileEdit" asp-controller="User" asp-route-id="@Model?.Id" class="btn btn-primary">Редактировать</a>
<a asp-action="Delete" asp-controller="User" asp-route-id="@Model?.Id" class="btn btn-danger" onclick="return confirm('Вы уверены, что хотите удалить профиль?');">Удалить</a>
<a asp-action="Logout" asp-controller="User" class="btn btn-secondary">Выйти</a>
</div>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-3">Email</dt>
<dd class="col-sm-9">@Model.Email</dd>
@if (!string.IsNullOrEmpty(@Model.PhoneNumber))
{
<dt class="col-sm-3">Телефон</dt>
<dd class="col-sm-9">@Model.PhoneNumber</dd>
}
<dt class="col-sm-3">Роль</dt>
<dd class="col-sm-9">@Model.Role</dd>
</dl>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,99 @@
@using CandidateReviewContracts.ViewModels
@model UserViewModel
@{
ViewData["Title"] = "Редактирование профиля";
}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 class="mb-4">Редактирование профиля</h2>
<form method="post" class="needs-validation" novalidate>
<input type="hidden" name="id" value="@Model?.Id" />
<div class="mb-3">
<label for="Name" class="form-label">Имя <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="Name" name="Name" value="@Model?.Name" required />
<div class="invalid-feedback">Пожалуйста, введите имя.</div>
</div>
<div class="mb-3">
<label for="Surname" class="form-label">Фамилия <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="Surname" name="Surname" value="@Model?.Surname" required />
<div class="invalid-feedback">Пожалуйста, введите фамилию.</div>
</div>
<div class="mb-3">
<label for="LastName" class="form-label">Отчество</label>
<input type="text" class="form-control" id="LastName" name="LastName" value="@Model?.LastName" />
<div class="invalid-feedback">Пожалуйста, введите отчество.</div>
</div>
<div class="mb-3">
<label for="PhoneNumber" class="form-label">Телефон</label>
<input type="tel" class="form-control" id="PhoneNumber" name="PhoneNumber" value="@Model?.PhoneNumber" />
<div class="invalid-feedback">Пожалуйста, введите телефон.</div>
</div>
<input type="hidden" name="Email" value="@Model?.Email" />
<input type="hidden" name="Password" value="@Model?.Password" />
<input type="hidden" name="Role" value="@Model?.Role" />
<input type="hidden" name="CompanyId" value="@Model?.CompanyId" />
<div class="mb-3">
<label for="AvatarFilePath" class="form-label">Аватар</label>
<input type="file" class="form-control" id="AvatarFilePath" name="AvatarFilePath" value="@Model?.AvatarFilePath" accept=".jpg,.jpeg,.png" />
<div class="invalid-feedback">Выберите файл изображения.</div>
</div>
<div class="d-flex justify-content-between">
<button type="submit" class="btn btn-primary">Сохранить</button>
<a asp-controller="User" asp-action="UserProfile" class="btn btn-secondary">Отмена</a>
</div>
</form>
</div>
</div>
</div>
@section Scripts {
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.inputmask/5.0.7-beta.29/jquery.inputmask.min.js"></script>
<script>
$(document).ready(function () {
$('#PhoneNumber').inputmask({
mask: '+7 (999) 999-99-99', // Adjust mask as needed
showMaskOnHover: false,
onincomplete: function () {
$(this).removeClass('is-valid').addClass('is-invalid');
},
oncleared: function () {
$(this).removeClass('is-valid is-invalid');
},
oncomplete: function () {
$(this).removeClass('is-invalid').addClass('is-valid');
}
});
});
(function () {
'use strict'
var forms = document.querySelectorAll('.needs-validation')
Array.prototype.slice.call(forms)
.forEach(function (form) {
form.addEventListener('submit', function (event) {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add('was-validated')
}, false)
})
})()
</script>
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}

View File

@ -8,7 +8,7 @@ namespace CandidateReviewContracts.BusinessLogicsContracts
{
List<CompanyViewModel>? ReadList(CompanySearchModel? model);
CompanyViewModel? ReadElement(CompanySearchModel model);
bool Create(CompanyBindingModel model);
int Create(CompanyBindingModel model);
bool Update(CompanyBindingModel model);
bool Delete(CompanyBindingModel model);
}

View File

@ -92,6 +92,7 @@ namespace CandidateReviewDatabaseImplement.Models
Role = model.Role;
CompanyId = model.CompanyId;
}
public UserViewModel GetViewModel => new()
{
Id = Id,

View File

@ -0,0 +1,81 @@
using CandidateReviewContracts.BindingModels;
using CandidateReviewContracts.BusinessLogicsContracts;
using CandidateReviewContracts.SearchModels;
using CandidateReviewContracts.ViewModels;
using Microsoft.AspNetCore.Mvc;
namespace CandidateReviewRestApi.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class CompanyController : Controller
{
private readonly ILogger _logger;
private readonly ICompanyLogic _logic;
public CompanyController(ICompanyLogic logic, ILogger<CompanyController> logger)
{
_logger = logger;
_logic = logic;
}
[HttpGet]
public CompanyViewModel? Profile(int id)
{
try
{
return _logic.ReadElement(new CompanySearchModel
{
Id = id
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка получения профиля компании");
throw;
}
}
[HttpPost]
public int Create(CompanyBindingModel model)
{
try
{
int newCompanyId = _logic.Create(model);
return newCompanyId;
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка создания профиля компании");
throw;
}
}
[HttpPost]
public void Update(CompanyBindingModel model)
{
try
{
_logic.Update(model);
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка обновления профиля компании");
throw;
}
}
[HttpDelete]
public void Delete(CompanyBindingModel model)
{
try
{
_logic.Delete(model);
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка удаления профиля компании");
throw;
}
}
}
}

View File

@ -36,6 +36,23 @@ namespace CandidateReviewRestApi.Controllers
}
}
[HttpGet]
public UserViewModel? Profile(int id)
{
try
{
return _logic.ReadElement(new UserSearchModel
{
Id = id
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка получения данных");
throw;
}
}
[HttpPost]
public void Register(UserBindingModel model)
{
@ -51,7 +68,7 @@ namespace CandidateReviewRestApi.Controllers
}
[HttpPost]
public void UpdateData(UserBindingModel model)
public void Update(UserBindingModel model)
{
try
{