#6 Добавила метод добавления заведения.

Добавила проверки на роли, добавила метод удаления
parent a412fc2b
package com.example.final_exam_l; package com.example.final_exam_l;
import com.example.final_exam_l.configuration.StorageProperties;
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.properties.EnableConfigurationProperties;
@SpringBootApplication @SpringBootApplication
@EnableConfigurationProperties(StorageProperties.class)
public class FinalExamLApplication { public class FinalExamLApplication {
public static void main(String[] args) { public static void main(String[] args) {
......
package com.example.final_exam_l.configuration;
import com.example.final_exam_l.service.StorageService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfig {
@Bean
CommandLineRunner init(StorageService service) {
return (args) -> {
service.init();
};
}
}
package com.example.final_exam_l.configuration;
import com.example.final_exam_l.entity.User;
import com.example.final_exam_l.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.Principal;
@RequiredArgsConstructor
public class PutUserInModelInterceptor implements HandlerInterceptor {
private final UserRepository userRepository;
@Override
public boolean preHandle(HttpServletRequest aRequest, HttpServletResponse aResponse, Object aHandler) throws Exception {
return true;
}
@Override
public void postHandle(HttpServletRequest aRequest, HttpServletResponse aResponse, Object aHandler, ModelAndView aModelAndView) throws Exception {
if(aModelAndView != null) {
Principal principal = aRequest.getUserPrincipal();
if (principal != null){
User user = userRepository.findByEmail(principal.getName()).orElseThrow();
aModelAndView.addObject("user", user);
}
}
}
@Override
public void afterCompletion(HttpServletRequest aRequest, HttpServletResponse aResponse, Object aHandler, Exception aEx) throws Exception { }
}
package com.example.final_exam_l.configuration;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Getter
@Setter
@ConfigurationProperties("storage")
public class StorageProperties {
private String location = "upload";
}
package com.example.final_exam_l.controller;
import org.springframework.data.domain.Page;
import org.springframework.ui.Model;
public class PageableView {
protected static <T> void constructPageable(Page<T> list, int pageSize, Model model, String uri) {
if (list.hasNext()) {
model.addAttribute("nextPageLink", constructPageUri(uri, list.nextPageable().getPageNumber(), list.nextPageable().getPageSize()));
}
if (list.hasPrevious()) {
model.addAttribute("prevPageLink", constructPageUri(uri, list.previousPageable().getPageNumber(), list.previousPageable().getPageSize()));
}
model.addAttribute("hasNext", list.hasNext());
model.addAttribute("hasPrev", list.hasPrevious());
model.addAttribute("places", list.getContent());
model.addAttribute("defaultPageSize", pageSize);
}
private static String constructPageUri(String uri, int page, int size) {
return String.format("%s?page=%s&size=%s", uri, page, size);
}
}
package com.example.final_exam_l.controller; package com.example.final_exam_l.controller;
import com.example.final_exam_l.service.PlaceService; import com.example.final_exam_l.dto.add.PlaceAddDTO;
import com.example.final_exam_l.entity.Place;
import com.example.final_exam_l.entity.PlacePhoto;
import com.example.final_exam_l.repository.PlaceRepository;
import com.example.final_exam_l.service.*;
import lombok.Getter;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.security.Principal;
@Controller @Controller
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/places") @RequestMapping
public class PlaceController { public class PlaceController {
private final PlaceService service; private final PlaceService service;
private final UserService userService;
private final FileSystemStorageService fileSystemStorageService;
private final PlacePhotoService placePhotoService;
private final PropertiesService propertiesService;
private final StorageService storageService;
private final PlaceRepository repository;
@GetMapping("/{id}")
public String one(@PathVariable Integer id, Model model){
model.addAttribute("place", service.findOne(id));
return "place/place";
}
@GetMapping
public String all(Model model, Pageable pageable, HttpServletRequest uriBuilder){
Page<Place> places = service.all(pageable);
model.addAttribute("places", places);
String uri = uriBuilder.getRequestURI();
PageableView.constructPageable(places, propertiesService.getDefaultPageSize(), model, uri);
return "place/places";
}
@PreAuthorize("hasAuthority('USER') || hasAuthority('ADMIN')")
@GetMapping("/add")
public String add(){
return "place/add";
}
@PreAuthorize("hasAuthority('USER') || hasAuthority('ADMIN')")
@PostMapping("/add")
public String add(@Valid PlaceAddDTO dto,
BindingResult validationResult,
RedirectAttributes attributes,
@RequestParam("file") MultipartFile file,
Principal principal){
attributes.addFlashAttribute("dto", dto);
if (validationResult.hasFieldErrors()) {
attributes.addFlashAttribute("errors", validationResult.getFieldErrors());
return "redirect:/add";
}
else if (service.isUnique(dto.getName())) {
attributes.addFlashAttribute("nameError", "Заведение с названием " + dto.getName() + " уже существует");
return "redirect:/add";
}
if (!service.isPhoto(file) || file.isEmpty()){
attributes.addFlashAttribute("fileError", "Загрузите фото в формате png или jpg");
return "redirect:/add";
}
Place place = service.add(dto, principal.getName());
PlacePhoto placePhoto = placePhotoService.add(principal.getName(), file.getOriginalFilename(), place);
place.setPlacePhoto(placePhoto);
service.save(place);
fileSystemStorageService.store(file);
return "redirect:/" + place.getId();
}
@PreAuthorize("hasAuthority('ADMIN')")
@PostMapping("/{id}/delete")
public String delete(@PathVariable Integer id){
service.delete(id);
return "redirect:/";
}
@GetMapping("/file/{filename:.+}")
public ResponseEntity<Resource> getFilePic(@PathVariable String filename) {
{
Resource file = storageService.loadAsResource(filename);
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFilename() + "\"")
.body(file);
}
}
} }
package com.example.final_exam_l.dto.add;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.hibernate.validator.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Getter
@AllArgsConstructor
public class PlaceAddDTO {
@NotNull
@Size(min = 2, message = "Минимальная длина наименования - два символа")
private String name;
@NotNull
@Size(min = 5, message = "Минимальная длина описания - семь символов")
private String description;
}
package com.example.final_exam_l.entity; package com.example.final_exam_l.entity;
import com.sun.istack.NotNull; import javax.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import javax.persistence.*; import javax.persistence.*;
...@@ -37,6 +37,12 @@ public class Place { ...@@ -37,6 +37,12 @@ public class Place {
@Builder.Default @Builder.Default
private boolean isDel = false; private boolean isDel = false;
@OneToOne
private PlacePhoto placePhoto;
// @Column
// private String mainPhoto;
@NotNull @NotNull
@ManyToOne @ManyToOne
private User user; private User user;
......
package com.example.final_exam_l.entity; package com.example.final_exam_l.entity;
import com.sun.istack.NotNull; import javax.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import javax.persistence.*; import javax.persistence.*;
......
package com.example.final_exam_l.entity; package com.example.final_exam_l.entity;
import com.sun.istack.NotNull; import javax.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*; import javax.persistence.*;
import java.time.LocalDate;
@Getter @Getter
@Setter @Setter
...@@ -25,6 +27,12 @@ public class Review { ...@@ -25,6 +27,12 @@ public class Review {
@NotNull @NotNull
private Integer grade; private Integer grade;
@Column
@NotNull
@Builder.Default
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date = LocalDate.now();
@Column @Column
@NotNull @NotNull
@Builder.Default @Builder.Default
......
...@@ -2,11 +2,17 @@ package com.example.final_exam_l.enumiration; ...@@ -2,11 +2,17 @@ package com.example.final_exam_l.enumiration;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum UserRole { public enum UserRole implements GrantedAuthority {
ADMIN("Админ"), USER("Пользователь"); ADMIN("Админ"), USER("Пользователь");
private String russianName; private String russianName;
@Override
public String getAuthority() {
return name();
}
} }
package com.example.final_exam_l.repository; package com.example.final_exam_l.repository;
import com.example.final_exam_l.entity.Place; import com.example.final_exam_l.entity.Place;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository @Repository
public interface PlaceRepository extends JpaRepository<Place, Integer> { public interface PlaceRepository extends JpaRepository<Place, Integer> {
boolean existsByName(String name);
Page<Place> findAll(Pageable pageable);
} }
package com.example.final_exam_l.service;
import com.example.final_exam_l.configuration.StorageProperties;
import com.example.final_exam_l.exception.StorageException;
import com.example.final_exam_l.exception.StorageFileNotFoundException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
@Service
public class FileSystemStorageService implements StorageService {
private final Path rootLocation;
public FileSystemStorageService(StorageProperties properties) {
this.rootLocation = Paths.get(properties.getLocation());
}
@Override
public void init() {
try{
Files.createDirectories(rootLocation);
}
catch (IOException e){
throw new StorageException("Невозможно создать хранилище", e);
}
}
@Override
public void store(MultipartFile file) {
try{
if (file.isEmpty()){
throw new StorageException("Невозможно загрузить пустой файл");
}
Path destinationFile = this.rootLocation.resolve(
Paths.get(file.getOriginalFilename()))
.normalize().toAbsolutePath();
if (!destinationFile.getParent().equals(this.rootLocation.toAbsolutePath())){
throw new StorageException("Нельзя хранить файл за пределами текущего каталога");
}
try(InputStream stream = file.getInputStream()) {
Files.copy(stream, destinationFile, StandardCopyOption.REPLACE_EXISTING);
}
}
catch (IOException e) {
throw new StorageException("Ошибка сохранения файла", e);
}
}
@Override
public Path load(String file) {
return rootLocation.resolve(file);
}
@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("Невозможно считать файл" + filename);
}
} catch (MalformedURLException e) {
throw new StorageFileNotFoundException("Невозможно считать файл" + filename);
}
}
}
package com.example.final_exam_l.service;
import com.example.final_exam_l.entity.Place;
import com.example.final_exam_l.entity.PlacePhoto;
import com.example.final_exam_l.repository.PlacePhotoRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class PlacePhotoService {
private final PlacePhotoRepository repository;
private final UserService userService;
public PlacePhoto add(String email, String path, Place place){
PlacePhoto newFile = repository.save(PlacePhoto.builder()
.filePath(path)
.place(place)
.user(userService.findByEmail(email))
.build());
return newFile;
}
}
package com.example.final_exam_l.service; package com.example.final_exam_l.service;
import com.example.final_exam_l.dto.add.PlaceAddDTO;
import com.example.final_exam_l.entity.Place;
import com.example.final_exam_l.entity.Review;
import com.example.final_exam_l.exception.ResourceNotFoundException;
import com.example.final_exam_l.repository.PlaceRepository; import com.example.final_exam_l.repository.PlaceRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class PlaceService { public class PlaceService {
private final PlaceRepository repository; private final PlaceRepository repository;
private final UserService userService;
public Place findOne(int id){
return repository.findById(id).orElseThrow(() -> {
return new ResourceNotFoundException("Завдеение", id);
});
}
public Page<Place> all(Pageable pageable){
return repository.findAll(pageable);
}
public boolean isUnique(String name){
return repository.existsByName(name);
}
public boolean isPhoto(MultipartFile multipartFile) {
String name = multipartFile.getOriginalFilename();
String[] words = name.split("\\.");
String format = words[words.length - 1];
return format.equals("png") || format.equals("jpg");
}
public Place add(PlaceAddDTO dto, String email){
Place place = repository.save(Place.builder()
.name(dto.getName())
.description(dto.getDescription())
.user(userService.findByEmail(email))
.build());
return place;
}
public void delete(int id){
Place place = findOne(id);
place.setDel(true);
repository.save(place);
}
public Place save(Place place){
return repository.save(place);
}
public double rating(int id){
int commonGrade = findOne(id).getReviews().stream().mapToInt(Review::getGrade).sum();
int amount = findOne(id).getReviews().size();
return commonGrade / amount;
}
} }
package com.example.final_exam_l.service;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class PropertiesService {
private final SpringDataWebProperties pageableDefaultProps;
public int getDefaultPageSize() {
return pageableDefaultProps.getPageable().getDefaultPageSize();
}
}
package com.example.final_exam_l.service;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Path;
public interface StorageService {
void init();
void store(MultipartFile file);
Path load(String file);
Resource loadAsResource(String filename);
}
spring.datasource.url= jdbc:mysql://localhost:3306/final_exam spring.datasource.url= jdbc:mysql://localhost:3306/final_exam
spring.datasource.username=root spring.datasource.username=root
spring.datasource.password=12345 spring.datasource.password=12345
server.port=8001 server.port=8002
...@@ -4,6 +4,7 @@ CREATE Table `places` ( ...@@ -4,6 +4,7 @@ CREATE Table `places` (
`id` int auto_increment not null, `id` int auto_increment not null,
`name` varchar(128) not null, `name` varchar(128) not null,
`description` varchar(128) not null, `description` varchar(128) not null,
`place_photo_id` int references `photos`(`id`),
`rating` double not null, `rating` double not null,
`is_del` boolean not null, `is_del` boolean not null,
`user_id` int not null references `users`(`id`), `user_id` int not null references `users`(`id`),
......
...@@ -2,8 +2,8 @@ USE `final_exam`; ...@@ -2,8 +2,8 @@ USE `final_exam`;
CREATE Table `photos` ( CREATE Table `photos` (
`id` int auto_increment not null, `id` int auto_increment not null,
`filePath` varchar(128) not null, `file_path` varchar(128) not null,
`is_main` boolean not null, `is_main` boolean default false not null,
`user_id` int not null references `users`(`id`), `user_id` int not null references `users`(`id`),
`place_id` int not null references `places`(`id`), `place_id` int not null references `places`(`id`),
primary key (`id`) primary key (`id`)
......
...@@ -5,6 +5,7 @@ CREATE Table `reviews` ( ...@@ -5,6 +5,7 @@ CREATE Table `reviews` (
`description` varchar(128) not null, `description` varchar(128) not null,
`grade` int not null, `grade` int not null,
`is_del` boolean not null, `is_del` boolean not null,
`date` date not null,
`user_id` int not null references `users`(`id`), `user_id` int not null references `users`(`id`),
`place_id` int not null references `places`(`id`), `place_id` int not null references `places`(`id`),
primary key (`id`) primary key (`id`)
......
...@@ -8,6 +8,7 @@ body{ ...@@ -8,6 +8,7 @@ body{
.container{ .container{
max-width: 1008px; max-width: 1008px;
margin: 0 auto; margin: 0 auto;
text-align: center;
} }
a{ a{
text-decoration: none; text-decoration: none;
...@@ -52,4 +53,18 @@ label{ ...@@ -52,4 +53,18 @@ label{
} }
.info-div{ .info-div{
text-align: center; text-align: center;
}
.items{
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.item{
display: flex;
flex-direction: column;
max-width: 300px;
}
.item img{
width: 70%;
height: 70%;
} }
\ No newline at end of file
...@@ -27,9 +27,9 @@ ...@@ -27,9 +27,9 @@
<@navbar.navbar> <@navbar.navbar>
</@navbar.navbar> </@navbar.navbar>
<div class="container">
<#nested> <#nested>
</div>
<@footer.footer> <@footer.footer>
</@footer.footer> </@footer.footer>
<script src="js/jquery-3.6.0.min.js"></script> <script src="js/jquery-3.6.0.min.js"></script>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<a href="/">Заведения</a> <a href="/">Заведения</a>
</div> </div>
<div class="nav-item"> <div class="nav-item">
<a href="/places">Добавить завeдение</a> <a href="/add">Добавить завeдение</a>
</div> </div>
</div> </div>
<div class="flex"> <div class="flex">
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
<div class="nav-item"> <div class="nav-item">
<a class="nav-link" href="/login">Вход</a> <a class="nav-link" href="/login">Вход</a>
</div> </div>
<div class="nav-item">
<a class="nav-link" href="/register">Регистрация</a>
</div>
<#else> <#else>
<div class="nav-item"> <div class="nav-item">
<span>${user.email}</span> <span>${user.email}</span>
......
<div>
<#if hasPrev??>
<a id="loadPrev" href="${prevPageLink!''}"><span id="loadPrev" class="page">Назад</span></a>
<#else>
<span id="loadPrev">Назад</span>
</#if>
<#if hasNext??>
<a id="loadNext" href="${nextPageLink!''}" data-default-page-size="${defaultPageSize!5}"><span class="page">Вперед</span></a>
<#else>
<span>Вперед</span>
</#if>
</div>
\ No newline at end of file
<#import "../main.ftlh" as main/>
<@main.renderWith title="Новое заведение">
<div class="container">
<div class="card">
<h1>Добавить новое заведение</h1>
<div class="form">
<form action="/add" method="post" enctype="multipart/form-data">
<input type='hidden' value='${_csrf.token}' name='${_csrf.parameterName}'/>
<div class="col">
<label for="name" class="form-label fs-4">Наименование</label>
<input type="text" name="name" class="form-control" id="name" value="${(dto.name)!''}">
</div>
<div class="col">
<label for="description" class="form-label fs-4">Описание</label>
<input type="text" name="description" class="form-control" id="description" value="${(dto.description)!''}">
</div>
<div class="col">
<label for="file" class="form-label fs-4">Изображение</label>
<input type="file" name="file" class="form-control" id="file">
</div>
<button type="submit" class="btn btn-primary mt-1" id="btn">Регистрация</button>
</form>
</div>
</div>
<div class="error_list">
<#if errors??>
<#list errors as e>
<h2>${e.defaultMessage!'__no message__'}</h2>
</#list>
<#elseif nameError??>
<h2>${nameError}</h2>
<#elseif fileError??>
<h2>${fileError}</h2>
</#if>
</div>
</div>
</@main.renderWith>
\ No newline at end of file
<#import "../main.ftlh" as main>
<@main.renderWith title="Заведения">
<h1>Заведение ${place.name}</h1>
<div class="item">
<a href="/${place.id}">
<img src="/file/${place.placePhoto.filePath}" alt="Изображение">
<p class="info">${place.name}</p>
<p class="info">${place.user.login}</p>
<p class="info">${place.rating}</p>
<p class="info">${place.reviews?size}</p>
<#list place.reviews as r>
<p>${r.description}</p>
<p>${r.user.login}</p>
<p>${r.grade}</p>
<p>${r.date}</p>
</#list>
</a>
</div>
</@main.renderWith>
\ No newline at end of file
<#import "../main.ftlh" as main>
<@main.renderWith title="Заведения">
<h1>Заведения</h1>
<div class="items">
<#if places?? && places?size != 0>
<#list places as c>
<div class="item">
<a href="/${c.id}">
<img src="/file/${c.placePhoto.filePath}" alt="Изображение">
<p class="info">Наименование ${c.name}</p>
<p class="info">Создал ${c.user.login}</p>
<p class="info">Рейтинг ${c.rating}</p>
<p class="info">Отзывов ${c.reviews?size}</p>
<p class="info">Фото ${c.photos?size}</p>
</a>
</div>
</#list>
<#include "../pagination.ftlh">
<#else>
<p>Заведений нет</p>
<a href="/add">Добавить заведение</a>
</#if>
</div>
</@main.renderWith>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment