Skip to content

Commit

Permalink
add media service (#148)
Browse files Browse the repository at this point in the history
* add media service

* fix some error and config download

* fix sonar
  • Loading branch information
khanhduzz authored Sep 27, 2024
1 parent e6bcf2e commit 83471c9
Show file tree
Hide file tree
Showing 12 changed files with 445 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/main/java/com/fjb/sunrise/config/WebServletConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,6 @@ public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/auth/login").setViewName(AUTH_VIEW);
registry.addViewController("/auth/register").setViewName(AUTH_VIEW);
registry.addViewController("/category/index").setViewName("category/index");
registry.addViewController("/medias").setViewName("media/index");
}
}
93 changes: 93 additions & 0 deletions src/main/java/com/fjb/sunrise/controllers/MediaController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.fjb.sunrise.controllers;

import com.fjb.sunrise.dtos.responses.MediaResponse;
import com.fjb.sunrise.exceptions.BadRequestException;
import com.fjb.sunrise.models.Media;
import com.fjb.sunrise.services.MediaService;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

@Controller
@RequiredArgsConstructor
@RequestMapping("/medias")
public class MediaController {

private final MediaService mediaService;

@GetMapping
public ModelAndView index() {
return new ModelAndView("media/index");
}

@PostMapping("/upload")
public ModelAndView uploadFile(@RequestParam("file") MultipartFile file) {
try {
mediaService.store(file);
} catch (Exception ignored) {
throw new BadRequestException("Error when upload file");
}
return new ModelAndView("redirect:/medias");
}

@GetMapping("/files")
public ModelAndView getListFiles() {
List<MediaResponse> files = mediaService.getAllMedias().map(dbFile -> {
String fileDownloadUri = ServletUriComponentsBuilder
.fromCurrentContextPath()
.path("/medias/files/")
.path(dbFile.getFileCode())
.toUriString();

return new MediaResponse(
dbFile.getName(),
dbFile.getFileCode(),
fileDownloadUri,
dbFile.getType(),
dbFile.getData().length);
}).toList();

ModelAndView modelAndView = new ModelAndView("media/index");

modelAndView.addObject("files", files);
return modelAndView;
}

@GetMapping("/files/{fileCode}")
public ResponseEntity<byte[]> getFile(@PathVariable String fileCode) {
Media fileDB = mediaService.getMedia(fileCode);

return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileDB.getName() + "\"")
.body(fileDB.getData());
}

@GetMapping("/media/{fileCode}")
public ResponseEntity<ByteArrayResource> getMedia(@PathVariable String fileCode) {
Media media = mediaService.getMedia(fileCode);
if (media == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}

ByteArrayResource resource = new ByteArrayResource(media.getData());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + media.getName() + "\"")
.contentType(MediaType.parseMediaType(media.getType()))
.contentLength(media.getData().length)
.body(resource);
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/fjb/sunrise/dtos/responses/MediaResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.fjb.sunrise.dtos.responses;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class MediaResponse {
private String name;
private String fileCode;
private String url;
private String type;
private long size;
}
39 changes: 39 additions & 0 deletions src/main/java/com/fjb/sunrise/models/Media.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.fjb.sunrise.models;

import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@Builder
@Table(name = "medias")
@AllArgsConstructor
@NoArgsConstructor
public class Media {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

private String type;

@Lob
@Basic(fetch = FetchType.LAZY)
private byte[] data;

private String fileCode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.fjb.sunrise.repositories;

import com.fjb.sunrise.models.Media;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MediaRepository extends JpaRepository<Media, Long> {
Media findByFileCode(String fileCode);
}
46 changes: 46 additions & 0 deletions src/main/java/com/fjb/sunrise/services/MediaService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.fjb.sunrise.services;

import com.fjb.sunrise.exceptions.BadRequestException;
import com.fjb.sunrise.models.Media;
import com.fjb.sunrise.repositories.MediaRepository;
import java.io.IOException;
import java.util.UUID;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MediaService {

private final MediaRepository mediaRepository;

public Media store(MultipartFile file) throws IOException {
if (file == null || file.getOriginalFilename() == null) {
throw new BadRequestException("File is null");
}
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
String fileCode = UUID.randomUUID().toString();
Media media = Media.builder()
.name(fileName)
.type(file.getContentType())
.data(file.getBytes())
.fileCode(fileCode)
.build();

return mediaRepository.save(media);
}

@Transactional
public Media getMedia(String fileCode) {
return mediaRepository.findByFileCode(fileCode);
}

public Stream<Media> getAllMedias() {
return mediaRepository.findAll().stream();
}
}
5 changes: 5 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ spring:
security: DEBUG
aop: DEBUG

servlet:
multipart:
max-file-size: 2MB
max-request-size: 2MB

server:
port: ${SERVER_LOCAL_PORT}
servlet:
Expand Down
10 changes: 0 additions & 10 deletions src/main/resources/templates/error.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,6 @@ <h2>Field Errors:</h2>
<!-- Core plugin JavaScript-->
<script th:src="@{/vendor/jquery-easing/jquery.easing.min.js}" type="application/javascript"></script>

<!-- Custom scripts for all pages-->
<script th:src="@{/js/sb-admin-2.min.js}" type="application/javascript"></script>

<!-- Page level plugins -->
<script th:src="@{/vendor/chart.js/Chart.min.js}" type="application/javascript"></script>

<!-- Page level custom scripts -->
<script th:src="@{/js/demo/chart-area-demo.js}" type="application/javascript"></script>
<script th:src="@{/js/demo/chart-pie-demo.js}" type="application/javascript"></script>

<!-- Page level plugins -->
<script th:src="@{/vendor/datatables/jquery.dataTables.min.js}" type="application/javascript"></script>
<script th:src="@{/vendor/datatables/dataTables.bootstrap4.min.js}" type="application/javascript"></script>
Expand Down
105 changes: 105 additions & 0 deletions src/main/resources/templates/media/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Media</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" type="text/css">
<script th:src="@{/js/bootstrap.bundle.min.js}" type="text/javascript"></script>

<!-- Custom fonts for this template-->
<link th:href="@{/vendor/fontawesome-free/css/all.min.css}" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">

<!-- Custom styles for this template-->
<link th:href="@{/css/sb-admin-2.min.css}" rel="stylesheet" type="text/css">
<!-- Custom styles for this page -->
<link th:href="@{/vendor/datatables/dataTables.bootstrap4.min.css}" rel="stylesheet" type="text/css">
<script th:src="@{/js/bootstrap.bundle.min.js}" type="text/javascript"></script>
</head>
<body id="page-top">

<!-- Page Wrapper -->
<div id="wrapper">

<!-- Sidebar from fragment -->
<div th:replace="~{tab_bar :: tab-bar}"></div>

<!-- Content Wrapper -->
<div id="content-wrapper" class="d-flex flex-column">

<!-- Main Content -->
<div id="content">

<!-- Header from fragment -->
<div th:replace="~{header :: header}"></div>

<div class="container-fluid">

<div class="container mt-5">
<h2>Upload Media Files</h2>

<form th:action="@{/medias/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="file">Choose file</label>
<input type="file" id="file" name="file" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Upload</button>
</form>

<hr>

<div th:if="${files != null and #lists.size(files) > 0}">
<h3>Uploaded Files</h3>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>File Code</th>
<th>Type</th>
<th>Size (bytes)</th>
<th>Download</th>
</tr>
</thead>
<tbody>
<tr th:each="file : ${files}">
<td th:text="${file.name}"></td>
<td th:text="${file.fileCode}"></td>
<td th:text="${file.type}"></td>
<td th:text="${file.size}"></td>
<td>
<a th:href="${file.url}" class="btn btn-success btn-sm">Download</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>

</div>

</div>

</div>

</div>

<!-- Bootstrap core JavaScript-->
<script th:src="@{/vendor/jquery/jquery.min.js}" type="application/javascript"></script>
<script th:src="@{/vendor/bootstrap/js/bootstrap.bundle.min.js}" type="application/javascript"></script>

<!-- Core plugin JavaScript-->
<script th:src="@{/vendor/jquery-easing/jquery.easing.min.js}" type="application/javascript"></script>

<!-- Custom scripts for all pages-->
<script th:src="@{/js/sb-admin-2.min.js}" type="application/javascript"></script>

<!-- Page level plugins -->
<script th:src="@{/vendor/datatables/jquery.dataTables.min.js}" type="application/javascript"></script>
<script th:src="@{/vendor/datatables/dataTables.bootstrap4.min.js}" type="application/javascript"></script>
</body>
</html>
7 changes: 7 additions & 0 deletions src/main/resources/templates/tab_bar.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@
</a>
</li>

<li class="nav-item">
<a class="nav-link collapsed" th:href="@{/medias/files}">
<i class="fas fa-fw fa-folder"></i>
<span>Media</span>
</a>
</li>

<!-- Divider -->
<hr class="sidebar-divider">

Expand Down
Loading

0 comments on commit 83471c9

Please sign in to comment.