feat: add upload file
This commit is contained in:
@@ -3,6 +3,7 @@ package aplp.backend.lms.common.storage;
|
||||
import aplp.backend.core.common.response.ApiResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@@ -26,4 +27,10 @@ public class FileController {
|
||||
return ResponseEntity.status(HttpStatus.CREATED)
|
||||
.body(ApiResponse.ok(fileStorageService.upload(file, folder)));
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public ResponseEntity<ApiResponse<Void>> delete(@RequestParam("objectKey") String objectKey) {
|
||||
fileStorageService.delete(objectKey);
|
||||
return ResponseEntity.ok(ApiResponse.ok("Deleted successfully", null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import aplp.backend.core.common.exception.DomainException;
|
||||
import aplp.backend.core.common.exception.ErrorCode;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.PutObjectArgs;
|
||||
import io.minio.RemoveObjectArgs;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -44,13 +45,53 @@ public class FileStorageService {
|
||||
}
|
||||
|
||||
return new FileResDto(
|
||||
properties.publicUrl() + "/" + properties.bucket() + "/" + objectKey,
|
||||
publicUrl(objectKey),
|
||||
objectKey,
|
||||
file.getSize(),
|
||||
contentType
|
||||
);
|
||||
}
|
||||
|
||||
public String publicUrl(String objectKey) {
|
||||
if (objectKey == null || objectKey.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
if (isAbsoluteUrl(objectKey)) {
|
||||
return objectKey;
|
||||
}
|
||||
return properties.publicUrl() + "/" + properties.bucket() + "/" + objectKey;
|
||||
}
|
||||
|
||||
public void delete(String objectKey) {
|
||||
String key = toObjectKey(objectKey);
|
||||
if (key == null) {
|
||||
throw new DomainException(ErrorCode.INVALID_ARGUMENT, "objectKey is required");
|
||||
}
|
||||
try {
|
||||
minioClient.removeObject(RemoveObjectArgs.builder()
|
||||
.bucket(properties.bucket())
|
||||
.object(key)
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
throw new DomainException(ErrorCode.INTERNAL_ERROR, "Failed to delete file: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String toObjectKey(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
String prefix = properties.publicUrl() + "/" + properties.bucket() + "/";
|
||||
if (value.startsWith(prefix)) {
|
||||
return value.substring(prefix.length());
|
||||
}
|
||||
return isAbsoluteUrl(value) ? null : value;
|
||||
}
|
||||
|
||||
private static boolean isAbsoluteUrl(String value) {
|
||||
return value.startsWith("http://") || value.startsWith("https://");
|
||||
}
|
||||
|
||||
static String buildObjectKey(String folder, String contentType, String originalFilename) {
|
||||
return sanitizeFolder(folder) + "/" + UUID.randomUUID() + "." + extensionFor(contentType, originalFilename);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package aplp.backend.lms.course.application.dtos;
|
||||
|
||||
import aplp.backend.lms.common.storage.FileStorageService;
|
||||
import aplp.backend.lms.course.domain.entities.Course;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -11,11 +12,12 @@ public record CourseResDto(
|
||||
String slug,
|
||||
String description,
|
||||
String image,
|
||||
String imageUrl,
|
||||
boolean isPublished,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
public static CourseResDto from(Course course) {
|
||||
public static CourseResDto from(Course course, FileStorageService fileStorageService) {
|
||||
return new CourseResDto(
|
||||
course.getId(),
|
||||
course.getCourseCode(),
|
||||
@@ -23,6 +25,7 @@ public record CourseResDto(
|
||||
course.getSlug(),
|
||||
course.getDescription(),
|
||||
course.getImage(),
|
||||
fileStorageService.publicUrl(course.getImage()),
|
||||
course.isPublished(),
|
||||
course.getCreatedAt(),
|
||||
course.getUpdatedAt()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package aplp.backend.lms.course.application.mappers;
|
||||
|
||||
import aplp.backend.lms.course.application.dtos.CourseReqDto;
|
||||
import aplp.backend.lms.course.application.dtos.CourseResDto;
|
||||
import aplp.backend.lms.course.domain.entities.Course;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
@@ -10,9 +9,6 @@ import org.mapstruct.MappingTarget;
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface CourseMapper {
|
||||
|
||||
@Mapping(target = "isPublished", source = "published")
|
||||
CourseResDto toResponse(Course course);
|
||||
|
||||
@Mapping(target = "published", source = "isPublished")
|
||||
Course toEntity(CourseReqDto request);
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package aplp.backend.lms.course.application.services;
|
||||
|
||||
import aplp.backend.core.common.exception.ResourceNotFoundException;
|
||||
import aplp.backend.lms.common.storage.FileStorageService;
|
||||
import aplp.backend.lms.course.application.dtos.CourseReqDto;
|
||||
import aplp.backend.lms.course.application.dtos.CourseResDto;
|
||||
import aplp.backend.lms.course.application.mappers.CourseMapper;
|
||||
import aplp.backend.lms.course.domain.entities.Course;
|
||||
import aplp.backend.lms.course.domain.repositories.CourseRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
@@ -13,18 +16,25 @@ import java.util.List;
|
||||
@Service
|
||||
public class CourseService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CourseService.class);
|
||||
|
||||
private final CourseRepository courseRepository;
|
||||
private final CourseMapper courseMapper;
|
||||
private final FileStorageService fileStorageService;
|
||||
|
||||
public CourseService(CourseRepository courseRepository, CourseMapper courseMapper) {
|
||||
public CourseService(
|
||||
CourseRepository courseRepository,
|
||||
CourseMapper courseMapper,
|
||||
FileStorageService fileStorageService) {
|
||||
this.courseRepository = courseRepository;
|
||||
this.courseMapper = courseMapper;
|
||||
this.fileStorageService = fileStorageService;
|
||||
}
|
||||
|
||||
public List<CourseResDto> getAll() {
|
||||
return courseRepository.findAll()
|
||||
.stream()
|
||||
.map(CourseResDto::from)
|
||||
.map(course -> CourseResDto.from(course, fileStorageService))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -32,26 +42,41 @@ public class CourseService {
|
||||
Course course = courseRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Course not found"));
|
||||
|
||||
return CourseResDto.from(course);
|
||||
return CourseResDto.from(course, fileStorageService);
|
||||
}
|
||||
|
||||
public CourseResDto create(CourseReqDto request) {
|
||||
Course course = courseMapper.toEntity(request);
|
||||
return courseMapper.toResponse(courseRepository.save(course));
|
||||
Course course = courseRepository.save(courseMapper.toEntity(request));
|
||||
return CourseResDto.from(course, fileStorageService);
|
||||
}
|
||||
|
||||
public CourseResDto update(Long id, CourseReqDto request) {
|
||||
Course course = courseRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Course not found"));
|
||||
|
||||
String previousImage = course.getImage();
|
||||
courseMapper.updateEntity(request, course);
|
||||
Course saved = courseRepository.save(course);
|
||||
|
||||
return courseMapper.toResponse(courseRepository.save(course));
|
||||
if (previousImage != null && !previousImage.isBlank() && !previousImage.equals(saved.getImage())) {
|
||||
deleteFileQuietly(previousImage);
|
||||
}
|
||||
|
||||
return CourseResDto.from(saved, fileStorageService);
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
courseRepository.findById(id)
|
||||
Course course = courseRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Course not found"));
|
||||
courseRepository.deleteById(id);
|
||||
deleteFileQuietly(course.getImage());
|
||||
}
|
||||
|
||||
private void deleteFileQuietly(String objectKey) {
|
||||
try {
|
||||
fileStorageService.delete(objectKey);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to delete image '{}': {}", objectKey, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ minio:
|
||||
endpoint: https://minio.koda.id.vn
|
||||
access-key: CHANGE_ME
|
||||
secret-key: CHANGE_ME
|
||||
bucket: lms
|
||||
bucket: aplp
|
||||
public-url: https://minio.koda.id.vn
|
||||
|
||||
springdoc:
|
||||
|
||||
Reference in New Issue
Block a user