Implement mime-type detection in S3 service

This commit is contained in:
2025-11-10 23:23:34 +05:00
parent 19b555e9f9
commit ef72a590d2
9 changed files with 186 additions and 1 deletions

View File

@ -0,0 +1,13 @@
package com.bivashy.backend.composer.config;
import org.apache.tika.Tika;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MimeDetectionConfig {
@Bean
public Tika tika() {
return new Tika();
}
}

View File

@ -22,9 +22,10 @@ public class StorageS3Config {
@Bean
public S3Client s3Client() {
return S3Client.builder()
.endpointOverride(URI.create(endpoint))
.region(Region.US_WEST_1)
.forcePathStyle(true)
.credentialsProvider(() -> AwsBasicCredentials.create(accessKey, secretKey))
.endpointOverride(URI.create(endpoint))
.build();
}

View File

@ -0,0 +1,37 @@
package com.bivashy.backend.composer.controller;
import java.io.IOException;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.bivashy.backend.composer.service.AudioBlobStorageService.Blob;
import com.bivashy.backend.composer.service.AudioS3StorageService;
@RestController
public class S3Controller {
private final AudioS3StorageService s3Service;
public S3Controller(AudioS3StorageService s3Service) {
this.s3Service = s3Service;
}
@PostMapping(path = "/upload", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE })
public String upload(@RequestPart MultipartFile document) throws IOException {
return s3Service.store(document.getBytes());
}
@GetMapping(path = "/read")
public ResponseEntity<byte[]> read(@RequestParam("document") String document) throws IOException {
Blob blob = s3Service.read(document);
return ResponseEntity.ok()
.contentType(blob.contentType())
.body(blob.stream().readAllBytes());
}
}

View File

@ -0,0 +1,8 @@
package com.bivashy.backend.composer.model;
public class SourceProviders {
public static final String YOUTUBE = "YOUTUBE";
public static final String LOCAL = "SOUNDCLOUD";
public static final String EXTERNAL = "EXTERNAL";
}

View File

@ -0,0 +1,8 @@
package com.bivashy.backend.composer.model;
public class SourceTypes {
public static final String AUDIO = "VIDEO";
public static final String PLAYLIST = "PLAYLIST";
public static final String FILE = "FILE";
public static final String URL = "URL";
}

View File

@ -0,0 +1,19 @@
package com.bivashy.backend.composer.service;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.http.MediaType;
public interface AudioBlobStorageService {
String store(InputStream inputStream);
String store(byte[] data);
byte[] readRaw(String path) throws IOException;
Blob read(String path);
public record Blob(InputStream stream, MediaType contentType) {
}
}

View File

@ -0,0 +1,87 @@
package com.bivashy.backend.composer.service;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
import org.apache.tika.Tika;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
@Service
public class AudioS3StorageService implements AudioBlobStorageService {
private static final Logger logger = LoggerFactory.getLogger(AudioS3StorageService.class);
private static final byte[] DEFAULT_BUFFER = new byte[8192];
private final S3Client s3Client;
private final Tika tika;
@Value("${spring.s3.bucket}")
private String bucket;
public AudioS3StorageService(S3Client s3Client, Tika tika) {
this.s3Client = s3Client;
this.tika = tika;
}
@Override
public String store(InputStream inputStream) {
return store(new ByteArrayInputStream(DEFAULT_BUFFER).readAllBytes());
}
@Override
public String store(byte[] data) {
String objectKey = newObjectName();
String contentType = detectContentType(data);
s3Client.putObject(PutObjectRequest.builder()
.bucket(bucket)
.key(objectKey)
.contentType(contentType)
.build(), RequestBody.fromBytes(data));
return String.join("/", bucket, objectKey);
}
@Override
public byte[] readRaw(String path) throws IOException {
return read(path).stream().readAllBytes();
}
@Override
public Blob read(String path) {
if (path.startsWith(bucket + "/")) {
path = path.substring(bucket.length());
}
ResponseInputStream<GetObjectResponse> response = s3Client.getObject(GetObjectRequest.builder()
.bucket(bucket)
.key(path)
.build());
MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
try {
mediaType = MediaType.parseMediaType(response.response().contentType());
} catch (InvalidMediaTypeException e) {
logger.error("invalid media type", e);
}
return new Blob(response, mediaType);
}
private String detectContentType(byte[] data) {
return tika.detect(data);
}
private String newObjectName() {
return String.join("/", "audio", UUID.randomUUID().toString());
}
}