1-db #2

Merged
romanov73 merged 6 commits from 1-db into master 2022-03-09 17:28:19 +04:00
17 changed files with 372 additions and 37 deletions

View File

@ -35,9 +35,16 @@ dependencies {
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-jetty' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-jetty'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation'
implementation group: 'org.springframework.boot', name:'spring-boot-starter-data-jpa'
//implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security'
implementation group: 'org.slf4j', name: 'slf4j-api', version: versionSLF4J implementation group: 'org.slf4j', name: 'slf4j-api', version: versionSLF4J
implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect' implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect'
implementation group: 'org.javassist', name: 'javassist', version: '3.25.0-GA' //implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity5'
implementation group: 'com.h2database', name:'h2'
implementation group: 'javax.xml.bind', name:'jaxb-api'
implementation group: 'org.javassist', name:'javassist'
implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: versionJetty implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: versionJetty
implementation group: 'org.webjars', name: 'jquery', version: '3.6.0' implementation group: 'org.webjars', name: 'jquery', version: '3.6.0'

BIN
data/db.mv.db Normal file

Binary file not shown.

View File

@ -20,7 +20,9 @@ import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
public class MvcConfiguration implements WebMvcConfigurer { public class MvcConfiguration implements WebMvcConfigurer {
@Override @Override
public void addViewControllers(ViewControllerRegistry registry) { public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/{articlename:\\w+}"); registry.addViewController("/index");
registry.addViewController("/admin");
registry.addViewController("/editNews");
} }
@Override @Override

View File

@ -9,20 +9,20 @@ package ru.ulstu.controller;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import ru.ulstu.model.News; import ru.ulstu.service.NewsService;
import java.util.GregorianCalendar;
import java.util.List;
@Controller @Controller
public class IndexController { public class IndexController {
private final NewsService newsService;
public IndexController(NewsService newsService) {
this.newsService = newsService;
}
@GetMapping("/") @GetMapping("/")
public String index(Model model) { public String index(Model model) {
model.addAttribute("news", List.of(new News("Открытие семинара", new GregorianCalendar(2022, 4, 1).getTime(), model.addAttribute("news", newsService.getAll());
"На кафере \"Информационные системы\" Ульяновского государственного технического университета состоится открытие постоянно действующего семинара \"Анализ данных и процессов\". Семинар планируется проводить ежемесячно."),
new News("Открытие семинара", new GregorianCalendar(2022, 4, 1).getTime(),
"На кафере \"Информационные системы\" Ульяновского государственного технического университета состоится открытие постоянно действующего семинара \"Анализ данных и процессов\". Семинар планируется проводить ежемесячно.")));
return "index"; return "index";
} }
} }

View File

@ -0,0 +1,56 @@
/*
* 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.controller;
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 ru.ulstu.model.News;
import ru.ulstu.service.NewsService;
import javax.validation.Valid;
@Controller
public class NewsController {
private final NewsService newsService;
public NewsController(NewsService newsService) {
this.newsService = newsService;
}
@GetMapping("/editNews/{newsId}")
public String editNews(@PathVariable(value = "newsId") Integer id, Model model) {
model.addAttribute("news", (id != null && id != 0) ? newsService.getById(id) : new News());
return "editNews";
}
@GetMapping("/news/{newsId}")
public String viewNews(@PathVariable(value = "newsId") Integer id, Model model) {
model.addAttribute("news", id != null ? newsService.getById(id) : new News());
return "news";
}
@PostMapping("saveNews")
public String saveNews(@Valid @ModelAttribute News news, BindingResult result) {
if (result.hasErrors()) {
return "editNews";
}
newsService.save(news);
return "redirect:/";
}
@GetMapping("deleteNews/{newsId}")
public String delete(@PathVariable(value = "newsId") Integer id) {
newsService.delete(id);
return "redirect:/";
}
}

View File

@ -0,0 +1,83 @@
package ru.ulstu.model;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@MappedSuperclass
public abstract class BaseEntity implements Serializable, Comparable<BaseEntity> {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private Integer id;
@Version
private Integer version;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!getClass().isAssignableFrom(obj.getClass())) {
return false;
}
BaseEntity other = (BaseEntity) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (id == null ? 0 : id.hashCode());
return result;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"id=" + id +
", version=" + version +
'}';
}
@Override
public int compareTo(@NotNull BaseEntity o) {
return id != null ? id.compareTo(o.getId()) : -1;
}
public void reset() {
this.id = null;
this.version = null;
}
}

View File

@ -1,12 +1,28 @@
package ru.ulstu.model; package ru.ulstu.model;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.validation.constraints.NotEmpty;
import java.util.Date; import java.util.Date;
public class News { @Entity
private final static int MAX_NEWS_TEXT_PREVIEW_LENGTH = 200; public class News extends BaseEntity {
private final String title; private final static int MAX_NEWS_TEXT_PREVIEW_LENGTH = 800;
private final Date date;
private final String text; @NotEmpty(message = "Заголовок не может быть пустым")
private String title;
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private Date date;
@Lob
@NotEmpty(message = "Текст новости не может быть пустым")
private String text;
public News() {
}
public News(String title, Date date, String text) { public News(String title, Date date, String text) {
this.title = title; this.title = title;
@ -22,12 +38,24 @@ public class News {
return date; 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() { public String getText() {
return text; return text;
} }
public String getPreview() { public String getPreview() {
return text.length() > MAX_NEWS_TEXT_PREVIEW_LENGTH return text != null && text.length() > MAX_NEWS_TEXT_PREVIEW_LENGTH
? text.substring(0, MAX_NEWS_TEXT_PREVIEW_LENGTH) + "..." ? text.substring(0, MAX_NEWS_TEXT_PREVIEW_LENGTH) + "..."
: text; : text;
} }

View File

@ -0,0 +1,7 @@
package ru.ulstu.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.ulstu.model.News;
public interface NewsRepository extends JpaRepository<News, Integer> {
}

View File

@ -0,0 +1,49 @@
package ru.ulstu.service;
import org.springframework.stereotype.Service;
import ru.ulstu.model.News;
import ru.ulstu.repository.NewsRepository;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.List;
@Service
public class NewsService {
private final NewsRepository newsRepository;
public NewsService(NewsRepository newsRepository) {
this.newsRepository = newsRepository;
}
public void create(String title, String text) {
newsRepository.save(new News(title, new Date(), text));
}
public void create(News news) {
news.setDate(new Date());
newsRepository.save(news);
}
public void save(News news) {
if (news.getId() != null && (news.getId() != 0)) {
newsRepository.save(news);
} else {
create(news);
}
}
public News getById(@NotNull Integer id) {
return newsRepository
.findById(id)
.orElseThrow(() -> new RuntimeException("Новость не найдена"));
}
public List<News> getAll() {
return newsRepository.findAll();
}
public void delete(Integer id) {
newsRepository.deleteById(id);
}
}

View File

@ -8,3 +8,11 @@ spring.main.banner-mode=off
logging.level.tech.athene=DEBUG logging.level.tech.athene=DEBUG
server.port=8080 server.port=8080
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
# go to http://localhost:8080/h2-console
spring.datasource.url=jdbc:h2:file:./data/db
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update

View File

@ -9,18 +9,26 @@
} }
.news-item { .news-item {
margin: 20px;
position: relative;
text-align: justify; text-align: justify;
} }
.news-date { .news-date {
font-size: 10px; font-size: 10px;
position: absolute; text-align: right;
bottom: 0;
right: 25px;
} }
.news-image { .news-image {
width: 300px; width: 300px;
} }
.error {
color: red;
}
.fa {
color: black;
}
.link-dark, .link-dark:visited, .link-dark:focus {
color: black;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,16 @@
<!--
~ 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"
layout:decorate="~{default}">
<div class="container" layout:fragment="content">
<a href="/editNews/0" class="btn btn-outline-dark">
<i class="fa fa-plus-square" aria-hidden="true"> Добавить новость</i>
</a>
</div>
</html>

View File

@ -17,12 +17,12 @@
<link rel="stylesheet" href="/webjars/bootstrap/4.6.0/css/bootstrap.min.css"/> <link rel="stylesheet" href="/webjars/bootstrap/4.6.0/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/webjars/bootstrap-select/1.13.8/css/bootstrap-select.min.css"/> <link rel="stylesheet" href="/webjars/bootstrap-select/1.13.8/css/bootstrap-select.min.css"/>
<link rel="stylesheet" href="/webjars/font-awesome/4.7.0/css/font-awesome.min.css"/> <link rel="stylesheet" href="/webjars/font-awesome/4.7.0/css/font-awesome.min.css"/>
<link rel="stylesheet" href="css/main.css"/> <link rel="stylesheet" href="/css/main.css"/>
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-md navbar-light bg-white"> <nav class="navbar navbar-expand-md navbar-light bg-white">
<a class="navbar-brand" href="/"> <a class="navbar-brand" href="/">
<img src="img/logo.svg" width="50px"> <img src="/img/logo.svg" width="50px">
<div class="navbar-text" th:text="#{messages.logo-title}" style="font-size: 16px"></div> <div class="navbar-text" th:text="#{messages.logo-title}" style="font-size: 16px"></div>
</a> </a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
@ -32,13 +32,16 @@
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="news">Новости</a> <a class="nav-link" href="/news">Новости</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="news">Заседания</a> <a class="nav-link" href="/news">Заседания</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="news">Отчеты</a> <a class="nav-link" href="/news">Отчеты</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin">Администратору</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,34 @@
<!--
~ 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="@{/saveNews}" th:object="${news}" method="post">
<input type="hidden" th:field="*{id}">
<input type="hidden" th:field="*{date}">
<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

@ -8,23 +8,32 @@
<html <html
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"
layout:decorate="~{default}"> layout:decorate="~{default}">
<head>
<title>Time series smoothing</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
</head>
<div class="container" layout:fragment="content"> <div class="container" layout:fragment="content">
<div th:each="n : ${news}" class="news"> <div th:each="n : ${news}" class="news">
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<img class="news-image" src="img/logo.svg"/> <img class="news-image" src="/img/logo.svg"/>
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
<h5 th:text="${n.title}"/> <div class="row">
<div class="col-md-10">
<a th:href="@{'/news/' + ${n.id}}" class="link-dark"><h5 th:text="${n.title}"/></a>
</div>
<div class="col-md-2" style="text-align: right">
<a th:href="@{'/editNews/' + ${n.id}}" class="link-dark">
<i class="fa fa-pencil" aria-hidden="true"></i>
</a>
<a th:href="@{'/deleteNews/' + ${n.id}}" class="link-dark"
onclick="return confirm('Удалить новость?')">
<i class="fa fa-trash" aria-hidden="true"></i>
</a>
</div>
</div>
<div th:text="${n.preview}" class="news-item"></div> <div th:text="${n.preview}" class="news-item"></div>
</div>
</div>
<div th:text="${'Опубликовано: ' + #calendars.format(n.date, 'dd.MM.yyyy HH:mm')}" <div th:text="${'Опубликовано: ' + #calendars.format(n.date, 'dd.MM.yyyy HH:mm')}"
class="news-date"></div> class="news-date"></div>
</div>
</div>
<hr/> <hr/>
</div> </div>
</div> </div>

View File

@ -0,0 +1,25 @@
<!--
~ 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="${news.title}"/>
<div th:text="${news.text}" class="news-item"></div>
</div>
</div>
<div th:text="${'Опубликовано: ' + #calendars.format(news.date, 'dd.MM.yyyy HH:mm')}"
class="news-date"></div>
<a href="javascript:history.back()" class="btn btn-outline-dark" style="text-align: right">Назад</a>
</div>
</html>