dev #12

Merged
mfnefd merged 5 commits from dev into registration 2024-06-20 04:20:37 +04:00
21 changed files with 880 additions and 315 deletions

View File

@ -21,147 +21,150 @@ using System.Threading.Tasks;
namespace BusinessLogic.BusinessLogic
{
public class UserLogic : IUserLogic
{
private readonly ILogger _logger;
private readonly IUserStorage _userStorage;
public class UserLogic : IUserLogic
{
private readonly ILogger _logger;
private readonly IUserStorage _userStorage;
public UserLogic(ILogger<UserLogic> logger, IUserStorage userStorage)
{
_logger = logger;
_userStorage = userStorage;
}
public UserLogic(ILogger<UserLogic> logger, IUserStorage userStorage)
{
_logger = logger;
_userStorage = userStorage;
}
public string Create(UserBindingModel model)
{
// Проверяем модель
_validate(model);
var usr = _userStorage.GetElement(new() { Email = model.Email });
if (usr is not null)
{
throw new AccountException("An account with that email already exists.");
}
// Хешируем пароль
model.PasswordHash = PasswordHasher.Hash(model.Password!);
var user = _userStorage.Insert(model);
if (user is null)
{
throw new Exception("Insert operation failed.");
}
public string Create(UserBindingModel model)
{
// Проверяем модель
_validate(model);
var usr = _userStorage.GetElement(new() { Email = model.Email });
if (usr is not null)
{
throw new AccountException("An account with that email already exists.");
}
// Хешируем пароль
model.PasswordHash = PasswordHasher.Hash(model.Password!);
model.Birthday = model.Birthday.ToUniversalTime();
MailSender.Send(new MailRegistration(user));
var user = _userStorage.Insert(model);
if (user is null)
{
throw new Exception("Insert operation failed.");
}
return JwtProvider.Generate(user);
}
MailSender.Send(new MailRegistration(user));
public UserViewModel Delete(UserSearchModel model)
{
ArgumentNullException.ThrowIfNull(model);
return JwtProvider.Generate(user);
}
_logger.LogInformation("Delete user. Id: {0}", model.Id);
var user = _userStorage.Delete(model);
if (user is null)
{
throw new ElementNotFoundException();
}
MailSender.Send(new MailDeleteUser(user));
public UserViewModel Delete(UserSearchModel model)
{
ArgumentNullException.ThrowIfNull(model);
return UserConverter.ToView(user);
}
_logger.LogInformation("Delete user. Id: {0}", model.Id);
var user = _userStorage.Delete(model);
if (user is null)
{
throw new ElementNotFoundException();
}
MailSender.Send(new MailDeleteUser(user));
public IEnumerable<UserViewModel> ReadElements(UserSearchModel? model)
{
_logger.LogInformation("ReadList. Id: {Id}", model?.Id);
var list = _userStorage.GetList(model);
if (list is null || list.Count() == 0)
{
_logger.LogWarning("ReadList return null list");
return [];
}
_logger.LogInformation("ReadList. Count: {Count}", list.Count());
return UserConverter.ToView(user);
}
return list.Select(UserConverter.ToView);
}
public IEnumerable<UserViewModel> ReadElements(UserSearchModel? model)
{
_logger.LogInformation("ReadList. Id: {Id}", model?.Id);
var list = _userStorage.GetList(model);
if (list is null || list.Count() == 0)
{
_logger.LogWarning("ReadList return null list");
return [];
}
_logger.LogInformation("ReadList. Count: {Count}", list.Count());
public UserViewModel ReadElement(UserSearchModel model)
{
ArgumentNullException.ThrowIfNull(model);
return list.Select(UserConverter.ToView);
}
_logger.LogInformation("ReadElement. Id: {0}", model.Id);
var user = _userStorage.GetElement(model);
if (user is null)
{
throw new ElementNotFoundException();
}
_logger.LogInformation("ReadElement find. Id: {0}", user.Id);
public UserViewModel ReadElement(UserSearchModel model)
{
ArgumentNullException.ThrowIfNull(model);
return UserConverter.ToView(user);
}
_logger.LogInformation("ReadElement. Id: {0}", model.Id);
var user = _userStorage.GetElement(model);
if (user is null)
{
throw new ElementNotFoundException();
}
_logger.LogInformation("ReadElement find. Id: {0}", user.Id);
public UserViewModel Update(UserBindingModel model)
{
_validate(model);
return UserConverter.ToView(user);
}
model.PasswordHash = PasswordHasher.Hash(model.Password!);
var user = _userStorage.Update(model);
if (user is null)
{
throw new Exception("Update operation failed.");
}
public UserViewModel Update(UserBindingModel model)
{
_validate(model);
MailSender.Send(new MailUpdateUserData(user));
model.PasswordHash = PasswordHasher.Hash(model.Password!);
model.Birthday = model.Birthday.ToUniversalTime();
var user = _userStorage.Update(model);
if (user is null)
{
throw new Exception("Update operation failed.");
}
return UserConverter.ToView(user);
}
MailSender.Send(new MailUpdateUserData(user));
public string Login(string email, string password)
{
_isValidEmail(email);
var user = _userStorage.GetElement(new() { Email = email });
return UserConverter.ToView(user);
}
if (user is null)
{
throw new ElementNotFoundException();
}
// Проверяем пароль
_isValidPassword(password);
if (!PasswordHasher.Verify(password, user.PasswordHash))
{
throw new AccountException("The passwords don't match.");
}
return JwtProvider.Generate(user);
}
public string Login(string email, string password)
{
_isValidEmail(email);
var user = _userStorage.GetElement(new() { Email = email });
private void _validate(UserBindingModel model)
{
ArgumentNullException.ThrowIfNull(model);
_isValidPassword(model.Password);
_isValidEmail(model.Email);
}
if (user is null)
{
throw new ElementNotFoundException();
}
// Проверяем пароль
_isValidPassword(password);
if (!PasswordHasher.Verify(password, user.PasswordHash))
{
throw new AccountException("The passwords don't match.");
}
return JwtProvider.Generate(user);
}
private void _isValidPassword(string? password)
{
if (string.IsNullOrWhiteSpace(password))
{
throw new AccountException("The password is null.");
}
var hasMin8Max15Chars = new Regex(@".{8,15}");
if (!hasMin8Max15Chars.IsMatch(password))
{
throw new AccountException("The password must not be less than 8 or more than 15 characters long.");
}
}
private void _validate(UserBindingModel model)
{
ArgumentNullException.ThrowIfNull(model);
_isValidPassword(model.Password);
_isValidEmail(model.Email);
}
private void _isValidEmail(string? email)
{
if (string.IsNullOrWhiteSpace(email))
{
throw new AccountException("The email is null.");
}
if (!MailAddress.TryCreate(email, out _))
{
throw new AccountException("The email is not valid.");
}
}
}
private void _isValidPassword(string? password)
{
if (string.IsNullOrWhiteSpace(password))
{
throw new AccountException("The password is null.");
}
var hasMin8Max15Chars = new Regex(@".{8,15}");
if (!hasMin8Max15Chars.IsMatch(password))
{
throw new AccountException("The password must not be less than 8 or more than 15 characters long.");
}
}
private void _isValidEmail(string? email)
{
if (string.IsNullOrWhiteSpace(email))
{
throw new AccountException("The email is null.");
}
if (!MailAddress.TryCreate(email, out _))
{
throw new AccountException("The email is not valid.");
}
}
}
}

View File

@ -6,8 +6,9 @@ using System.Threading.Tasks;
namespace Contracts.SearchModels
{
public class RoleSearchModel
{
public Guid? Id { get; set; }
}
public class RoleSearchModel
{
public Guid? Id { get; set; }
public string? Name { get; set; }
}
}

View File

@ -9,80 +9,82 @@ using System.Threading.Tasks;
namespace DatabaseImplement.Implements
{
public class RoleStorage : IRoleStorage
{
public RoleBindingModel? Delete(RoleSearchModel model)
{
if (model.Id is null)
{
return null;
}
public class RoleStorage : IRoleStorage
{
public RoleBindingModel? Delete(RoleSearchModel model)
{
if (model.Id is null)
{
return null;
}
var context = new Database();
var role = context.Roles.FirstOrDefault(r => r.Id == model.Id);
if (role is null)
{
return null;
}
var context = new Database();
var role = context.Roles.FirstOrDefault(r => r.Id == model.Id);
if (role is null)
{
return null;
}
context.Remove(role);
context.SaveChanges();
return role.GetBindingModel();
}
context.Remove(role);
context.SaveChanges();
return role.GetBindingModel();
}
public RoleBindingModel? GetElement(RoleSearchModel model)
{
if (model.Id is null)
{
return null;
}
var context = new Database();
return context.Roles
.FirstOrDefault(r => r.Id == model.Id)
?.GetBindingModel();
}
public RoleBindingModel? GetElement(RoleSearchModel model)
{
if (model.Id is null && string.IsNullOrWhiteSpace(model.Name))
{
return null;
}
var context = new Database();
return context.Roles
.FirstOrDefault(r => (model.Id.HasValue && r.Id == model.Id)
|| (!string.IsNullOrWhiteSpace(model.Name) && r.Name.Contains(model.Name)))
?.GetBindingModel();
}
public IEnumerable<RoleBindingModel> GetList(RoleSearchModel? model)
{
var context = new Database();
if (model is null)
{
return context.Roles.Select(r => r.GetBindingModel());
}
if (model.Id is null)
{
return [];
}
return context.Roles
.Where(r => r.Id == model.Id)
.Select(r => r.GetBindingModel());
}
public IEnumerable<RoleBindingModel> GetList(RoleSearchModel? model)
{
var context = new Database();
if (model is null && string.IsNullOrWhiteSpace(model.Name))
{
return context.Roles.Select(r => r.GetBindingModel());
}
if (model.Id is null)
{
return [];
}
return context.Roles
.Where(r => (model.Id.HasValue && r.Id == model.Id)
|| (!string.IsNullOrWhiteSpace(model.Name) && r.Name.Contains(model.Name)))
.Select(r => r.GetBindingModel());
}
public RoleBindingModel? Insert(RoleBindingModel model)
{
var context = new Database();
var newRole = Models.Role.ToRoleFromBinding(model);
public RoleBindingModel? Insert(RoleBindingModel model)
{
var context = new Database();
var newRole = Models.Role.ToRoleFromBinding(model);
context.Roles.Add(newRole);
context.SaveChanges();
context.Roles.Add(newRole);
context.SaveChanges();
return newRole.GetBindingModel();
}
return newRole.GetBindingModel();
}
public RoleBindingModel? Update(RoleBindingModel model)
{
var context = new Database();
var role = context.Roles.FirstOrDefault(r => r.Id == model.Id);
public RoleBindingModel? Update(RoleBindingModel model)
{
var context = new Database();
var role = context.Roles.FirstOrDefault(r => r.Id == model.Id);
if (role is null)
{
return null;
}
if (role is null)
{
return null;
}
role.Update(model);
role.Update(model);
context.SaveChanges();
return role.GetBindingModel();
}
}
context.SaveChanges();
return role.GetBindingModel();
}
}
}

View File

@ -1,4 +1,5 @@
using BusinessLogic.BusinessLogic;
using BusinessLogic.Tools.Mail.MailTemplates;
using Contracts.BindingModels;
using Contracts.BusinessLogicContracts;
using Contracts.Exceptions;
@ -7,122 +8,124 @@ using Microsoft.AspNetCore.Mvc;
namespace RestAPI.Controllers
{
[Route("[controller]/[action]")]
[ApiController]
public class UserController
{
private readonly ILogger _logger;
private readonly IUserLogic _userLogic;
[Route("[controller]/[action]")]
[ApiController]
public class UserController
{
private readonly ILogger _logger;
private readonly IUserLogic _userLogic;
public UserController(ILogger<UserController> logger, IUserLogic userLogic)
{
_userLogic = userLogic;
_logger = logger;
}
public UserController(ILogger<UserController> logger, IUserLogic userLogic)
{
_userLogic = userLogic;
_logger = logger;
}
[HttpPost]
public IResult Login(string email, string password)
{
try
{
var res = _userLogic.Login(email, password);
return Results.Ok(res);
}
catch (ElementNotFoundException ex)
{
_logger.LogInformation(ex, "User not found");
return Results.NoContent();
}
catch (AccountException ex)
{
_logger.LogWarning(ex, "Wrong login data");
return Results.BadRequest(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error get user");
return Results.Problem(ex.Message);
}
}
[HttpPost]
public IResult Login([FromBody] UserData data)
{
try
{
var res = _userLogic.Login(data.email, data.password);
return Results.Ok(res);
}
catch (ElementNotFoundException ex)
{
_logger.LogInformation(ex, "User not found");
return Results.NoContent();
}
catch (AccountException ex)
{
_logger.LogWarning(ex, "Wrong login data");
return Results.BadRequest(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error get user");
return Results.Problem(ex.Message);
}
}
[HttpPost]
public IResult Registration([FromBody] UserBindingModel model)
{
try
{
var res = _userLogic.Create(model);
return Results.Ok(res);
}
catch (AccountException ex)
{
_logger.LogWarning(ex, "Wrong registration data");
return Results.BadRequest(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error create user");
return Results.Problem(ex.Message);
}
}
[HttpPost]
public IResult Registration([FromBody] UserBindingModel model)
{
try
{
var res = _userLogic.Create(model);
return Results.Ok(res);
}
catch (AccountException ex)
{
_logger.LogWarning(ex, "Wrong registration data");
return Results.BadRequest(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error create user");
return Results.Problem(ex.Message);
}
}
[HttpGet]
public IResult Get([FromQuery] UserSearchModel model)
{
try
{
var res = _userLogic.ReadElement(model);
return Results.Ok(res);
}
catch (ElementNotFoundException ex)
{
_logger.LogInformation(ex, "User not found");
return Results.NoContent();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error get user");
return Results.Problem(ex.Message);
}
}
[HttpGet]
public IResult Get([FromQuery] UserSearchModel model)
{
try
{
var res = _userLogic.ReadElement(model);
return Results.Ok(res);
}
catch (ElementNotFoundException ex)
{
_logger.LogInformation(ex, "User not found");
return Results.NoContent();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error get user");
return Results.Problem(ex.Message);
}
}
[HttpPatch]
public IResult Update([FromBody] UserBindingModel model)
{
try
{
var res = _userLogic.Update(model);
return Results.Ok(res);
}
catch (AccountException ex)
{
_logger.LogWarning(ex, "Wrong update data");
return Results.BadRequest(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error update user");
return Results.Problem(ex.Message);
}
}
[HttpPatch]
public IResult Update([FromBody] UserBindingModel model)
{
try
{
var res = _userLogic.Update(model);
return Results.Ok(res);
}
catch (AccountException ex)
{
_logger.LogWarning(ex, "Wrong update data");
return Results.BadRequest(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error update user");
return Results.Problem(ex.Message);
}
}
[HttpDelete]
public IResult Delete(Guid id)
{
try
{
var res = _userLogic.Delete(new() { Id = id });
return Results.Ok(res);
}
catch (ElementNotFoundException ex)
{
_logger.LogInformation(ex, "User not found");
return Results.NoContent();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error delete user");
return Results.Problem(ex.Message);
}
}
}
[HttpDelete]
public IResult Delete(Guid id)
{
try
{
var res = _userLogic.Delete(new() { Id = id });
return Results.Ok(res);
}
catch (ElementNotFoundException ex)
{
_logger.LogInformation(ex, "User not found");
return Results.NoContent();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error delete user");
return Results.Problem(ex.Message);
}
}
}
public record class UserData(string email, string password);
}

70
WebApp/APIClient.cs Normal file
View File

@ -0,0 +1,70 @@
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Text;
namespace WebApp;
public class APIClient
{
private static readonly HttpClient _client = new();
public static void Connect(IConfiguration configuration)
{
_client.BaseAddress = new Uri(configuration["API"]);
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public static T? GetRequest<T>(string requestUrl)
{
var response = _client.GetAsync(requestUrl);
var result = response.Result.Content.ReadAsStringAsync().Result;
if (!response.Result.IsSuccessStatusCode)
{
throw new Exception(response.Result.ReasonPhrase);
}
return JsonConvert.DeserializeObject<T>(result);
}
public static object? PostRequest<T>(string requestUrl, T model)
{
var json = JsonConvert.SerializeObject(model);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var response = _client.PostAsync(requestUrl, data);
var result = response.Result.Content.ReadAsStringAsync().Result;
if (!response.Result.IsSuccessStatusCode)
{
throw new Exception(response.Result.ReasonPhrase);
}
return result;
}
public static object? DeleteRequest(string requestUrl)
{
var response = _client.DeleteAsync(requestUrl);
var result = response.Result.Content.ReadAsStringAsync().Result;
if (!response.Result.IsSuccessStatusCode)
{
throw new Exception(response.Result.ReasonPhrase);
}
return result;
}
public static object? PatchRequest<T>(string requestUrl, T model)
{
var json = JsonConvert.SerializeObject(model);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var response = _client.PatchAsync(requestUrl, data);
var result = response.Result.Content.ReadAsStringAsync().Result;
if (!response.Result.IsSuccessStatusCode)
{
throw new Exception(response.Result.ReasonPhrase);
}
return result;
}
}

10
WebApp/Helpers/Roles.cs Normal file
View File

@ -0,0 +1,10 @@
namespace WebApp.Helpers;
public static class Roles
{
public const string User = "Обычный пользователь";
public const string Admin = "Админ";
// TODO: Добавить нужные роли
public const string Worker = "Сотрудник";
}

57
WebApp/Pages/Login.cshtml Normal file
View File

@ -0,0 +1,57 @@
@page
@model WebApp.Pages.LoginModel
@{
ViewData["Title"] = "Log In";
}
<section class="vh-100">
<style>
.bg-image-vertical {
position: relative;
overflow: hidden;
background-repeat: no-repeat;
background-position: right center;
background-size: auto 100%;
}
@@media (min-width: 1025px) {
.h-custom-2 {
height: 100%;
}
}
</style>
<div class="container-fluid">
<div class="row">
<div class="col-sm-6 text-black">
<div class="d-flex align-items-center h-custom-2 px-5 ms-xl-4 mt-5 pt-5 pt-xl-0 mt-xl-n5">
<form style="width: 23rem;" method="post">
<h3 class="fw-normal mb-3 pb-3" style="letter-spacing: 1px;">Log in</h3>
<div data-mdb-input-init class="form-outline mb-4">
<input type="email" id="email" class="form-control form-control-lg" name="email" />
<label class="form-label" for="email">Email address</label>
</div>
<div data-mdb-input-init class="form-outline mb-4">
<input type="password" id="password" class="form-control form-control-lg" name="password" />
<label class="form-label" for="password">Password</label>
</div>
<div class="pt-1 mb-4">
<button data-mdb-button-init data-mdb-ripple-init class="btn btn-info btn-lg btn-block" type="submit">Login</button>
</div>
<p>Don't have an account? <a asp-page="/SignUp" class="link-info">Register here</a></p>
</form>
</div>
</div>
<div class="col-sm-6 px-0 d-none d-sm-block">
<img src="~/background-login.jpg"
alt="Login image" class="w-100 vh-100" style="object-fit: cover; object-position: left;">
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,28 @@
using Contracts.BindingModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Newtonsoft.Json;
namespace WebApp.Pages
{
public class LoginModel : PageModel
{
public void OnGet()
{
}
public IActionResult OnPostAsync(string email, string password)
{
var response = APIClient.PostRequest("user/login", new { email, password });
if (response is null || response is not string)
{
throw new Exception("Something wrong LOL!");
}
this.SetJWT((string)response);
return RedirectToPage("Index");
}
}
}

View File

@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using Newtonsoft.Json;
namespace WebApp.Pages
{
public static class PageModelExtension
{
public static string? GetUserId(this PageModel pageModel)
{
if (pageModel.User.Identity.IsAuthenticated)
{
var userIdClaim = pageModel.User.Claims.FirstOrDefault(c => c.Type == "userId");
return userIdClaim?.Value;
}
return null;
}
public static void SetJWT(this PageModel pageModel, string jwt)
{
string token = (string)JsonConvert.DeserializeObject(jwt);
// Сохраняем в кукис токен
pageModel.Response.Cookies.Append("21gunsthebest", token);
}
public static void DeleteJWT(this PageModel pageModel)
{
pageModel.Response.Cookies.Delete("21gunsthebest");
}
}
}

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApp</title>
<title>@ViewData["Title"] - 21 GUNS</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="~/WebApp.styles.css" asp-append-version="true" />
@ -12,7 +12,7 @@
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-page="/Index">WebApp</a>
<a class="navbar-brand" asp-area="" asp-page="/Index">21 GUNS</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
@ -25,6 +25,18 @@
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
</li>
@if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/User/Index">Profile</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Login">Login</a>
</li>
}
</ul>
</div>
</div>
@ -38,7 +50,7 @@
<footer class="border-top footer text-muted">
<div class="container">
&copy; 2024 - WebApp - <a asp-area="" asp-page="/Privacy">Privacy</a>
&copy; 2024 - 21 GUNS - <a asp-area="" asp-page="/Privacy">Privacy</a>
</div>
</footer>

View File

@ -0,0 +1,93 @@
@page
@model WebApp.Pages.SignUpModel
@{
ViewData["Title"] = "Sign Up";
}
<!-- Section: Design Block -->
<section class="overflow-hidden">
<style>
.bg-glass {
background-color: hsla(0, 0%, 100%, 0.9) !important;
backdrop-filter: saturate(200%) blur(25px);
}
</style>
<div class="container px-4 py-5 px-md-5 text-center text-lg-start my-5">
<div class="row gx-lg-5 align-items-center mb-5">
<div class="col-lg-6 mb-5 mb-lg-0" style="z-index: 10">
<h1 class="my-5 display-5 fw-bold ls-tight" style="color: #373A40">
Time to buy some... guns!*<br />
<span style="color: #DC5F00">In the store** of death*** and despair****</span>
</h1>
<div class="mb-4 opacity-70 text-body-emphasis" style="color: #686D76">
<p>
We would like to draw your attention to the fact that our company does not sell products to anyone under the age of 18. All of our products are intended for adult audiences only. We also do not ship to countries where our products are prohibited or restricted by law. Please make sure you meet all the necessary requirements before placing your order. We appreciate your understanding and co-operation in this matter.
<div style="font-size: 6px">
*toy guns,
**21 guns,
***a metaphor for death of the happiness of buying our merchandise,
****a metaphor for despair over the consumer's failure to find this shop previously
</div>
</div>
</div>
<div class="col-lg-6 mb-5 mb-lg-0 position-relative">
<div class="card bg-glass">
<div class="card-body px-4 py-5 px-md-5">
<form method="post">
<!-- 2 column grid layout with text inputs for the first and last names -->
<div class="row">
<div class="col-md-6 mb-4">
<div data-mdb-input-init class="form-outline">
<input asp-for="UserModel.FirstName" type="text" id="firstname" class="form-control" />
<label class="form-label" for="firstname">First name</label>
</div>
</div>
<div class="col-md-6 mb-4">
<div data-mdb-input-init class="form-outline">
<input asp-for="UserModel.SecondName" type="text" id="lastname" class="form-control" />
<label class="form-label" for="lastname">Last name</label>
</div>
</div>
</div>
<!-- Email input -->
<div data-mdb-input-init class="form-outline mb-4">
<input asp-for="UserModel.Email" type="email" id="email" class="form-control" />
<label class="form-label" for="email">Email address</label>
</div>
<!-- Password input -->
<div data-mdb-input-init class="form-outline mb-4">
<input asp-for="UserModel.Password" type="password" id="password" class="form-control" />
<label class="form-label" for="password">Password</label>
</div>
<!-- Checkbox -->
<div class="form-check d-flex justify-content-center mb-4">
<input class="form-check-input me-2" type="checkbox" value="" id="confirming" />
<label class="form-check-label" for="confirming">
I confirm that I am 18 years of age and have read the Privacy Policy and Terms of Agreement
</label>
</div>
<!-- Submit button -->
<button type="submit" data-mdb-button-init data-mdb-ripple-init class="btn btn-primary btn-block mb-4">
Sign up
</button>
<div>
<p class="mb-0">
Already have an account? <a class="fw-bold" asp-area="" asp-page="/Login">Login</a>
</p>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Section: Design Block -->

View File

@ -0,0 +1,38 @@
using Contracts.BindingModels;
using Contracts.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using WebApp.Helpers;
namespace WebApp.Pages
{
public class SignUpModel : PageModel
{
[BindProperty]
public UserBindingModel UserModel { get; set; }
public void OnGet()
{
}
public IActionResult OnPostAsync()
{
var userRole = APIClient.GetRequest<RoleViewModel>($"role/get?name={Roles.User}");
if (userRole is null)
{
throw new Exception("User role is not found");
}
UserModel.Role = new() { Id = userRole.Id };
var response = APIClient.PostRequest("user/registration", UserModel);
if (response is null || response is not string)
{
throw new Exception("Something wrong LOL!");
}
this.SetJWT((string)response);
return RedirectToPage("Index");
}
}
}

View File

@ -0,0 +1,23 @@
@page
@model WebApp.Pages.User.IndexModel
@{
ViewData["Title"] = "User page";
}
<div class="container mt-5">
<div class="row">
<div class="col-md-6 offset-md-3">
<h1 class="text-center">@Model.UserModel.FirstName @Model.UserModel.SecondName</h1>
<p class="text-center">@Model.UserModel.Email</p>
<hr>
<div class="mt-3">
<a class="btn btn-primary" asp-page="/User/Settings">Settings</a>
</div>
<form asp-page-handler="SignOut" method="post">
<div class="mt-3">
<button class="btn btn-danger">Sign out</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,32 @@
using Contracts.BindingModels;
using Contracts.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using WebApp.Helpers;
namespace WebApp.Pages.User
{
[Authorize(Roles = Roles.User)]
public class IndexModel : PageModel
{
public UserViewModel UserModel { get; set; }
public void OnGet()
{
var id = this.GetUserId();
if (id is null)
{
return;
}
UserModel = APIClient.GetRequest<UserViewModel>($"user/get?id={id}");
}
public IActionResult OnPostSignOut()
{
this.DeleteJWT();
return RedirectToPage("../Index");
}
}
}

View File

@ -0,0 +1,54 @@
@page
@model WebApp.Pages.User.SettingsModel
<div class="card bg-glass">
<div class="card-body px-4 py-5 px-md-5">
<form method="post">
<!-- 2 column grid layout with text inputs for the first and last names -->
<div class="row">
<div class="col-md-6 mb-4">
<div data-mdb-input-init class="form-outline">
<input asp-for="UserModel.FirstName" type="text" id="firstname" class="form-control" />
<label class="form-label" for="firstname">First name</label>
</div>
</div>
<div class="col-md-6 mb-4">
<div data-mdb-input-init class="form-outline">
<input asp-for="UserModel.SecondName" type="text" id="lastname" class="form-control" />
<label class="form-label" for="lastname">Last name</label>
</div>
</div>
</div>
<!-- Email input -->
<div data-mdb-input-init class="form-outline mb-4">
<input asp-for="UserModel.Email" type="email" id="email" class="form-control" />
<label class="form-label" for="email">Email address</label>
</div>
<!-- Password input -->
<div data-mdb-input-init class="form-outline mb-4">
<input asp-for="UserModel.Password" type="password" id="password" class="form-control" />
<label class="form-label" for="password">Password</label>
</div>
<!-- Birthday input -->
<div data-mdb-input-init class="form-outline mb-4">
<input asp-for="UserModel.Birthday" type="date" id="birthday" class="form-control" />
<label class="form-label" for="birthday">Birthday</label>
</div>
<!-- Hidden inputs -->
<input type="hidden" asp-for="UserModel.Id" />
<input type="hidden" asp-for="UserModel.Role.Id" />
<!-- Submit button -->
<button type="submit" data-mdb-button-init data-mdb-ripple-init class="btn btn-primary btn-block mb-4">
Save settings
</button>
</form>
<form asp-page-handler="DeleteProfile" method="post">
<button class="btn btn-danger btn-block mb-4">Delete this account permanently</button>
</form>
</div>
</div>

View File

@ -0,0 +1,65 @@
using Contracts.BindingModels;
using Contracts.Converters;
using Contracts.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using WebApp.Helpers;
namespace WebApp.Pages.User
{
[Authorize(Roles = Roles.User)]
public class SettingsModel : PageModel
{
[BindProperty]
public UserBindingModel UserModel { get; set; }
public void OnGet()
{
var id = this.GetUserId();
if (id is null)
{
return;
}
var userView = APIClient.GetRequest<UserViewModel>($"user/get?id={id}");
if (userView is null)
{
throw new Exception("User is not found.");
}
UserModel = UserConverter.ToBinding(userView);
}
public IActionResult OnPostDeleteProfile()
{
var id = this.GetUserId();
if (id is null)
{
throw new Exception("User not found!");
}
var response = APIClient.DeleteRequest($"user/delete?id={id}");
if (response is null)
{
throw new Exception("Something wrong LOL!");
}
this.DeleteJWT();
return RedirectToPage("../Index");
}
public IActionResult OnPostAsync()
{
var response = APIClient.PatchRequest("user/update", UserModel);
if (response is null)
{
throw new Exception("Something wrong LOL!");
}
return RedirectToPage("Index");
}
}
}

View File

@ -1,8 +1,40 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using WebApp;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["SecretKey"]));
options.TokenValidationParameters = new()
{
ValidateLifetime = true,
IssuerSigningKey = secretKey,
ValidateIssuer = false,
ValidateAudience = false,
};
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
// Ïîëó÷àåì èç êóêèñ JWT òîêåí
context.Token = context.Request.Cookies["21gunsthebest"];
return Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization();
// Ïîäêëþ÷àåìñÿ ê API
APIClient.Connect(builder.Configuration);
var app = builder.Build();
// Configure the HTTP request pipeline.
@ -18,6 +50,7 @@ app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();

View File

@ -6,4 +6,13 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Contracts\Contracts.csproj" />
</ItemGroup>
</Project>

View File

@ -3,19 +3,19 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApp", "WebApp.csproj", "{494318C5-209C-42B9-B15F-BF0D5A8ECF18}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApp", "WebApp.csproj", "{494318C5-209C-42B9-B15F-BF0D5A8ECF18}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinFormsApp", "..\WinFormsApp\WinFormsApp.csproj", "{11F917BB-0ABC-41A0-91B9-B3FD9CEC5277}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinFormsApp", "..\WinFormsApp\WinFormsApp.csproj", "{11F917BB-0ABC-41A0-91B9-B3FD9CEC5277}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestAPI", "..\RestAPI\RestAPI.csproj", "{D3211E26-438E-48B6-9396-2FFC28271DE1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RestAPI", "..\RestAPI\RestAPI.csproj", "{D3211E26-438E-48B6-9396-2FFC28271DE1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BusinessLogic", "..\BusinessLogic\BusinessLogic.csproj", "{919726B5-89B3-43B3-AA9A-25C1348D86B1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BusinessLogic", "..\BusinessLogic\BusinessLogic.csproj", "{919726B5-89B3-43B3-AA9A-25C1348D86B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataModels", "..\DataModels\DataModels.csproj", "{645ED499-8C00-4F04-91FB-A9EF6F1A438E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataModels", "..\DataModels\DataModels.csproj", "{645ED499-8C00-4F04-91FB-A9EF6F1A438E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "..\Contracts\Contracts.csproj", "{D7BD8791-F687-460F-8BF7-8F9CD2301EB5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Contracts", "..\Contracts\Contracts.csproj", "{D7BD8791-F687-460F-8BF7-8F9CD2301EB5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DatabaseImplement", "..\DatabaseImplement\DatabaseImplement.csproj", "{527EAA88-4EAF-42D7-93E9-494221351F9C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DatabaseImplement", "..\DatabaseImplement\DatabaseImplement.csproj", "{527EAA88-4EAF-42D7-93E9-494221351F9C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -5,5 +5,7 @@
"Microsoft.AspNetCore": "Warning"
}
},
"API": "https://localhost:7051/",
"SecretKey": "secretkey_secretkey_secretkey_secretkey",
"AllowedHosts": "*"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB