Add fileName property to models and update related components for file handling

This commit is contained in:
liosha84
2025-08-09 22:22:02 +03:00
parent ca73cf31df
commit d45fd83593
64 changed files with 1644 additions and 433 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
FROM openjdk:24
WORKDIR /jambotron/
VOLUME /jambotron_data/uploads
#VOLUME /jambotron_data/uploads
COPY './build/libs/jambotron-0.0.1-SNAPSHOT.jar' '/app/jambotron.jar'
#COPY --from=BUILD_IMAGE /jambotron/build/libs/jambotron-0.0.1-SNAPSHOT.jar .
#EXPOSE 8080
+1 -1
View File
@@ -4,7 +4,7 @@ services:
container_name: 'jambotron-container'
build:
context: ../../
dockerfile: ./src/Docker/Dockerfile
dockerfile: ./src/DevOps/Dockerfile
ports:
# - "8081:80"
- "8443:443"
@@ -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;
}
}
@@ -33,11 +33,11 @@ spring.ai.zhipuai.api-key = 628447c8c65845a48a7226391464a2ea.Dw8ci6TiW0BRF5LI
app.jwtSecret= ======================jambotron=back=================
app.jwtCookieName=jambotron-jwt-cookie
app.jwtRefreshCookieName= jambotron-jwt-cookie-refresh
#app.jwtExpirationMs= 800000
#app.jwtRefreshExpirationMs= 86400000
app.jwtExpirationMs= 800000
app.jwtRefreshExpirationMs= 86400000
app.jwtExpirationMs= 30000
app.jwtRefreshExpirationMs= 60000
#app.jwtExpirationMs= 30000
#app.jwtRefreshExpirationMs= 60000
#=============File Upload Configurations========================
spring.servlet.multipart.max-file-size=50MB