Merge pull request 'meetings' (#8) from 7-meetings into master
Reviewed-on: #8
This commit is contained in:
commit
80c0424157
19
build.gradle
19
build.gradle
@ -1,14 +1,7 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
|
|
||||||
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
|
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
||||||
id 'org.springframework.boot' version '2.3.3.RELEASE'
|
id 'org.springframework.boot' version '2.6.4'
|
||||||
id "org.sonarqube" version "2.7"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@ -18,6 +11,7 @@ jar {
|
|||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url "http://repo.athene.tech/repository/maven-central/"
|
url "http://repo.athene.tech/repository/maven-central/"
|
||||||
|
allowInsecureProtocol(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +33,7 @@ dependencies {
|
|||||||
implementation group: 'org.springframework.boot', name:'spring-boot-starter-data-jpa'
|
implementation group: 'org.springframework.boot', name:'spring-boot-starter-data-jpa'
|
||||||
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security'
|
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', version: '3.1.0'
|
||||||
implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity5'
|
implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity5'
|
||||||
implementation group: 'com.h2database', name:'h2'
|
implementation group: 'com.h2database', name:'h2'
|
||||||
implementation group: 'javax.xml.bind', name: 'jaxb-api'
|
implementation group: 'javax.xml.bind', name: 'jaxb-api'
|
||||||
@ -48,9 +42,12 @@ dependencies {
|
|||||||
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'
|
||||||
implementation group: 'org.webjars', name: 'bootstrap', version: '4.6.0'
|
implementation group: 'org.webjars', name: 'bootstrap', version: '4.3.0'
|
||||||
implementation group: 'org.webjars', name: 'bootstrap-select', version: '1.13.8'
|
implementation group: 'org.webjars', name: 'bootstrap-select', version: '1.13.8'
|
||||||
|
implementation group: 'org.webjars', name: 'bootstrap-datetimepicker', version: '2.4.4'
|
||||||
implementation group: 'org.webjars', name: 'font-awesome', version: '4.7.0'
|
implementation group: 'org.webjars', name: 'font-awesome', version: '4.7.0'
|
||||||
|
implementation group: 'org.webjars', name: 'momentjs', version: '2.24.0'
|
||||||
|
implementation group: 'org.webjars', name: 'bootstrap-glyphicons', version: 'bdd2cbfba0'
|
||||||
|
|
||||||
testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test'
|
testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test'
|
||||||
}
|
}
|
||||||
|
BIN
data/db.mv.db
BIN
data/db.mv.db
Binary file not shown.
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||||
|
@ -3,10 +3,12 @@ package ru.ulstu;
|
|||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import ru.ulstu.user.UserService;
|
import ru.ulstu.user.UserService;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@EnableConfigurationProperties
|
||||||
public class SeminarApplication {
|
public class SeminarApplication {
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ public class MvcConfiguration implements WebMvcConfigurer {
|
|||||||
registry.addViewController("/loginError");
|
registry.addViewController("/loginError");
|
||||||
registry.addViewController("/index");
|
registry.addViewController("/index");
|
||||||
registry.addViewController("/admin");
|
registry.addViewController("/admin");
|
||||||
|
registry.addViewController("/organizers");
|
||||||
|
registry.addViewController("/docs");
|
||||||
registry.addViewController("/editNews");
|
registry.addViewController("/editNews");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
|||||||
log.debug("Security enabled");
|
log.debug("Security enabled");
|
||||||
http.authorizeRequests()
|
http.authorizeRequests()
|
||||||
.antMatchers("/").permitAll()
|
.antMatchers("/").permitAll()
|
||||||
.antMatchers("/login", "/index", "/news/**", "/h2-console/*", "/h2-console").permitAll()
|
.antMatchers("/login", "/index", "/news/**", "/meetings/**", "/files/**", "/docs/**",
|
||||||
|
"/public/**", "/organizers", "/webjars/**", "/h2-console/*", "/h2-console").permitAll()
|
||||||
.antMatchers("/swagger-ui.html").hasAuthority(UserRoleConstants.ADMIN)
|
.antMatchers("/swagger-ui.html").hasAuthority(UserRoleConstants.ADMIN)
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
.and()
|
.and()
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
package ru.ulstu.configuration;
|
package ru.ulstu.configuration;
|
||||||
|
|
||||||
import nz.net.ultraq.thymeleaf.LayoutDialect;
|
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect;
|
import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect;
|
||||||
|
@ -9,7 +9,7 @@ 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.service.NewsService;
|
import ru.ulstu.news.NewsService;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class IndexController {
|
public class IndexController {
|
||||||
|
80
src/main/java/ru/ulstu/files/FileSystemStorageService.java
Normal file
80
src/main/java/ru/ulstu/files/FileSystemStorageService.java
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package ru.ulstu.files;
|
||||||
|
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.UrlResource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.FileSystemUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class FileSystemStorageService implements StorageService {
|
||||||
|
public static final String UPLOAD_DIR = "upload";
|
||||||
|
|
||||||
|
private final Path rootLocation = Paths.get(UPLOAD_DIR);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void store(MultipartFile file) {
|
||||||
|
try {
|
||||||
|
if (file.isEmpty()) {
|
||||||
|
throw new StorageException("Failed to store empty file " + file.getOriginalFilename());
|
||||||
|
}
|
||||||
|
Files.copy(file.getInputStream(), this.rootLocation.resolve(file.getOriginalFilename()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new StorageException("Failed to store file " + file.getOriginalFilename(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<Path> loadAll() {
|
||||||
|
try {
|
||||||
|
return Files.walk(this.rootLocation, 1)
|
||||||
|
.filter(path -> !path.equals(this.rootLocation))
|
||||||
|
.map(path -> this.rootLocation.relativize(path));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new StorageException("Failed to read stored files", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path load(String filename) {
|
||||||
|
return rootLocation.resolve(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resource loadAsResource(String filename) {
|
||||||
|
try {
|
||||||
|
Path file = load(filename);
|
||||||
|
Resource resource = new UrlResource(file.toUri());
|
||||||
|
if (resource.exists() || resource.isReadable()) {
|
||||||
|
return resource;
|
||||||
|
} else {
|
||||||
|
throw new StorageFileNotFoundException("Could not read file: " + filename);
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new StorageFileNotFoundException("Could not read file: " + filename, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteAll() {
|
||||||
|
FileSystemUtils.deleteRecursively(rootLocation.toFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
try {
|
||||||
|
Files.createDirectory(rootLocation);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new StorageException("Could not initialize storage", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ru.ulstu.service;
|
package ru.ulstu.files;
|
||||||
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
28
src/main/java/ru/ulstu/files/FilesController.java
Normal file
28
src/main/java/ru/ulstu/files/FilesController.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package ru.ulstu.files;
|
||||||
|
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class FilesController {
|
||||||
|
private final StorageService storageService;
|
||||||
|
|
||||||
|
public FilesController(StorageService storageService) {
|
||||||
|
this.storageService = storageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/files/{filename:.+}")
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseEntity<Resource> serveFile(@PathVariable String filename) {
|
||||||
|
Resource file = storageService.loadAsResource((filename == null || filename.equals("null") || filename.isEmpty())
|
||||||
|
? "logo.png"
|
||||||
|
: filename);
|
||||||
|
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
"filename=\"" + file.getFilename() + "\"").body(file);
|
||||||
|
}
|
||||||
|
}
|
12
src/main/java/ru/ulstu/files/StorageException.java
Normal file
12
src/main/java/ru/ulstu/files/StorageException.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package ru.ulstu.files;
|
||||||
|
|
||||||
|
public class StorageException extends RuntimeException {
|
||||||
|
|
||||||
|
public StorageException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StorageException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package ru.ulstu.files;
|
||||||
|
|
||||||
|
public class StorageFileNotFoundException extends StorageException {
|
||||||
|
|
||||||
|
public StorageFileNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StorageFileNotFoundException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
23
src/main/java/ru/ulstu/files/StorageService.java
Normal file
23
src/main/java/ru/ulstu/files/StorageService.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package ru.ulstu.files;
|
||||||
|
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public interface StorageService {
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
void store(MultipartFile file);
|
||||||
|
|
||||||
|
Stream<Path> loadAll();
|
||||||
|
|
||||||
|
Path load(String filename);
|
||||||
|
|
||||||
|
Resource loadAsResource(String filename);
|
||||||
|
|
||||||
|
void deleteAll();
|
||||||
|
|
||||||
|
}
|
85
src/main/java/ru/ulstu/meeting/Meeting.java
Normal file
85
src/main/java/ru/ulstu/meeting/Meeting.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package ru.ulstu.meeting;
|
||||||
|
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import ru.ulstu.model.BaseEntity;
|
||||||
|
import ru.ulstu.news.News;
|
||||||
|
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.Lob;
|
||||||
|
import javax.persistence.OneToOne;
|
||||||
|
import javax.persistence.Transient;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class Meeting extends BaseEntity {
|
||||||
|
@NotEmpty(message = "Заголовок не может быть пустым")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@DateTimeFormat(pattern = "dd.MM.yyyy HH:mm")
|
||||||
|
private Date date;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@NotEmpty(message = "Текст заседания не может быть пустым")
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
@OneToOne(cascade = CascadeType.ALL)
|
||||||
|
@JoinColumn(name = "news_id", referencedColumnName = "id")
|
||||||
|
private News news;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private Integer newsId;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public News getNews() {
|
||||||
|
return news;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNews(News news) {
|
||||||
|
this.news = news;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getNewsId() {
|
||||||
|
return newsId == null
|
||||||
|
? (news != null) ? news.getId() : null
|
||||||
|
: newsId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewsId(Integer newsId) {
|
||||||
|
this.newsId = newsId;
|
||||||
|
}
|
||||||
|
}
|
88
src/main/java/ru/ulstu/meeting/MeetingController.java
Normal file
88
src/main/java/ru/ulstu/meeting/MeetingController.java
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
9
src/main/java/ru/ulstu/meeting/MeetingRepository.java
Normal file
9
src/main/java/ru/ulstu/meeting/MeetingRepository.java
Normal 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);
|
||||||
|
}
|
58
src/main/java/ru/ulstu/meeting/MeetingService.java
Normal file
58
src/main/java/ru/ulstu/meeting/MeetingService.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package ru.ulstu.meeting;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.ulstu.news.News;
|
||||||
|
import ru.ulstu.news.NewsService;
|
||||||
|
|
||||||
|
import javax.transaction.Transactional;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Meeting create(Meeting meeting) {
|
||||||
|
meeting = meetingRepository.save(meeting);
|
||||||
|
News news = newsService.create("Объявление о заседании семинара", meeting);
|
||||||
|
meeting.setNews(news);
|
||||||
|
return save(meeting);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Meeting save(Meeting meeting) {
|
||||||
|
if (meeting.getNewsId() != null) {
|
||||||
|
meeting.setNews(newsService.getById(meeting.getNewsId()));
|
||||||
|
}
|
||||||
|
return (meeting.getId() != null && (meeting.getId() != 0))
|
||||||
|
? meetingRepository.save(meeting)
|
||||||
|
: create(meeting);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Meeting getById(@NotNull Integer id) {
|
||||||
|
return meetingRepository
|
||||||
|
.findById(id)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Запись о заседании не найдена"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void delete(Integer id) {
|
||||||
|
Meeting meeting = meetingRepository.getById(id);
|
||||||
|
if (meeting.getNews() != null) {
|
||||||
|
newsService.delete(meeting.getNews().getId());
|
||||||
|
}
|
||||||
|
meetingRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page<Meeting> getMeetings(Pageable pageable) {
|
||||||
|
return meetingRepository.findByOrderByDateDesc(pageable);
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import org.springframework.data.domain.Pageable;
|
|||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class OffsetablePageRequest implements Pageable, Serializable {
|
public class OffsetablePageRequest implements Pageable, Serializable {
|
||||||
private final int offset;
|
private final int offset;
|
||||||
@ -31,11 +32,26 @@ public class OffsetablePageRequest implements Pageable, Serializable {
|
|||||||
return sort;
|
return sort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sort getSortOr(Sort sort) {
|
||||||
|
return Pageable.super.getSortOr(sort);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPageSize() {
|
public int getPageSize() {
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPaged() {
|
||||||
|
return Pageable.super.isPaged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUnpaged() {
|
||||||
|
return Pageable.super.isUnpaged();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPageNumber() {
|
public int getPageNumber() {
|
||||||
return offset / count;
|
return offset / count;
|
||||||
@ -51,6 +67,11 @@ public class OffsetablePageRequest implements Pageable, Serializable {
|
|||||||
return offset > 0;
|
return offset > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Pageable> toOptional() {
|
||||||
|
return Pageable.super.toOptional();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pageable next() {
|
public Pageable next() {
|
||||||
return new OffsetablePageRequest(getOffset() + getPageSize(), getPageSize(), getSort());
|
return new OffsetablePageRequest(getOffset() + getPageSize(), getPageSize(), getSort());
|
||||||
@ -70,6 +91,11 @@ public class OffsetablePageRequest implements Pageable, Serializable {
|
|||||||
return new OffsetablePageRequest(0, getPageSize(), getSort());
|
return new OffsetablePageRequest(0, getPageSize(), getSort());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pageable withPage(int pageNumber) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package ru.ulstu.model;
|
package ru.ulstu.news;
|
||||||
|
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import ru.ulstu.meeting.Meeting;
|
||||||
|
import ru.ulstu.model.BaseEntity;
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Lob;
|
import javax.persistence.Lob;
|
||||||
|
import javax.persistence.OneToOne;
|
||||||
import javax.persistence.Transient;
|
import javax.persistence.Transient;
|
||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -28,6 +31,9 @@ public class News extends BaseEntity {
|
|||||||
@Transient
|
@Transient
|
||||||
private MultipartFile imageFile;
|
private MultipartFile imageFile;
|
||||||
|
|
||||||
|
@OneToOne(mappedBy = "news")
|
||||||
|
private Meeting meeting;
|
||||||
|
|
||||||
public News() {
|
public News() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +83,14 @@ public class News extends BaseEntity {
|
|||||||
this.imageFile = imageFile;
|
this.imageFile = imageFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Meeting getMeeting() {
|
||||||
|
return meeting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMeeting(Meeting meeting) {
|
||||||
|
this.meeting = meeting;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPreview() {
|
public String getPreview() {
|
||||||
return text != null && 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) + "..."
|
@ -4,7 +4,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.ulstu.controller;
|
package ru.ulstu.news;
|
||||||
|
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.security.access.annotation.Secured;
|
import org.springframework.security.access.annotation.Secured;
|
||||||
@ -17,10 +17,8 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import ru.ulstu.model.News;
|
|
||||||
import ru.ulstu.model.OffsetablePageRequest;
|
import ru.ulstu.model.OffsetablePageRequest;
|
||||||
import ru.ulstu.model.UserRoleConstants;
|
import ru.ulstu.model.UserRoleConstants;
|
||||||
import ru.ulstu.service.NewsService;
|
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -59,6 +57,7 @@ public class NewsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/editNews/{newsId}")
|
@GetMapping("/editNews/{newsId}")
|
||||||
|
@Secured({UserRoleConstants.ADMIN})
|
||||||
public String editNews(@PathVariable(value = "newsId") Integer id, Model model) {
|
public String editNews(@PathVariable(value = "newsId") Integer id, Model model) {
|
||||||
model.addAttribute("news", (id != null && id != 0) ? newsService.getById(id) : new News());
|
model.addAttribute("news", (id != null && id != 0) ? newsService.getById(id) : new News());
|
||||||
return "editNews";
|
return "editNews";
|
||||||
@ -82,6 +81,7 @@ public class NewsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("deleteNews/{newsId}")
|
@GetMapping("deleteNews/{newsId}")
|
||||||
|
@Secured({UserRoleConstants.ADMIN})
|
||||||
public String delete(@PathVariable(value = "newsId") Integer id) {
|
public String delete(@PathVariable(value = "newsId") Integer id) {
|
||||||
newsService.delete(id);
|
newsService.delete(id);
|
||||||
return "redirect:/news/news";
|
return "redirect:/news/news";
|
@ -1,9 +1,8 @@
|
|||||||
package ru.ulstu.repository;
|
package ru.ulstu.news;
|
||||||
|
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import ru.ulstu.model.News;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -1,11 +1,13 @@
|
|||||||
package ru.ulstu.service;
|
package ru.ulstu.news;
|
||||||
|
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import ru.ulstu.model.News;
|
import ru.ulstu.files.FileSystemStorageService;
|
||||||
import ru.ulstu.repository.NewsRepository;
|
import ru.ulstu.files.FileUtil;
|
||||||
|
import ru.ulstu.meeting.Meeting;
|
||||||
|
|
||||||
|
import javax.transaction.Transactional;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -20,12 +22,19 @@ public class NewsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void create(String title, String text) {
|
public void create(String title, String text) {
|
||||||
newsRepository.save(new News(title, new Date(), text));
|
create(new News(title, new Date(), text));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void create(News news) {
|
@Transactional
|
||||||
|
public News create(String title, Meeting meeting) {
|
||||||
|
News news = new News(title, new Date(), "Новость о заседении семинара");
|
||||||
|
news.setMeeting(meeting);
|
||||||
|
return create(news);
|
||||||
|
}
|
||||||
|
|
||||||
|
public News create(News news) {
|
||||||
news.setDate(new Date());
|
news.setDate(new Date());
|
||||||
newsRepository.save(news);
|
return newsRepository.save(news);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(News news) throws IOException {
|
public void save(News news) throws IOException {
|
||||||
@ -38,9 +47,7 @@ public class NewsService {
|
|||||||
create(news);
|
create(news);
|
||||||
}
|
}
|
||||||
|
|
||||||
String uploadDir = "news-photos/";
|
FileUtil.saveFile(FileSystemStorageService.UPLOAD_DIR, fileName, news.getImageFile());
|
||||||
FileUtil.saveFile(uploadDir, fileName, news.getImageFile());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public News getById(@NotNull Integer id) {
|
public News getById(@NotNull Integer id) {
|
BIN
src/main/resources/public/docs/plan2022.docx
Normal file
BIN
src/main/resources/public/docs/plan2022.docx
Normal file
Binary file not shown.
BIN
src/main/resources/public/docs/polozh.docx
Normal file
BIN
src/main/resources/public/docs/polozh.docx
Normal file
Binary file not shown.
@ -10,5 +10,8 @@
|
|||||||
<a href="/news/editNews/0" class="btn btn-outline-dark">
|
<a href="/news/editNews/0" class="btn btn-outline-dark">
|
||||||
<i class="fa fa-plus-square" aria-hidden="true">Добавить новость</i>
|
<i class="fa fa-plus-square" aria-hidden="true">Добавить новость</i>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/meetings/editMeeting/0" class="btn btn-outline-dark">
|
||||||
|
<i class="fa fa-plus-square" aria-hidden="true">Добавить заседание</i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
<!--
|
|
||||||
~ Copyright (C) 2022 Anton Romanov - All Rights Reserved
|
|
||||||
~ You may use, distribute and modify this code, please write to: romanov73@gmail.com.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
|
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
|
||||||
@ -13,11 +7,17 @@
|
|||||||
<title th:text="#{messages.app-name}">app-name</title>
|
<title th:text="#{messages.app-name}">app-name</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<script type="text/javascript" src="/webjars/jquery/3.6.0/jquery.min.js"></script>
|
<script type="text/javascript" src="/webjars/jquery/3.6.0/jquery.min.js"></script>
|
||||||
<script type="text/javascript" src="/webjars/bootstrap/4.6.0/js/bootstrap.bundle.min.js"></script>
|
<script type="text/javascript" src="/webjars/momentjs/2.24.0/moment.js"></script>
|
||||||
|
<script type="text/javascript" src="/webjars/momentjs/2.24.0/locale/ru.js"></script>
|
||||||
|
<script type="text/javascript" src="/webjars/bootstrap/4.3.0/js/bootstrap.bundle.min.js"></script>
|
||||||
<script type="text/javascript" src="/webjars/bootstrap-select/1.13.8/js/bootstrap-select.min.js"></script>
|
<script type="text/javascript" src="/webjars/bootstrap-select/1.13.8/js/bootstrap-select.min.js"></script>
|
||||||
<link rel="stylesheet" href="/webjars/bootstrap/4.6.0/css/bootstrap.min.css"/>
|
<script type="text/javascript"
|
||||||
|
src="/webjars/bootstrap-datetimepicker/2.4.4/js/bootstrap-datetimepicker.js"></script>
|
||||||
|
<link rel="stylesheet" href="/webjars/bootstrap/4.3.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="/webjars/bootstrap-glyphicons/bdd2cbfba0/css/bootstrap-glyphicons.css"/>
|
||||||
|
<link rel="stylesheet" href="/webjars/bootstrap-datetimepicker/2.4.4/css/bootstrap-datetimepicker.css"/>
|
||||||
<link rel="stylesheet" href="/css/main.css"/>
|
<link rel="stylesheet" href="/css/main.css"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -36,10 +36,16 @@
|
|||||||
<a class="nav-link" href="/news/news">Новости</a>
|
<a class="nav-link" href="/news/news">Новости</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/news">Заседания</a>
|
<a class="nav-link" href="/meetings/meetings">Заседания</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/news">Отчеты</a>
|
<a class="nav-link" href="/organizers">Организаторы</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/docs">Документы</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/reports">Отчеты</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/admin">Администратору</a>
|
<a class="nav-link" href="/admin">Администратору</a>
|
||||||
|
23
src/main/resources/templates/docs.html
Normal file
23
src/main/resources/templates/docs.html
Normal 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" layout:decorate="~{default}">
|
||||||
|
<div class="container" layout:fragment="content">
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<a class="link-secondary" href="/docs/polozh.docx">
|
||||||
|
Положение
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<a class="link-secondary" href="/docs/plan2022.docx">
|
||||||
|
План заседаний на 2022 год.
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</html>
|
42
src/main/resources/templates/editMeeting.html
Normal file
42
src/main/resources/templates/editMeeting.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<!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}">
|
||||||
|
<input type="hidden" th:field="*{newsId}">
|
||||||
|
<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>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="date">Дата заседания</label>
|
||||||
|
<input type="text" id="date" class="form-control" data-target="#date" th:field="*{date}"/>
|
||||||
|
<p th:if="${#fields.hasErrors('date')}" th:class="${#fields.hasErrors('date')}? error">
|
||||||
|
Не может быть пустым</p>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-outline-dark">Сохранить</button>
|
||||||
|
<a href="javascript:history.back()" class="btn btn-outline-dark">Отмена</a>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
$("#date").datetimepicker({
|
||||||
|
format: 'dd.mm.yyyy hh:ii',
|
||||||
|
locale: 'ru'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</html>
|
@ -11,15 +11,24 @@
|
|||||||
<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" th:src="@{'/files/' + ${n.imageFileName}}"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<a th:href="@{'/news/news/' + ${n.id}}" class="link-dark"><h5 th:text="${n.title}"/></a>
|
<a th:if="${n.meeting == null}" th:href="@{'/news/news/' + ${n.id}}" class="link-dark"><h5
|
||||||
|
th:text="${n.title}"/></a>
|
||||||
|
<a th:if="${n.meeting != null}" th:href="@{'/meetings/meetings/' + ${n.meeting.id}}"
|
||||||
|
class="link-dark"><h5 th:text="${n.title}"/></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div th:text="${n.preview}" class="news-item"></div>
|
<div th:if="${n.meeting == null}" th:text="${n.preview}" class="news-item"></div>
|
||||||
|
<div th:if="${n.meeting != null}" th:text="${'Тема заседания: ' + n.meeting.title}"
|
||||||
|
class="news-item"></div>
|
||||||
|
<div th:if="${n.meeting != null}"
|
||||||
|
th:text="${'Дата: ' + #calendars.format(n.meeting.date, 'dd.MM.yyyy HH:mm')}"
|
||||||
|
class="news-item"></div>
|
||||||
|
<div th:if="${n.meeting != null}" th:text="${n.meeting.text}" class="news-item"></div>
|
||||||
</div>
|
</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')}"
|
||||||
|
34
src/main/resources/templates/meetings.html
Normal file
34
src/main/resources/templates/meetings.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!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>
|
@ -12,15 +12,18 @@
|
|||||||
<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" th:src="@{'/files/' + ${n.imageFileName}}"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<a th:href="@{'/news/news/' + ${n.id}}" class="link-dark"><h5 th:text="${n.title}"/></a>
|
<a th:if="${n.meeting == null}" th:href="@{'/news/news/' + ${n.id}}" class="link-dark"><h5
|
||||||
|
th:text="${n.title}"/></a>
|
||||||
|
<a th:if="${n.meeting != null}" th:href="@{'/meetings/meetings/' + ${n.meeting.id}}"
|
||||||
|
class="link-dark"><h5 th:text="${n.title}"/></a>
|
||||||
</div>
|
</div>
|
||||||
<div sec:authorize="hasRole('ROLE_ADMIN')" class="col-md-2" style="text-align: right">
|
<div sec:authorize="hasRole('ROLE_ADMIN')" class="col-md-2" style="text-align: right">
|
||||||
<a th:href="@{'/news/editNews/' + ${n.id}}" class="link-dark">
|
<a th:if="${n.meeting == null}" th:href="@{'/news/editNews/' + ${n.id}}" class="link-dark">
|
||||||
<i class="fa fa-pencil" aria-hidden="true"></i>
|
<i class="fa fa-pencil" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
<a th:href="@{'/news/deleteNews/' + ${n.id}}" class="link-dark"
|
<a th:href="@{'/news/deleteNews/' + ${n.id}}" class="link-dark"
|
||||||
@ -29,7 +32,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div th:text="${n.preview}" class="news-item"></div>
|
<div th:if="${n.meeting == null}" th:text="${n.preview}" class="news-item"></div>
|
||||||
|
<div th:if="${n.meeting != null}" th:text="${'Тема заседания: ' + n.meeting.title}"
|
||||||
|
class="news-item"></div>
|
||||||
|
<div th:if="${n.meeting != null}"
|
||||||
|
th:text="${'Дата: ' + #calendars.format(n.meeting.date, 'dd.MM.yyyy HH:mm')}"
|
||||||
|
class="news-item"></div>
|
||||||
|
<div th:if="${n.meeting != null}" th:text="${n.meeting.text}" class="news-item"></div>
|
||||||
</div>
|
</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')}"
|
||||||
|
29
src/main/resources/templates/organizers.html
Normal file
29
src/main/resources/templates/organizers.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<!--
|
||||||
|
~ 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">
|
||||||
|
<h3>Состав организаторов семинара</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Ярушкина Н.Г., профессор, д.т.н., профессор
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Мошкин В.С., доцент, к.т.н.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Романов А.А., зав. кафедрой, доцент, к.т.н., доцент
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Гуськов Г.Ю., доцент, к.т.н.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Филиппов А.А., доцент, к.т.н.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</html>
|
23
src/main/resources/templates/viewMeeting.html
Normal file
23
src/main/resources/templates/viewMeeting.html
Normal 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>
|
@ -11,7 +11,7 @@
|
|||||||
<div class="container" layout:fragment="content">
|
<div class="container" layout:fragment="content">
|
||||||
<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" th:src="@{'/files/' + ${news.imageFileName}}"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<h5 th:text="${news.title}"/>
|
<h5 th:text="${news.title}"/>
|
||||||
|
BIN
upload/1647261445926
Normal file
BIN
upload/1647261445926
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
BIN
upload/1647262081164
Normal file
BIN
upload/1647262081164
Normal file
Binary file not shown.
After Width: | Height: | Size: 182 KiB |
58
upload/1647262775259
Normal file
58
upload/1647262775259
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
width="1000"
|
||||||
|
height="1000"
|
||||||
|
viewBox="0 0 500 500"
|
||||||
|
xml:space="preserve"
|
||||||
|
id="svg45"
|
||||||
|
sodipodi:docname="IS_logo_black.svg"
|
||||||
|
inkscape:version="1.1.2 (b8e25be833, 2022-02-05)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview47"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
showgrid="false"
|
||||||
|
showborder="false"
|
||||||
|
inkscape:zoom="0.769"
|
||||||
|
inkscape:cx="500"
|
||||||
|
inkscape:cy="500"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="991"
|
||||||
|
inkscape:window-x="-9"
|
||||||
|
inkscape:window-y="-9"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg45" />
|
||||||
|
<desc
|
||||||
|
id="desc29">Created with Fabric.js 4.6.0</desc>
|
||||||
|
<defs
|
||||||
|
id="defs31">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
id="path33"
|
||||||
|
style="fill:#000000;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
|
||||||
|
d="M 614.48828 177.41406 C 585.03403 177.64615 555.102 180 528 180 C 509.268 180 474.11592 173.20281 457.41992 181.83789 C 441.12592 190.26483 422.34 188.47467 404 190.33789 C 365.3278 194.26683 326.787 197.16411 288 199.82031 C 261.38 201.64331 230.50279 201.95901 212.77539 226.25781 C 196.16547 249.02541 217.5264 250.08661 219.7832 270.00781 C 223.4616 302.47861 220 337.3084 220 370 C 220 424.688 216.64862 482.378 228.69922 536 C 234.13282 560.178 241.07448 585.21445 258.58008 603.81445 C 286.96128 633.96645 327.183 628.83584 364 632.33984 C 434.958 639.09184 507.266 629.02169 578 624.17969 C 620.728 621.25169 671.094 620.42538 710 600.35938 C 785.886 561.21938 770.028 472.564 770 402 C 769.978 343.7938 783.18837 263.01536 746.73438 213.03516 C 735.77437 198.01096 714.65742 193.13757 698.10742 187.56055 C 672.91842 179.07202 643.94253 177.18197 614.48828 177.41406 z M 451.28516 278.24414 C 508.18157 277.94477 575.51534 292.60888 617.99609 321.03906 C 665.52009 352.84446 673.38009 409.60016 625.99609 446.66016 C 609.19809 459.79816 583.47409 470.56595 561.99609 465.75195 C 553.62409 463.87395 546.58609 456.87805 537.99609 456.49805 C 522.09609 455.79605 512.77609 463.8217 495.99609 456.5957 C 476.36009 448.1397 462.80717 414.5508 471.20117 394.2168 C 474.36317 386.5588 491.70145 379.35103 497.93945 387.20703 C 508.38745 400.36503 494.40964 432.52702 516.18164 440.79102 C 530.60964 446.26702 534.58192 411.00817 555.66992 414.32617 C 563.02792 415.48417 559.99497 424.656 562.04297 430 C 565.00297 437.722 572.19609 445.56767 579.99609 448.51367 C 613.82609 461.29167 642.2743 406.694 629.9043 380 C 614.3683 346.4774 578.65809 326.98059 545.99609 313.90039 C 482.01609 288.27899 396.70598 263.8648 347.51758 330 C 318.46498 369.0616 331.97353 426.708 360.51953 462 C 409.14233 522.116 497.53009 557.79392 573.99609 553.91992 C 589.34209 553.14192 620.65517 534.20281 630.95117 522.88281 C 639.49717 513.48281 642.08522 495.18492 661.69922 503.79492 C 677.95722 510.93492 656.70214 530.07166 649.99414 535.34766 C 623.11814 556.49766 583.00409 562 549.99609 562 C 468.35409 562 372.36956 530.03 324.10156 460 C 268.98216 380.028 324.58909 293.80382 413.99609 280.85742 C 425.58059 279.17993 438.15521 278.31323 451.28516 278.24414 z "
|
||||||
|
transform="scale(0.5)" />
|
||||||
|
|
||||||
|
<path
|
||||||
|
id="path39"
|
||||||
|
style="fill:#000000;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
|
||||||
|
d="M 668.33594 643.47656 C 662.50706 643.55654 656.40463 644.00634 650 644.84766 C 557.68 656.97566 466.83 659.8418 374 659.8418 C 334.445 659.8418 285.9012 644.14103 248 660.95703 C 236.5832 666.02303 220.35223 674.28205 214.67383 685.99805 C 208.78323 698.15205 227.05259 701.474 224.27539 714 C 219.13139 737.202 163.19081 780.43105 192.00977 796.62305 C 213.03631 808.43705 243.23832 802.31272 265.85352 808.63672 C 272.15212 810.39872 271.30588 817.50928 276.70508 819.36328 C 297.12228 826.37328 330.3592 820 352 820 L 522 820 C 564.108 820 614.34895 827.63112 655.62695 819.70312 C 662.58095 818.36712 661.85506 809.62887 670.03906 808.29688 C 720.71106 800.04488 767.32912 814.45956 813.70312 783.85156 C 829.97513 773.11156 793.83588 738.344 786.17188 726 C 755.44819 676.52238 724.68174 642.70346 668.33594 643.47656 z M 632.96094 696.08594 C 656.9772 696.33526 670.5987 710.58 684.6582 732 C 687.2762 735.988 699.90202 751.25736 696.16602 755.94336 C 692.75802 760.21936 680.742 757.31823 676 757.49023 C 655.976 758.22223 636.016 761.75419 616 761.99219 C 537.304 762.92819 456.724 769.21231 378 763.82031 C 355.2814 762.26631 332.628 759.24231 310 756.57031 C 305.7648 756.07031 289.0863 758.23892 287.8125 752.79492 C 284.9009 740.34892 309.79135 708.10719 322.00195 708.11719 C 423.72835 708.20119 520.668 711.33947 622 696.85547 C 625.88675 696.29997 629.53004 696.05032 632.96094 696.08594 z "
|
||||||
|
transform="scale(0.5)" />
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.1 KiB |
0
upload/1647262911771
Normal file
0
upload/1647262911771
Normal file
0
upload/1647262925015
Normal file
0
upload/1647262925015
Normal file
BIN
upload/logo.png
Normal file
BIN
upload/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Loading…
Reference in New Issue
Block a user