diff --git a/pom.xml b/pom.xml
index 21dd489..54591a1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,6 @@
17
-
org.springdoc
@@ -98,7 +97,6 @@
4.21.1
-
@@ -133,5 +131,4 @@
-
-
+
\ No newline at end of file
diff --git a/src/main/java/com/provedcode/ProvedCodeApplication.java b/src/main/java/com/provedcode/ProvedCodeApplication.java
index 78360e0..58b471f 100644
--- a/src/main/java/com/provedcode/ProvedCodeApplication.java
+++ b/src/main/java/com/provedcode/ProvedCodeApplication.java
@@ -3,13 +3,13 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
+import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@ConfigurationPropertiesScan
+@EnableScheduling
public class ProvedCodeApplication {
-
public static void main(String[] args) {
SpringApplication.run(ProvedCodeApplication.class, args);
}
-
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/provedcode/aws/controller/AWSS3BucketController.java b/src/main/java/com/provedcode/aws/controller/AWSS3BucketController.java
index 64141d0..926e991 100644
--- a/src/main/java/com/provedcode/aws/controller/AWSS3BucketController.java
+++ b/src/main/java/com/provedcode/aws/controller/AWSS3BucketController.java
@@ -1,12 +1,16 @@
package com.provedcode.aws.controller;
import com.provedcode.aws.service.FileService;
+import com.provedcode.util.annotations.doc.controller.aws.GetAllAWSBucketFilesDevApiDoc;
+import com.provedcode.util.annotations.doc.controller.aws.GetFileInfoDevApiDoc;
+import com.provedcode.util.annotations.doc.controller.aws.PostSetNewUserImageApiDoc;
import lombok.AllArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
+import java.util.Arrays;
import java.util.List;
@RestController
@@ -15,6 +19,7 @@
public class AWSS3BucketController {
FileService fileService;
+ @PostSetNewUserImageApiDoc
@PreAuthorize("hasRole('TALENT')")
@PostMapping("/image/upload")
public void setNewUserImage(@RequestParam("file") MultipartFile file,
@@ -22,9 +27,19 @@ public void setNewUserImage(@RequestParam("file") MultipartFile file,
fileService.setNewUserImage(file, authentication);
}
+ @GetAllAWSBucketFilesDevApiDoc
@PreAuthorize("hasRole('TALENT')")
@GetMapping("/files")
List getAllFiles() {
return fileService.listAllFiles();
}
+
+ @GetFileInfoDevApiDoc
+ @PreAuthorize("hasRole('TALENT')")
+ @PostMapping("/aws/test-of-filetype")
+ String testTypeOfFile(@RequestParam("file") MultipartFile file,
+ Authentication authentication) {
+ return Arrays.stream(file.getContentType().split("/")).toList().get(1) + " " + file.getOriginalFilename() +
+ " " + file.getName() + " " + file.getResource();
+ }
}
diff --git a/src/main/java/com/provedcode/aws/scheduler/UpdateScheduler.java b/src/main/java/com/provedcode/aws/scheduler/UpdateScheduler.java
new file mode 100644
index 0000000..c9988dc
--- /dev/null
+++ b/src/main/java/com/provedcode/aws/scheduler/UpdateScheduler.java
@@ -0,0 +1,30 @@
+package com.provedcode.aws.scheduler;
+
+import com.provedcode.aws.service.FileService;
+import com.provedcode.talent.repo.TalentRepository;
+import lombok.AllArgsConstructor;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional
+@AllArgsConstructor
+public class UpdateScheduler {
+
+ FileService fileService;
+ TalentRepository talentRepository;
+
+ @Scheduled(cron = "0 0 13 * * SAT")
+ public void updateTalentsImages() {
+
+ talentRepository.findAll().stream()
+ .map(i -> {
+ if (i.getImageName() != null) {
+ i.setImage(fileService.generetePresingedUrlFor7Days(i.getImageName()).toString());
+ }
+ return i;
+ }).forEach(talentRepository::save);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/provedcode/aws/service/FileService.java b/src/main/java/com/provedcode/aws/service/FileService.java
index 78d722b..1c1d9e6 100644
--- a/src/main/java/com/provedcode/aws/service/FileService.java
+++ b/src/main/java/com/provedcode/aws/service/FileService.java
@@ -3,14 +3,20 @@
import org.springframework.security.core.Authentication;
import org.springframework.web.multipart.MultipartFile;
+import java.net.URL;
import java.util.List;
public interface FileService {
String saveFile(MultipartFile file);
+
byte[] downloadFile(String filename);
+
String deleteFile(String filename);
+
List listAllFiles();
void setNewUserImage(MultipartFile file, Authentication authentication);
+
+ URL generetePresingedUrlFor7Days(String fileFullPath);
}
diff --git a/src/main/java/com/provedcode/aws/service/S3Service.java b/src/main/java/com/provedcode/aws/service/S3Service.java
index 679c443..e961c20 100644
--- a/src/main/java/com/provedcode/aws/service/S3Service.java
+++ b/src/main/java/com/provedcode/aws/service/S3Service.java
@@ -1,6 +1,7 @@
package com.provedcode.aws.service;
+import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.util.IOUtils;
@@ -8,37 +9,42 @@
import com.provedcode.talent.repo.TalentRepository;
import com.provedcode.user.model.entity.UserInfo;
import com.provedcode.user.repo.UserInfoRepository;
+import com.provedcode.util.PhotoService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.net.URL;
+import java.time.Instant;
+import java.util.Date;
import java.util.List;
-import static org.apache.http.entity.ContentType.*;
-import static org.springframework.http.HttpStatus.NOT_FOUND;
-import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED;
+import static org.springframework.http.HttpStatus.*;
@Service
@AllArgsConstructor
@Slf4j
+@Transactional
public class S3Service implements FileService {
AWSProperties awsProperties;
- AmazonS3 s3;
+ AmazonS3 amazonS3;
UserInfoRepository userInfoRepository;
TalentRepository talentRepository;
+ PhotoService photoService;
@Override
public String saveFile(MultipartFile file) {
String originalFilename = file.getOriginalFilename();
try {
File file1 = convertMultiPartToFile(file);
- PutObjectResult objectResult = s3.putObject(awsProperties.bucket(), originalFilename, file1);
+ PutObjectResult objectResult = amazonS3.putObject(awsProperties.bucket(), originalFilename, file1);
return objectResult.getContentMd5();
} catch (IOException e) {
throw new ResponseStatusException(NOT_IMPLEMENTED);
@@ -46,8 +52,8 @@ public String saveFile(MultipartFile file) {
}
@Override
- public byte[] downloadFile(String filename) {
- S3Object object = s3.getObject(awsProperties.bucket(), filename);
+ public byte[] downloadFile(String fullFilePath) {
+ S3Object object = amazonS3.getObject(awsProperties.bucket(), fullFilePath);
S3ObjectInputStream objectContent = object.getObjectContent();
try {
return IOUtils.toByteArray(objectContent);
@@ -57,43 +63,70 @@ public byte[] downloadFile(String filename) {
}
@Override
- public String deleteFile(String filename) {
- s3.deleteObject(awsProperties.bucket(), filename);
+ public String deleteFile(String fullFilePath) {
+ amazonS3.deleteObject(awsProperties.bucket(), fullFilePath);
return "file deleted";
}
@Override
public List listAllFiles() {
- ListObjectsV2Result listObjectsV2Result = s3.listObjectsV2(awsProperties.bucket());
+ ListObjectsV2Result listObjectsV2Result = amazonS3.listObjectsV2(awsProperties.bucket());
return listObjectsV2Result.getObjectSummaries().stream().map(S3ObjectSummary::getKey).toList();
}
@Override
public void setNewUserImage(MultipartFile file, Authentication authentication) {
if (file.isEmpty()) {
- throw new ResponseStatusException(NOT_IMPLEMENTED, "file must be not empty, actual file-size: %s".formatted(file.getSize()));
+ throw new ResponseStatusException(BAD_REQUEST, "file must be not empty, actual file-size: %s".formatted(file.getSize()));
}
- if (!List.of(IMAGE_JPEG.getMimeType(), IMAGE_PNG.getMimeType(), IMAGE_GIF.getMimeType()).contains(file.getContentType())) {
- throw new ResponseStatusException(NOT_IMPLEMENTED, "not supported type: %s".formatted(file.getContentType()));
+ if (photoService.isFileImage(file)) {
+ throw new ResponseStatusException(BAD_REQUEST, "not supported type: %s".formatted(file.getContentType()));
}
UserInfo user = userInfoRepository.findByLogin(authentication.getName())
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "user with login = {%s} not found".formatted(authentication.getName())));
- String fileName = file.getOriginalFilename();
-
- String userLogin = authentication.getName();
- String fullPath = "%s/%s".formatted(userLogin, fileName);
try {
- File f = convertMultiPartToFile(file);
+ String fileType = getFileType(file);
+ String fullPath = getFullPath(fileType, authentication.getName());
+ File degradePhoto = photoService.degradePhoto(convertMultiPartToFile(file));
+
+ if (user.getTalent().getImageName() != null) // delete old user image
+ amazonS3.deleteObject(awsProperties.bucket(), user.getTalent().getImageName());
+
+ amazonS3.putObject(awsProperties.bucket(), fullPath, degradePhoto);
+
+ user.getTalent().setImageName(fullPath);
- s3.putObject(awsProperties.bucket(), fullPath, f);
+ URL url = generetePresingedUrlFor7Days(fullPath); // generation url with expiration
+
+ log.info("image = {}", amazonS3.getUrl(awsProperties.bucket(), fullPath).toString());
+ log.info("image-url = {}", url);
+
+ user.getTalent().setImage(url.toString());
- log.info("image = {}", s3.getUrl(awsProperties.bucket(), fullPath).toString());
- user.getTalent().setImage(s3.getUrl(awsProperties.bucket(), fullPath).toString());
talentRepository.save(user.getTalent());
- } catch (RuntimeException | IOException e) {
- throw new ResponseStatusException(NOT_IMPLEMENTED);
+ } catch (AmazonS3Exception | IOException e) {
+ throw new ResponseStatusException(SERVICE_UNAVAILABLE, "problems with connection to aws s3");
}
+
+ }
+
+ public URL generetePresingedUrlFor7Days(String fileFullPath) {
+ GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(awsProperties.bucket(), fileFullPath)
+ .withMethod(HttpMethod.GET);
+
+ Instant expiration = Instant.now().plusMillis(1000L * 60 * 60 * 24 * 7); // expiration time
+ urlRequest.setExpiration(Date.from(expiration));
+ return amazonS3.generatePresignedUrl(urlRequest); // generation url with expiration
+ }
+
+ private String getFullPath(String fileType, String userLogin) {
+ return "%s/%s".formatted(userLogin, "image.%s".formatted(fileType));
+ }
+
+ private String getFileType(MultipartFile file) {
+ String fileName = file.getOriginalFilename();
+ return fileName.substring(fileName.lastIndexOf('.') + 1);
}
private File convertMultiPartToFile(MultipartFile file)
diff --git a/src/main/java/com/provedcode/config/SecurityConfig.java b/src/main/java/com/provedcode/config/SecurityConfig.java
index 67c7c98..1997071 100644
--- a/src/main/java/com/provedcode/config/SecurityConfig.java
+++ b/src/main/java/com/provedcode/config/SecurityConfig.java
@@ -51,7 +51,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers("/actuator/health").permitAll() // for DevOps
.requestMatchers(antMatcher("/h2/**")).permitAll()
.requestMatchers(antMatcher("/api/*/talents/**")).permitAll()
- .requestMatchers(antMatcher("/api/*/talent/**")).permitAll()
+ .requestMatchers(antMatcher("/api/*/sponsors/**")).permitAll()
+ .requestMatchers(antMatcher("/api/*/login")).permitAll()
.requestMatchers(antMatcher("/error")).permitAll()
.requestMatchers(antMatcher("/v3/api-docs/**")).permitAll() // for openAPI
.requestMatchers(antMatcher("/swagger-ui/**")).permitAll() // for openAPI
diff --git a/src/main/java/com/provedcode/handlers/ApiError.java b/src/main/java/com/provedcode/handlers/ApiError.java
new file mode 100644
index 0000000..5326437
--- /dev/null
+++ b/src/main/java/com/provedcode/handlers/ApiError.java
@@ -0,0 +1,28 @@
+package com.provedcode.handlers;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Getter
+public class ApiError {
+ private HttpStatus status;
+ private String message;
+ private List errors;
+
+ public ApiError(HttpStatus status, String message, List errors) {
+ super();
+ this.status = status;
+ this.message = message;
+ this.errors = errors;
+ }
+
+ public ApiError(HttpStatus status, String message, String error) {
+ super();
+ this.status = status;
+ this.message = message;
+ errors = Arrays.asList(error);
+ }
+}
diff --git a/src/main/java/com/provedcode/handlers/AwsS3ExceptionHandler.java b/src/main/java/com/provedcode/handlers/AwsS3ExceptionHandler.java
new file mode 100644
index 0000000..a77af66
--- /dev/null
+++ b/src/main/java/com/provedcode/handlers/AwsS3ExceptionHandler.java
@@ -0,0 +1,17 @@
+package com.provedcode.handlers;
+
+import com.provedcode.handlers.dto.ErrorDTO;
+import org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+
+@ControllerAdvice
+public class AwsS3ExceptionHandler {
+ @ExceptionHandler(FileSizeLimitExceededException.class)
+ public ResponseEntity> fileSizeLimitExceededExceptionHandler(FileSizeLimitExceededException e) {
+ return ResponseEntity.status(BAD_REQUEST).body(new ErrorDTO("file size is too large"));
+ }
+}
diff --git a/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java
index 1c63234..7da4cc8 100644
--- a/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java
+++ b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java
@@ -1,14 +1,106 @@
package com.provedcode.handlers;
+import jakarta.validation.ConstraintViolationException;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.server.ResponseStatusException;
+import java.sql.SQLException;
+
@ControllerAdvice
public class TalentExceptionHandler {
+ public static boolean ignoreSQLException(String sqlState) {
+
+ if (sqlState == null) {
+ System.out.println("The SQL state is not defined!");
+ return false;
+ }
+
+ // X0Y32: Jar file already exists in schema
+ if (sqlState.equalsIgnoreCase("X0Y32"))
+ return true;
+
+ // 42Y55: Table already exists in schema
+ if (sqlState.equalsIgnoreCase("42Y55"))
+ return true;
+
+ return false;
+ }
+
+ @ExceptionHandler({SQLException.class})
+ public void printSQLException(SQLException ex) {
+ for (Throwable e : ex) {
+ if (e instanceof SQLException) {
+ if (ignoreSQLException(((SQLException) e).getSQLState()) == false) {
+
+ e.printStackTrace(System.err);
+ System.err.println("SQLState: " +
+ ((SQLException) e).getSQLState());
+
+ System.err.println("Error Code: " +
+ ((SQLException) e).getErrorCode());
+
+ System.err.println("Message: " + e.getMessage());
+
+ Throwable t = ex.getCause();
+ while (t != null) {
+ System.out.println("Cause: " + t);
+ t = t.getCause();
+ }
+ }
+ }
+ }
+ }
+
@ExceptionHandler(ResponseStatusException.class)
private ResponseEntity> responseStatusExceptionHandler(ResponseStatusException exception) {
return ResponseEntity.status(exception.getStatusCode()).body(exception.getBody());
}
+
+ @ExceptionHandler(ConstraintViolationException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ private ResponseEntity> responseStatusExceptionHandler(ConstraintViolationException exception) {
+ ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, exception.getMessage(), exception.toString());
+ return new ResponseEntity<>(apiError, new HttpHeaders(), apiError.getStatus());
+ }
+
+// @ExceptionHandler({ Exception.class })
+// public ResponseEntity> handleAll(Exception ex, WebRequest request) {
+// ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), "error occurred");
+// return new ResponseEntity<>(apiError, new HttpHeaders(), apiError.getStatus());
+// }
+
+//MethodArgumentNotValidException – This exception is thrown
+// when an argument annotated with @Valid failed validation:
+// @ResponseStatus(HttpStatus.BAD_REQUEST)
+// @ExceptionHandler(MethodArgumentNotValidException.class)
+// public Map handleValidationExceptions(MethodArgumentNotValidException ex) {
+// Map errors = new HashMap<>();
+// ex.getBindingResult().getAllErrors().forEach(
+// (error) -> {
+// String fieldName = ((FieldError) error).getField();
+// String errorMessage = error.getDefaultMessage();
+// errors.put(fieldName, errorMessage);
+// });
+// return errors;
+// }
+
+// @ExceptionHandler({ ConstraintViolationException.class })
+// public ResponseEntity