diff --git a/src/main/java/ru/mch/dreamjob/controller/FileController.java b/src/main/java/ru/mch/dreamjob/controller/FileController.java new file mode 100644 index 0000000..ffaf243 --- /dev/null +++ b/src/main/java/ru/mch/dreamjob/controller/FileController.java @@ -0,0 +1,28 @@ +package ru.mch.dreamjob.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.mch.dreamjob.service.FileService; + +@RestController +@RequestMapping("/files") +public class FileController { + + private final FileService fileService; + + public FileController(FileService fileService) { + this.fileService = fileService; + } + + @GetMapping("/{id}") + public ResponseEntity getById(@PathVariable int id) { + var contentOptional = fileService.getFileById(id); + if (contentOptional.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(contentOptional.get().getContent()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/mch/dreamjob/controller/VacancyController.java b/src/main/java/ru/mch/dreamjob/controller/VacancyController.java index 0cdc90f..1d95dbf 100644 --- a/src/main/java/ru/mch/dreamjob/controller/VacancyController.java +++ b/src/main/java/ru/mch/dreamjob/controller/VacancyController.java @@ -3,6 +3,8 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import ru.mch.dreamjob.dto.FileDto; import ru.mch.dreamjob.entity.Vacancy; import ru.mch.dreamjob.service.CityService; import ru.mch.dreamjob.service.VacancyService; @@ -33,9 +35,14 @@ public String getCreationPage(Model model) { } @PostMapping("/create") - public String create(@ModelAttribute Vacancy vacancy) { - vacancyService.save(vacancy); - return "redirect:/vacancies"; + public String create(@ModelAttribute Vacancy vacancy, @RequestParam MultipartFile file, Model model) { + try { + vacancyService.save(vacancy, new FileDto(file.getOriginalFilename(), file.getBytes())); + return "redirect:/vacancies"; + } catch (Exception exception) { + model.addAttribute("message", exception.getMessage()); + return "errors/404"; + } } @GetMapping("/{id}") @@ -51,13 +58,18 @@ public String getById(Model model, @PathVariable int id) { } @PostMapping("/update") - public String update(@ModelAttribute Vacancy vacancy, Model model) { - var isUpdated = vacancyService.update(vacancy); - if (!isUpdated) { - model.addAttribute("message", "Вакансия с указанным идентификатором не найдена"); + public String update(@ModelAttribute Vacancy vacancy, @RequestParam MultipartFile file, Model model) { + try { + var isUpdated = vacancyService.update(vacancy, new FileDto(file.getOriginalFilename(), file.getBytes())); + if (!isUpdated) { + model.addAttribute("message", "Вакансия с указанным идентификатором не найдена"); + return "errors/404"; + } + return "redirect:/vacancies"; + } catch (Exception exception) { + model.addAttribute("message", exception.getMessage()); return "errors/404"; } - return "redirect:/vacancies"; } @GetMapping("/delete/{id}") diff --git a/src/main/java/ru/mch/dreamjob/dto/FileDto.java b/src/main/java/ru/mch/dreamjob/dto/FileDto.java new file mode 100644 index 0000000..61095a2 --- /dev/null +++ b/src/main/java/ru/mch/dreamjob/dto/FileDto.java @@ -0,0 +1,28 @@ +package ru.mch.dreamjob.dto; + +public class FileDto { + private String name; + + private byte[] content; + + public FileDto(String name, byte[] content) { + this.name = name; + this.content = content; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } +} \ No newline at end of file diff --git a/src/main/java/ru/mch/dreamjob/entity/File.java b/src/main/java/ru/mch/dreamjob/entity/File.java new file mode 100644 index 0000000..8d2f474 --- /dev/null +++ b/src/main/java/ru/mch/dreamjob/entity/File.java @@ -0,0 +1,58 @@ +package ru.mch.dreamjob.entity; + +import java.util.Objects; + +public class File { + + private int id; + + private String name; + + private String path; + + public File(String name, String path) { + this.name = name; + this.path = path; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + File file = (File) o; + return id == file.id && Objects.equals(path, file.path); + } + + @Override + public int hashCode() { + return Objects.hash(id, path); + } +} \ No newline at end of file diff --git a/src/main/java/ru/mch/dreamjob/entity/Vacancy.java b/src/main/java/ru/mch/dreamjob/entity/Vacancy.java index 367535c..f7745ef 100644 --- a/src/main/java/ru/mch/dreamjob/entity/Vacancy.java +++ b/src/main/java/ru/mch/dreamjob/entity/Vacancy.java @@ -17,15 +17,18 @@ public class Vacancy { private int cityId; + private int fileId; + public Vacancy() { } - public Vacancy(int id, String title, String description, boolean visible, int cityId) { + public Vacancy(int id, String title, String description, boolean visible, int cityId, int fileId) { this.id = id; this.title = title; this.description = description; this.visible = visible; this.cityId = cityId; + this.fileId = fileId; } public int getId() { @@ -76,6 +79,14 @@ public void setCityId(int cityId) { this.cityId = cityId; } + public int getFileId() { + return fileId; + } + + public void setFileId(int fileId) { + this.fileId = fileId; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/ru/mch/dreamjob/repository/FileRepository.java b/src/main/java/ru/mch/dreamjob/repository/FileRepository.java new file mode 100644 index 0000000..92c679a --- /dev/null +++ b/src/main/java/ru/mch/dreamjob/repository/FileRepository.java @@ -0,0 +1,14 @@ +package ru.mch.dreamjob.repository; + +import ru.mch.dreamjob.entity.File; + +import java.util.Optional; + +public interface FileRepository { + + File save(File file); + + Optional findById(int id); + + boolean deleteById(int id); +} \ No newline at end of file diff --git a/src/main/java/ru/mch/dreamjob/repository/MemoryFileRepository.java b/src/main/java/ru/mch/dreamjob/repository/MemoryFileRepository.java new file mode 100644 index 0000000..512b320 --- /dev/null +++ b/src/main/java/ru/mch/dreamjob/repository/MemoryFileRepository.java @@ -0,0 +1,34 @@ +package ru.mch.dreamjob.repository; + +import org.springframework.stereotype.Repository; +import ru.mch.dreamjob.entity.File; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +@Repository +public class MemoryFileRepository implements FileRepository { + + private final AtomicInteger nextId = new AtomicInteger(0); + + private final Map files = new ConcurrentHashMap<>(); + + @Override + public File save(File file) { + file.setId(nextId.incrementAndGet()); + files.put(file.getId(), file); + return file; + } + + @Override + public Optional findById(int id) { + return Optional.ofNullable(files.get(id)); + } + + @Override + public boolean deleteById(int id) { + return files.remove(id) != null; + } +} \ No newline at end of file diff --git a/src/main/java/ru/mch/dreamjob/repository/MemoryVacancyRepository.java b/src/main/java/ru/mch/dreamjob/repository/MemoryVacancyRepository.java index 2c8db33..4eecc32 100644 --- a/src/main/java/ru/mch/dreamjob/repository/MemoryVacancyRepository.java +++ b/src/main/java/ru/mch/dreamjob/repository/MemoryVacancyRepository.java @@ -16,12 +16,12 @@ public class MemoryVacancyRepository implements VacancyRepository { private final Map vacancies = new HashMap<>(); private MemoryVacancyRepository() { - save(new Vacancy(0, "Intern Java Developer", "description", false, 1)); - save(new Vacancy(0, "Junior Java Developer", "description", false, 1)); - save(new Vacancy(0, "Junior+ Java Developer", "description", false, 1)); - save(new Vacancy(0, "Middle Java Developer", "description", false, 2)); - save(new Vacancy(0, "Middle+ Java Developer", "description", false, 2)); - save(new Vacancy(0, "Senior Java Developer", "description", false, 3)); + save(new Vacancy(0, "Intern Java Developer", "description", false, 1,0)); + save(new Vacancy(0, "Junior Java Developer", "description", false, 1,0)); + save(new Vacancy(0, "Junior+ Java Developer", "description", false, 1,0)); + save(new Vacancy(0, "Middle Java Developer", "description", false, 2,0)); + save(new Vacancy(0, "Middle+ Java Developer", "description", false, 2,0)); + save(new Vacancy(0, "Senior Java Developer", "description", false, 3,0)); } @Override @@ -44,7 +44,8 @@ public boolean update(Vacancy vacancy) { vacancy.getTitle(), vacancy.getDescription(), vacancy.getVisible(), - vacancy.getCityId())) + vacancy.getCityId(), + vacancy.getFileId())) != null; } diff --git a/src/main/java/ru/mch/dreamjob/service/FileService.java b/src/main/java/ru/mch/dreamjob/service/FileService.java new file mode 100644 index 0000000..470f761 --- /dev/null +++ b/src/main/java/ru/mch/dreamjob/service/FileService.java @@ -0,0 +1,15 @@ +package ru.mch.dreamjob.service; + +import ru.mch.dreamjob.dto.FileDto; +import ru.mch.dreamjob.entity.File; + +import java.util.Optional; + +public interface FileService { + + File save(FileDto fileDto); + + Optional getFileById(int id); + + boolean deleteById(int id); +} \ No newline at end of file diff --git a/src/main/java/ru/mch/dreamjob/service/SimpleFileService.java b/src/main/java/ru/mch/dreamjob/service/SimpleFileService.java new file mode 100644 index 0000000..c46c476 --- /dev/null +++ b/src/main/java/ru/mch/dreamjob/service/SimpleFileService.java @@ -0,0 +1,92 @@ +package ru.mch.dreamjob.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import ru.mch.dreamjob.dto.FileDto; +import ru.mch.dreamjob.entity.File; +import ru.mch.dreamjob.repository.FileRepository; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.UUID; + +@Service +public class SimpleFileService implements FileService { + + private final FileRepository fileRepository; + + private final String storageDirectory; + + public SimpleFileService(FileRepository fileRepository, + @Value("${file.directory}") String storageDirectory) { + this.fileRepository = fileRepository; + this.storageDirectory = storageDirectory; + createStorageDirectory(storageDirectory); + } + + private void createStorageDirectory(String path) { + try { + Files.createDirectories(Path.of(path)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public File save(FileDto fileDto) { + var path = getNewFilePath(fileDto.getName()); + writeFileBytes(path, fileDto.getContent()); + return fileRepository.save(new File(fileDto.getName(), path)); + } + + private String getNewFilePath(String sourceName) { + return storageDirectory + java.io.File.separator + UUID.randomUUID() + sourceName; + } + + private void writeFileBytes(String path, byte[] content) { + try { + Files.write(Path.of(path), content); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Optional getFileById(int id) { + var fileOptional = fileRepository.findById(id); + if (fileOptional.isEmpty()) { + return Optional.empty(); + } + var content = readFileAsBytes(fileOptional.get().getPath()); + return Optional.of(new FileDto(fileOptional.get().getName(), content)); + } + + private byte[] readFileAsBytes(String path) { + try { + return Files.readAllBytes(Path.of(path)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean deleteById(int id) { + var fileOptional = fileRepository.findById(id); + if (fileOptional.isEmpty()) { + return false; + } + deleteFile(fileOptional.get().getPath()); + return fileRepository.deleteById(id); + } + + private void deleteFile(String path) { + try { + Files.deleteIfExists(Path.of(path)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file diff --git a/src/main/java/ru/mch/dreamjob/service/SimpleVacancyService.java b/src/main/java/ru/mch/dreamjob/service/SimpleVacancyService.java index c65b5a9..f45222e 100644 --- a/src/main/java/ru/mch/dreamjob/service/SimpleVacancyService.java +++ b/src/main/java/ru/mch/dreamjob/service/SimpleVacancyService.java @@ -1,6 +1,7 @@ package ru.mch.dreamjob.service; import org.springframework.stereotype.Service; +import ru.mch.dreamjob.dto.FileDto; import ru.mch.dreamjob.entity.Vacancy; import ru.mch.dreamjob.repository.VacancyRepository; @@ -12,23 +13,46 @@ public class SimpleVacancyService implements VacancyService { private final VacancyRepository vacancyRepository; - public SimpleVacancyService(VacancyRepository vacancyRepository) { - this.vacancyRepository = vacancyRepository; + private final FileService fileService; + + public SimpleVacancyService(VacancyRepository sql2oVacancyRepository, FileService fileService) { + this.vacancyRepository = sql2oVacancyRepository; + this.fileService = fileService; } @Override - public Vacancy save(Vacancy vacancy) { + public Vacancy save(Vacancy vacancy, FileDto image) { + saveNewFile(vacancy, image); return vacancyRepository.save(vacancy); } + private void saveNewFile(Vacancy vacancy, FileDto image) { + var file = fileService.save(image); + vacancy.setFileId(file.getId()); + } + @Override public boolean deleteById(int id) { - return vacancyRepository.deleteById(id); + var fileOptional = findById(id); + if (fileOptional.isEmpty()) { + return false; + } + var isDeleted = vacancyRepository.deleteById(id); + fileService.deleteById(fileOptional.get().getFileId()); + return isDeleted; } @Override - public boolean update(Vacancy vacancy) { - return vacancyRepository.update(vacancy); + public boolean update(Vacancy vacancy, FileDto image) { + var isNewFileEmpty = image.getContent().length == 0; + if (isNewFileEmpty) { + return vacancyRepository.update(vacancy); + } + var oldFileId = vacancy.getFileId(); + saveNewFile(vacancy, image); + var isUpdated = vacancyRepository.update(vacancy); + fileService.deleteById(oldFileId); + return isUpdated; } @Override diff --git a/src/main/java/ru/mch/dreamjob/service/VacancyService.java b/src/main/java/ru/mch/dreamjob/service/VacancyService.java index 6380d6a..ed920d2 100644 --- a/src/main/java/ru/mch/dreamjob/service/VacancyService.java +++ b/src/main/java/ru/mch/dreamjob/service/VacancyService.java @@ -1,5 +1,6 @@ package ru.mch.dreamjob.service; +import ru.mch.dreamjob.dto.FileDto; import ru.mch.dreamjob.entity.Vacancy; import java.util.Collection; @@ -7,11 +8,11 @@ public interface VacancyService { - Vacancy save(Vacancy vacancy); + Vacancy save(Vacancy vacancy, FileDto image); boolean deleteById(int id); - boolean update(Vacancy vacancy); + boolean update(Vacancy vacancy, FileDto image); Optional findById(int id); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..070c422 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,3 @@ +file.directory=files + +spring.servlet.multipart.max-file-size=10MB \ No newline at end of file diff --git a/src/main/resources/templates/vacancies/create.html b/src/main/resources/templates/vacancies/create.html index c11875d..54aa513 100644 --- a/src/main/resources/templates/vacancies/create.html +++ b/src/main/resources/templates/vacancies/create.html @@ -33,11 +33,15 @@
-
+
+
+ + +
-
- - -
-
- - + +
+
+
+ No image +
+
+ +
+
+
+
+ + +
+
+ + +
+