diff --git a/.env.example b/.env.example
index 20dac54..2d5ea2d 100644
--- a/.env.example
+++ b/.env.example
@@ -5,3 +5,4 @@ DATABASE_USERNAME=user
S3_ENDPOINT=http://s3:9000
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
+S3_BUCKET=composer-dev
diff --git a/pom.xml b/pom.xml
index 4655418..b5836eb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,6 +32,7 @@
3.2.4
4.0.0
+ 3.2.3
@@ -87,6 +88,16 @@
spring-dotenv
${spring-dotenv.version}
+
+ org.apache.tika
+ tika-core
+ ${apache-tika.version}
+
+
+ org.apache.tika
+ tika-parsers-standard-package
+ ${apache-tika.version}
+
org.postgresql
diff --git a/src/main/java/com/bivashy/backend/composer/config/MimeDetectionConfig.java b/src/main/java/com/bivashy/backend/composer/config/MimeDetectionConfig.java
new file mode 100644
index 0000000..84053c0
--- /dev/null
+++ b/src/main/java/com/bivashy/backend/composer/config/MimeDetectionConfig.java
@@ -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();
+ }
+}
diff --git a/src/main/java/com/bivashy/backend/composer/config/StorageS3Config.java b/src/main/java/com/bivashy/backend/composer/config/StorageS3Config.java
index 8307caa..194f435 100644
--- a/src/main/java/com/bivashy/backend/composer/config/StorageS3Config.java
+++ b/src/main/java/com/bivashy/backend/composer/config/StorageS3Config.java
@@ -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();
}
diff --git a/src/main/java/com/bivashy/backend/composer/controller/S3Controller.java b/src/main/java/com/bivashy/backend/composer/controller/S3Controller.java
new file mode 100644
index 0000000..2a88836
--- /dev/null
+++ b/src/main/java/com/bivashy/backend/composer/controller/S3Controller.java
@@ -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 read(@RequestParam("document") String document) throws IOException {
+ Blob blob = s3Service.read(document);
+ return ResponseEntity.ok()
+ .contentType(blob.contentType())
+ .body(blob.stream().readAllBytes());
+ }
+}
diff --git a/src/main/java/com/bivashy/backend/composer/model/SourceProviders.java b/src/main/java/com/bivashy/backend/composer/model/SourceProviders.java
new file mode 100644
index 0000000..b1e48af
--- /dev/null
+++ b/src/main/java/com/bivashy/backend/composer/model/SourceProviders.java
@@ -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";
+
+}
diff --git a/src/main/java/com/bivashy/backend/composer/model/SourceTypes.java b/src/main/java/com/bivashy/backend/composer/model/SourceTypes.java
new file mode 100644
index 0000000..f416568
--- /dev/null
+++ b/src/main/java/com/bivashy/backend/composer/model/SourceTypes.java
@@ -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";
+}
diff --git a/src/main/java/com/bivashy/backend/composer/service/AudioBlobStorageService.java b/src/main/java/com/bivashy/backend/composer/service/AudioBlobStorageService.java
new file mode 100644
index 0000000..c4e1b02
--- /dev/null
+++ b/src/main/java/com/bivashy/backend/composer/service/AudioBlobStorageService.java
@@ -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) {
+ }
+}
diff --git a/src/main/java/com/bivashy/backend/composer/service/AudioS3StorageService.java b/src/main/java/com/bivashy/backend/composer/service/AudioS3StorageService.java
new file mode 100644
index 0000000..1161f29
--- /dev/null
+++ b/src/main/java/com/bivashy/backend/composer/service/AudioS3StorageService.java
@@ -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 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());
+ }
+
+}