что-то мутится

This commit is contained in:
Алексей Крюков 2024-05-20 00:55:53 +04:00
parent d47f50ba29
commit e59d4c9465
45 changed files with 60679 additions and 92 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,12 +1,23 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.3'
id 'org.springframework.boot' version '3.2.4'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
defaultTasks 'bootRun'
jar {
enabled = false
}
bootJar {
archiveFileName = String.format('%s-%s.jar', rootProject.name, version)
}
assert System.properties['java.specification.version'] == '17' || '21'
java {
sourceCompatibility = '17'
}
@ -18,12 +29,19 @@ 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 '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'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

View File

@ -0,0 +1,67 @@
html,
body {
height: 100%;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.25em;
}
h3 {
font-size: 1.1em;
}
td form {
margin: 0;
padding: 0;
margin-top: -.25em;
}
.button-fixed-width {
width: 150px;
}
.button-link {
padding: 0;
}
.invalid-feedback {
display: block;
}
.w-10 {
width: 10% !important;
}
.my-navbar {
background-color: #3c3c3c !important;
}
.my-navbar .link a:hover {
text-decoration: underline;
}
.my-navbar .logo {
width: 26px;
height: 26px;
}
.my-footer {
background-color: #2c2c2c;
height: 32px;
color: rgba(255, 255, 255, 0.5);
}
.cart-image {
width: 3.1rem;
padding: 0.25rem;
border-radius: 0.5rem;
}
.cart-item {
height: auto;
}

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

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="ru" data-bs-theme="dark" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
<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>
<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" />
<link rel="stylesheet" href="/css/style.css" />
</head>
<body class="h-100 d-flex flex-column">
<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>
Туда сюда и пицца
</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"
aria-controls="main-navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="main-navbar">
<ul class="navbar-nav me-auto link" th:with="activeLink=${#objects.nullSafe(servletPath, '')}">
<th:block sec:authorize="hasRole('ADMIN')">
<a class="nav-link" href="/admin/user"
th:classappend="${activeLink.startsWith('/admin/user') ? 'active' : ''}">
Пользователи
</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>
<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>
</ul>
<ul class="navbar-nav" th:if="${not #strings.isEmpty(userName)}">
<form th:action="@{/logout}" method="post">
<button type="submit" class="navbar-brand nav-link" onclick="return confirm('Вы уверены?')">
Выход ([[${userName}]])
</button>
</form>
<a class="navbar-brand" href="/cart">
<i class="bi bi-cart2 d-inline-block align-top me-1 logo"></i>
[[${#numbers.formatDecimal(totalCart, 1, 2)}]] &#8381;
</a>
</ul>
</div>
</th:block>
</div>
</nav>
<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())}]]
</footer>
</body>
</html>

View File

@ -0,0 +1,44 @@
<!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">
<form action="#" th:action="@{/login}" method="post">
<div th:if="${param.error}" class="alert alert-danger">
Неверный логин или пароль
</div>
<div th:if="${param.logout}" class="alert alert-success">
Выход успешно произведен
</div>
<div th:if="${param.signup}" class="alert alert-success">
Пользователь успешно создан
</div>
<div class="mb-3">
<label for="username" class="form-label">Имя пользователя</label>
<input type="text" id="username" name="username" class="form-control" required minlength="3"
maxlength="20">
</div>
<div class="mb-3">
<label for="password" class="form-label">Пароль</label>
<input type="password" id="password" name="password" class="form-control" required minlength="3"
maxlength="20">
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="remember-me" name="remember-me" checked>
<label class="form-check-label" for="remember-me">Запомнить меня</label>
</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="/signup">Регистрация</a>
</div>
</form>
</main>
</body>
</html>
</html>

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">
<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="password" class="form-label">Пароль</label>
<input type="password" th:field="*{password}" id="password" class="form-control">
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="passwordConfirm" class="form-label">Пароль (подтверждение)</label>
<input type="password" th:field="*{passwordConfirm}" id="passwordConfirm" class="form-control">
<div th:if="${#fields.hasErrors('passwordConfirm')}" th:errors="*{passwordConfirm}"
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="/">Отмена</a>
</div>
</form>
</main>
</body>
</html>
</html>

View File

@ -0,0 +1,28 @@
<!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">
<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>
<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-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/type">Отмена</a>
</div>
</form>
</main>
</body>
</html>

View File

@ -0,0 +1,50 @@
<!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">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2>Типы заказов</h2>
<div>
<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-10"></th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="type : ${items}">
<th scope="row" th:text="${type.id}"></th>
<td th:text="${type.name}"></td>
<td>
<form th:action="@{/admin/type/edit/{id}(id=${type.id})}" method="get">
<button type="submit" class="btn btn-link button-link">Редактировать</button>
</form>
</td>
<td>
<form th:action="@{/admin/type/delete/{id}(id=${type.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>

Binary file not shown.

59827
data.trace.db Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,5 @@
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
@ -10,9 +8,6 @@ import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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.types.model.TypeEntity;
@ -27,14 +22,11 @@ public class DemoApplication implements CommandLineRunner {
private final TypeService typeService;
private final ProductService productService;
private final UserService userService;
private final OrderService orderService;
public DemoApplication(TypeService typeService, ProductService productService, UserService userService,
OrderService orderService) {
public DemoApplication(TypeService typeService, ProductService productService, UserService userService) {
this.typeService = typeService;
this.productService = productService;
this.userService = userService;
this.orderService = orderService;
}
public static void main(String[] args) {
@ -62,13 +54,6 @@ public class DemoApplication implements CommandLineRunner {
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"));
log.info("Create default orders values");
@SuppressWarnings({ "rawtypes", "unchecked" })
List<OrderLineEntity> lines = new ArrayList();
lines.add(new OrderLineEntity(null, 3));
final var user1 = userService.create(new UserEntity("Misha", "Kryukov", "akryu132@mail.ru", "password"));
orderService.create(new OrderEntity(user1));
}
}
}

View File

@ -1,14 +1,13 @@
package com.example.demo.order_lines.model;
import java.util.Objects;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.Objects;
import com.example.demo.core.model.BaseEntity;
import com.example.demo.products.model.ProductEntity;
@ -17,19 +16,22 @@ import com.example.demo.products.model.ProductEntity;
public class OrderLineEntity extends BaseEntity {
@Column(nullable = false)
private Integer count;
@ManyToOne
@JoinColumn(name = "productId", nullable = false)
private ProductEntity product;
@Column(nullable = false)
private Double totalPrice;
public OrderLineEntity() {
// Конструктор
}
public OrderLineEntity(ProductEntity product, Integer count) {
this.product = product;
this.count = count;
calculateTotalPrice(); // Рассчитываем сумму при создании
}
public ProductEntity getProduct() {
@ -38,6 +40,7 @@ public class OrderLineEntity extends BaseEntity {
public void setProduct(ProductEntity product) {
this.product = product;
calculateTotalPrice(); // Пересчитываем сумму при установке продукта
}
public Integer getCount() {
@ -46,6 +49,7 @@ public class OrderLineEntity extends BaseEntity {
public void setCount(Integer count) {
this.count = count;
calculateTotalPrice(); // Пересчитываем сумму при установке количества
}
public Double getTotalPrice() {
@ -56,6 +60,14 @@ public class OrderLineEntity extends BaseEntity {
this.totalPrice = totalPrice;
}
private void calculateTotalPrice() {
if (product != null && product.getPrice() != null && count != null) {
totalPrice = product.getPrice() * count;
} else {
totalPrice = 0.0;
}
}
@Override
public int hashCode() {
return Objects.hash(id, product, count, totalPrice);

View File

@ -2,6 +2,8 @@ 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;
@ -11,6 +13,7 @@ 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;
@ -48,19 +51,26 @@ public class OrderController {
}
public OrderEntity toEntity(OrderDto dto) {
final OrderEntity entity = modelMapper.map(dto, OrderEntity.class);
if (dto.getUserId() != null) {
entity.setUser(userService.get(dto.getUserId()));
}
entity.getLines().clear();
for (OrderLineDto lineDto : dto.getLines()) {
OrderLineEntity orderLineEntity = modelMapper.map(lineDto, OrderLineEntity.class);
orderLineEntity.setId(null);
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);
ProductEntity product = productService.get(lineDto.getProductId());
orderLineEntity.setProduct(product); // Устанавливаем продукт для строки заказа
entity.addOrderLine(orderLineEntity);
}
return entity;
}

View File

@ -6,6 +6,7 @@ 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;
@ -21,8 +22,9 @@ public class OrderEntity extends BaseEntity {
@ManyToOne
@JoinColumn(name = "userId", nullable = false)
private UserEntity user;
@ManyToMany(cascade = CascadeType.ALL)
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private final List<OrderLineEntity> lines = new ArrayList<>();
private Double totalPrice; // Поле для общей стоимости заказа
public OrderEntity() {
super();
@ -44,17 +46,30 @@ public class OrderEntity extends BaseEntity {
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();
}
}
@Override
public int hashCode() {
return Objects.hash(id, user, lines);
return Objects.hash(id, user, lines, totalPrice);
}
@SuppressWarnings("unlikely-arg-user")
@ -67,7 +82,7 @@ public class OrderEntity extends BaseEntity {
final OrderEntity other = (OrderEntity) obj;
return Objects.equals(other.getId(), id)
&& Objects.equals(other.getUser(), user)
&& Objects.equals(other.getLines(), lines);
&& Objects.equals(other.getLines(), lines)
&& Objects.equals(other.getTotalPrice(), totalPrice);
}
}

View File

@ -12,7 +12,7 @@ public interface OrderRepository extends CrudRepository<OrderEntity, Long> {
List<OrderEntity> findByUserIdAndLinesProductId(long userId, List<Long> lines);
List<OrderEntity> findByLinesProductId(List<Long> lines);
List<OrderEntity> findById(long id);
List<OrderEntity> findByUserId(long userId);

View File

@ -1,6 +1,7 @@
package com.example.demo.orders.service;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -24,7 +25,7 @@ public class OrderService {
return repository.findByUserIdAndLinesProductId(userId, lines);
}
if (userId == 0L && !lines.isEmpty()) {
return repository.findByLinesProductId(lines);
return repository.findAll();
}
if (userId != 0L && lines.isEmpty()) {
return repository.findByUserId(userId);
@ -34,8 +35,12 @@ public class OrderService {
@Transactional(readOnly = true)
public OrderEntity get(Long id) {
return repository.findById(id)
.orElseThrow(() -> new NotFoundException(OrderEntity.class, 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 create(OrderEntity entity) {

View File

@ -1,7 +1,6 @@
package com.example.demo.products.service;
import java.util.List;
import org.springframework.stereotype.Service;
import java.util.stream.StreamSupport;
@ -33,6 +32,11 @@ public class ProductService {
return repository.findById(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();
}
@Transactional
public ProductEntity create(ProductEntity entity) {
if (entity == null) {

View File

@ -0,0 +1,67 @@
html,
body {
height: 100%;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.25em;
}
h3 {
font-size: 1.1em;
}
td form {
margin: 0;
padding: 0;
margin-top: -.25em;
}
.button-fixed-width {
width: 150px;
}
.button-link {
padding: 0;
}
.invalid-feedback {
display: block;
}
.w-10 {
width: 10% !important;
}
.my-navbar {
background-color: #3c3c3c !important;
}
.my-navbar .link a:hover {
text-decoration: underline;
}
.my-navbar .logo {
width: 26px;
height: 26px;
}
.my-footer {
background-color: #2c2c2c;
height: 32px;
color: rgba(255, 255, 255, 0.5);
}
.cart-image {
width: 3.1rem;
padding: 0.25rem;
border-radius: 0.5rem;
}
.cart-item {
height: auto;
}

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

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="ru" data-bs-theme="dark" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
<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>
<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" />
<link rel="stylesheet" href="/css/style.css" />
</head>
<body class="h-100 d-flex flex-column">
<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>
Туда сюда и пицца
</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"
aria-controls="main-navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="main-navbar">
<ul class="navbar-nav me-auto link" th:with="activeLink=${#objects.nullSafe(servletPath, '')}">
<th:block sec:authorize="hasRole('ADMIN')">
<a class="nav-link" href="/admin/user"
th:classappend="${activeLink.startsWith('/admin/user') ? 'active' : ''}">
Пользователи
</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>
<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>
</ul>
<ul class="navbar-nav" th:if="${not #strings.isEmpty(userName)}">
<form th:action="@{/logout}" method="post">
<button type="submit" class="navbar-brand nav-link" onclick="return confirm('Вы уверены?')">
Выход ([[${userName}]])
</button>
</form>
<a class="navbar-brand" href="/cart">
<i class="bi bi-cart2 d-inline-block align-top me-1 logo"></i>
[[${#numbers.formatDecimal(totalCart, 1, 2)}]] &#8381;
</a>
</ul>
</div>
</th:block>
</div>
</nav>
<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())}]]
</footer>
</body>
</html>

View File

@ -0,0 +1,44 @@
<!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">
<form action="#" th:action="@{/login}" method="post">
<div th:if="${param.error}" class="alert alert-danger">
Неверный логин или пароль
</div>
<div th:if="${param.logout}" class="alert alert-success">
Выход успешно произведен
</div>
<div th:if="${param.signup}" class="alert alert-success">
Пользователь успешно создан
</div>
<div class="mb-3">
<label for="username" class="form-label">Имя пользователя</label>
<input type="text" id="username" name="username" class="form-control" required minlength="3"
maxlength="20">
</div>
<div class="mb-3">
<label for="password" class="form-label">Пароль</label>
<input type="password" id="password" name="password" class="form-control" required minlength="3"
maxlength="20">
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="remember-me" name="remember-me" checked>
<label class="form-check-label" for="remember-me">Запомнить меня</label>
</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="/signup">Регистрация</a>
</div>
</form>
</main>
</body>
</html>
</html>

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">
<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="password" class="form-label">Пароль</label>
<input type="password" th:field="*{password}" id="password" class="form-control">
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="passwordConfirm" class="form-label">Пароль (подтверждение)</label>
<input type="password" th:field="*{passwordConfirm}" id="passwordConfirm" class="form-control">
<div th:if="${#fields.hasErrors('passwordConfirm')}" th:errors="*{passwordConfirm}"
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="/">Отмена</a>
</div>
</form>
</main>
</body>
</html>
</html>

View File

@ -0,0 +1,28 @@
<!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">
<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>
<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-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/type">Отмена</a>
</div>
</form>
</main>
</body>
</html>

View File

@ -0,0 +1,50 @@
<!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">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2>Типы заказов</h2>
<div>
<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-10"></th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="type : ${items}">
<th scope="row" th:text="${type.id}"></th>
<td th:text="${type.name}"></td>
<td>
<form th:action="@{/admin/type/edit/{id}(id=${type.id})}" method="get">
<button type="submit" class="btn btn-link button-link">Редактировать</button>
</form>
</td>
<td>
<form th:action="@{/admin/type/delete/{id}(id=${type.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

@ -1,72 +1,98 @@
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
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.order_lines.model.OrderLineEntity;
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 org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderServiceTests {
@Autowired
private OrderService orderService;
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Autowired
private TypeService typeService;
private OrderEntity order;
@BeforeEach
void createData() {
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);
}
@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());
}
@Test
void getTest() {
// Тестируем, что найденный заказ совпадает с ожидаемым
OrderEntity foundOrder = orderService.get(order.getId());
Assertions.assertNotNull(foundOrder);
Assertions.assertEquals(order, foundOrder);
// Проверяем, что вызов с несуществующим id бросает исключение
Assertions.assertThrows(NotFoundException.class, () -> orderService.get(0L));
}
@Test
@Order(1)
void createTest() {
orderService.create(new OrderEntity(null));
@SuppressWarnings({ "rawtypes", "unchecked" })
List<OrderLineEntity> lines = new ArrayList();
lines.add(new OrderLineEntity(null, 4));
lines.add(new OrderLineEntity(null, 5));
lines.add(new OrderLineEntity(null, 6));
lines.add(new OrderLineEntity(null, 7));
// Создаем тестовую сущность OrderEntity
OrderEntity testOrder = new OrderEntity(null);
// Вызываем метод create() и сохраняем созданную сущность
OrderEntity createdOrder = orderService.create(testOrder);
// Проверяем, что метод create() вернул не null
Assertions.assertNotNull(createdOrder);
// Проверяем, что созданная сущность имеет назначенный ID
Assertions.assertNotNull(createdOrder.getId());
// Проверяем, что созданный заказ совпадает с ожидаемым
Assertions.assertNotNull(order.getId());
OrderEntity foundOrder = orderService.get(order.getId());
Assertions.assertEquals(order, foundOrder);
}
@Test
@Order(2)
void updateTest() {
// Получаем сущность OrderEntity для обновления
OrderEntity existingOrder = orderService.get(1L);
// Вызываем метод update() и сохраняем обновленную сущность
OrderEntity updatedOrder = orderService.update(1L, existingOrder);
// Проверяем, что метод update() вернул не null
// Обновляем заказ и проверяем, что он изменился
OrderEntity updatedOrder = orderService.update(order.getId(), order);
Assertions.assertNotNull(updatedOrder);
Assertions.assertEquals(order, updatedOrder);
}
@Test
@Order(3)
void deleteTest() {
// Удаляем сущность OrderEntity по ее ID
OrderEntity deletedOrder = orderService.delete(1L);
// Проверяем, что метод delete() вернул не null
Assertions.assertNotNull(deletedOrder);
// Проверяем, что удаленная сущность имеет тот же ID, что и удаленная
Assertions.assertEquals(1L, deletedOrder.getId());
// Проверяем, что сущность больше не существует в репозитории
Assertions.assertThrows(NotFoundException.class, () -> orderService.get(1L));
// Удаляем заказ и проверяем, что его нет в базе данных
orderService.delete(order.getId());
Assertions.assertThrows(NotFoundException.class, () -> orderService.get(order.getId()));
}
}

View File

@ -1,6 +1,8 @@
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;
@ -18,6 +20,23 @@ class UserServiceTests {
@Autowired
private UserService userService;
@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);
}
@AfterEach
void clearData() {
userService.delete(1L);
userService.delete(2L);
userService.delete(3L);
}
@Test
void getTest() {
Assertions.assertThrows(NotFoundException.class, () -> userService.get(0L));
@ -26,12 +45,10 @@ class UserServiceTests {
@Test
@Order(1)
void createTest() {
userService.create(new UserEntity("John", "Doe", "gge@fkgjfdj", "password"));
userService.create(new UserEntity("Alex", "Kryukov", "fhegehr@ghsjg.com", "password"));
final UserEntity last = userService.create(new UserEntity("Alex", "selivanov", "fhegehr@ghsjg.com",
"password"));
Assertions.assertEquals(3, userService.getAll().size());
Assertions.assertEquals(last, userService.get(3L));
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));
}
@Test