S3Storage.java

package access.api;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.auth.signer.AwsS3V4Signer;
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URI;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.UUID;


@Service
public class S3Storage {

    private final String bucketName;
    private final String s3URL;
    private final ObjectMapper objectMapper;
    private final S3Client s3Client;
    private boolean bucketExists;

    @SneakyThrows
    @SuppressWarnings("deprecation")
    public S3Storage(@Value("${s3storage.url}") String s3URL,
                     @Value("${s3storage.key}") String s3AccessKey,
                     @Value("${s3storage.secret}") String s3SecretKey,
                     @Value("${s3storage.bucket}") String s3BucketName,
                     ObjectMapper objectMapper) {
        this.s3URL = s3URL;
        AwsBasicCredentials credentials =
                AwsBasicCredentials.create(s3AccessKey, s3SecretKey);

        this.s3Client = S3Client.builder()
                .endpointOverride(new URI(s3URL))
                .region(Region.EU_CENTRAL_1)
                .forcePathStyle(true)
                .overrideConfiguration(c -> {
                    c.putAdvancedOption(SdkAdvancedClientOption.SIGNER,
                            AwsS3V4Signer.create());
                })
                .credentialsProvider(StaticCredentialsProvider.create(credentials))
                .build();
        this.bucketName = s3BucketName;
        this.objectMapper = objectMapper;
    }

    @SneakyThrows
    public String uploadFile(String content) {
        byte[] decodedBytes = Base64.getDecoder().decode(content);

        if (!bucketExists) {
            createBucket(s3Client);
        }

        ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedBytes);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Thumbnails.of(inputStream)
                .size(200, 200)
                .keepAspectRatio(true)
                .toOutputStream(baos);

        inputStream = new ByteArrayInputStream(baos.toByteArray());

        String uuid = UUID.randomUUID().toString();
        s3Client.putObject(PutObjectRequest.builder()
                        .bucket(bucketName)
                        .key(uuid)
                        .contentType("image/jpeg")
                        .cacheControl("public, max-age=31536000, immutable")
                        .build(),
                RequestBody.fromInputStream(inputStream, inputStream.available()));

        return String.format("%s/%s/%s", s3URL, bucketName, uuid);
    }


    private void createBucket(S3Client s3Client) {
        HeadBucketRequest headBucketRequest = HeadBucketRequest.builder()
                .bucket(bucketName)
                .build();
        try {
            s3Client.headBucket(headBucketRequest);
        } catch (NoSuchBucketException e) {
            s3Client.createBucket(CreateBucketRequest.builder()
                    .bucket(bucketName)
                    .build());
            PutBucketPolicyRequest putBucketPolicyRequest = PutBucketPolicyRequest
                    .builder()
                    .bucket(bucketName)
                    .policy(getPublicBucketPolicy(bucketName))
                    .build();
            s3Client.putBucketPolicy(putBucketPolicyRequest);
            bucketExists = true;
        }
    }

    @SneakyThrows
    private String getPublicBucketPolicy(String bucketName) {
        return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(Map.of(
                "Version", "2012-10-17",
                "Statement", List.of(Map.of(
                        "Effect", "Allow",
                        "Principal", Map.of("AWS", "*"),
                        "Action", "s3:GetObject",
                        "Resource", String.format("arn:aws:s3:::%s/*", bucketName)
                ))
        ));
    }


}