Skip to content
Merged

S3.2 #120

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b9991ed
* Edit db structure
LordRenDS Apr 21, 2023
7226375
* finished login and registration for sponsor
LordRenDS Apr 22, 2023
8cab03e
S3.2US2&4 (#103)
Maslyna Apr 23, 2023
f408263
S3.2 us3 (#105)
Denis973 Apr 25, 2023
a1cd2d4
Updated liquibase:
Maslyna Apr 25, 2023
1ddf4d1
BUGFIX: `GET(/api/v3/proofs/1/kudos)`` has problems after deleting a …
Maslyna Apr 25, 2023
1b4588d
Little update
Maslyna Apr 25, 2023
d4da2b9
Removed exception handler for Exception.class
Maslyna Apr 26, 2023
1bea5c8
Code refactor:
Maslyna Apr 26, 2023
631829b
bug fix
Denis973 Apr 26, 2023
8097335
aws update
Maslyna Apr 28, 2023
8410148
Created PhotoService
Maslyna Apr 29, 2023
1e9560e
minore update
Maslyna Apr 29, 2023
44b3666
refactored and optimized code
LordRenDS Apr 29, 2023
8605ed4
fix .findByLogin(authentication.getName()) - getTalentProofs
Denis973 Apr 29, 2023
0382ddc
fix !userInfo.getTalent().getId().equals(talentId)
Denis973 Apr 29, 2023
ae08a0c
Doc update
Maslyna May 1, 2023
a607b07
Merge remote-tracking branch 'origin/S3.2-refactor' into S3.2-refactor
Maslyna May 1, 2023
1a1f027
New test endpoint for images
Maslyna May 2, 2023
c840232
DB EDIT: SIZE UP
Maslyna May 3, 2023
98aa8a2
S3.2 refactor (#107)
Maslyna May 3, 2023
fdc5c11
AWS update: PresignedURL
Maslyna May 4, 2023
63b7c04
AWS update: Scheduler for presigned url
Maslyna May 4, 2023
372ba13
Merge branch 'S3.2-refactor' into S3.2
Maslyna May 4, 2023
85b4f12
AWS update
Maslyna May 5, 2023
40e27fb
Merge branch 'dev' into S3.2
LordRenDS May 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
<java.version>17</java.version>
</properties>
<dependencies>

<!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui -->
<dependency>
<groupId>org.springdoc</groupId>
Expand Down Expand Up @@ -98,7 +97,6 @@
<version>4.21.1</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
Expand Down Expand Up @@ -133,5 +131,4 @@
</plugin>
</plugins>
</build>

</project>
</project>
6 changes: 3 additions & 3 deletions src/main/java/com/provedcode/ProvedCodeApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -15,16 +19,27 @@
public class AWSS3BucketController {
FileService fileService;

@PostSetNewUserImageApiDoc
@PreAuthorize("hasRole('TALENT')")
@PostMapping("/image/upload")
public void setNewUserImage(@RequestParam("file") MultipartFile file,
Authentication authentication) {
fileService.setNewUserImage(file, authentication);
}

@GetAllAWSBucketFilesDevApiDoc
@PreAuthorize("hasRole('TALENT')")
@GetMapping("/files")
List<String> 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();
}
}
30 changes: 30 additions & 0 deletions src/main/java/com/provedcode/aws/scheduler/UpdateScheduler.java
Original file line number Diff line number Diff line change
@@ -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);
}

}
6 changes: 6 additions & 0 deletions src/main/java/com/provedcode/aws/service/FileService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> listAllFiles();

void setNewUserImage(MultipartFile file, Authentication authentication);

URL generetePresingedUrlFor7Days(String fileFullPath);
}
79 changes: 56 additions & 23 deletions src/main/java/com/provedcode/aws/service/S3Service.java
Original file line number Diff line number Diff line change
@@ -1,53 +1,59 @@
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;
import com.provedcode.config.AWSProperties;
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);
}
}

@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);
Expand All @@ -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<String> 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)
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/provedcode/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/provedcode/handlers/ApiError.java
Original file line number Diff line number Diff line change
@@ -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<String> errors;

public ApiError(HttpStatus status, String message, List<String> 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);
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/provedcode/handlers/AwsS3ExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -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"));
}
}
Loading