#7 -- add mvc for meetings

This commit is contained in:
Anton Romanov 2022-03-14 12:18:38 +04:00
parent 8eef40a885
commit 27ff4a3f4e
12 changed files with 303 additions and 3 deletions

Binary file not shown.

View File

@ -45,7 +45,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
log.debug("Security enabled");
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login", "/index", "/news/**", "/h2-console/*", "/h2-console").permitAll()
.antMatchers("/login", "/index", "/news/**", "/meetings/**", "/h2-console/*", "/h2-console").permitAll()
.antMatchers("/swagger-ui.html").hasAuthority(UserRoleConstants.ADMIN)
.anyRequest().authenticated()
.and()

View File

@ -59,6 +59,7 @@ public class NewsController {
}
@GetMapping("/editNews/{newsId}")
@Secured({UserRoleConstants.ADMIN})
public String editNews(@PathVariable(value = "newsId") Integer id, Model model) {
model.addAttribute("news", (id != null && id != 0) ? newsService.getById(id) : new News());
return "editNews";
@ -82,6 +83,7 @@ public class NewsController {
}
@GetMapping("deleteNews/{newsId}")
@Secured({UserRoleConstants.ADMIN})
public String delete(@PathVariable(value = "newsId") Integer id) {
newsService.delete(id);
return "redirect:/news/news";

View File

@ -0,0 +1,55 @@
package ru.ulstu.meeting;
import org.springframework.format.annotation.DateTimeFormat;
import ru.ulstu.model.BaseEntity;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.validation.constraints.NotEmpty;
import java.util.Date;
@Entity
public class Meeting extends BaseEntity {
@NotEmpty(message = "Заголовок не может быть пустым")
private String title;
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private Date date;
@Lob
@NotEmpty(message = "Текст заседания не может быть пустым")
private String text;
public Meeting() {
}
public Meeting(String title, String text, Date date) {
this.title = title;
this.date = date;
this.text = text;
}
public String getTitle() {
return title;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public void setTitle(String title) {
this.title = title;
}
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*
*/
package ru.ulstu.meeting;
import org.springframework.data.domain.Page;
import org.springframework.security.access.annotation.Secured;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import ru.ulstu.model.OffsetablePageRequest;
import ru.ulstu.model.UserRoleConstants;
import javax.validation.Valid;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Controller
@RequestMapping("meetings")
public class MeetingController {
private final static int DEFAULT_PAGE_SIZE = 10;
private final MeetingService meetingService;
public MeetingController(MeetingService meetingService) {
this.meetingService = meetingService;
}
@GetMapping("/meetings")
public String listMeetings(Model model,
@RequestParam Optional<Integer> page,
@RequestParam Optional<Integer> size) {
int currentPage = page.orElse(1);
int pageSize = size.orElse(DEFAULT_PAGE_SIZE);
Page<Meeting> meetingsPage = meetingService.getMeetings(new OffsetablePageRequest(currentPage - 1, pageSize));
model.addAttribute("meetings", meetingsPage);
int totalPages = meetingsPage.getTotalPages();
if (totalPages > 0) {
List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
.boxed()
.collect(Collectors.toList());
model.addAttribute("pageNumbers", pageNumbers);
}
return "meetings";
}
@GetMapping("/editMeeting/{meetingId}")
@Secured({UserRoleConstants.ADMIN})
public String editMeeting(@PathVariable(value = "meetingId") Integer id, Model model) {
model.addAttribute("meeting", (id != null && id != 0) ? meetingService.getById(id) : new Meeting());
return "editMeeting";
}
@GetMapping("/meetings/{meetingId}")
public String viewMeeting(@PathVariable(value = "meetingId") Integer id, Model model) {
model.addAttribute("meeting", id != null ? meetingService.getById(id) : new Meeting());
return "viewMeeting";
}
@PostMapping("saveMeeting")
@Secured({UserRoleConstants.ADMIN})
public String saveNews(@Valid @ModelAttribute Meeting meeting,
BindingResult result) {
if (result.hasErrors()) {
return "editMeeting";
}
meetingService.save(meeting);
return "redirect:/meetings/meetings";
}
@GetMapping("deleteMeeting/{meetingId}")
@Secured({UserRoleConstants.ADMIN})
public String delete(@PathVariable(value = "meetingId") Integer id) {
meetingService.delete(id);
return "redirect:/meetings/meetings";
}
}

View File

@ -0,0 +1,9 @@
package ru.ulstu.meeting;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
interface MeetingRepository extends JpaRepository<Meeting, Integer> {
Page<Meeting> findByOrderByDateDesc(Pageable pageable);
}

View File

@ -0,0 +1,47 @@
package ru.ulstu.meeting;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import ru.ulstu.service.NewsService;
import javax.validation.constraints.NotNull;
@Service
public class MeetingService {
private final MeetingRepository meetingRepository;
private final NewsService newsService;
public MeetingService(MeetingRepository meetingRepository,
NewsService newsService) {
this.meetingRepository = meetingRepository;
this.newsService = newsService;
}
public void create(Meeting meeting) {
meetingRepository.save(meeting);
newsService.create("Очередное заседание семинара", "Заседание семинара состоится " + meeting.getDate());
}
public void save(Meeting meeting) {
if (meeting.getId() != null && (meeting.getId() != 0)) {
meetingRepository.save(meeting);
} else {
create(meeting);
}
}
public Meeting getById(@NotNull Integer id) {
return meetingRepository
.findById(id)
.orElseThrow(() -> new RuntimeException("Запись о заседании не найдена"));
}
public void delete(Integer id) {
meetingRepository.deleteById(id);
}
public Page<Meeting> getMeetings(Pageable pageable) {
return meetingRepository.findByOrderByDateDesc(pageable);
}
}

View File

@ -10,5 +10,8 @@
<a href="/news/editNews/0" class="btn btn-outline-dark">
<i class="fa fa-plus-square" aria-hidden="true">Добавить новость</i>
</a>
<a href="/meetings/editMeeting/0" class="btn btn-outline-dark">
<i class="fa fa-plus-square" aria-hidden="true">Добавить заседание</i>
</a>
</div>
</html>

View File

@ -36,10 +36,10 @@
<a class="nav-link" href="/news/news">Новости</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/news">Заседания</a>
<a class="nav-link" href="/meetings/meetings">Заседания</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/news">Отчеты</a>
<a class="nav-link" href="/reports">Отчеты</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin">Администратору</a>

View File

@ -0,0 +1,33 @@
<!--
~ Copyright (C) 2021 Anton Romanov - All Rights Reserved
~ You may use, distribute and modify this code, please write to: romanov73@gmail.com.
~
-->
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"
layout:decorate="~{default}">
<div class="container" layout:fragment="content">
<h3>Редактирование заседания:</h3>
<form action="#" th:action="@{/meetings/saveMeeting}" th:object="${meeting}" method="post">
<input type="hidden" th:field="*{id}">
<input type="hidden" th:field="*{version}">
<div class="form-group">
<label for="title">Тема заседания</label>
<input type="text" class="form-control" id="title" th:field="*{title}" placeholder="Тема заседания">
<p th:if="${#fields.hasErrors('title')}" th:class="${#fields.hasErrors('title')}? error">
Не может быть пустым</p>
</div>
<div class="form-group">
<label for="text">Текст о заседании</label>
<textarea class="form-control" id="text" th:field="*{text}" placeholder="Текст о заседании"
style="height: 300px"></textarea>
<p th:if="${#fields.hasErrors('text')}" th:class="${#fields.hasErrors('text')}? error">
Не может быть пустым</p>
</div>
<button type="submit" class="btn btn-outline-dark">Сохранить</button>
<a href="javascript:history.back()" class="btn btn-outline-dark">Отмена</a>
</form>
</div>
</html>

View File

@ -0,0 +1,40 @@
<!--
~ Copyright (C) 2021 Anton Romanov - All Rights Reserved
~ You may use, distribute and modify this code, please write to: romanov73@gmail.com.
~
-->
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
layout:decorate="~{default}">
<div class="container" layout:fragment="content">
<div th:each="m : ${meetings}" class="news">
<div class="row">
<div class="col-md-10">
<a th:href="@{'/meetings/meetings/' + ${m.id}}" class="link-dark"><h5 th:text="${m.title}"/></a>
</div>
<div sec:authorize="hasRole('ROLE_ADMIN')" class="col-md-2" style="text-align: right">
<a th:href="@{'/meetings/editMeeting/' + ${m.id}}" class="link-dark">
<i class="fa fa-pencil" aria-hidden="true"></i>
</a>
<a th:href="@{'/meetings/deleteMeeting/' + ${m.id}}" class="link-dark"
onclick="return confirm('Удалить запись о заседании?')">
<i class="fa fa-trash" aria-hidden="true"></i>
</a>
</div>
</div>
<hr/>
</div>
<div th:if="${meetings.totalPages > 0}" class="pagination">
<span style="float: left; padding: 5px 5px;">Страницы:</span>
</div>
<div th:if="${meetings.totalPages > 0}" class="pagination"
th:each="pageNumber : ${pageNumbers}">
<a th:href="@{/news/news(size=${meetings.size}, page=${pageNumber})}"
th:text=${pageNumber}
th:class="${pageNumber == meetings.number+1} ? active"></a>
</div>
</div>
</html>

View File

@ -0,0 +1,23 @@
<!--
~ Copyright (C) 2021 Anton Romanov - All Rights Reserved
~ You may use, distribute and modify this code, please write to: romanov73@gmail.com.
~
-->
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"
layout:decorate="~{default}">
<div class="container" layout:fragment="content">
<div class="row">
<div class="col-md-4">
<img class="news-image" src="/img/logo.svg"/>
</div>
<div class="col-md-8">
<h5 th:text="${meeting.title}"/>
<div th:text="${meeting.text}" class="news-item"></div>
</div>
</div>
<a href="javascript:history.back()" class="btn btn-outline-dark" style="text-align: right">Назад</a>
</div>
</html>