diff --git a/build.gradle b/build.gradle index 11f98dc..f9157cd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,23 +1,17 @@ -/* - * Copyright (C) 2021 Anton Romanov - All Rights Reserved - * You may use, distribute and modify this code, please write to: romanov73@gmail.com. - * - */ - plugins { id 'java' - id 'io.spring.dependency-management' version '1.0.9.RELEASE' - id 'org.springframework.boot' version '2.3.3.RELEASE' - id "org.sonarqube" version "2.7" + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id 'org.springframework.boot' version '2.6.4' } jar { - archivesBaseName='seminar' + archivesBaseName = 'seminar' } repositories { maven { url "http://repo.athene.tech/repository/maven-central/" + allowInsecureProtocol(true) } } @@ -39,20 +33,23 @@ dependencies { 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: '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: 'com.h2database', name:'h2' - implementation group: 'javax.xml.bind', name:'jaxb-api' - implementation group: 'org.javassist', name:'javassist' + 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: '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-datetimepicker', version: '2.4.4' 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' } diff --git a/data/db.mv.db b/data/db.mv.db deleted file mode 100644 index 8e24a07..0000000 Binary files a/data/db.mv.db and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 83d6bd2..d66f36f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME 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 diff --git a/src/main/java/ru/ulstu/SeminarApplication.java b/src/main/java/ru/ulstu/SeminarApplication.java index 04b8e36..072438e 100644 --- a/src/main/java/ru/ulstu/SeminarApplication.java +++ b/src/main/java/ru/ulstu/SeminarApplication.java @@ -3,10 +3,12 @@ package ru.ulstu; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.event.EventListener; import ru.ulstu.user.UserService; @SpringBootApplication +@EnableConfigurationProperties public class SeminarApplication { private final UserService userService; diff --git a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java index 9154347..92b7272 100644 --- a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java @@ -24,6 +24,8 @@ public class MvcConfiguration implements WebMvcConfigurer { registry.addViewController("/loginError"); registry.addViewController("/index"); registry.addViewController("/admin"); + registry.addViewController("/organizers"); + registry.addViewController("/docs"); registry.addViewController("/editNews"); } diff --git a/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java index f893257..5da4970 100644 --- a/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java @@ -45,7 +45,8 @@ 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/**", "/files/**", "/docs/**", + "/public/**", "/organizers", "/webjars/**", "/h2-console/*", "/h2-console").permitAll() .antMatchers("/swagger-ui.html").hasAuthority(UserRoleConstants.ADMIN) .anyRequest().authenticated() .and() diff --git a/src/main/java/ru/ulstu/configuration/TemplateConfiguration.java b/src/main/java/ru/ulstu/configuration/TemplateConfiguration.java index f9ab6a7..3b697a0 100644 --- a/src/main/java/ru/ulstu/configuration/TemplateConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/TemplateConfiguration.java @@ -6,7 +6,7 @@ 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.Configuration; import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect; diff --git a/src/main/java/ru/ulstu/controller/IndexController.java b/src/main/java/ru/ulstu/controller/IndexController.java index 9774e53..15eb735 100644 --- a/src/main/java/ru/ulstu/controller/IndexController.java +++ b/src/main/java/ru/ulstu/controller/IndexController.java @@ -9,7 +9,7 @@ package ru.ulstu.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -import ru.ulstu.service.NewsService; +import ru.ulstu.news.NewsService; @Controller public class IndexController { diff --git a/src/main/java/ru/ulstu/files/FileSystemStorageService.java b/src/main/java/ru/ulstu/files/FileSystemStorageService.java new file mode 100644 index 0000000..805aa2f --- /dev/null +++ b/src/main/java/ru/ulstu/files/FileSystemStorageService.java @@ -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 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); + } + } +} diff --git a/src/main/java/ru/ulstu/files/FileUtil.java b/src/main/java/ru/ulstu/files/FileUtil.java new file mode 100644 index 0000000..e2ccfc2 --- /dev/null +++ b/src/main/java/ru/ulstu/files/FileUtil.java @@ -0,0 +1,29 @@ +package ru.ulstu.files; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +public class FileUtil { + + public static void saveFile(String uploadDir, String fileName, + MultipartFile multipartFile) throws IOException { + Path uploadPath = Paths.get(uploadDir); + + if (!Files.exists(uploadPath)) { + Files.createDirectories(uploadPath); + } + + try (InputStream inputStream = multipartFile.getInputStream()) { + Path filePath = uploadPath.resolve(fileName); + Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ioe) { + throw new IOException("Could not save image file: " + fileName, ioe); + } + } +} diff --git a/src/main/java/ru/ulstu/files/FilesController.java b/src/main/java/ru/ulstu/files/FilesController.java new file mode 100644 index 0000000..5df8814 --- /dev/null +++ b/src/main/java/ru/ulstu/files/FilesController.java @@ -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 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); + } +} diff --git a/src/main/java/ru/ulstu/files/StorageException.java b/src/main/java/ru/ulstu/files/StorageException.java new file mode 100644 index 0000000..eb7da9f --- /dev/null +++ b/src/main/java/ru/ulstu/files/StorageException.java @@ -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); + } +} diff --git a/src/main/java/ru/ulstu/files/StorageFileNotFoundException.java b/src/main/java/ru/ulstu/files/StorageFileNotFoundException.java new file mode 100644 index 0000000..085c94d --- /dev/null +++ b/src/main/java/ru/ulstu/files/StorageFileNotFoundException.java @@ -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); + } +} diff --git a/src/main/java/ru/ulstu/files/StorageService.java b/src/main/java/ru/ulstu/files/StorageService.java new file mode 100644 index 0000000..48e3461 --- /dev/null +++ b/src/main/java/ru/ulstu/files/StorageService.java @@ -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 loadAll(); + + Path load(String filename); + + Resource loadAsResource(String filename); + + void deleteAll(); + +} diff --git a/src/main/java/ru/ulstu/meeting/Meeting.java b/src/main/java/ru/ulstu/meeting/Meeting.java new file mode 100644 index 0000000..c118b45 --- /dev/null +++ b/src/main/java/ru/ulstu/meeting/Meeting.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/meeting/MeetingController.java b/src/main/java/ru/ulstu/meeting/MeetingController.java new file mode 100644 index 0000000..f85eea3 --- /dev/null +++ b/src/main/java/ru/ulstu/meeting/MeetingController.java @@ -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 page, + @RequestParam Optional size) { + int currentPage = page.orElse(1); + int pageSize = size.orElse(DEFAULT_PAGE_SIZE); + + Page meetingsPage = meetingService.getMeetings(new OffsetablePageRequest(currentPage - 1, pageSize)); + model.addAttribute("meetings", meetingsPage); + int totalPages = meetingsPage.getTotalPages(); + if (totalPages > 0) { + List 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"; + } +} diff --git a/src/main/java/ru/ulstu/meeting/MeetingRepository.java b/src/main/java/ru/ulstu/meeting/MeetingRepository.java new file mode 100644 index 0000000..e3e964d --- /dev/null +++ b/src/main/java/ru/ulstu/meeting/MeetingRepository.java @@ -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 { + Page findByOrderByDateDesc(Pageable pageable); +} diff --git a/src/main/java/ru/ulstu/meeting/MeetingService.java b/src/main/java/ru/ulstu/meeting/MeetingService.java new file mode 100644 index 0000000..42f8634 --- /dev/null +++ b/src/main/java/ru/ulstu/meeting/MeetingService.java @@ -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 getMeetings(Pageable pageable) { + return meetingRepository.findByOrderByDateDesc(pageable); + } +} diff --git a/src/main/java/ru/ulstu/model/OffsetablePageRequest.java b/src/main/java/ru/ulstu/model/OffsetablePageRequest.java index c715296..cd4a603 100644 --- a/src/main/java/ru/ulstu/model/OffsetablePageRequest.java +++ b/src/main/java/ru/ulstu/model/OffsetablePageRequest.java @@ -4,6 +4,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import java.io.Serializable; +import java.util.Optional; public class OffsetablePageRequest implements Pageable, Serializable { private final int offset; @@ -31,11 +32,26 @@ public class OffsetablePageRequest implements Pageable, Serializable { return sort; } + @Override + public Sort getSortOr(Sort sort) { + return Pageable.super.getSortOr(sort); + } + @Override public int getPageSize() { return count; } + @Override + public boolean isPaged() { + return Pageable.super.isPaged(); + } + + @Override + public boolean isUnpaged() { + return Pageable.super.isUnpaged(); + } + @Override public int getPageNumber() { return offset / count; @@ -51,6 +67,11 @@ public class OffsetablePageRequest implements Pageable, Serializable { return offset > 0; } + @Override + public Optional toOptional() { + return Pageable.super.toOptional(); + } + @Override public Pageable next() { return new OffsetablePageRequest(getOffset() + getPageSize(), getPageSize(), getSort()); @@ -70,6 +91,11 @@ public class OffsetablePageRequest implements Pageable, Serializable { return new OffsetablePageRequest(0, getPageSize(), getSort()); } + @Override + public Pageable withPage(int pageNumber) { + return null; + } + @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/src/main/java/ru/ulstu/model/News.java b/src/main/java/ru/ulstu/news/News.java similarity index 61% rename from src/main/java/ru/ulstu/model/News.java rename to src/main/java/ru/ulstu/news/News.java index f4dd2c8..d15e7e7 100644 --- a/src/main/java/ru/ulstu/model/News.java +++ b/src/main/java/ru/ulstu/news/News.java @@ -1,9 +1,14 @@ -package ru.ulstu.model; +package ru.ulstu.news; import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.multipart.MultipartFile; +import ru.ulstu.meeting.Meeting; +import ru.ulstu.model.BaseEntity; import javax.persistence.Entity; import javax.persistence.Lob; +import javax.persistence.OneToOne; +import javax.persistence.Transient; import javax.validation.constraints.NotEmpty; import java.util.Date; @@ -21,6 +26,14 @@ public class News extends BaseEntity { @NotEmpty(message = "Текст новости не может быть пустым") private String text; + private String imageFileName; + + @Transient + private MultipartFile imageFile; + + @OneToOne(mappedBy = "news") + private Meeting meeting; + public News() { } @@ -54,6 +67,30 @@ public class News extends BaseEntity { return text; } + public String getImageFileName() { + return imageFileName; + } + + public void setImageFileName(String imageFileName) { + this.imageFileName = imageFileName; + } + + public MultipartFile getImageFile() { + return imageFile; + } + + public void setImageFile(MultipartFile imageFile) { + this.imageFile = imageFile; + } + + public Meeting getMeeting() { + return meeting; + } + + public void setMeeting(Meeting meeting) { + this.meeting = meeting; + } + public String getPreview() { return text != null && text.length() > MAX_NEWS_TEXT_PREVIEW_LENGTH ? text.substring(0, MAX_NEWS_TEXT_PREVIEW_LENGTH) + "..." diff --git a/src/main/java/ru/ulstu/controller/NewsController.java b/src/main/java/ru/ulstu/news/NewsController.java similarity index 87% rename from src/main/java/ru/ulstu/controller/NewsController.java rename to src/main/java/ru/ulstu/news/NewsController.java index 34e8ba3..5d811b4 100644 --- a/src/main/java/ru/ulstu/controller/NewsController.java +++ b/src/main/java/ru/ulstu/news/NewsController.java @@ -4,9 +4,10 @@ * */ -package ru.ulstu.controller; +package ru.ulstu.news; 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; @@ -16,11 +17,11 @@ 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.News; import ru.ulstu.model.OffsetablePageRequest; -import ru.ulstu.service.NewsService; +import ru.ulstu.model.UserRoleConstants; import javax.validation.Valid; +import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -56,6 +57,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"; @@ -68,16 +70,18 @@ public class NewsController { } @PostMapping("saveNews") - public String saveNews(@Valid @ModelAttribute News news, BindingResult result) { + @Secured({UserRoleConstants.ADMIN}) + public String saveNews(@Valid @ModelAttribute News news, + BindingResult result) throws IOException { if (result.hasErrors()) { return "editNews"; } newsService.save(news); - return "redirect:/news/news"; } @GetMapping("deleteNews/{newsId}") + @Secured({UserRoleConstants.ADMIN}) public String delete(@PathVariable(value = "newsId") Integer id) { newsService.delete(id); return "redirect:/news/news"; diff --git a/src/main/java/ru/ulstu/repository/NewsRepository.java b/src/main/java/ru/ulstu/news/NewsRepository.java similarity index 86% rename from src/main/java/ru/ulstu/repository/NewsRepository.java rename to src/main/java/ru/ulstu/news/NewsRepository.java index f7f4fcf..fcf5c7f 100644 --- a/src/main/java/ru/ulstu/repository/NewsRepository.java +++ b/src/main/java/ru/ulstu/news/NewsRepository.java @@ -1,9 +1,8 @@ -package ru.ulstu.repository; +package ru.ulstu.news; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import ru.ulstu.model.News; import java.util.List; diff --git a/src/main/java/ru/ulstu/service/NewsService.java b/src/main/java/ru/ulstu/news/NewsService.java similarity index 55% rename from src/main/java/ru/ulstu/service/NewsService.java rename to src/main/java/ru/ulstu/news/NewsService.java index f49dbdd..8c3d016 100644 --- a/src/main/java/ru/ulstu/service/NewsService.java +++ b/src/main/java/ru/ulstu/news/NewsService.java @@ -1,12 +1,15 @@ -package ru.ulstu.service; +package ru.ulstu.news; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import ru.ulstu.model.News; -import ru.ulstu.repository.NewsRepository; +import ru.ulstu.files.FileSystemStorageService; +import ru.ulstu.files.FileUtil; +import ru.ulstu.meeting.Meeting; +import javax.transaction.Transactional; import javax.validation.constraints.NotNull; +import java.io.IOException; import java.util.Date; import java.util.List; @@ -19,15 +22,30 @@ public class NewsService { } 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()); - newsRepository.save(news); + return newsRepository.save(news); } - public void save(News news) { + public void save(News news) throws IOException { + String fileName = System.currentTimeMillis() + ""; + if (!news.getImageFile().isEmpty()) { + news.setImageFileName(fileName); + FileUtil.saveFile(FileSystemStorageService.UPLOAD_DIR, fileName, news.getImageFile()); + } else { + news.setImageFileName(news.getImageFileName().isEmpty() ? "logo.png" : news.getImageFileName()); + } + if (news.getId() != null && (news.getId() != 0)) { newsRepository.save(news); } else { diff --git a/src/main/java/ru/ulstu/user/UserService.java b/src/main/java/ru/ulstu/user/UserService.java index 752f234..c8c9f43 100644 --- a/src/main/java/ru/ulstu/user/UserService.java +++ b/src/main/java/ru/ulstu/user/UserService.java @@ -2,6 +2,7 @@ package ru.ulstu.user; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -24,6 +25,8 @@ public class UserService implements UserDetailsService { private final PasswordEncoder passwordEncoder; private final UserRepository userRepository; private final UserRoleRepository userRoleRepository; + @Value("${admin-password}") + private String adminPassword; public UserService(PasswordEncoder passwordEncoder, UserRepository userRepository, @@ -63,7 +66,6 @@ public class UserService implements UserDetailsService { public void initDefaultAdmin() { String adminLogin = "admin"; - String adminPassword = "adminadmin"; if (getUserByLogin(adminLogin) == null) { UserRole adminRole = userRoleRepository.save(new UserRole(UserRoleConstants.ADMIN)); createUser(new User(adminLogin, adminPassword, Set.of(adminRole))); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d91e294..7c55a71 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,10 @@ +admin-password=admin spring.main.banner-mode=off logging.level.tech.athene=DEBUG server.port=8080 spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false +spring.servlet.multipart.max-file-size=100000000 +spring.servlet.multipart.max-request-size=100000000 # go to http://localhost:8080/h2-console spring.datasource.url=jdbc:h2:file:./data/db spring.datasource.driverClassName=org.h2.Driver diff --git a/src/main/resources/public/docs/plan2022.docx b/src/main/resources/public/docs/plan2022.docx new file mode 100644 index 0000000..fb2a0f7 Binary files /dev/null and b/src/main/resources/public/docs/plan2022.docx differ diff --git a/src/main/resources/public/docs/polozh.docx b/src/main/resources/public/docs/polozh.docx new file mode 100644 index 0000000..58d8c75 Binary files /dev/null and b/src/main/resources/public/docs/polozh.docx differ diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html index 2a3c093..f513109 100644 --- a/src/main/resources/templates/admin.html +++ b/src/main/resources/templates/admin.html @@ -4,5 +4,8 @@ + + + diff --git a/src/main/resources/templates/default.html b/src/main/resources/templates/default.html index 3e4c8ea..68fa32e 100644 --- a/src/main/resources/templates/default.html +++ b/src/main/resources/templates/default.html @@ -7,11 +7,17 @@ app-name - + + + - + + + + @@ -30,10 +36,16 @@ Новости + +