LabWork04-05 / WIP 2.0

This commit is contained in:
parent 0297ba61e5
commit 6b5d17f0cb
18 changed files with 317 additions and 48 deletions

Binary file not shown.

View File

@ -66,6 +66,9 @@ public class DemoApplication implements CommandLineRunner {
final var user1 = userService.create(new UserEntity("User1", "password", "mail1@gmail.com"));
final var user2 = userService.create(new UserEntity("User2", "password", "mail2@gmail.com"));
final var user3 = userService.create(new UserEntity("User3", "password", "mail3@gmail.com"));
final var admin = new UserEntity("admin", "admin", "admin@gmail.com");
admin.setRole(UserRole.ADMIN);
userService.create(admin);
log.info("Create default order values");
final var orders = List.of(

View File

@ -11,10 +11,10 @@ public class PageAttributesMapper {
}
// Метод преобразования
public static <E, D> Map<String, Object> toAttributes(Page<E> page, Function<E, D> mapper) {
public static <E, D> Map<String, Object> toAttributes(String prefix, Page<E> page, Function<E, D> mapper) {
return Map.of(
"items", page.getContent().stream().map(mapper::apply).toList(),
"currentPage", page.getNumber(),
"totalPages", page.getTotalPages());
prefix + "Items", page.getContent().stream().map(mapper::apply).toList(),
prefix + "CurrentPage", page.getNumber(),
prefix + "TotalPages", page.getTotalPages());
}
}

View File

@ -0,0 +1,87 @@
package com.example.demo.messages.api;
import java.util.Map;
import org.modelmapper.ModelMapper;
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.RequestMapping;
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.messages.model.MessageEntity;
import com.example.demo.messages.service.MessageService;
// Контроллер для сущности "Сообщение"
@Controller
@RequestMapping(MessageController.URL)
public class MessageController {
// URL для доступа к методам контроллера
public static final String URL = Constants.ADMIN_PREFIX + "/message";
// Представление для отображения списка сообщений
private static final String MESSAGE_VIEW = "messages";
// Атрибут модели для пагинации
private static final String PAGE_ATTRIBUTE = "page";
// Бизнес-логика для сущности "Сообщение"
private final MessageService messageService;
// Библиотека для преобразования сущности
private final ModelMapper modelMapper;
// Конструктор
public MessageController(MessageService messageService, ModelMapper modelMapper) {
this.messageService = messageService;
this.modelMapper = modelMapper;
}
// Преобразовать из сущности в DTO
private MessageDto toDto(MessageEntity entity) {
return modelMapper.map(entity, MessageDto.class);
}
// Получить все элементы
@GetMapping
public String getAll(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
final Map<String, Object> attributes = PageAttributesMapper.toAttributes(
"message",
messageService.getAll(0L, page, Constants.DEFAULT_PAGE_SIZE), this::toDto);
model.addAllAttributes(attributes);
model.addAttribute(PAGE_ATTRIBUTE, page);
return MESSAGE_VIEW;
}
// Удалить элемент
@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);
final var messageEntity = messageService.get(id);
messageService.delete(messageEntity.getUser().getId(), id);
return Constants.REDIRECT_VIEW + URL;
}
// Опубликовать сообщение
@PostMapping("/publish/{id}")
public String publish(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
final var messageEntity = messageService.get(id);
messageEntity.setIsPublished(true);
messageService.update(messageEntity.getUser().getId(), id, messageEntity);
return Constants.REDIRECT_VIEW + URL;
}
}

View File

@ -4,9 +4,7 @@ import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
// DTO для сущности "Сообщение"
public class MessageDto {
@ -14,8 +12,6 @@ public class MessageDto {
private Long id;
// Электронная почта отправителя
@NotNull
@Min(1)
private String userEmail;
// Текст сообщения

View File

@ -3,6 +3,7 @@ package com.example.demo.messages.service;
import java.util.List;
import java.util.stream.StreamSupport;
import org.hibernate.Hibernate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
@ -62,6 +63,15 @@ public class MessageService {
.orElseThrow(() -> new NotFoundException(MessageEntity.class, id));
}
// Получить элемент по идентификатору
@Transactional(readOnly = true)
public MessageEntity get(Long id) {
MessageEntity message = repository.findById(id)
.orElseThrow(() -> new NotFoundException(MessageEntity.class, id));
Hibernate.initialize(message.getUser().getMessages());
return message;
}
// Создать элемент
@Transactional
public MessageEntity create(Long userId, MessageEntity entity) {
@ -80,6 +90,7 @@ public class MessageService {
final MessageEntity existsEntity = get(userId, id);
existsEntity.setUser(entity.getUser());
existsEntity.setText(entity.getText());
existsEntity.setIsPublished(entity.getIsPublished());
return repository.save(existsEntity);
}

View File

@ -3,6 +3,7 @@ package com.example.demo.users.api;
import java.util.Map;
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;
@ -11,7 +12,6 @@ import org.springframework.web.bind.annotation.PathVariable;
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.annotation.RestController;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.demo.core.api.PageAttributesMapper;
@ -22,8 +22,8 @@ import com.example.demo.users.service.UserService;
import jakarta.validation.Valid;
// Контроллер для сущности "Пользователь"
@RestController
@RequestMapping(UserController.URL + "/user")
@Controller
@RequestMapping(UserController.URL)
public class UserController {
// URL для доступа к методам контроллера
public static final String URL = Constants.ADMIN_PREFIX + "/user";
@ -68,6 +68,7 @@ public class UserController {
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
final Map<String, Object> attributes = PageAttributesMapper.toAttributes(
"user",
userService.getAll(page, Constants.DEFAULT_PAGE_SIZE), this::toDto);
model.addAllAttributes(attributes);
model.addAttribute(PAGE_ATTRIBUTE, page);

View File

@ -0,0 +1,85 @@
package com.example.demo.users.api;
import java.time.LocalDateTime;
import org.modelmapper.ModelMapper;
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.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.demo.core.configuration.Constants;
import com.example.demo.core.security.UserPrincipal;
import com.example.demo.messages.api.MessageDto;
import com.example.demo.messages.model.MessageEntity;
import com.example.demo.messages.service.MessageService;
import jakarta.validation.Valid;
// Контроллер для работы с корзиной покупок пользователя
@Controller
@RequestMapping(UserMessageController.URL)
public class UserMessageController {
// URL для доступа к методам контроллера
public static final String URL = "/message";
// Представление для написания сообщения
private static final String MESSAGE_EDIT_VIEW = "message-edit";
// Атрибут модели для обработки данных
private static final String MESSAGE_ATTRIBUTE = "message";
// Атрибут модели для пагинации
private static final String PAGE_ATTRIBUTE = "page";
// Бизнес-логика для сущости "Сообщение"
private final MessageService messageService;
// Библиотека для преобразования сущности
private final ModelMapper modelMapper;
public UserMessageController(MessageService messageService, ModelMapper modelMapper) {
this.messageService = messageService;
this.modelMapper = modelMapper;
}
private MessageEntity toEntity(MessageDto dto) {
return modelMapper.map(dto, MessageEntity.class);
}
// Отправить сообщение
@GetMapping("/send/")
public String sendMessage(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
model.addAttribute(MESSAGE_ATTRIBUTE, new MessageDto());
model.addAttribute(PAGE_ATTRIBUTE, page);
return MESSAGE_EDIT_VIEW;
}
// Отправить сообщение
@PostMapping("/send/")
public String sendMessage(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@ModelAttribute(name = MESSAGE_ATTRIBUTE) @Valid MessageDto message,
BindingResult bindingResult,
Model model,
@AuthenticationPrincipal UserPrincipal principal,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute(PAGE_ATTRIBUTE, page);
return MESSAGE_EDIT_VIEW;
}
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
message.setDate(LocalDateTime.now());
messageService.create(principal.getId(), toEntity(message));
return Constants.REDIRECT_VIEW + "/";
}
}

View File

@ -87,6 +87,7 @@ public class UserProfileController {
model.addAttribute(TYPEID_ATTRIBUTE, typeId);
model.addAllAttributes(PageAttributesMapper.toAttributes(
"order",
orderService.getAll(userId, typeId, page, Constants.DEFAULT_PAGE_SIZE),
this::toDto));
@ -101,6 +102,7 @@ public class UserProfileController {
.toList());
model.addAllAttributes(PageAttributesMapper.toAttributes(
"message",
messageService.getAll(userId, page, Constants.DEFAULT_PAGE_SIZE),
this::toMessageDto));
return PROFILE_VIEW;

View File

@ -107,7 +107,6 @@ public class UserService implements UserDetailsService {
public UserEntity update(Long id, UserEntity entity) {
final UserEntity existsEntity = get(id);
existsEntity.setUsername(entity.getUsername());
existsEntity.setPassword(entity.getPassword());
existsEntity.setEmail(entity.getEmail());
return repository.save(existsEntity);
}

View File

@ -40,7 +40,7 @@
</a>
<a class="nav-link" href="/admin/message"
th:classappend="${activeLink.startsWith('/admin/message') ? 'active' : ''}">
Список сообщений
Сообщения
</a>
<a class="nav-link" href="/h2-console/" target="_blank">Консоль H2</a>
</th:block>

View File

@ -0,0 +1,24 @@
<!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="@{/message/send/}" th:object="${message}" method="post">
<div class="mb-3">
<label for="text" class="form-label">Текст</label>
<input type="text" th:field="*{text}" id="text" class="form-control">
<div th:if="${#fields.hasErrors('text')}" th:errors="*{text}" 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>

View File

@ -1,17 +1,17 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Сообщения</title>
</head>
<body>
<th:block th:fragment="messages (items, totalPages, currentPage)">
<th:block th:switch="${items.size()}">
<main layout:fragment="content">
<th:block th:switch="${messageItems.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<form th:action="@{/}" method="get" class="row mt-2">
<div class="col-sm-10">
<input type="hidden" th:name="page" th:value="${page}">
</div>
</form>
<table class="table mt-2">
<h2>Сообщения</h2>
<table class="table">
<caption></caption>
<thead>
<tr>
@ -19,28 +19,44 @@
<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>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="message : ${items}">
<tr th:each="message : ${messageItems}">
<th scope="row" th:text="${message.id}"></th>
<td th:text="${message.userEmail}"></td>
<td th:text="${message.text}"></td>
<td th:text="${message.date}"></td>
<td style="text-align: center;">
<input type="checkbox" th:checked="${message.isPublished}" style="display: inline-block;" disabled/>
</td>
<td>
<form th:action="@{/admin/message/publish/{id}(id=${message.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>
<td>
<form th:action="@{/admin/message/delete/{id}(id=${message.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="mt-2 d-flex justify-content-center">
<a class="btn btn-primary" href="/message/edit">Написать сообщение</a>
</div>
url=${'admin/message'},
totalPages=${messageTotalPages},
currentPage=${messageCurrentPage}) }" />
</th:block>
</th:block>
</main>
</body>
</html>

View File

@ -22,9 +22,9 @@
<div class="tab-content mt-2">
<div class="tab-pane container active" id="orders">
<th:block th:replace="~{ orders :: orders (
items=${items},
totalPages=${totalPages},
currentPage=${currentPage}) }" />
items=${orderItems},
totalPages=${orderTotalPages},
currentPage=${orderCurrentPage}) }" />
</div>
<div class="tab-pane container fade" id="stats">
<ul class="list-group mb-2">
@ -36,10 +36,10 @@
</ul>
</div>
<div class="tab-pane container fade" id="messages">
<th:block th:replace="~{ messages :: messages (
items=${items},
totalPages=${totalPages},
currentPage=${currentPage}) }" />
<th:block th:replace="~{ user-messages :: messages (
items=${messageItems},
totalPages=${messageTotalPages},
currentPage=${messageCurrentPage}) }" />
</div>
</div>
</main>

View File

@ -2,7 +2,7 @@
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редакторовать тип заказа</title>
<title>Редактировать тип заказа</title>
</head>
<body>

View File

@ -2,7 +2,7 @@
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редакторовать пользователя</title>
<title>Редактировать пользователя</title>
</head>
<body>
@ -14,12 +14,12 @@
<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>
<label for="username" class="form-label">Имя пользователя</label>
<input type="text" th:field="*{username}" id="username" class="form-control">
<div th:if="${#fields.hasErrors('username')}" th:errors="*{username}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="login" class="form-label">Электронная почта</label>
<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>

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="messages (items, totalPages, currentPage)">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<table class="table mt-2">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</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="message : ${items}">
<th scope="row" th:text="${message.id}"></th>
<td th:text="${message.userEmail}"></td>
<td th:text="${message.text}"></td>
<td th:text="${message.date}"></td>
<td style="text-align: center;">
<input type="checkbox" th:checked="${message.isPublished}" style="display: inline-block;" disabled/>
</td>
</tr>
</tbody>
</table>
</th:block>
<th:block th:replace="~{ pagination :: pagination (
url='',
totalPages=${totalPages},
currentPage=${currentPage}) }" />
<div class="mt-2 d-flex justify-content-center">
<a class="btn btn-primary" href="/message/send/">Написать сообщение</a>
</div>
</th:block>
</th:block>
</body>
</html>

View File

@ -7,7 +7,7 @@
<body>
<main layout:fragment="content">
<th:block th:switch="${items.size()}">
<th:block th:switch="${userItems.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2>Пользователи</h2>
@ -26,7 +26,7 @@
</tr>
</thead>
<tbody>
<tr th:each="user : ${items}">
<tr th:each="user : ${userItems}">
<th scope="row" th:text="${user.id}"></th>
<td th:text="${user.username}"></td>
<td th:text="${user.email}"></td>
@ -49,8 +49,8 @@
</th:block>
<th:block th:replace="~{ pagination :: pagination (
url=${'admin/user'},
totalPages=${totalPages},
currentPage=${currentPage}) }" />
totalPages=${userTotalPages},
currentPage=${userCurrentPage}) }" />
</th:block>
</main>
</body>