This commit is contained in:
Алексей Крюков 2024-06-03 17:05:34 +04:00
parent e59d4c9465
commit ab0b1cb0bf
98 changed files with 2734 additions and 60509 deletions

Binary file not shown.

View File

@ -29,13 +29,13 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
implementation 'org.modelmapper:modelmapper:3.2.0'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2:2.2.224'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0'
runtimeOnly 'org.webjars.npm:bootstrap:5.3.3'
runtimeOnly 'org.webjars.npm:bootstrap-icons:1.11.3'

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-emoji-sunglasses-fill" viewBox="0 0 16 16">
<path
d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16M2.31 5.243A1 1 0 0 1 3.28 4H6a1 1 0 0 1 1 1v.116A4.2 4.2 0 0 1 8 5c.35 0 .69.04 1 .116V5a1 1 0 0 1 1-1h2.72a1 1 0 0 1 .97 1.243l-.311 1.242A2 2 0 0 1 11.439 8H11a2 2 0 0 1-1.994-1.839A3 3 0 0 0 8 6c-.393 0-.74.064-1.006.161A2 2 0 0 1 5 8h-.438a2 2 0 0 1-1.94-1.515zM4.969 9.75A3.5 3.5 0 0 0 8 11.5a3.5 3.5 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.5 4.5 0 0 1 8 12.5a4.5 4.5 0 0 1-3.898-2.25.5.5 0 0 1 .866-.5z" />
</svg>

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Корзина</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto py-3">
<strong class="flex-fill">Корзина</strong>
<div class="d-flex flex-column align-items-center">
<div class="card col-12 col-md-8 col-lg-6 w-75 align-items-center" th:each="cartItem : ${cart}">
<div class="card-body col-12 p-2 d-flex flex-row align-items-center justify-content-center">
<div class="col-5"><img src="/kola.jpg" class="w-75"></div>
<div class="col-3">
Название: [[${cartItem.productName}]]
</div>
<div class="col-4">
Цена: [[${cartItem.Price}]]
</div>
</div>
</div>
<div class=" mb-2 col-12 col-md-8 col-lg-6 d-flex justify-content-end">
</div>
<div class="mb-2 col-12 col-md-8 col-lg-6 d-flex justify-content-center"
th:if="${not #lists.isEmpty(cart)}">
<form action="#" th:action="@{/cart/save}" method="post">
<button type="submit" class="btn btn-primary" onclick="return confirm('Вы уверены?')">
Оформить заказ
</button>
</form>
</div>
<div class="mb-2 col-12 col-md-8 col-lg-6 d-flex align-items-center">
<form action="#" th:action="@{/cart/clear}" method="post">
<button type="submit" class="btn btn-danger button-fixed-width"
onclick="return confirm('Вы уверены?')">
<i class="bi bi-x-lg"></i> Очистить
</button>
</form>
</div>
</div>
</main>
</body>
</html>
</html>

View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Каталог</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto main">
<div class="tab-content mt-2">
<h2 class="text-center">Меню</h2>
<div class="row mx-auto">
<div class="pt-4 ps-2 text-center" style="font-size: larger;">Обязательно возьми</div>
<div><img class="p-2" src="/kola.jpg" width="100%" /></div>
</div>
<div class="tab-pane container active table-responsive" id="orders">
<form th:action="@{/catalog}" method="get" class="row mt-2 w-50 mx-auto pb-3">
<div class="pb-3">
<input type="hidden" th:name="page" th:value="${page}">
<select th:name="typeId" id="typeId" class="form-select">
<option selected value="">Фильтр по продукции</option>
<option th:each="type : ${types}" th:value="${type.id}" th:selected="${type.id==typeId}">
[[${type.name}]]
</option>
</select>
<input type="hidden" th:name="page" th:value="${page}">
</div>
<button type="submit" class="btn btn-primary">Показать</button>
</form>
<table class="table">
<thead>
<th class="w-25"></th>
<th>Название</th>
<th>Тип</th>
<th class="w-auto">Описание</th>
<th>Цена</th>
<th></th>
</thead>
<tbody>
<tr th:each="producte : ${items}">
<th scope="row"><img src="/kola.jpg" class="w-100"></th>
<th scope="row" th:text="${producte.name}"></th>
<th:block th:each="type : ${types}">
<th:block th:if="${type.Id} eq ${producte.typeId}">
<th scope="row" th:text="${type.name}"></th>
</th:block>
</th:block>
<th scope="row" th:text="${producte.description}"></th>
<th scope="row" th:text="${producte.Price}"></th>
<th>
<form th:action="@{/catalog}" th:object="${order}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<select hidden="hidden" th:field="*{product}" class="form-select">
<option th:value="${producte.id}"></option>
</select>
<button type="submit" class="btn btn-link button-link"><i
class="bi bi-cart2 d-inline-block align-top me-1 logo"></i></button>
</form>
</th>
</tr>
</tbody>
</table>
<th:block th:replace="~{ pagination :: pagination (
url='catalog',
totalPages=${totalPages},
currentPage=${currentPage}) }" />
</div>
</div>
</main>
</body>
</html>

View File

@ -7,7 +7,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">My shop</title>
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">FasterPizza.ru</title>
<script type="text/javascript" src="/webjars/bootstrap/5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="/webjars/bootstrap/5.3.3/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="/webjars/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" />
@ -18,8 +18,8 @@
<nav class="navbar navbar-expand-md my-navbar" data-bs-theme="dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<i class="bi bi-cart2 d-inline-block align-top me-1 logo"></i>
Туда сюда и пицца
<i class="bi-pizza-slice d-inline-block align-top me-1 logo"></i>
БыстроПицца
</a>
<th:block sec:authorize="isAuthenticated()" th:with="userName=${#authentication.name}">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar"
@ -35,16 +35,25 @@
</a>
<a class="nav-link" href="/admin/type"
th:classappend="${activeLink.startsWith('/admin/type') ? 'active' : ''}">
Типы заказов
Типы Продукции
</a>
<a class="nav-link" href="/admin/subscription"
th:classappend="${activeLink.startsWith('/admin/subscription') ? 'active' : ''}">
Списки рассылки
<a class="nav-link" href="/admin/product"
th:classappend="${activeLink.startsWith('/admin/product') ? 'active' : ''}">
Продукция
</a>
<a class="nav-link" href="/admin/product/top"
th:classappend="${activeLink.startsWith('/admin/product/top') ? 'active' : ''}">
Топ Продуктов
</a>
<a class="nav-link" href="/h2-console/" target="_blank">Консоль H2</a>
</th:block>
<a class="nav-link" href="/123" target="_blank">Ошибка 1</a>
<a class="nav-link" href="/admin/123" target="_blank">Ошибка 2</a>
<a class="nav-link" href="/catalog"
th:classappend="${activeLink.startsWith('/catalog') ? 'active' : ''}">
Каталог
</a>
<a class="nav-link" href="/">
Заказы
</a>
</ul>
<ul class="navbar-nav" th:if="${not #strings.isEmpty(userName)}">
<form th:action="@{/logout}" method="post">
@ -64,7 +73,7 @@
<main class="container-fluid p-2" layout:fragment="content">
</main>
<footer class="my-footer mt-auto d-flex flex-shrink-0 justify-content-center align-items-center">
Автор, [[${#dates.year(#dates.createNow())}]]
Крюков А.И., [[${#dates.year(#dates.createNow())}]]
</footer>
</body>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Ошибка</title>
</head>
<body>
<main layout:fragment="content">
<ul class="list-group mb-2">
<th:block th:if="${#strings.isEmpty(message)}">
<li class="list-group-item">
Неизвестная ошибка
</li>
</th:block>
<th:block th:if="${not #strings.isEmpty(message)}">
<li class="list-group-item">
<strong>Ошибка:</strong> [[${message}]]
</li>
</th:block>
<th:block th:if="${not #strings.isEmpty(url)}">
<li class="list-group-item">
<strong>Адрес:</strong> [[${url}]]
</li>
<li class="list-group-item">
<strong>Класс исключения:</strong> [[${exception}]]
</li>
<li class="list-group-item">
[[${method}]] ([[${file}]]:[[${line}]])
</li>
</th:block>
</ul>
<a class="btn btn-primary button-fixed-width" href="/">На главную</a>
</main>
</body>
</html>

View File

@ -7,7 +7,7 @@
<body>
<main layout:fragment="content">
<form action="#" th:action="@{/login}" method="post">
<form action="#" th:action="@{/login}" method="post" class="w-25 mx-auto">
<div th:if="${param.error}" class="alert alert-danger">
Неверный логин или пароль
</div>
@ -39,6 +39,4 @@
</main>
</body>
</html>
</html>

View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="orders (items, totalPages, currentPage, products)">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*" class="w-50 mx-auto">
<table class="table mt-2">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-auto">Игры</th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="order : ${items}">
<th scope="row" th:text="${order.id}"></th>
<td>
<table class="table">
<thead>
<tr>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-25"></th>
<th scope="col" class="w-auto">Название продукции</th>
<th scope="col" class="w-10">Цена</th>
</tr>
</thead>
<tbody>
<tr th:each="productId : ${order.products}">
<th:block th:each="product : ${products}">
<th:block th:if="${productId} eq ${product.id}">
<th scope="row" th:text="${productId}"></th>
<th scope="row"><img src="/kola.jpg" class="w-100">
</th>
<th scope="row" th:text="${product.name}"></th>
<th scope="row" th:text="${product.price}"></th>
</th:block>
</th:block>
</tr>
</tbody>
</table>
</td>
<td>
<form th:action="@{/delete/{id}(id=${order.id})}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link"
onclick="return confirm('Вы уверены?')">Удалить</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
<th:block th:replace="~{ pagination :: pagination (
url='',
totalPages=${totalPages},
currentPage=${currentPage}) }" />
<div class="mb-2 d-flex justify-content-center">
<a class="btn btn-primary" href="/cart">Создать заказ</a>
</div>
</th:block>
</th:block>
</body>
</html>

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="pagination (url, totalPages, currentPage)">
<nav th:if="${totalPages > 1}" th:with="
maxPage=2,
currentPage=${currentPage + 1}">
<ul class="pagination justify-content-center"
th:with="
seqFrom=${currentPage - maxPage < 1 ? 1 : currentPage - maxPage},
seqTo=${currentPage + maxPage > totalPages ? totalPages : currentPage + maxPage}">
<th:block th:if="${currentPage > maxPage + 1}">
<li class="page-item">
<a class="page-link" aria-label="Previous" th:href="@{/{url}?page=0(url=${url})}">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item disabled">
<span class="page-link" aria-label="Previous">
<span aria-hidden="true">&hellip;</span>
</span>
</li>
</th:block>
<li class="page-item" th:each="page : ${#numbers.sequence(seqFrom, seqTo)}"
th:classappend="${page == currentPage} ? 'active' : ''">
<a class=" page-link" th:href="@{/{url}?page={page}(url=${url},page=${page - 1})}">
<span th:text="${page}" />
</a>
</li>
<th:block th:if="${currentPage < totalPages - maxPage}">
<li class="page-item disabled">
<span class="page-link" aria-label="Previous">
<span aria-hidden="true">&hellip;</span>
</span>
</li>
<li class="page-item">
<a class="page-link" aria-label="Next"
th:href="@{/{url}?page={page}(url=${url},page=${totalPages - 1})}">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</th:block>
</ul>
</nav>
</th:block>
</body>
</html>

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редакторовать игру</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto">
<form action="#" th:action="@{/admin/product/edit/{id}(id=${product.id})}" th:object="${product}" method="post">
<div class="mb-3">
<label for="id" class="form-label">ID</label>
<input type="text" th:value="*{id}" id="id" class="form-control" readonly disabled>
</div>
<div class="mb-3">
<label for="name" class="form-label">Название</label>
<input type="text" th:field="*{name}" id="name" class="form-control">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="invalid-feedback"></div>
</div>
<div class="mb-2">
<label for="typeId" class="form-label">Тип</label>
<select th:field="*{typeId}" id="typeId" class="form-select">
<option selected value="">Укажите тип</option>
<option th:each="type : ${types}" th:value="${type.id}">[[${type.name}]]</option>
</select>
</div>
<div class="mb-3">
<label for="price" class="form-label">Цена</label>
<input type="number" th:field="*{price}" id="price" class="form-control">
<div th:if="${#fields.hasErrors('price')}" th:errors="*{price}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="description" class="form-label">Описание</label>
<textarea type="textarea" rows="5" cols="80" th:field="*{description}" id="description"
class="form-control"></textarea>
<div th:if="${#fields.hasErrors('description')}" th:errors="*{description}" class="invalid-feedback">
</div>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Сохранить</button>
<a class="btn btn-secondary button-fixed-width" href="/admin/product">Отмена</a>
</div>
</form>
</main>
</body>
</html>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Меню</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2 class="text-center">Меню</h2>
<div>
<a href="/admin/product/edit/" class="btn btn-primary">Добавить продукцию</a>
</div>
<table class="table">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-10">Название</th>
<th scope="col" class="w-10">Цена</th>
<th scope="col" class="w-auto">Описание</th>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="product : ${items}">
<th scope="row" th:text="${product.id}"></th>
<td th:text="${product.name}"></td>
<td th:text="${product.price}"></td>
<td th:text="${product.description}"></td>
<td>
<form th:action="@{/admin/product/edit/{id}(id=${product.id})}" method="get">
<button type="submit" class="btn btn-link button-link">Редактировать</button>
</form>
</td>
<td>
<form th:action="@{/admin/product/delete/{id}(id=${product.id})}" method="post">
<button type="submit" class="btn btn-link button-link"
onclick="return confirm('Вы уверены?')">Удалить</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
</th:block>
</main>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Личный кабинет</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto main">
<div class="tab-content mt-2">
<h2 class="text-center">Заказы</h2>
<div class="tab-pane container active" id="orders">
<th:block
th:replace="~{ orders :: orders (items=${items}, totalPages=${totalPages}, currentPage=${currentPage}, products=${products})}" />
</div>
</div>
</main>
</body>
</html>

View File

@ -6,13 +6,18 @@
</head>
<body>
<main layout:fragment="content">
<main layout:fragment="content" class="w-25 mx-auto">
<form action="#" th:action="@{/signup}" th:object="${user}" method="post">
<div class="mb-3">
<label for="login" class="form-label">Имя пользователя</label>
<input type="text" th:field="*{login}" id="login" class="form-control">
<div th:if="${#fields.hasErrors('login')}" th:errors="*{login}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Почта</label>
<input type="email" th:field="*{email}" id="email" class="form-control">
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Пароль</label>
<input type="password" th:field="*{password}" id="password" class="form-control">
@ -32,6 +37,4 @@
</main>
</body>
</html>
</html>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<meta charset="UTF-8">
<title>Top 5 Products</title>
</head>
<body>
<main layout:fragment="content" class="w-25 mx-auto">
<h1>Top 5 Most Purchased Products</h1>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr th:each="product : ${topProducts}">
<td th:text="${product.id}"></td>
<td th:text="${product.name}"></td>
<td th:text="${product.description}"></td>
<td th:text="${product.price}"></td>
</tr>
</tbody>
</table>
</main>
</body>
</html>

View File

@ -2,18 +2,18 @@
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редакторовать тип заказа</title>
<title>Редакторовать тип продукции</title>
</head>
<body>
<main layout:fragment="content">
<main layout:fragment="content" class="w-50 mx-auto">
<form action="#" th:action="@{/admin/type/edit/{id}(id=${type.id})}" th:object="${type}" method="post">
<div class="mb-3">
<label for="id" class="form-label">ID</label>
<input type="text" th:value="*{id}" id="id" class="form-control" readonly disabled>
</div>
<div class="mb-3">
<label for="name" class="form-label">Тип заказа</label>
<label for="name" class="form-label">Тип продукции</label>
<input type="text" th:field="*{name}" id="name" class="form-control">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="invalid-feedback"></div>
</div>

View File

@ -2,24 +2,24 @@
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Типы заказов</title>
<title>Типы продукции</title>
</head>
<body>
<main layout:fragment="content">
<main layout:fragment="content" class="w-50 mx-auto">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2>Типы заказов</h2>
<h2 class="text-center">Типы продукции</h2>
<div>
<a href="/admin/type/edit/" class="btn btn-primary">Добавить тип заказа</a>
<a href="/admin/type/edit/" class="btn btn-primary">Добавить тип продукции</a>
</div>
<table class="table">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-auto">Тип заказа</th>
<th scope="col" class="w-auto">Тип продукции</th>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-10"></th>
</tr>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редакторовать пользователя</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto">
<form action="#" th:action="@{/admin/user/edit/{id}(id=${user.id},page=${page})}" th:object="${user}"
method="post">
<div class="mb-3">
<label for="id" class="form-label">ID</label>
<input type="text" th:value="*{id}" id="id" class="form-control" readonly disabled>
</div>
<div class="mb-3">
<label for="login" class="form-label">Имя пользователя</label>
<input type="text" th:field="*{login}" id="login" class="form-control">
<div th:if="${#fields.hasErrors('login')}" th:errors="*{login}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Почта</label>
<input type="text" th:field="*{email}" id="email" class="form-control">
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="invalid-feedback"></div>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Сохранить</button>
<a class="btn btn-secondary button-fixed-width" th:href="@{/admin/user(page=${page})}">Отмена</a>
</div>
</form>
</main>
</body>
</html>

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Пользователи</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2 class=" text-center">Пользователи</h2>
<div>
<a th:href="@{/admin/user/edit/(page=${page})}" class="btn btn-primary">Добавить пользователя</a>
</div>
<table class="table">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-auto">Имя пользователя</th>
<th scope="col" class="w-auto">Почта</th>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${items}">
<th scope="row" th:text="${user.id}"></th>
<td th:text="${user.login}"></td>
<td th:text="${user.email}"></td>
<td>
<form th:action="@{/admin/user/edit/{id}(id=${user.id})}" method="get">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link">Редактировать</button>
</form>
</td>
<td>
<form th:action="@{/admin/user/delete/{id}(id=${user.id})}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link"
onclick="return confirm('Вы уверены?')">Удалить</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
<th:block th:replace="~{ pagination :: pagination (
url=${'admin/user'},
totalPages=${totalPages},
currentPage=${currentPage}) }" />
</th:block>
</main>
</body>
</html>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,25 @@
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.example.demo.core.configuration.Constants;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.products.service.ProductService;
import com.example.demo.orders.model.OrderEntity;
import com.example.demo.orders.service.OrderService;
import com.example.demo.types.model.TypeEntity;
import com.example.demo.types.service.TypeService;
import com.example.demo.users.model.UserEntity;
import com.example.demo.users.model.UserRole;
import com.example.demo.users.service.UserService;
@SpringBootApplication
@ -21,11 +28,14 @@ public class DemoApplication implements CommandLineRunner {
private final TypeService typeService;
private final ProductService productService;
private final OrderService orderService;
private final UserService userService;
public DemoApplication(TypeService typeService, ProductService productService, UserService userService) {
public DemoApplication(TypeService typeService, ProductService productService,
OrderService orderService, UserService userService) {
this.typeService = typeService;
this.productService = productService;
this.orderService = orderService;
this.userService = userService;
}
@ -36,24 +46,56 @@ public class DemoApplication implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
if (args.length > 0 && Objects.equals("--populate", args[0])) {
log.info("Create default types values");
log.info("start");
log.info("Create default user values");
final var admin = new UserEntity("admin", "admin@mail.com", "admin");
admin.setRole(UserRole.ADMIN);
userService.create(admin);
final var user1 = userService.create(new UserEntity("user", "user@gmail.com",
Constants.DEFAULT_PASSWORD));
final var type1 = typeService.create(new TypeEntity("Пицца"));
final var type2 = typeService.create(new TypeEntity("Напиток"));
final var type3 = typeService.create(new TypeEntity("Закуска"));
final var type2 = typeService.create(new TypeEntity("Лимонад"));
log.info("Create default products values");
productService.create(new ProductEntity("Маргарита", type1, 499.00));
productService.create(new ProductEntity("Эль Дьябло", type1, 699.00));
productService.create(new ProductEntity("Гавайская", type1, 399.00));
productService.create(new ProductEntity("Лимонад", type2, 99.00));
productService.create(new ProductEntity("Сок", type2, 99.00));
productService.create(new ProductEntity("Чай", type2, 49.00));
productService.create(new ProductEntity("Картошка фри", type3, 199.00));
productService.create(new ProductEntity("Нагетсы", type3, 199.00));
final var product1 = productService.create(new ProductEntity(type1, "Цыпленок барбекью", 510.0,
"Сочная пицца с кусочками пиццы под вкуснейшим соусом барбекью"));
final var product2 = productService.create(new ProductEntity(type2, "Кола", 100.0, "Старая добрая кола"));
productService
.create(new ProductEntity(type1, "Мексиканская", 500.0,
"Обжигающая пицца с БОЛЬШИМ количеством мяса"));
productService.create(new ProductEntity(type2, "Фанта", 80.0, "Добрый Фанта"));
productService.create(new ProductEntity(type1, "4 сыра", 400.0, "4 нежнейших сыра на хрустящей корочке"));
productService.create(new ProductEntity(type2, "Щай", 60.0, "Татарам с молоком)"));
productService.create(new ProductEntity(type1, "Пепперени", 400.0, "Классика"));
log.info("Create default users values");
userService.create(new UserEntity("Alex", "Kryukov", "akryu@mail.ru", "password123"));
userService.create(new UserEntity("Oleg", "Zyngin", "@mail.ru", "password"));
final var product4 = productService
.create(new ProductEntity(type1, "Пеппирони", 400.0, "много мяса и сыра"));
final var product5 = productService.create(new ProductEntity(type2, "Фанта", 100.0, "Старая добрая кола"));
final var product3 = productService
.create(new ProductEntity(type1, "Гавайская", 400.0, "много мяса и сыра и ананасик"));
final var product6 = productService
.create(new ProductEntity(type2, "Спрайт", 100.0, "Освежает, не правда-ли?"));
final var product7 = productService
.create(new ProductEntity(type1, "Маргарита", 400.0, "много мяса и сыра"));
final List<ProductEntity> products = new ArrayList<ProductEntity>();
products.add(product1);
products.add(product2);
final List<ProductEntity> products2 = new ArrayList<ProductEntity>();
products.add(product4);
products.add(product5);
final List<ProductEntity> products3 = new ArrayList<ProductEntity>();
products.add(product3);
products.add(product6);
products.add(product7);
log.info("Create default order values");
orderService.create(user1.getId(), new OrderEntity(products2));
orderService.create(admin.getId(), new OrderEntity(products));
orderService.create(user1.getId(), new OrderEntity(products3));
orderService.create(admin.getId(), new OrderEntity(products3));
}
}
}

View File

@ -0,0 +1,28 @@
package com.example.demo.core.api;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import com.example.demo.core.session.SessionCart;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
@ControllerAdvice
public class GlobalController {
private final SessionCart cart;
public GlobalController(SessionCart cart) {
this.cart = cart;
}
@ModelAttribute("servletPath")
String getRequestServletPath(HttpServletRequest request) {
return request.getServletPath();
}
@ModelAttribute("totalCart")
double getTotalCart(HttpSession session) {
return cart.getSum();
}
}

View File

@ -0,0 +1,18 @@
package com.example.demo.core.api;
import java.util.Map;
import java.util.function.Function;
import org.springframework.data.domain.Page;
public class PageAttributesMapper {
private PageAttributesMapper() {
}
public static <E, D> Map<String, Object> toAttributes(Page<E> page, Function<E, D> mapper) {
return Map.of(
"items", page.getContent().stream().map(mapper::apply).toList(),
"currentPage", page.getNumber(),
"totalPages", page.getTotalPages());
}
}

View File

@ -3,7 +3,16 @@ package com.example.demo.core.configuration;
public class Constants {
public static final String SEQUENCE_NAME = "hibernate_sequence";
public static final String API_URL = "/api/1.0";
public static final int DEFAULT_PAGE_SIZE = 5;
public static final String REDIRECT_VIEW = "redirect:";
public static final String ADMIN_PREFIX = "/admin";
public static final String LOGIN_URL = "/login";
public static final String LOGOUT_URL = "/logout";
public static final String DEFAULT_PASSWORD = "123456";
private Constants() {
}

View File

@ -1,13 +1,23 @@
package com.example.demo.core.configuration;
import org.modelmapper.ModelMapper;
import org.modelmapper.PropertyMap;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.demo.core.model.BaseEntity;
@Configuration
public class MapperConfiguration {
@Bean
ModelMapper modelMapper() {
return new ModelMapper();
final ModelMapper mapper = new ModelMapper();
mapper.addMappings(new PropertyMap<Object, BaseEntity>() {
@Override
protected void configure() {
skip(destination.getId());
}
});
return mapper;
}
}

View File

@ -1,15 +1,13 @@
package com.example.demo.core.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(@NonNull CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE");
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}

View File

@ -0,0 +1,53 @@
package com.example.demo.core.error;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
@ControllerAdvice
public class AdviceController {
private final Logger log = LoggerFactory.getLogger(AdviceController.class);
private static Throwable getRootCause(Throwable throwable) {
Throwable rootCause = throwable;
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
rootCause = rootCause.getCause();
}
return rootCause;
}
private static Map<String, Object> getAttributes(HttpServletRequest request, Throwable throwable) {
final Throwable rootCause = getRootCause(throwable);
final StackTraceElement firstError = rootCause.getStackTrace()[0];
return Map.of(
"message", rootCause.getMessage(),
"url", request.getRequestURL(),
"exception", rootCause.getClass().getName(),
"file", firstError.getFileName(),
"method", firstError.getMethodName(),
"line", firstError.getLineNumber());
}
@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest request, Throwable throwable) throws Throwable {
if (AnnotationUtils.findAnnotation(throwable.getClass(),
ResponseStatus.class) != null) {
throw throwable;
}
log.error("{}", throwable.getMessage());
throwable.printStackTrace();
final ModelAndView model = new ModelAndView();
model.addAllObjects(getAttributes(request, throwable));
model.setViewName("error");
return model;
}
}

View File

@ -3,6 +3,7 @@ package com.example.demo.core.model;
import com.example.demo.core.configuration.Constants;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.SequenceGenerator;
@ -10,7 +11,7 @@ import jakarta.persistence.SequenceGenerator;
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue()
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = Constants.SEQUENCE_NAME)
@SequenceGenerator(name = Constants.SEQUENCE_NAME, sequenceName = Constants.SEQUENCE_NAME, allocationSize = 1)
protected Long id;

View File

@ -0,0 +1,63 @@
package com.example.demo.core.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import com.example.demo.core.configuration.Constants;
import com.example.demo.users.api.UserSignupController;
import com.example.demo.users.model.UserRole;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.headers(headers -> headers.frameOptions(FrameOptionsConfig::sameOrigin));
httpSecurity.csrf(AbstractHttpConfigurer::disable);
httpSecurity.cors(Customizer.withDefaults());
httpSecurity.authorizeHttpRequests(requests -> requests
.requestMatchers("/css/**", "/webjars/**", "/*.svg")
.permitAll());
httpSecurity.authorizeHttpRequests(requests -> requests
.requestMatchers(Constants.ADMIN_PREFIX + "/**").hasRole(UserRole.ADMIN.name())
.requestMatchers("/h2-console/**").hasRole(UserRole.ADMIN.name())
.requestMatchers(UserSignupController.URL).anonymous()
.requestMatchers(Constants.LOGIN_URL).anonymous()
.anyRequest().authenticated());
httpSecurity.formLogin(formLogin -> formLogin
.loginPage(Constants.LOGIN_URL));
httpSecurity.rememberMe(rememberMe -> rememberMe.key("uniqueAndSecret"));
httpSecurity.logout(logout -> logout
.deleteCookies("JSESSIONID"));
return httpSecurity.build();
}
@Bean
DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) {
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,64 @@
package com.example.demo.core.security;
import java.util.Collection;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.example.demo.users.model.UserEntity;
public class UserPrincipal implements UserDetails{
private final long id;
private final String username;
private final String password;
private final Set<? extends GrantedAuthority> roles;
private final boolean active;
public UserPrincipal(UserEntity user) {
this.id = user.getId();
this.username = user.getLogin();
this.password = user.getPassword();
this.roles = Set.of(user.getRole());
this.active = true;
}
public Long getId() {
return id;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
@Override
public boolean isEnabled() {
return active;
}
@Override
public boolean isAccountNonExpired() {
return isEnabled();
}
@Override
public boolean isAccountNonLocked() {
return isEnabled();
}
@Override
public boolean isCredentialsNonExpired() {
return isEnabled();
}
}

View File

@ -0,0 +1,14 @@
package com.example.demo.core.session;
import java.util.HashMap;
import com.example.demo.users.api.UserCartDto;
public class SessionCart extends HashMap<Integer, UserCartDto> {
public double getSum() {
return this.values().stream()
.map(item -> item.getPrice())
.mapToDouble(Double::doubleValue)
.sum();
}
}

View File

@ -0,0 +1,16 @@
package com.example.demo.core.session;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.web.context.WebApplicationContext;
@Configuration
public class SessionHelper {
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
SessionCart todos() {
return new SessionCart();
}
}

View File

@ -1,103 +0,0 @@
package com.example.demo.orders.api;
import java.sql.Date;
import java.util.List;
import java.util.stream.Collectors;
import org.modelmapper.ModelMapper;
import org.springframework.web.bind.annotation.*;
import com.example.demo.core.configuration.Constants;
import com.example.demo.order_lines.api.OrderLineDto;
import com.example.demo.order_lines.model.OrderLineEntity;
import com.example.demo.orders.model.OrderEntity;
import com.example.demo.orders.service.OrderService;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.products.service.ProductService;
import com.example.demo.users.model.UserEntity;
import com.example.demo.users.service.UserService;
import jakarta.validation.Valid;
@RestController
@RequestMapping(Constants.API_URL + "/order")
public class OrderController {
private final OrderService orderService;
private final UserService userService;
private final ModelMapper modelMapper;
private final ProductService productService;
public OrderController(OrderService orderService, UserService userService, ModelMapper modelMapper,
ProductService productService) {
this.orderService = orderService;
this.userService = userService;
this.productService = productService;
this.modelMapper = modelMapper;
}
private OrderDto toDto(OrderEntity entity) {
var dto = new OrderDto();
dto.setId(entity.getId());
dto.setUserId(entity.getUser().getId());
dto.setDate(new Date(System.currentTimeMillis()));
for (OrderLineEntity orderLineEntity : entity.getLines()) {
OrderLineDto orderLineDto = new OrderLineDto();
orderLineDto.setProductId(orderLineEntity.getProduct().getId());
orderLineDto.setCount(orderLineEntity.getCount());
// Вычисляем общую цену строки заказа
Double totalPriceLine = orderLineEntity.getProduct().getPrice() * orderLineEntity.getCount();
orderLineDto.setTotalPriceLine(totalPriceLine);
dto.addOrderLine(orderLineDto);
}
return dto;
}
public OrderEntity toEntity(OrderDto dto) {
OrderEntity entity = modelMapper.map(dto, OrderEntity.class);
UserEntity user = userService.get(dto.getUserId());
entity.setUser(user);
List<OrderLineEntity> orderLines = dto.getLines().stream()
.map(lineDto -> {
OrderLineEntity orderLineEntity = modelMapper.map(lineDto, OrderLineEntity.class);
orderLineEntity.setId(null);
// Получаем продукт по id
ProductEntity product = productService.get(lineDto.getProductId());
orderLineEntity.setProduct(product);
return orderLineEntity;
})
.collect(Collectors.toList());
orderLines.stream().forEach(entity::addOrderLine);
return entity;
}
@GetMapping
public List<OrderDto> getAll(@RequestParam(name = "userId", defaultValue = "0") Long userId,
@RequestParam(name = "lines", defaultValue = "") List<Long> lines) {
return orderService.getAll(userId, lines).stream().map(this::toDto).toList();
}
@GetMapping("/{id}")
public OrderDto get(@PathVariable(name = "id") Long id) {
return toDto(orderService.get(id));
}
@PostMapping
public OrderDto create(@RequestBody @Valid OrderDto dto) {
OrderEntity orderEntity = toEntity(dto);
return toDto(orderService.create(orderEntity));
}
@PutMapping("/{id}")
public OrderDto update(@PathVariable(name = "id") Long id, @RequestBody OrderDto dto) {
return toDto(orderService.update(id, toEntity(dto)));
}
@DeleteMapping("/{id}")
public OrderDto delete(@PathVariable(name = "id") Long id) {
return toDto(orderService.delete(id));
}
}

View File

@ -1,24 +1,14 @@
package com.example.demo.orders.api;
import com.example.demo.order_lines.api.OrderLineDto;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;
import jakarta.validation.constraints.NotNull;
public class OrderDto {
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Long id;
@NotNull
@Min(1)
private Long userId;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Date date = getDate();
private final List<OrderLineDto> lines = new ArrayList<>();
private final List<Long> products = new ArrayList<>();
public Long getId() {
return id;
@ -28,30 +18,12 @@ public class OrderDto {
this.id = id;
}
public Long getUserId() {
return userId;
public List<Long> getProducts() {
return products;
}
public void setUserId(Long userId) {
this.userId = userId;
public void setProducts(List<Long> products) {
this.products.clear();
this.products.addAll(products);
}
public void setDate(Date date) {
this.date = date;
}
public List<OrderLineDto> getLines() {
return lines;
}
// метод для добавления строк заказа
public void addOrderLine(OrderLineDto orderLine) {
this.lines.add(orderLine);
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Date getDate() {
return date;
}
}

View File

@ -1,37 +1,36 @@
package com.example.demo.orders.model;
import java.util.List;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import com.example.demo.core.model.BaseEntity;
import com.example.demo.order_lines.model.OrderLineEntity;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.users.model.UserEntity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name = "orders")
public class OrderEntity extends BaseEntity {
@ManyToOne
@JoinColumn(name = "userId", nullable = false)
private UserEntity user;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private final List<OrderLineEntity> lines = new ArrayList<>();
private Double totalPrice; // Поле для общей стоимости заказа
@ManyToMany()
private Set<ProductEntity> products = new HashSet<>();
public OrderEntity() {
super();
}
public OrderEntity(UserEntity user) {
this.user = user;
public OrderEntity(List<ProductEntity> products) {
this.products.clear();
this.products.addAll(products);
}
public UserEntity getUser() {
@ -40,49 +39,33 @@ public class OrderEntity extends BaseEntity {
public void setUser(UserEntity user) {
this.user = user;
}
public List<OrderLineEntity> getLines() {
return lines;
}
public Double getTotalPrice() {
return totalPrice;
}
public void addOrderLine(OrderLineEntity orderLine) {
this.lines.add(orderLine);
recalculateTotalPrice();
}
public void removeOrderLine(OrderLineEntity orderLine) {
this.lines.remove(orderLine);
recalculateTotalPrice();
}
private void recalculateTotalPrice() {
totalPrice = 0.0;
for (OrderLineEntity line : lines) {
totalPrice += line.getTotalPrice();
if (!user.getOrders().contains(this)) {
user.getOrders().add(this);
}
}
public Set<ProductEntity> getProducts() {
return products;
}
public void setProducts(ProductEntity product) {
this.products.add(product);
}
@Override
public int hashCode() {
return Objects.hash(id, user, lines, totalPrice);
return Objects.hash(id, products);
}
@SuppressWarnings("unlikely-arg-user")
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
final OrderEntity other = (OrderEntity) obj;
return Objects.equals(other.getId(), id)
&& Objects.equals(other.getUser(), user)
&& Objects.equals(other.getLines(), lines)
&& Objects.equals(other.getTotalPrice(), totalPrice);
&& Objects.equals(other.getProducts(), products);
}
}

View File

@ -1,19 +1,26 @@
package com.example.demo.orders.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import com.example.demo.orders.model.OrderEntity;
public interface OrderRepository extends CrudRepository<OrderEntity, Long> {
@SuppressWarnings("null")
List<OrderEntity> findAll();
List<OrderEntity> findByUserIdAndLinesProductId(long userId, List<Long> lines);
List<OrderEntity> findById(long id);
public interface OrderRepository
extends CrudRepository<OrderEntity, Long>, PagingAndSortingRepository<OrderEntity, Long> {
Optional<OrderEntity> findOneByUserIdAndId(long userId, long id);
@Query("select o from OrderEntity o join fetch o.products where o.user.id = ?1")
List<OrderEntity> findByUserId(long userId);
@Query("select o from OrderEntity o join fetch o.products where o.user.id = ?1")
Page<OrderEntity> findByUserId(long userId, Pageable pageable);
List<OrderEntity> findAll();
}

View File

@ -1,69 +1,78 @@
package com.example.demo.orders.service;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.core.error.NotFoundException;
import com.example.demo.order_lines.model.OrderLineEntity;
import com.example.demo.orders.model.OrderEntity;
import com.example.demo.orders.repository.OrderRepository;
import com.example.demo.users.model.UserEntity;
import com.example.demo.users.service.UserService;
@Service
public class OrderService {
private final OrderRepository repository;
private final UserService userService;
public OrderService(OrderRepository repository) {
public OrderService(OrderRepository repository, UserService userService) {
this.repository = repository;
this.userService = userService;
}
@Transactional(readOnly = true)
public List<OrderEntity> getAll(Long userId, List<Long> lines) {
if (userId != 0L && !lines.isEmpty()) {
return repository.findByUserIdAndLinesProductId(userId, lines);
}
if (userId == 0L && !lines.isEmpty()) {
return repository.findAll();
}
if (userId != 0L && lines.isEmpty()) {
return repository.findByUserId(userId);
}
public Page<OrderEntity> getAll(long userId, int page, int size) {
final Pageable pageRequest = PageRequest.of(page, size, Sort.by("id"));
userService.get(userId);
return repository.findByUserId(userId, pageRequest);
}
@Transactional(readOnly = true)
public List<OrderEntity> getAll(long userId) {
userService.get(userId);
return repository.findByUserId(userId);
}
public List<OrderEntity> getAll() {
return repository.findAll();
}
@Transactional(readOnly = true)
public OrderEntity get(Long id) {
// Используем findById репозитория для извлечения OrderEntity по id
Optional<OrderEntity> optionalOrderEntity = repository.findById(id);
// Используем orElseThrow для выброса исключения, если OrderEntity не найден
OrderEntity orderEntity = optionalOrderEntity.orElseThrow(() -> new NotFoundException(OrderEntity.class, id));
return orderEntity;
public OrderEntity get(long userId, long id) {
userService.get(userId);
return repository.findOneByUserIdAndId(userId, id)
.orElseThrow(() -> new NotFoundException(OrderEntity.class, id));
}
public OrderEntity create(OrderEntity entity) {
@Transactional
public OrderEntity create(long userId, OrderEntity entity) {
if (entity == null) {
throw new IllegalArgumentException("Entity is null");
}
final UserEntity existsUser = userService.get(userId);
entity.setUser(existsUser);
return repository.save(entity);
}
@Transactional
public OrderEntity update(Long id, OrderEntity entity) {
final OrderEntity existEntity = get(id);
existEntity.setUser(entity.getUser());
// Очищаем текущие строки заказа и добавляем новые
existEntity.getLines().clear();
for (OrderLineEntity newOrderLine : entity.getLines()) {
existEntity.addOrderLine(newOrderLine);
public OrderEntity createAll(long userId, OrderEntity entitiy) {
if (entitiy == null) {
throw new IllegalArgumentException("Orders list is null or empty");
}
return repository.save(existEntity);
final UserEntity existsUser = userService.get(userId);
entitiy.setUser(existsUser);
return repository.save(entitiy);
}
@Transactional
public OrderEntity delete(Long id) {
final OrderEntity existsEntity = get(id);
public OrderEntity delete(long userId, long id) {
userService.get(userId);
final OrderEntity existsEntity = get(userId, id);
repository.delete(existsEntity);
return existsEntity;
}

View File

@ -1,43 +1,65 @@
package com.example.demo.products.api;
import java.util.List;
import java.util.Map;
import org.modelmapper.ModelMapper;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.demo.core.api.PageAttributesMapper;
import com.example.demo.core.configuration.Constants;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.products.service.ProductService;
import com.example.demo.types.api.TypeDto;
import com.example.demo.types.model.TypeEntity;
import com.example.demo.types.service.TypeService;
import jakarta.validation.Valid;
@RestController
@RequestMapping(Constants.API_URL + "/product")
@Controller
@RequestMapping(ProductController.URL)
public class ProductController {
public static final String URL = Constants.ADMIN_PREFIX + "/product";
private static final String PRODUCT_VIEW = "product";
private static final String TOP_PRODUCTS_VIEW = "top-products";
private static final String PRODUCT_EDIT_VIEW = "product-edit";
private static final String PAGE_ATTRIBUTE = "page";
private static final String PRODUCT_ATTRIBUTE = "product";
private final ProductService productService;
private final TypeService typeService;
private final ModelMapper modelMapper;
public ProductController(ProductService productService, TypeService typeService, ModelMapper modelMapper) {
this.productService = productService;
this.typeService = typeService;
this.modelMapper = modelMapper;
this.typeService = typeService;
}
private ProductDto toDto(ProductEntity entity) {
ProductDto dto = modelMapper.map(entity, ProductDto.class);
var dto = new ProductDto();
dto.setId(entity.getId());
dto.setDescription(entity.getDescription());
dto.setName(entity.getName());
dto.setPrice(entity.getPrice());
dto.setTypeId(entity.getType().getId());
return dto;
}
private TypeDto toTypeDto(TypeEntity entity) {
return modelMapper.map(entity, TypeDto.class);
}
private ProductEntity toEntity(ProductDto dto) {
final ProductEntity entity = modelMapper.map(dto, ProductEntity.class);
entity.setType(typeService.get(dto.getTypeId()));
@ -45,27 +67,90 @@ public class ProductController {
}
@GetMapping
public List<ProductDto> getAll(@RequestParam(name = "typeId", defaultValue = "0") Long typeId) {
return productService.getAll(typeId).stream().map(this::toDto).toList();
public String getAll(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
final Map<String, Object> attributes = PageAttributesMapper.toAttributes(
productService.getAll(0, page, Constants.DEFAULT_PAGE_SIZE), this::toDto);
model.addAllAttributes(attributes);
model.addAttribute(PAGE_ATTRIBUTE, page);
return PRODUCT_VIEW;
}
@GetMapping("/{id}")
public ProductDto get(@PathVariable(name = "id") Long id) {
return toDto(productService.get(id));
@GetMapping("/edit/")
public String create(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
model.addAttribute(PRODUCT_ATTRIBUTE, new ProductDto());
model.addAttribute("types", typeService.getAll().stream().map(this::toTypeDto).toList());
model.addAttribute(PAGE_ATTRIBUTE, page);
return PRODUCT_EDIT_VIEW;
}
@PostMapping
public ProductDto create(@RequestBody @Valid ProductDto dto) {
return toDto(productService.create(toEntity(dto)));
@PostMapping("/edit/")
public String create(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@ModelAttribute(name = PRODUCT_ATTRIBUTE) @Valid ProductDto product,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute(PAGE_ATTRIBUTE, page);
return PRODUCT_EDIT_VIEW;
}
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
productService.create(toEntity(product));
return Constants.REDIRECT_VIEW + URL;
}
@PutMapping("/{id}")
public ProductDto update(@PathVariable(name = "id") Long id, @RequestBody ProductDto dto) {
return toDto(productService.update(id, toEntity(dto)));
@GetMapping("/edit/{id}")
public String update(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
if (id <= 0) {
throw new IllegalArgumentException();
}
model.addAttribute(PRODUCT_ATTRIBUTE, toDto(productService.get(id)));
model.addAttribute(PAGE_ATTRIBUTE, page);
model.addAttribute("types", typeService.getAll().stream().map(this::toTypeDto).toList());
return PRODUCT_EDIT_VIEW;
}
@DeleteMapping("/{id}")
public ProductDto delete(@PathVariable(name = "id") Long id) {
return toDto(productService.delete(id));
@PostMapping("/edit/{id}")
public String update(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@ModelAttribute(name = PRODUCT_ATTRIBUTE) @Valid ProductDto product,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute(PAGE_ATTRIBUTE, page);
return PRODUCT_EDIT_VIEW;
}
if (id <= 0) {
throw new IllegalArgumentException();
}
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
productService.update(id, toEntity(product));
return Constants.REDIRECT_VIEW + URL;
}
@PostMapping("/delete/{id}")
public String delete(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
productService.delete(id);
return Constants.REDIRECT_VIEW + URL;
}
@GetMapping("/top")
public String getTopProducts(Model model) {
List<ProductEntity> topProducts = productService.getTopFiveMostSoldProducts();
model.addAttribute("topProducts", topProducts.stream().map(this::toDto).toList());
return TOP_PRODUCTS_VIEW;
}
}

View File

@ -1,6 +1,7 @@
package com.example.demo.products.api;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
@ -11,13 +12,16 @@ public class ProductDto {
@NotNull
@Min(1)
private Long typeId;
@NotNull
private final List<Long> genres = new ArrayList<>();
@NotNull
@Min(100)
private Double price;
@NotBlank
private String name;
@NotNull
@Min(1)
private Double price;
@NotBlank
private String description;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Long getId() {
return id;
}
@ -34,12 +38,13 @@ public class ProductDto {
this.typeId = typeId;
}
public String getName() {
return name;
public List<Long> getGenres() {
return genres;
}
public void setName(String name) {
this.name = name;
public void setGenres(List<Long> genres) {
this.genres.clear();
this.genres.addAll(genres);
}
public Double getPrice() {
@ -50,8 +55,20 @@ public class ProductDto {
this.price = price;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Double getSum() {
return price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@ -1,5 +1,7 @@
package com.example.demo.products.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import com.example.demo.core.model.BaseEntity;
@ -8,29 +10,31 @@ import com.example.demo.types.model.TypeEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Entity
@Table(name = "product")
@Table(name = "products")
public class ProductEntity extends BaseEntity {
@ManyToOne
@JoinColumn(name = "typeId", nullable = false)
private TypeEntity type;
@Column(nullable = false, length = 50)
@Column(nullable = false)
private String name;
@Column(nullable = false, length = 50)
@Column(nullable = false)
private Double price;
@Column(nullable = false)
private String description;
@ManyToOne
@JoinColumn(name = "typeId", nullable = false, unique = false)
private TypeEntity type;
public ProductEntity() {
super();
}
public ProductEntity(String name, TypeEntity type, Double price) {
public ProductEntity(TypeEntity type, String name, Double price, String description) {
this.type = type;
this.name = name;
this.price = price;
this.description = description;
}
public TypeEntity getType() {
@ -41,14 +45,6 @@ public class ProductEntity extends BaseEntity {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
@ -57,12 +53,27 @@ public class ProductEntity extends BaseEntity {
this.price = price;
}
@Override
public int hashCode() {
return Objects.hash(id, type, name, price);
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public int hashCode() {
return Objects.hash(id, type, price, description, name);
}
@SuppressWarnings("unlikely-arg-type")
@Override
public boolean equals(Object obj) {
if (this == obj)
@ -72,7 +83,8 @@ public class ProductEntity extends BaseEntity {
final ProductEntity other = (ProductEntity) obj;
return Objects.equals(other.getId(), id)
&& Objects.equals(other.getType(), type)
&& Objects.equals(other.getType(), name)
&& Objects.equals(other.getPrice(), price);
&& Objects.equals(other.getPrice(), price)
&& Objects.equals(other.getDescription(), description)
&& Objects.equals(other.getName(), name);
}
}

View File

@ -1,14 +1,35 @@
package com.example.demo.products.repository;
import java.util.Optional;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import com.example.demo.products.model.ProductEntity;
public interface ProductRepository extends CrudRepository<ProductEntity, Long> {
Optional<ProductEntity> findByNameIgnoreCase(String name);
public interface ProductRepository
extends CrudRepository<ProductEntity, Long>, PagingAndSortingRepository<ProductEntity, Long> {
@Query("select distinct p from ProductEntity p join fetch p.type t where p.id = ?1")
Optional<ProductEntity> findOneById(long id);
@Query("select distinct p from ProductEntity p join fetch p.type t where t.id = ?1")
Page<ProductEntity> findByTypeId(long typeId, Pageable pageable);
@Query("select distinct p from ProductEntity p join fetch p.type t where t.id = ?1")
List<ProductEntity> findByTypeId(long typeId);
@Query("select distinct p from ProductEntity p join fetch p.type t")
Page<ProductEntity> findAll(Pageable pageable);
@Query("select distinct p from ProductEntity p join fetch p.type t")
List<ProductEntity> findAll();
@Query("SELECT p FROM OrderEntity o JOIN o.products p GROUP BY p.id ORDER BY COUNT(p.id) DESC")
List<ProductEntity> findTop5MostPurchasedProducts(Pageable pageable);
List<ProductEntity> findByType_Id(Long typeId);
}

View File

@ -1,40 +1,62 @@
package com.example.demo.products.service;
import java.util.Collection;
import java.util.List;
import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.stream.StreamSupport;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.data.domain.Pageable;
import com.example.demo.core.error.NotFoundException;
import com.example.demo.orders.repository.OrderRepository;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.products.repository.ProductRepository;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ProductService {
private final ProductRepository repository;
private final OrderRepository orderRepository;
public ProductService(ProductRepository repository) {
public ProductService(ProductRepository repository, OrderRepository orderRepository) {
this.repository = repository;
this.orderRepository = orderRepository;
}
@Transactional(readOnly = true)
public List<ProductEntity> getAll(Long typeId) {
if (typeId != null && typeId != 0L) {
return repository.findByType_Id(typeId);
} else {
return StreamSupport.stream(repository.findAll().spliterator(), false).toList();
public List<ProductEntity> getAll(long typeId) {
if (!Objects.equals(typeId, 0L)) {
return repository.findByTypeId(typeId);
}
return repository.findAll();
}
@Transactional(readOnly = true)
public Page<ProductEntity> getAll(long typeId, int page, int size) {
final Pageable pageRequest = PageRequest.of(page, size);
if (!Objects.equals(typeId, 0L)) {
return repository.findByTypeId(typeId, pageRequest);
}
return repository.findAll(pageRequest);
}
@Transactional(readOnly = true)
public ProductEntity get(Long id) {
return repository.findById(id).orElseThrow(() -> new NotFoundException(ProductEntity.class, id));
return repository.findOneById(id).orElseThrow(() -> new NotFoundException(ProductEntity.class, id));
}
@Transactional(readOnly = true)
public List<ProductEntity> get(List<Long> id) {
return StreamSupport.stream(repository.findAllById(id).spliterator(), false).toList();
public List<ProductEntity> getByIds(Collection<Long> ids) {
final List<ProductEntity> products = StreamSupport.stream(repository.findAllById(ids).spliterator(), false)
.toList();
if (products.size() < ids.size()) {
throw new IllegalArgumentException("Invalid type");
}
return products;
}
@Transactional
@ -47,9 +69,12 @@ public class ProductService {
@Transactional
public ProductEntity update(Long id, ProductEntity entity) {
final ProductEntity exisEntity = get(id);
exisEntity.setName(entity.getName());
return repository.save(exisEntity);
final ProductEntity existEntity = get(id);
existEntity.setName(entity.getName());
existEntity.setPrice(entity.getPrice());
existEntity.setDescription(entity.getDescription());
existEntity.setType(entity.getType());
return repository.save(existEntity);
}
@Transactional
@ -58,4 +83,9 @@ public class ProductService {
repository.delete(existEntity);
return existEntity;
}
public List<ProductEntity> getTopFiveMostSoldProducts() {
Pageable topFive = PageRequest.of(0, 5);
return repository.findTop5MostPurchasedProducts(topFive);
}
}

View File

@ -1,16 +1,14 @@
package com.example.demo.types.api;
import java.util.List;
import org.modelmapper.ModelMapper;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.core.configuration.Constants;
import com.example.demo.types.model.TypeEntity;
@ -18,9 +16,15 @@ import com.example.demo.types.service.TypeService;
import jakarta.validation.Valid;
@RestController
@RequestMapping(Constants.API_URL + "/type")
@Controller
@RequestMapping(TypeController.URL)
public class TypeController {
public static final String URL = Constants.ADMIN_PREFIX + "/type";
private static final String TYPE_VIEW = "type";
private static final String TYPE_EDIT_VIEW = "type-edit";
private static final String TYPE_ATTRIBUTE = "type";
private final TypeService typeService;
private final ModelMapper modelMapper;
@ -38,27 +42,64 @@ public class TypeController {
}
@GetMapping
public List<TypeDto> getAll() {
return typeService.getAll().stream().map(this::toDto).toList();
public String getAll(Model model) {
model.addAttribute(
"items",
typeService.getAll().stream()
.map(this::toDto)
.toList());
return TYPE_VIEW;
}
@GetMapping("/{id}")
public TypeDto get(@PathVariable(name = "id") Long id) {
return toDto(typeService.get(id));
@GetMapping("/edit/")
public String create(Model model) {
model.addAttribute(TYPE_ATTRIBUTE, new TypeDto());
return TYPE_EDIT_VIEW;
}
@PostMapping
public TypeDto create(@RequestBody @Valid TypeDto dto) {
return toDto(typeService.create(toEntity(dto)));
@PostMapping("/edit/")
public String create(
@ModelAttribute(name = TYPE_ATTRIBUTE) @Valid TypeDto type,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return TYPE_EDIT_VIEW;
}
typeService.create(toEntity(type));
return Constants.REDIRECT_VIEW + URL;
}
@PutMapping("/{id}")
public TypeDto update(@PathVariable(name = "id") Long id, @RequestBody TypeDto dto) {
return toDto(typeService.update(id, toEntity(dto)));
@GetMapping("/edit/{id}")
public String update(
@PathVariable(name = "id") Long id,
Model model) {
if (id <= 0) {
throw new IllegalArgumentException();
}
model.addAttribute(TYPE_ATTRIBUTE, toDto(typeService.get(id)));
return TYPE_EDIT_VIEW;
}
@DeleteMapping("/{id}")
public TypeDto delete(@PathVariable(name = "id") Long id) {
return toDto(typeService.delete(id));
@PostMapping("/edit/{id}")
public String update(
@PathVariable(name = "id") Long id,
@ModelAttribute(name = TYPE_ATTRIBUTE) @Valid TypeDto type,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return TYPE_EDIT_VIEW;
}
if (id <= 0) {
throw new IllegalArgumentException();
}
typeService.update(id, toEntity(type));
return Constants.REDIRECT_VIEW + URL;
}
@PostMapping("/delete/{id}")
public String delete(
@PathVariable(name = "id") Long id) {
typeService.delete(id);
return Constants.REDIRECT_VIEW + URL;
}
}

View File

@ -1,17 +1,14 @@
package com.example.demo.types.api;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class TypeDto {
private Long id;
@NotBlank
@Size(min = 5, max = 50)
@Size(min = 1, max = 50)
private String name;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Long getId() {
return id;
}

View File

@ -3,45 +3,44 @@ package com.example.demo.types.model;
import java.util.Objects;
import com.example.demo.core.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = "types")
public class TypeEntity extends BaseEntity {
public class TypeEntity extends BaseEntity{
@Column(nullable = false, unique = true, length = 50)
private String name;
public TypeEntity() {
public TypeEntity(){
}
public TypeEntity(String name) {
public TypeEntity(String name){
this.name = name;
}
public String getName() {
public String getName(){
return name;
}
public void setName(String name) {
public void setName(String name){
this.name = name;
}
@Override
public int hashCode() {
return Objects.hash(id, name);
public int hashCode(){
return Objects.hash(id,name);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
public boolean equals(Object obj){
if(this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
final TypeEntity other = (TypeEntity) obj;
return Objects.equals(other.getId(), id)
&& Objects.equals(other.getName(), name);
return Objects.equals(other.getId(), id) && Objects.equals(other.getName(), name);
}
}

View File

@ -8,4 +8,5 @@ import com.example.demo.types.model.TypeEntity;
public interface TypeRepository extends CrudRepository<TypeEntity, Long> {
Optional<TypeEntity> findByNameIgnoreCase(String name);
Optional<TypeEntity> findById(long id);
}

View File

@ -3,6 +3,7 @@ package com.example.demo.types.service;
import java.util.List;
import java.util.stream.StreamSupport;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -13,48 +14,38 @@ import com.example.demo.types.repository.TypeRepository;
@Service
public class TypeService {
private final TypeRepository repository;
public TypeService(TypeRepository repository) {
public TypeService(TypeRepository repository){
this.repository = repository;
}
private void checkName(String name) {
if (repository.findByNameIgnoreCase(name).isPresent()) {
throw new IllegalArgumentException(
String.format("Type with name %s is already exists", name));
}
}
@Transactional(readOnly = true)
public List<TypeEntity> getAll() {
public List<TypeEntity> getAll(){
return StreamSupport.stream(repository.findAll().spliterator(), false).toList();
}
@Transactional(readOnly = true)
public TypeEntity get(long id) {
return repository.findById(id)
.orElseThrow(() -> new NotFoundException(TypeEntity.class, id));
public TypeEntity get(Long id){
return repository.findById(id).orElseThrow(() -> new NotFoundException(TypeEntity.class, id));
}
@Transactional
public TypeEntity create(TypeEntity entity) {
if (entity == null) {
public TypeEntity create(TypeEntity entity){
if(entity == null){
throw new IllegalArgumentException("Entity is null");
}
checkName(entity.getName());
return repository.save(entity);
}
@Transactional
public TypeEntity update(Long id, TypeEntity entity) {
public TypeEntity update(Long id, TypeEntity entity){
final TypeEntity existsEntity = get(id);
checkName(entity.getName());
existsEntity.setName(entity.getName());
return repository.save(existsEntity);
}
@Transactional
public TypeEntity delete(Long id) {
public TypeEntity delete(Long id){
final TypeEntity existsEntity = get(id);
repository.delete(existsEntity);
return existsEntity;

View File

@ -0,0 +1,114 @@
package com.example.demo.users.api;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import com.example.demo.core.configuration.Constants;
import com.example.demo.core.security.UserPrincipal;
import com.example.demo.core.session.SessionCart;
import com.example.demo.products.api.ProductDto;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.products.service.ProductService;
import com.example.demo.orders.model.OrderEntity;
import com.example.demo.orders.service.OrderService;
import jakarta.validation.Valid;
@Controller
@RequestMapping(UserCartController.URL)
@SessionAttributes("products")
public class UserCartController {
public static final String URL = "/cart";
private static final String ORDER_VIEW = "cart";
private static final String ORDER_ATTRIBUTE = "order";
private static final String CART_ATTRIBUTE = "cart";
private final ProductService productService;
private final OrderService orderService;
private final SessionCart cart;
public UserCartController(
ProductService productService,
OrderService orderService,
SessionCart cart) {
this.productService = productService;
this.orderService = orderService;
this.cart = cart;
}
private ProductDto toProductDto(ProductEntity entity) {
var dto = new ProductDto();
dto.setId(entity.getId());
dto.setDescription(entity.getDescription());
dto.setName(entity.getName());
dto.setPrice(entity.getPrice());
dto.setTypeId(entity.getType().getId());
return dto;
}
private OrderEntity toOrderEntities(Collection<UserCartDto> dtos) {
final Set<Long> productIds = dtos.stream()
.map(UserCartDto::getProduct)
.collect(Collectors.toSet());
final List<ProductEntity> products = productService.getByIds(productIds);
return new OrderEntity(products);
}
@GetMapping
public String getCart(Model model) {
model.addAttribute("products",
productService.getAll(0).stream()
.map(this::toProductDto)
.toList());
model.addAttribute(ORDER_ATTRIBUTE, new UserCartDto());
model.addAttribute(CART_ATTRIBUTE, cart.values());
return ORDER_VIEW;
}
@PostMapping
public String addOrderToCart(
@ModelAttribute(name = ORDER_ATTRIBUTE) @Valid UserCartDto order,
BindingResult bindingResult,
SessionStatus status,
Model model) {
if (bindingResult.hasErrors()) {
return ORDER_VIEW;
}
status.setComplete();
order.setProductName(productService.get(order.getProduct()).getName());
order.setPrice(productService.get(order.getProduct()).getPrice());
cart.computeIfPresent(order.hashCode(), (key, value) -> {
return value;
});
cart.put(order.hashCode(), order);
return Constants.REDIRECT_VIEW + URL;
}
@PostMapping("/save")
public String saveCart(
Model model,
@AuthenticationPrincipal UserPrincipal principal) {
orderService.create(principal.getId(), toOrderEntities(cart.values()));
cart.clear();
return Constants.REDIRECT_VIEW + URL;
}
@PostMapping("/clear")
public String clearCart() {
cart.clear();
return Constants.REDIRECT_VIEW + URL;
}
}

View File

@ -0,0 +1,53 @@
package com.example.demo.users.api;
import java.util.Objects;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
public class UserCartDto {
@NotNull
private Long product;
private String productName;
@Min(1000)
private Double price;
public Long getProduct() {
return product;
}
public void setProduct(Long productId) {
this.product = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public int hashCode() {
return Objects.hash(product, price);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
UserCartDto other = (UserCartDto) obj;
return Objects.equals(product, other.product) && Objects.equals(price, other.price);
}
}

View File

@ -0,0 +1,98 @@
package com.example.demo.users.api;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.SessionStatus;
import org.modelmapper.ModelMapper;
import com.example.demo.core.api.PageAttributesMapper;
import com.example.demo.core.configuration.Constants;
import com.example.demo.core.session.SessionCart;
import com.example.demo.products.api.ProductDto;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.products.service.ProductService;
import com.example.demo.types.api.TypeDto;
import com.example.demo.types.model.TypeEntity;
import com.example.demo.types.service.TypeService;
import jakarta.validation.Valid;
@Controller
@RequestMapping(UserCatalogController.URL)
public class UserCatalogController {
private static final String CATALOG_VIEW = "catalog";
public static final String URL = "/catalog";
private static final String TYPEID_ATTRIBUTE = "typeId";
private static final String PAGE_ATTRIBUTE = "page";
private static final String ORDER_ATTRIBUTE = "order";
private final ProductService productService;
private final TypeService typeService;
private final ModelMapper modelMapper;
private final SessionCart cart;
public UserCatalogController(ProductService productService, SessionCart cart, TypeService typeService,
ModelMapper modelMapper) {
this.productService = productService;
this.modelMapper = modelMapper;
this.typeService = typeService;
this.cart = cart;
}
private ProductDto toProductDto(ProductEntity entity) {
var dto = new ProductDto();
dto.setId(entity.getId());
dto.setDescription(entity.getDescription());
dto.setName(entity.getName());
dto.setPrice(entity.getPrice());
dto.setTypeId(entity.getType().getId());
return dto;
}
private TypeDto toTypeDto(TypeEntity entity) {
return modelMapper.map(entity, TypeDto.class);
}
@GetMapping
public String getCatalog(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@RequestParam(name = TYPEID_ATTRIBUTE, defaultValue = "0") int typeId,
Model model) {
model.addAttribute(ORDER_ATTRIBUTE, new UserCartDto());
model.addAttribute(PAGE_ATTRIBUTE, page);
model.addAllAttributes(PageAttributesMapper.toAttributes(
productService.getAll(typeId, page, Constants.DEFAULT_PAGE_SIZE), this::toProductDto));
model.addAttribute("types",
typeService.getAll().stream()
.map(this::toTypeDto)
.toList());
return CATALOG_VIEW;
}
@PostMapping
public String addOrderToCart(
@ModelAttribute(name = ORDER_ATTRIBUTE) @Valid UserCartDto order,
BindingResult bindingResult,
SessionStatus status,
Model model) {
if (bindingResult.hasErrors()) {
return CATALOG_VIEW;
}
status.setComplete();
order.setProductName(productService.get(order.getProduct()).getName());
order.setPrice(productService.get(order.getProduct()).getPrice());
cart.computeIfPresent(order.hashCode(), (key, value) -> {
return value;
});
cart.put(order.hashCode(), order);
return Constants.REDIRECT_VIEW + URL;
}
}

View File

@ -1,31 +1,45 @@
package com.example.demo.users.api;
import java.util.List;
import java.util.Map;
import org.modelmapper.ModelMapper;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.demo.core.api.PageAttributesMapper;
import com.example.demo.core.configuration.Constants;
import com.example.demo.orders.service.OrderService;
import com.example.demo.users.model.UserEntity;
import com.example.demo.users.service.UserService;
import jakarta.validation.Valid;
@RestController
@RequestMapping(Constants.API_URL + "/user")
@Controller
@RequestMapping(UserController.URL)
public class UserController {
private final UserService userService;
private final ModelMapper modelMapper;
public UserController(UserService userService, ModelMapper modelMapper) {
this.userService = userService;
public static final String URL = Constants.ADMIN_PREFIX + "/user";
private static final String USER_VIEW = "user";
private static final String USER_EDIT_VIEW = "user-edit";
private static final String PAGE_ATTRIBUTE = "page";
private static final String USER_ATTRIBUTE = "user";
private final ModelMapper modelMapper;
private final UserService userService;
public UserController(OrderService orderService, ModelMapper modelMapper, UserService userService) {
this.modelMapper = modelMapper;
this.userService = userService;
}
private UserDto toDto(UserEntity entity) {
@ -37,26 +51,75 @@ public class UserController {
}
@GetMapping
public List<UserDto> getAll() {
return userService.getAll().stream().map(this::toDto).toList();
public String getAll(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
final Map<String, Object> attributes = PageAttributesMapper.toAttributes(
userService.getAll(page, Constants.DEFAULT_PAGE_SIZE), this::toDto);
model.addAllAttributes(attributes);
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_VIEW;
}
@GetMapping("/{id}")
public UserDto get(@PathVariable(name = "id") Long id) {
return toDto(userService.get(id));
@GetMapping("/edit/")
public String create(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
model.addAttribute(USER_ATTRIBUTE, new UserDto());
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
}
@PostMapping
public UserDto create(@RequestBody @Valid UserDto dto) {
return toDto(userService.create(toEntity(dto)));
@PostMapping("/edit/")
public String create(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@ModelAttribute(name = USER_ATTRIBUTE) @Valid UserDto user,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
}
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
userService.create(toEntity(user));
return Constants.REDIRECT_VIEW + URL;
}
@PutMapping("/{id}")
public UserDto update(@PathVariable(name = "id") Long id, @RequestBody UserDto dto) {
return toDto(userService.update(id, toEntity(dto)));
@GetMapping("/edit/{id}")
public String update(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
if (id <= 0) {
throw new IllegalArgumentException();
}
model.addAttribute(USER_ATTRIBUTE, toDto(userService.get(id)));
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
}
@DeleteMapping("/{id}")
@PostMapping("/edit/{id}")
public String update(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@ModelAttribute(name = USER_ATTRIBUTE) @Valid UserDto user,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
}
if (id <= 0) {
throw new IllegalArgumentException();
}
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
userService.update(id, toEntity(user));
return Constants.REDIRECT_VIEW + URL;
}
@PostMapping("/delete/{id}")
public UserDto delete(@PathVariable(name = "id") Long id) {
return toDto(userService.delete(id));
}

View File

@ -1,27 +1,18 @@
package com.example.demo.users.api;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class UserDto {
private Long id;
@NotBlank
private String fullname;
@Size(min = 2, max = 20)
private String login;
@NotBlank
private String surname;
@NotBlank
@Email
@Size(min = 2, max = 20)
private String email;
private String role;
@NotBlank
private String password;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Long getId() {
return id;
}
@ -30,20 +21,12 @@ public class UserDto {
this.id = id;
}
public String getFullname() {
return fullname;
public String getLogin() {
return login;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
public void setLogin(String login) {
this.login = login;
}
public String getEmail() {
@ -54,11 +37,11 @@ public class UserDto {
this.email = email;
}
public String getPassword() {
return password;
public String getRole() {
return role;
}
public void setPassword(String password) {
this.password = password;
public void setRole(String role) {
this.role = role;
}
}

View File

@ -0,0 +1,82 @@
package com.example.demo.users.api;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.demo.core.api.PageAttributesMapper;
import com.example.demo.core.configuration.Constants;
import com.example.demo.core.security.UserPrincipal;
import com.example.demo.products.api.ProductDto;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.products.service.ProductService;
import com.example.demo.orders.api.OrderDto;
import com.example.demo.orders.model.OrderEntity;
import com.example.demo.orders.service.OrderService;
@Controller
public class UserProfileController {
private static final String PROFILE_VIEW = "profile";
private static final String PAGE_ATTRIBUTE = "page";
private final OrderService orderService;
private final ProductService productService;
public UserProfileController(
OrderService orderService,
ProductService productService) {
this.orderService = orderService;
this.productService = productService;
}
private OrderDto toDto(OrderEntity entity) {
var dto = new OrderDto();
dto.setId(entity.getId());
dto.setProducts(entity.getProducts().stream().map(ProductEntity::getId).toList());
return dto;
}
private ProductDto toProductDto(ProductEntity entity) {
var dto = new ProductDto();
dto.setId(entity.getId());
dto.setDescription(entity.getDescription());
dto.setName(entity.getName());
dto.setPrice(entity.getPrice());
dto.setTypeId(entity.getType().getId());
return dto;
}
@GetMapping
public String getProfile(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model,
@AuthenticationPrincipal UserPrincipal principal) {
final long userId = principal.getId();
model.addAttribute(PAGE_ATTRIBUTE, page);
model.addAllAttributes(PageAttributesMapper.toAttributes(
orderService.getAll(userId, page, Constants.DEFAULT_PAGE_SIZE),
this::toDto));
model.addAttribute("products",
productService.getAll(0).stream()
.map(this::toProductDto)
.toList());
return PROFILE_VIEW;
}
@PostMapping("/delete/{id}")
public String deleteOrder(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
RedirectAttributes redirectAttributes,
@AuthenticationPrincipal UserPrincipal principal) {
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
orderService.delete(principal.getId(), id);
return Constants.REDIRECT_VIEW + "/";
}
}

View File

@ -0,0 +1,63 @@
package com.example.demo.users.api;
import java.util.Objects;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demo.core.configuration.Constants;
import com.example.demo.users.model.UserEntity;
import com.example.demo.users.service.UserService;
import jakarta.validation.Valid;
@Controller
@RequestMapping(UserSignupController.URL)
public class UserSignupController {
public static final String URL = "/signup";
private static final String SIGNUP_VIEW = "signup";
private static final String USER_ATTRIBUTE = "user";
private final UserService userService;
private final ModelMapper modelMapper;
public UserSignupController(
UserService userService,
ModelMapper modelMapper) {
this.userService = userService;
this.modelMapper = modelMapper;
}
private UserEntity toEntity(UserSignupDto dto) {
return modelMapper.map(dto, UserEntity.class);
}
@GetMapping
public String getSignup(Model model) {
model.addAttribute(USER_ATTRIBUTE, new UserSignupDto());
return SIGNUP_VIEW;
}
@PostMapping
public String signup(
@ModelAttribute(name = USER_ATTRIBUTE) @Valid UserSignupDto user,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return SIGNUP_VIEW;
}
if (!Objects.equals(user.getPassword(), user.getPasswordConfirm())) {
bindingResult.rejectValue("password", "signup:passwords", "Пароли не совпадают.");
model.addAttribute(USER_ATTRIBUTE, user);
return SIGNUP_VIEW;
}
userService.create(toEntity(user));
return Constants.REDIRECT_VIEW + Constants.LOGIN_URL + "?signup";
}
}

View File

@ -0,0 +1,51 @@
package com.example.demo.users.api;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class UserSignupDto {
@NotBlank
@Size(min = 3, max = 20)
private String login;
@NotBlank
@Size(min = 3, max = 20)
private String email;
@NotBlank
@Size(min = 3, max = 20)
private String password;
@NotBlank
@Size(min = 3, max = 20)
private String passwordConfirm;
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPasswordConfirm() {
return passwordConfirm;
}
public void setPasswordConfirm(String passwordConfirm) {
this.passwordConfirm = passwordConfirm;
}
}

View File

@ -1,84 +1,79 @@
package com.example.demo.users.model;
import java.util.Objects;
import java.util.ArrayList;
import java.util.List;
import com.example.demo.core.model.BaseEntity;
import com.example.demo.orders.model.OrderEntity;
import jakarta.persistence.Table;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
@Entity
@Table(name = "users")
public class UserEntity extends BaseEntity {
@Column(nullable = false, length = 50)
private String fullname;
@Column(nullable = false, length = 50)
private String surname;
@Column(nullable = false, unique = true, length = 50)
public class UserEntity extends BaseEntity{
@Column(nullable = false, unique = true, length = 20)
private String login;
@Column(nullable = false, unique = true, length = 20)
private String email;
@Column(nullable = false, length = 50)
@Column(nullable = false, length = 60)
private String password;
private UserRole role;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
@OrderBy("id ASC")
private List<OrderEntity> orders = new ArrayList<>();
public UserEntity() {
super();
public UserEntity(){
}
public UserEntity(String fullname, String surname, String email, String password) {
this.fullname = fullname;
this.surname = surname;
public UserEntity(String login, String email, String password){
this.login = login;
this.email = email;
this.password = password;
this.role = UserRole.USER;
}
public String getFullname() {
return fullname;
public String getLogin(){
return login;
}
public void setLogin(String login){
this.login = login;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getEmail() {
public String getEmail(){
return email;
}
public void setEmail(String email) {
public void setEmail(String email){
this.email = email;
}
public String getPassword() {
public String getPassword(){
return password;
}
public void setPassword(String password) {
public void setPassword(String password){
this.password = password;
}
@Override
public int hashCode() {
return Objects.hash(id, fullname, surname, email, password);
public UserRole getRole() {
return role;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
final UserEntity other = (UserEntity) obj;
return Objects.equals(other.getId(), id)
&& Objects.equals(other.getFullname(), fullname)
&& Objects.equals(other.getSurname(), surname)
&& Objects.equals(other.getEmail(), email)
&& Objects.equals(other.getPassword(), password);
public void setRole(UserRole role) {
this.role = role;
}
public List<OrderEntity> getOrders(){
return orders;
}
public void addOrder(OrderEntity order){
if(order.getUser() != this){
order.setUser(this);
}
orders.add(order);
}
}

View File

@ -0,0 +1,15 @@
package com.example.demo.users.model;
import org.springframework.security.core.GrantedAuthority;
public enum UserRole implements GrantedAuthority{
ADMIN,
USER;
private static final String PREFIX = "ROLE_";
@Override
public String getAuthority() {
return PREFIX + this.name();
}
}

View File

@ -8,5 +8,5 @@ import org.springframework.data.repository.PagingAndSortingRepository;
import com.example.demo.users.model.UserEntity;
public interface UserRepository extends CrudRepository<UserEntity, Long>, PagingAndSortingRepository<UserEntity, Long> {
Optional<UserEntity> findByEmailIgnoreCase(String email);
}
Optional<UserEntity> findByLoginIgnoreCase(String login);
}

View File

@ -1,27 +1,43 @@
package com.example.demo.users.service;
import java.util.List;
import java.util.stream.StreamSupport;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.stream.StreamSupport;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.data.domain.PageRequest;
import org.springframework.util.StringUtils;
import com.example.demo.core.configuration.Constants;
import com.example.demo.core.error.NotFoundException;
import com.example.demo.core.security.UserPrincipal;
import com.example.demo.users.model.UserEntity;
import com.example.demo.users.model.UserRole;
import com.example.demo.users.repository.UserRepository;
@Service
public class UserService {
public class UserService implements UserDetailsService {
private final UserRepository repository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository repository) {
public UserService(UserRepository repository, PasswordEncoder passwordEncoder) {
this.repository = repository;
this.passwordEncoder = passwordEncoder;
}
private void checkEmail(String email) {
if (repository.findByEmailIgnoreCase(email).isPresent()) {
private void checkLogin(Long id, String login) {
final Optional<UserEntity> existsUser = repository.findByLoginIgnoreCase(login);
if (existsUser.isPresent() && !existsUser.get().getId().equals(id)) {
throw new IllegalArgumentException(
String.format("User with email %s is already exists", email));
String.format("User with login %s is already exists", login));
}
}
@ -31,9 +47,19 @@ public class UserService {
}
@Transactional(readOnly = true)
public UserEntity get(long id) {
return repository.findById(id)
.orElseThrow(() -> new NotFoundException(UserEntity.class, id));
public Page<UserEntity> getAll(int page, int size) {
return repository.findAll(PageRequest.of(page, size, Sort.by("id")));
}
@Transactional(readOnly = true)
public UserEntity get(Long id) {
return repository.findById(id).orElseThrow(() -> new NotFoundException(UserEntity.class, id));
}
@Transactional(readOnly = true)
public UserEntity getByLogin(String login) {
return repository.findByLoginIgnoreCase(login)
.orElseThrow(() -> new IllegalArgumentException("Invalid login"));
}
@Transactional
@ -41,18 +67,22 @@ public class UserService {
if (entity == null) {
throw new IllegalArgumentException("Entity is null");
}
checkEmail(entity.getEmail());
checkLogin(null, entity.getLogin());
final String password = Optional.ofNullable(entity.getPassword()).orElse("");
entity.setPassword(
passwordEncoder.encode(
StringUtils.hasText(password.strip()) ? password : Constants.DEFAULT_PASSWORD));
entity.setRole(Optional.ofNullable(entity.getRole()).orElse(UserRole.USER));
repository.save(entity);
return repository.save(entity);
}
@Transactional
public UserEntity update(Long id, UserEntity entity) {
checkEmail(entity.getEmail());
final UserEntity existEntity = get(id);
existEntity.setFullname(entity.getFullname());
existEntity.setSurname(entity.getSurname());
checkLogin(id, entity.getLogin());
existEntity.setLogin(entity.getLogin());
existEntity.setEmail(entity.getEmail());
existEntity.setPassword(entity.getPassword());
repository.save(existEntity);
return existEntity;
}
@ -63,4 +93,11 @@ public class UserService {
repository.delete(existEntity);
return existEntity;
}
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final UserEntity existsUser = getByLogin(username);
return new UserPrincipal(existsUser);
}
}

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-emoji-sunglasses-fill" viewBox="0 0 16 16">
<path
d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16M2.31 5.243A1 1 0 0 1 3.28 4H6a1 1 0 0 1 1 1v.116A4.2 4.2 0 0 1 8 5c.35 0 .69.04 1 .116V5a1 1 0 0 1 1-1h2.72a1 1 0 0 1 .97 1.243l-.311 1.242A2 2 0 0 1 11.439 8H11a2 2 0 0 1-1.994-1.839A3 3 0 0 0 8 6c-.393 0-.74.064-1.006.161A2 2 0 0 1 5 8h-.438a2 2 0 0 1-1.94-1.515zM4.969 9.75A3.5 3.5 0 0 0 8 11.5a3.5 3.5 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.5 4.5 0 0 1 8 12.5a4.5 4.5 0 0 1-3.898-2.25.5.5 0 0 1 .866-.5z" />
</svg>

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Корзина</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto py-3">
<strong class="flex-fill">Корзина</strong>
<div class="d-flex flex-column align-items-center">
<div class="card col-12 col-md-8 col-lg-6 w-75 align-items-center" th:each="cartItem : ${cart}">
<div class="card-body col-12 p-2 d-flex flex-row align-items-center justify-content-center">
<div class="col-5"><img src="/kola.jpg" class="w-75"></div>
<div class="col-3">
Название: [[${cartItem.productName}]]
</div>
<div class="col-4">
Цена: [[${cartItem.Price}]]
</div>
</div>
</div>
<div class=" mb-2 col-12 col-md-8 col-lg-6 d-flex justify-content-end">
</div>
<div class="mb-2 col-12 col-md-8 col-lg-6 d-flex justify-content-center"
th:if="${not #lists.isEmpty(cart)}">
<form action="#" th:action="@{/cart/save}" method="post">
<button type="submit" class="btn btn-primary" onclick="return confirm('Вы уверены?')">
Оформить заказ
</button>
</form>
</div>
<div class="mb-2 col-12 col-md-8 col-lg-6 d-flex align-items-center">
<form action="#" th:action="@{/cart/clear}" method="post">
<button type="submit" class="btn btn-danger button-fixed-width"
onclick="return confirm('Вы уверены?')">
<i class="bi bi-x-lg"></i> Очистить
</button>
</form>
</div>
</div>
</main>
</body>
</html>
</html>

View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Каталог</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto main">
<div class="tab-content mt-2">
<h2 class="text-center">Меню</h2>
<div class="row mx-auto">
<div class="pt-4 ps-2 text-center" style="font-size: larger;">Обязательно возьми</div>
<div><img class="p-2" src="/kola.jpg" width="100%" /></div>
</div>
<div class="tab-pane container active table-responsive" id="orders">
<form th:action="@{/catalog}" method="get" class="row mt-2 w-50 mx-auto pb-3">
<div class="pb-3">
<input type="hidden" th:name="page" th:value="${page}">
<select th:name="typeId" id="typeId" class="form-select">
<option selected value="">Фильтр по продукции</option>
<option th:each="type : ${types}" th:value="${type.id}" th:selected="${type.id==typeId}">
[[${type.name}]]
</option>
</select>
<input type="hidden" th:name="page" th:value="${page}">
</div>
<button type="submit" class="btn btn-primary">Показать</button>
</form>
<table class="table">
<thead>
<th class="w-25"></th>
<th>Название</th>
<th>Тип</th>
<th class="w-auto">Описание</th>
<th>Цена</th>
<th></th>
</thead>
<tbody>
<tr th:each="producte : ${items}">
<th scope="row"><img src="/kola.jpg" class="w-100"></th>
<th scope="row" th:text="${producte.name}"></th>
<th:block th:each="type : ${types}">
<th:block th:if="${type.Id} eq ${producte.typeId}">
<th scope="row" th:text="${type.name}"></th>
</th:block>
</th:block>
<th scope="row" th:text="${producte.description}"></th>
<th scope="row" th:text="${producte.Price}"></th>
<th>
<form th:action="@{/catalog}" th:object="${order}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<select hidden="hidden" th:field="*{product}" class="form-select">
<option th:value="${producte.id}"></option>
</select>
<button type="submit" class="btn btn-link button-link"><i
class="bi bi-cart2 d-inline-block align-top me-1 logo"></i></button>
</form>
</th>
</tr>
</tbody>
</table>
<th:block th:replace="~{ pagination :: pagination (
url='catalog',
totalPages=${totalPages},
currentPage=${currentPage}) }" />
</div>
</div>
</main>
</body>
</html>

View File

@ -7,7 +7,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">My shop</title>
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">FasterPizza.ru</title>
<script type="text/javascript" src="/webjars/bootstrap/5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="/webjars/bootstrap/5.3.3/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="/webjars/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" />
@ -18,8 +18,8 @@
<nav class="navbar navbar-expand-md my-navbar" data-bs-theme="dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<i class="bi bi-cart2 d-inline-block align-top me-1 logo"></i>
Туда сюда и пицца
<i class="bi-pizza-slice d-inline-block align-top me-1 logo"></i>
БыстроПицца
</a>
<th:block sec:authorize="isAuthenticated()" th:with="userName=${#authentication.name}">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar"
@ -35,16 +35,25 @@
</a>
<a class="nav-link" href="/admin/type"
th:classappend="${activeLink.startsWith('/admin/type') ? 'active' : ''}">
Типы заказов
Типы Продукции
</a>
<a class="nav-link" href="/admin/subscription"
th:classappend="${activeLink.startsWith('/admin/subscription') ? 'active' : ''}">
Списки рассылки
<a class="nav-link" href="/admin/product"
th:classappend="${activeLink.startsWith('/admin/product') ? 'active' : ''}">
Продукция
</a>
<a class="nav-link" href="/admin/product/top"
th:classappend="${activeLink.startsWith('/admin/product/top') ? 'active' : ''}">
Топ Продуктов
</a>
<a class="nav-link" href="/h2-console/" target="_blank">Консоль H2</a>
</th:block>
<a class="nav-link" href="/123" target="_blank">Ошибка 1</a>
<a class="nav-link" href="/admin/123" target="_blank">Ошибка 2</a>
<a class="nav-link" href="/catalog"
th:classappend="${activeLink.startsWith('/catalog') ? 'active' : ''}">
Каталог
</a>
<a class="nav-link" href="/">
Заказы
</a>
</ul>
<ul class="navbar-nav" th:if="${not #strings.isEmpty(userName)}">
<form th:action="@{/logout}" method="post">
@ -64,7 +73,7 @@
<main class="container-fluid p-2" layout:fragment="content">
</main>
<footer class="my-footer mt-auto d-flex flex-shrink-0 justify-content-center align-items-center">
Автор, [[${#dates.year(#dates.createNow())}]]
Крюков А.И., [[${#dates.year(#dates.createNow())}]]
</footer>
</body>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Ошибка</title>
</head>
<body>
<main layout:fragment="content">
<ul class="list-group mb-2">
<th:block th:if="${#strings.isEmpty(message)}">
<li class="list-group-item">
Неизвестная ошибка
</li>
</th:block>
<th:block th:if="${not #strings.isEmpty(message)}">
<li class="list-group-item">
<strong>Ошибка:</strong> [[${message}]]
</li>
</th:block>
<th:block th:if="${not #strings.isEmpty(url)}">
<li class="list-group-item">
<strong>Адрес:</strong> [[${url}]]
</li>
<li class="list-group-item">
<strong>Класс исключения:</strong> [[${exception}]]
</li>
<li class="list-group-item">
[[${method}]] ([[${file}]]:[[${line}]])
</li>
</th:block>
</ul>
<a class="btn btn-primary button-fixed-width" href="/">На главную</a>
</main>
</body>
</html>

View File

@ -7,7 +7,7 @@
<body>
<main layout:fragment="content">
<form action="#" th:action="@{/login}" method="post">
<form action="#" th:action="@{/login}" method="post" class="w-25 mx-auto">
<div th:if="${param.error}" class="alert alert-danger">
Неверный логин или пароль
</div>
@ -39,6 +39,4 @@
</main>
</body>
</html>
</html>

View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="orders (items, totalPages, currentPage, products)">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*" class="w-50 mx-auto">
<table class="table mt-2">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-auto">Игры</th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="order : ${items}">
<th scope="row" th:text="${order.id}"></th>
<td>
<table class="table">
<thead>
<tr>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-25"></th>
<th scope="col" class="w-auto">Название продукции</th>
<th scope="col" class="w-10">Цена</th>
</tr>
</thead>
<tbody>
<tr th:each="productId : ${order.products}">
<th:block th:each="product : ${products}">
<th:block th:if="${productId} eq ${product.id}">
<th scope="row" th:text="${productId}"></th>
<th scope="row"><img src="/kola.jpg" class="w-100">
</th>
<th scope="row" th:text="${product.name}"></th>
<th scope="row" th:text="${product.price}"></th>
</th:block>
</th:block>
</tr>
</tbody>
</table>
</td>
<td>
<form th:action="@{/delete/{id}(id=${order.id})}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link"
onclick="return confirm('Вы уверены?')">Удалить</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
<th:block th:replace="~{ pagination :: pagination (
url='',
totalPages=${totalPages},
currentPage=${currentPage}) }" />
<div class="mb-2 d-flex justify-content-center">
<a class="btn btn-primary" href="/cart">Создать заказ</a>
</div>
</th:block>
</th:block>
</body>
</html>

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="pagination (url, totalPages, currentPage)">
<nav th:if="${totalPages > 1}" th:with="
maxPage=2,
currentPage=${currentPage + 1}">
<ul class="pagination justify-content-center"
th:with="
seqFrom=${currentPage - maxPage < 1 ? 1 : currentPage - maxPage},
seqTo=${currentPage + maxPage > totalPages ? totalPages : currentPage + maxPage}">
<th:block th:if="${currentPage > maxPage + 1}">
<li class="page-item">
<a class="page-link" aria-label="Previous" th:href="@{/{url}?page=0(url=${url})}">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item disabled">
<span class="page-link" aria-label="Previous">
<span aria-hidden="true">&hellip;</span>
</span>
</li>
</th:block>
<li class="page-item" th:each="page : ${#numbers.sequence(seqFrom, seqTo)}"
th:classappend="${page == currentPage} ? 'active' : ''">
<a class=" page-link" th:href="@{/{url}?page={page}(url=${url},page=${page - 1})}">
<span th:text="${page}" />
</a>
</li>
<th:block th:if="${currentPage < totalPages - maxPage}">
<li class="page-item disabled">
<span class="page-link" aria-label="Previous">
<span aria-hidden="true">&hellip;</span>
</span>
</li>
<li class="page-item">
<a class="page-link" aria-label="Next"
th:href="@{/{url}?page={page}(url=${url},page=${totalPages - 1})}">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</th:block>
</ul>
</nav>
</th:block>
</body>
</html>

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редакторовать игру</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto">
<form action="#" th:action="@{/admin/product/edit/{id}(id=${product.id})}" th:object="${product}" method="post">
<div class="mb-3">
<label for="id" class="form-label">ID</label>
<input type="text" th:value="*{id}" id="id" class="form-control" readonly disabled>
</div>
<div class="mb-3">
<label for="name" class="form-label">Название</label>
<input type="text" th:field="*{name}" id="name" class="form-control">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="invalid-feedback"></div>
</div>
<div class="mb-2">
<label for="typeId" class="form-label">Тип</label>
<select th:field="*{typeId}" id="typeId" class="form-select">
<option selected value="">Укажите тип</option>
<option th:each="type : ${types}" th:value="${type.id}">[[${type.name}]]</option>
</select>
</div>
<div class="mb-3">
<label for="price" class="form-label">Цена</label>
<input type="number" th:field="*{price}" id="price" class="form-control">
<div th:if="${#fields.hasErrors('price')}" th:errors="*{price}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="description" class="form-label">Описание</label>
<textarea type="textarea" rows="5" cols="80" th:field="*{description}" id="description"
class="form-control"></textarea>
<div th:if="${#fields.hasErrors('description')}" th:errors="*{description}" class="invalid-feedback">
</div>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Сохранить</button>
<a class="btn btn-secondary button-fixed-width" href="/admin/product">Отмена</a>
</div>
</form>
</main>
</body>
</html>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Меню</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2 class="text-center">Меню</h2>
<div>
<a href="/admin/product/edit/" class="btn btn-primary">Добавить продукцию</a>
</div>
<table class="table">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-10">Название</th>
<th scope="col" class="w-10">Цена</th>
<th scope="col" class="w-auto">Описание</th>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="product : ${items}">
<th scope="row" th:text="${product.id}"></th>
<td th:text="${product.name}"></td>
<td th:text="${product.price}"></td>
<td th:text="${product.description}"></td>
<td>
<form th:action="@{/admin/product/edit/{id}(id=${product.id})}" method="get">
<button type="submit" class="btn btn-link button-link">Редактировать</button>
</form>
</td>
<td>
<form th:action="@{/admin/product/delete/{id}(id=${product.id})}" method="post">
<button type="submit" class="btn btn-link button-link"
onclick="return confirm('Вы уверены?')">Удалить</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
</th:block>
</main>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Личный кабинет</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto main">
<div class="tab-content mt-2">
<h2 class="text-center">Заказы</h2>
<div class="tab-pane container active" id="orders">
<th:block
th:replace="~{ orders :: orders (items=${items}, totalPages=${totalPages}, currentPage=${currentPage}, products=${products})}" />
</div>
</div>
</main>
</body>
</html>

View File

@ -6,13 +6,18 @@
</head>
<body>
<main layout:fragment="content">
<main layout:fragment="content" class="w-25 mx-auto">
<form action="#" th:action="@{/signup}" th:object="${user}" method="post">
<div class="mb-3">
<label for="login" class="form-label">Имя пользователя</label>
<input type="text" th:field="*{login}" id="login" class="form-control">
<div th:if="${#fields.hasErrors('login')}" th:errors="*{login}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Почта</label>
<input type="email" th:field="*{email}" id="email" class="form-control">
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Пароль</label>
<input type="password" th:field="*{password}" id="password" class="form-control">
@ -32,6 +37,4 @@
</main>
</body>
</html>
</html>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<meta charset="UTF-8">
<title>Top 5 Products</title>
</head>
<body>
<main layout:fragment="content" class="w-25 mx-auto">
<h1>Top 5 Most Purchased Products</h1>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr th:each="product : ${topProducts}">
<td th:text="${product.id}"></td>
<td th:text="${product.name}"></td>
<td th:text="${product.description}"></td>
<td th:text="${product.price}"></td>
</tr>
</tbody>
</table>
</main>
</body>
</html>

View File

@ -2,18 +2,18 @@
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редакторовать тип заказа</title>
<title>Редакторовать тип продукции</title>
</head>
<body>
<main layout:fragment="content">
<main layout:fragment="content" class="w-50 mx-auto">
<form action="#" th:action="@{/admin/type/edit/{id}(id=${type.id})}" th:object="${type}" method="post">
<div class="mb-3">
<label for="id" class="form-label">ID</label>
<input type="text" th:value="*{id}" id="id" class="form-control" readonly disabled>
</div>
<div class="mb-3">
<label for="name" class="form-label">Тип заказа</label>
<label for="name" class="form-label">Тип продукции</label>
<input type="text" th:field="*{name}" id="name" class="form-control">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="invalid-feedback"></div>
</div>

View File

@ -2,24 +2,24 @@
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Типы заказов</title>
<title>Типы продукции</title>
</head>
<body>
<main layout:fragment="content">
<main layout:fragment="content" class="w-50 mx-auto">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2>Типы заказов</h2>
<h2 class="text-center">Типы продукции</h2>
<div>
<a href="/admin/type/edit/" class="btn btn-primary">Добавить тип заказа</a>
<a href="/admin/type/edit/" class="btn btn-primary">Добавить тип продукции</a>
</div>
<table class="table">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-auto">Тип заказа</th>
<th scope="col" class="w-auto">Тип продукции</th>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-10"></th>
</tr>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редакторовать пользователя</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto">
<form action="#" th:action="@{/admin/user/edit/{id}(id=${user.id},page=${page})}" th:object="${user}"
method="post">
<div class="mb-3">
<label for="id" class="form-label">ID</label>
<input type="text" th:value="*{id}" id="id" class="form-control" readonly disabled>
</div>
<div class="mb-3">
<label for="login" class="form-label">Имя пользователя</label>
<input type="text" th:field="*{login}" id="login" class="form-control">
<div th:if="${#fields.hasErrors('login')}" th:errors="*{login}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Почта</label>
<input type="text" th:field="*{email}" id="email" class="form-control">
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="invalid-feedback"></div>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Сохранить</button>
<a class="btn btn-secondary button-fixed-width" th:href="@{/admin/user(page=${page})}">Отмена</a>
</div>
</form>
</main>
</body>
</html>

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Пользователи</title>
</head>
<body>
<main layout:fragment="content" class="w-50 mx-auto">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2 class=" text-center">Пользователи</h2>
<div>
<a th:href="@{/admin/user/edit/(page=${page})}" class="btn btn-primary">Добавить пользователя</a>
</div>
<table class="table">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-auto">Имя пользователя</th>
<th scope="col" class="w-auto">Почта</th>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${items}">
<th scope="row" th:text="${user.id}"></th>
<td th:text="${user.login}"></td>
<td th:text="${user.email}"></td>
<td>
<form th:action="@{/admin/user/edit/{id}(id=${user.id})}" method="get">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link">Редактировать</button>
</form>
</td>
<td>
<form th:action="@{/admin/user/delete/{id}(id=${user.id})}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link"
onclick="return confirm('Вы уверены?')">Удалить</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
<th:block th:replace="~{ pagination :: pagination (
url=${'admin/user'},
totalPages=${totalPages},
currentPage=${currentPage}) }" />
</th:block>
</main>
</body>
</html>

View File

@ -1,98 +1,101 @@
package com.example.demo;
import com.example.demo.core.error.NotFoundException;
import com.example.demo.orders.model.OrderEntity;
import com.example.demo.orders.service.OrderService;
import com.example.demo.users.model.UserEntity;
import com.example.demo.users.service.UserService;
import com.example.demo.order_lines.model.OrderLineEntity;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.products.service.ProductService;
import com.example.demo.types.model.TypeEntity;
import com.example.demo.types.service.TypeService;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import com.example.demo.core.error.NotFoundException;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.products.service.ProductService;
import com.example.demo.orders.model.OrderEntity;
import com.example.demo.orders.service.OrderService;
import com.example.demo.types.model.TypeEntity;
import com.example.demo.types.service.TypeService;
import com.example.demo.users.model.UserEntity;
import com.example.demo.users.service.UserService;
@SpringBootTest
class OrderServiceTests {
@TestMethodOrder(OrderAnnotation.class)
class OrderServiceTest {
@Autowired
private ProductService productService;
@Autowired
private TypeService typeService;
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Autowired
private TypeService typeService;
private OrderEntity order;
private TypeEntity type1;
private TypeEntity type2;
private ProductEntity product1;
private ProductEntity product2;
private List<ProductEntity> products = new ArrayList<>();
private UserEntity user1;
private OrderEntity order1;
@BeforeEach
void createData() {
type1 = typeService.create(new TypeEntity("Игра"));
type2 = typeService.create(new TypeEntity("Программа"));
product1 = productService.create(new ProductEntity(type1, "Product1", 2100.0, "good product"));
product2 = productService.create(new ProductEntity(type2, "Product2", 1200.0, "bad product"));
products = new ArrayList<ProductEntity>();
products.add(product1);
products.add(product2);
user1 = userService.create(new UserEntity("login1", "email@mail.com", "qwerty123"));
order1 = orderService.create(user1.getId(), new OrderEntity(products));
removeData();
// Создаем данные для тестирования
TypeEntity type = typeService.create(new TypeEntity("musor"));
UserEntity user = userService.create(new UserEntity("John", "Doe", "privet@gmail.ru", "passwd"));
ProductEntity product = productService.create(new ProductEntity("Laptop", type, 400.00));
order = new OrderEntity(user);
OrderLineEntity line = new OrderLineEntity(product, 2);
order.addOrderLine(line);
orderService.create(order);
type1 = typeService.create(new TypeEntity("Игра"));
type2 = typeService.create(new TypeEntity("Программа"));
product1 = productService.create(new ProductEntity(type1, "Product1", 2100.0, "good product"));
product2 = productService.create(new ProductEntity(type2, "Product2", 1200.0, "bad product"));
products = new ArrayList<ProductEntity>();
products.add(product1);
products.add(product2);
user1 = userService.create(new UserEntity("login1", "email@mail.com", "qwerty123"));
order1 = orderService.create(user1.getId(), new OrderEntity(products));
orderService.create(user1.getId(), new OrderEntity(products));
}
@AfterEach
void removeData() {
// Удаляем все данные после каждого теста
orderService.delete(order.getId());
userService.delete(order.getUser().getId());
productService.delete(order.getLines().get(0).getProduct().getId());
typeService.delete(order.getLines().get(0).getProduct().getType().getId());
}
@Test
void getAllTest() {
List<OrderEntity> orders = orderService.getAll(0L, List.of());
Assertions.assertNotNull(orders);
Assertions.assertFalse(orders.isEmpty());
orderService.getAll().forEach(item -> orderService.delete(item.getUser().getId(), item.getId()));
userService.getAll().forEach(item -> userService.delete(item.getId()));
productService.getAll(0).forEach(item -> productService.delete(item.getId()));
typeService.getAll().forEach(item -> typeService.delete(item.getId()));
}
@Test
void getTest() {
// Тестируем, что найденный заказ совпадает с ожидаемым
OrderEntity foundOrder = orderService.get(order.getId());
Assertions.assertNotNull(foundOrder);
Assertions.assertEquals(order, foundOrder);
// Проверяем, что вызов с несуществующим id бросает исключение
Assertions.assertThrows(NotFoundException.class, () -> orderService.get(0L));
Assertions.assertThrows(NotFoundException.class, () -> productService.get(0L));
}
@Test
@Order(1)
void createTest() {
// Проверяем, что созданный заказ совпадает с ожидаемым
Assertions.assertNotNull(order.getId());
OrderEntity foundOrder = orderService.get(order.getId());
Assertions.assertEquals(order, foundOrder);
}
@Test
void updateTest() {
// Обновляем заказ и проверяем, что он изменился
OrderEntity updatedOrder = orderService.update(order.getId(), order);
Assertions.assertNotNull(updatedOrder);
Assertions.assertEquals(order, updatedOrder);
Assertions.assertEquals(2, orderService.getAll(user1.getId()).size());
}
@Test
@Order(2)
void deleteTest() {
// Удаляем заказ и проверяем, что его нет в базе данных
orderService.delete(order.getId());
Assertions.assertThrows(NotFoundException.class, () -> orderService.get(order.getId()));
orderService.delete(user1.getId(), order1.getId());
Assertions.assertEquals(1, orderService.getAll(user1.getId()).size());
}
}
}

View File

@ -1,24 +1,57 @@
package com.example.demo;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.example.demo.core.error.NotFoundException;
import com.example.demo.products.api.ProductDto;
import com.example.demo.products.model.ProductEntity;
import com.example.demo.products.service.ProductService;
import com.example.demo.types.model.TypeEntity;
import com.example.demo.types.service.TypeService;
@SpringBootTest
@TestMethodOrder(OrderAnnotation.class)
class ProductServiceTests {
@Autowired
private ProductService productService;
@Autowired
private TypeService typeService;
private TypeEntity type1;
private TypeEntity type2;
private ProductEntity product1;
private ProductEntity product2;
@BeforeEach
void createData() {
removeData();
type1 = typeService.create(new TypeEntity("Напиток"));
type2 = typeService.create(new TypeEntity("Пицца"));
product1 = productService.create(new ProductEntity(type1, "Product1", 2100.0, "good product"));
product2 = productService.create(new ProductEntity(type2, "Product2", 1200.0, "bad product"));
}
@AfterEach
void removeData() {
productService.getAll(0).forEach(item -> productService.delete(item.getId()));
typeService.getAll().forEach(item -> typeService.delete(item.getId()));
}
@Test
void getTest() {
Assertions.assertThrows(NotFoundException.class, () -> productService.get(0L));
@ -27,31 +60,34 @@ class ProductServiceTests {
@Test
@Order(1)
void createTest() {
final var type1 = new TypeEntity("Пицца");
productService.create(new ProductEntity("Mocarela", type1, 20.00));
productService.create(new ProductEntity("El'Diablo", type1, 20.00));
productService.create(new ProductEntity("Маргарита", type1, 499.00));
productService.create(new ProductEntity("Эль Дьябло", type1, 699.00));
productService.create(new ProductEntity("Гавайская", type1, 399.00));
Assertions.assertEquals(5, productService.getAll(0L).size());
Assertions.assertEquals(2, productService.getAll(0).size());
}
@Test
@Order(2)
void updateTest() {
final ProductEntity newProduct = new ProductEntity("El'Diablo", new TypeEntity("Пицца"), 20.00);
final ProductEntity updProduct = productService.update(1L, newProduct);
Assertions.assertEquals(5, productService.getAll(0L).size());
Assertions.assertEquals(updProduct, productService.get(1L));
Assertions.assertEquals(newProduct.getName(), updProduct.getName());
Assertions.assertEquals(newProduct.getPrice(), updProduct.getPrice());
TypeEntity newType = typeService.create(new TypeEntity("Закуска"));
ProductEntity updatedProduct = new ProductEntity(newType, "testProduct", 1200.0, "hehproduct");
productService.update(product1.getId(), updatedProduct);
List<ProductEntity> allProducts = productService.getAll(0);
Assertions.assertEquals(2, allProducts.size());
ProductEntity retrievedProduct = productService.get(product1.getId());
Assertions.assertEquals("testProduct", retrievedProduct.getName());
Assertions.assertEquals(1200.0, retrievedProduct.getPrice());
Assertions.assertEquals("hehproduct", retrievedProduct.getDescription());
Assertions.assertEquals(newType.getId(), retrievedProduct.getType().getId());
}
@Test
@Order(3)
void deleteTest() {
productService.delete(2L);
Assertions.assertEquals(4L, productService.getAll(0L).size());
productService.delete(product1.getId());
Assertions.assertEquals(1, productService.getAll(0).size());
ProductEntity remainingProduct = productService.get(product2.getId());
Assertions.assertEquals(product2.getId(), remainingProduct.getId());
}
}
}

View File

@ -1,10 +1,12 @@
package com.example.demo;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@ -12,50 +14,61 @@ import com.example.demo.core.error.NotFoundException;
import com.example.demo.types.model.TypeEntity;
import com.example.demo.types.service.TypeService;
import jakarta.transaction.Transactional;
@SpringBootTest
@TestMethodOrder(OrderAnnotation.class)
class TypeServiceTests {
@Autowired
private TypeService typeService;
private TypeEntity type1;
private TypeEntity type2;
@BeforeEach
void createData() {
removeData();
type2 = typeService.create(new TypeEntity("Программа"));
type1 = typeService.create(new TypeEntity("Игра"));
}
@AfterEach
void removeData() {
typeService.getAll().forEach(item -> typeService.delete(item.getId()));
}
@Test
@Transactional
void getTest() {
Assertions.assertThrows(NotFoundException.class, () -> typeService.get(0L));
}
@Test
@Order(1)
@Transactional
void createTest() {
typeService.create(new TypeEntity("Пицца"));
typeService.create(new TypeEntity("Напиток"));
final TypeEntity last = typeService.create(new TypeEntity("Закуска"));
final TypeEntity last = typeService.create(new TypeEntity("Игра2"));
Assertions.assertEquals(3, typeService.getAll().size());
Assertions.assertEquals(last, typeService.get(3L));
Assertions.assertEquals(last, typeService.get(last.getId()));
}
@Test
@Order(2)
@Transactional
void updateTest() {
final String test = "TEST";
final TypeEntity entity = typeService.get(3L);
final String oldName = entity.getName();
final TypeEntity newEntity = typeService.update(3L, new TypeEntity(test));
Assertions.assertEquals(3, typeService.getAll().size());
Assertions.assertEquals(newEntity, typeService.get(3L));
final TypeEntity newEntity = typeService.update(type1.getId(), new TypeEntity(test));
Assertions.assertEquals(2, typeService.getAll().size());
Assertions.assertEquals(test, newEntity.getName());
Assertions.assertNotEquals(oldName, newEntity.getName());
}
@Test
@Order(3)
@Transactional
void deleteTest() {
typeService.delete(3L);
Assertions.assertEquals(2, typeService.getAll().size());
final TypeEntity last = typeService.get(2L);
Assertions.assertEquals(2L, last.getId());
final TypeEntity newEntity = typeService.create(new TypeEntity("Закуска"));
Assertions.assertEquals(3, typeService.getAll().size());
Assertions.assertEquals(4L, newEntity.getId());
typeService.delete(type1.getId());
Assertions.assertEquals(1, typeService.getAll().size());
final TypeEntity last = typeService.get(type2.getId());
Assertions.assertEquals(type2.getId(), last.getId());
}
}

View File

@ -3,10 +3,10 @@ package com.example.demo;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@ -20,21 +20,20 @@ class UserServiceTests {
@Autowired
private UserService userService;
private UserEntity user1;
private UserEntity user2;
@BeforeEach
void createData() {
UserEntity user1 = new UserEntity("John", "Doe", "gt434ge@fkgjfdj.com", "password");
UserEntity user2 = new UserEntity("Alex", "Kryukov", "fhegehr@ghsjg.com", "password");
UserEntity user3 = new UserEntity("Alex", "Kryukov", "fhegeуйцуйцhr@ghsjg.com", "password");
userService.create(user1);
userService.create(user2);
userService.create(user3);
removeData();
user1 = userService.create(new UserEntity("login1", "email@mail.com", "qwerty123"));
user2 = userService.create(new UserEntity("login2", "email@gmail.com", "qwerty1234"));
}
@AfterEach
void clearData() {
userService.delete(1L);
userService.delete(2L);
userService.delete(3L);
void removeData() {
userService.getAll().forEach(item -> userService.delete(item.getId()));
}
@Test
@ -45,34 +44,23 @@ class UserServiceTests {
@Test
@Order(1)
void createTest() {
final UserEntity last = userService
.create(new UserEntity("Alex", "selivanov", "fheg123ehr@ghsjg.com", "password"));
Assertions.assertEquals(4, userService.getAll().size());
Assertions.assertEquals(last, userService.get(4L));
Assertions.assertEquals(2, userService.getAll().size());
}
@Test
@Order(2)
void updateTest() {
final UserEntity entity = userService.get(3L);
final String oldName = entity.getFullname();
final UserEntity newEntity = userService.update(3L, new UserEntity("test", "test", "test", "test"));
Assertions.assertEquals(3, userService.getAll().size());
Assertions.assertEquals(newEntity, userService.get(3L));
Assertions.assertEquals("test", newEntity.getFullname());
Assertions.assertNotEquals(oldName, newEntity.getFullname());
final UserEntity newEntity = userService.update(user2.getId(), new UserEntity("user11", "mail11", "qwerty11"));
Assertions.assertEquals(2, userService.getAll().size());
Assertions.assertEquals(newEntity.getLogin(), "user11");
}
@Test
@Order(3)
void deleteTest() {
userService.delete(3L);
Assertions.assertEquals(2, userService.getAll().size());
final UserEntity last = userService.get(2L);
Assertions.assertEquals(2L, last.getId());
final UserEntity newEntity = userService.create(new UserEntity());
Assertions.assertEquals(3, userService.getAll().size());
Assertions.assertEquals(4L, newEntity.getId());
userService.delete(user2.getId());
Assertions.assertEquals(1, userService.getAll().size());
final UserEntity last = userService.get(user1.getId());
Assertions.assertEquals(user1.getId(), last.getId());
}
}
}