Add fileName property to models and update related components for file handling
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package com.jambotronGroup.jambotron.controllers;
|
||||
|
||||
import com.jambotronGroup.jambotron.fileUpload.Base64ImageResponse;
|
||||
import com.jambotronGroup.jambotron.fileUpload.FilesStorageService;
|
||||
import com.jambotronGroup.jambotron.fileUpload.ResponseImageUploadResult;
|
||||
import com.jambotronGroup.jambotron.fileUpload.ResponseMessage;
|
||||
import com.jambotronGroup.jambotron.model.FileInfo;
|
||||
import com.jambotronGroup.jambotron.model.User;
|
||||
import com.jambotronGroup.jambotron.payload.request.Base64ImageRequest;
|
||||
import com.jambotronGroup.jambotron.payload.request.SaveZhipuAiImageRequest;
|
||||
import com.jambotronGroup.jambotron.payload.request.UploadImageResponse;
|
||||
import com.jambotronGroup.jambotron.security.AuthenticationFacade;
|
||||
import com.jambotronGroup.jambotron.system.SystemServiceImpl;
|
||||
import com.jambotronGroup.jambotron.utils.FilesRoutingHelper;
|
||||
@@ -16,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -28,7 +32,9 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
@@ -129,7 +135,6 @@ public class FilesController {
|
||||
return ResponseEntity.status(HttpStatus.OK).body(fileInfos);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/api/file/files")
|
||||
public ResponseEntity<List<FileInfo>> getListFiles() {
|
||||
List<FileInfo> fileInfos = storageService.loadAll().map(path -> {
|
||||
@@ -157,4 +162,48 @@ public class FilesController {
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file);
|
||||
}
|
||||
|
||||
@PostMapping("/api/user/user-images/base64-image")
|
||||
public ResponseEntity<Base64ImageResponse> getUserBase64Image(@RequestBody String imageUrl) {
|
||||
User user = authenticationFacade.getUser();
|
||||
String message = "";
|
||||
try {
|
||||
String filename = storageService.getFileNameFromUrl(imageUrl);
|
||||
Base64ImageResponse base64Image = storageService.getUserImageAsBase64(user.getId().toString(),filename);
|
||||
|
||||
return ResponseEntity.ok(base64Image);
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("Error retrieving base64 image for user {}: {}", user.getId(), e.getMessage());
|
||||
message = "Error retrieving base64 image for fileUrl" + imageUrl + ". \tError: " + e.getMessage();
|
||||
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new Base64ImageResponse(message));
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/api/user/user-images/upload-base64-image")
|
||||
public ResponseEntity<UploadImageResponse> uploadBase64Image(@RequestBody Base64ImageRequest base64ImageRequest) {
|
||||
if (base64ImageRequest.getMime() == null || base64ImageRequest.getData() == null) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
User user = authenticationFacade.getUser();
|
||||
|
||||
try {
|
||||
|
||||
String fullFileName = storageService.saveUserBase64Image(user.getId().toString(),base64ImageRequest.getData(), base64ImageRequest.getMime());
|
||||
|
||||
String url = FilesRoutingHelper.getUserImageUrl(fullFileName);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.OK).body(new UploadImageResponse(
|
||||
storageService.getFileNameFromUrl(url),
|
||||
url,
|
||||
base64ImageRequest.getMime(),
|
||||
base64ImageRequest.getData().length()
|
||||
));
|
||||
} catch (Exception e) {
|
||||
logger.error("Error uploading base64 image for user {}: {}", user.getId(), e.getMessage());
|
||||
String message = "Error uploading base64 image. Error: " + e.getMessage();
|
||||
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new UploadImageResponse(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,6 +177,7 @@ public class TutorialController {
|
||||
servTutorial.setDescription(tutorial.getDescription());
|
||||
servTutorial.setPublished(tutorial.isPublished());
|
||||
servTutorial.setTobepublished(tutorial.isTobepublished());
|
||||
servTutorial.setBody(tutorial.getBody());
|
||||
|
||||
try {
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.jambotronGroup.jambotron.fileUpload;
|
||||
|
||||
public class Base64ImageResponse extends ResponseMessage {
|
||||
private String mime;
|
||||
private String data;
|
||||
|
||||
public Base64ImageResponse(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public Base64ImageResponse(String mime, String data) {
|
||||
super("Image data retrieved successfully");
|
||||
this.mime = mime;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getMime() {
|
||||
return mime;
|
||||
}
|
||||
|
||||
public void setMime(String mime) {
|
||||
this.mime = mime;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.jambotronGroup.jambotron.fileUpload;
|
||||
|
||||
import com.jambotronGroup.jambotron.payload.request.UploadImageResponse;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -14,12 +15,16 @@ public interface FilesStorageService {
|
||||
|
||||
public String save(String userID,MultipartFile file);
|
||||
|
||||
public String saveUserBase64Image(String userID, String base64Image, String mimeType);
|
||||
|
||||
public void save(MultipartFile file);
|
||||
|
||||
public Resource load(String filename);
|
||||
|
||||
public Resource loadUserImage(String userID, String filename);
|
||||
|
||||
public Base64ImageResponse getUserImageAsBase64(String userID, String filename);
|
||||
|
||||
public void deleteAll();
|
||||
|
||||
public Stream<Path> loadAll();
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.jambotronGroup.jambotron.fileUpload;
|
||||
|
||||
|
||||
import com.jambotronGroup.jambotron.controllers.AuthController;
|
||||
import com.jambotronGroup.jambotron.payload.request.UploadImageResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -17,6 +18,7 @@ import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
@@ -24,6 +26,17 @@ public class FilesStorageServiceImpl implements FilesStorageService {
|
||||
|
||||
private static final Logger _logger = LoggerFactory.getLogger(AuthController.class);
|
||||
|
||||
private static final Set<String> ALLOWED_MIME = Set.of(
|
||||
"image/png", "image/jpeg", "image/webp", "image/gif"
|
||||
);
|
||||
|
||||
private static final Map<String, String> EXT_BY_MIME = Map.of(
|
||||
"image/png", "png",
|
||||
"image/jpeg", "jpg",
|
||||
"image/webp", "webp",
|
||||
"image/gif", "gif"
|
||||
);
|
||||
|
||||
|
||||
// Update paths to use the Docker volume
|
||||
private final Path root = Paths.get("/jambotron_data/uploads/user-images/");
|
||||
@@ -54,6 +67,29 @@ public class FilesStorageServiceImpl implements FilesStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base64ImageResponse getUserImageAsBase64(String userID,String filename){
|
||||
|
||||
Path userPath = this.root.resolve(userID);
|
||||
Path file = userPath.resolve(filename);
|
||||
try {
|
||||
|
||||
|
||||
byte[] bytes = Files.readAllBytes(file);
|
||||
String mime = Files.probeContentType(file);
|
||||
if (mime == null || mime.isEmpty()) {
|
||||
mime = "application/octet-stream"; // Default MIME type if not found
|
||||
}
|
||||
|
||||
String base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
|
||||
return new Base64ImageResponse(mime,base64);
|
||||
}catch (Exception e) {
|
||||
_logger.error(" getUserImageAsBase64 Error loading user image: " + filename, e);
|
||||
throw new RuntimeException("getUserImageAsBase64 Could not load user image: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a file from a user's directory to the public directory with a new name.
|
||||
* StandardCopyOption.REPLACE_EXISTING
|
||||
@@ -192,4 +228,70 @@ public class FilesStorageServiceImpl implements FilesStorageService {
|
||||
throw new RuntimeException("Could not load the files!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String saveUserBase64Image(String userID, String base64Image, String mimeType) {
|
||||
|
||||
if (!ALLOWED_MIME.contains(mimeType)) {
|
||||
_logger.error("Unsupported MIME type: " + mimeType);
|
||||
throw new RuntimeException("Unsupported MIME type: " + mimeType);
|
||||
|
||||
}
|
||||
|
||||
String ext = EXT_BY_MIME.get(mimeType);
|
||||
if (ext == null) {
|
||||
throw new RuntimeException("No extension found for MIME type: " + mimeType);
|
||||
}
|
||||
|
||||
byte[] bytes;
|
||||
try {
|
||||
bytes = Base64.getDecoder().decode(base64Image);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Invalid Base64 data: " + e.getMessage());
|
||||
}
|
||||
if (!looksLikeMime(mimeType,bytes)) {
|
||||
throw new RuntimeException("Base64 data does not match the expected MIME type: " + mimeType);
|
||||
}
|
||||
|
||||
String id = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
String safeBaseName = "user-image-" + userID;
|
||||
String storedName = safeBaseName + "-" + id + "." + ext;
|
||||
|
||||
|
||||
try {
|
||||
Path userPath = this.root.resolve(userID);
|
||||
userPath.toFile().mkdirs(); // Ensure user directory exists
|
||||
|
||||
Path targetPath = userPath.resolve(storedName);
|
||||
|
||||
Files.write(targetPath, bytes);
|
||||
|
||||
return targetPath.getFileName().toString();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not save the image: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Very lightweight magic-number checks
|
||||
private boolean looksLikeMime(String mime, byte[] b) {
|
||||
if (b.length < 12) return false;
|
||||
switch (mime) {
|
||||
case "image/png":
|
||||
return b[0]==(byte)0x89 && b[1]==0x50 && b[2]==0x4E && b[3]==0x47 && // 89 'P''N''G'
|
||||
b[4]==0x0D && b[5]==0x0A && b[6]==0x1A && b[7]==0x0A;
|
||||
case "image/jpeg":
|
||||
return b[0]==(byte)0xFF && b[1]==(byte)0xD8 && b[b.length-2]==(byte)0xFF && b[b.length-1]==(byte)0xD9;
|
||||
case "image/gif":
|
||||
return b[0]==0x47 && b[1]==0x49 && b[2]==0x46 && b[3]==0x38; // "GIF8"
|
||||
case "image/webp":
|
||||
// "RIFF"...."WEBP"
|
||||
return b[0]==0x52 && b[1]==0x49 && b[2]==0x46 && b[3]==0x46 &&
|
||||
b[8]==0x57 && b[9]==0x45 && b[10]==0x42 && b[11]==0x50;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.jambotronGroup.jambotron.payload.request;
|
||||
|
||||
public class Base64ImageRequest {
|
||||
private String mime; // e.g., "image/png"
|
||||
private String data; // base64 without "data:" prefix
|
||||
|
||||
public String getMime() {
|
||||
return mime;
|
||||
}
|
||||
|
||||
public void setMime(String mime) {
|
||||
this.mime = mime;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.jambotronGroup.jambotron.payload.request;
|
||||
|
||||
import com.jambotronGroup.jambotron.fileUpload.ResponseMessage;
|
||||
|
||||
public class UploadImageResponse extends ResponseMessage {
|
||||
private String fileName;
|
||||
private String url;
|
||||
private String mime;
|
||||
private long size;
|
||||
|
||||
public UploadImageResponse(String message) {
|
||||
super(message);
|
||||
}
|
||||
public UploadImageResponse(String fileName, String url, String mime, long size) {
|
||||
super(
|
||||
String.format("Image uploaded successfully: %s, URL: %s, MIME: %s, Size: %d bytes", fileName, url, mime, size)
|
||||
);
|
||||
this.fileName = fileName;
|
||||
this.url = url;
|
||||
this.mime = mime;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getMime() {
|
||||
return mime;
|
||||
}
|
||||
|
||||
public void setMime(String mime) {
|
||||
this.mime = mime;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user