From 902c223e69ec35ca1257f8ca391c54c0f9bfffe8 Mon Sep 17 00:00:00 2001 From: bivashy Date: Sun, 16 Nov 2025 14:28:33 +0500 Subject: [PATCH] Implement proper ordering --- .../composer/controller/TrackController.java | 14 ++- .../dto/track/PlaylistTrackResponse.java | 10 ++ .../backend/composer/model/TrackPlaylist.java | 14 ++- .../repository/TrackMetadataRepository.java | 3 + .../repository/TrackPlaylistRepository.java | 14 +++ .../service/AudioBlobStorageService.java | 7 +- .../service/AudioS3StorageService.java | 16 ++- .../service/MetadataParseService.java | 54 ++++++++-- .../service/TrackMetadataService.java | 29 +++++ .../service/TrackPlaylistService.java | 67 ++++++++++++ .../composer/service/TrackService.java | 102 +++++++++--------- .../composer/service/TrackSourceService.java | 33 ++++++ src/main/resources/application.yaml | 4 + .../migration/V1_10__create_base_tables.sql | 8 +- 14 files changed, 302 insertions(+), 73 deletions(-) create mode 100644 src/main/java/com/bivashy/backend/composer/dto/track/PlaylistTrackResponse.java create mode 100644 src/main/java/com/bivashy/backend/composer/service/TrackMetadataService.java create mode 100644 src/main/java/com/bivashy/backend/composer/service/TrackPlaylistService.java create mode 100644 src/main/java/com/bivashy/backend/composer/service/TrackSourceService.java diff --git a/src/main/java/com/bivashy/backend/composer/controller/TrackController.java b/src/main/java/com/bivashy/backend/composer/controller/TrackController.java index be7fc7b..8c60940 100644 --- a/src/main/java/com/bivashy/backend/composer/controller/TrackController.java +++ b/src/main/java/com/bivashy/backend/composer/controller/TrackController.java @@ -1,17 +1,19 @@ package com.bivashy.backend.composer.controller; import java.io.IOException; +import java.util.List; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.bivashy.backend.composer.dto.track.AddLocalTrackRequest; +import com.bivashy.backend.composer.dto.track.PlaylistTrackResponse; import com.bivashy.backend.composer.dto.track.TrackResponse; import com.bivashy.backend.composer.model.User; import com.bivashy.backend.composer.service.TrackService; @@ -24,7 +26,7 @@ public class TrackController { this.trackService = trackService; } - @PostMapping(path = "/playlist/{playlistId}/track", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PostMapping(path = "/playlist/{playlistId}/track/local", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity addLocalTrack( @AuthenticationPrincipal User user, @PathVariable Long playlistId, @@ -32,4 +34,12 @@ public class TrackController { TrackResponse response = trackService.addLocalTrack(user, playlistId, request); return ResponseEntity.ok(response); } + + @GetMapping("/playlist/{playlistId}/tracks") + public ResponseEntity> getPlaylistTracks( + @AuthenticationPrincipal User user, + @PathVariable Long playlistId) { + List tracks = trackService.getPlaylistTracks(user, playlistId); + return ResponseEntity.ok(tracks); + } } diff --git a/src/main/java/com/bivashy/backend/composer/dto/track/PlaylistTrackResponse.java b/src/main/java/com/bivashy/backend/composer/dto/track/PlaylistTrackResponse.java new file mode 100644 index 0000000..8fb906b --- /dev/null +++ b/src/main/java/com/bivashy/backend/composer/dto/track/PlaylistTrackResponse.java @@ -0,0 +1,10 @@ +package com.bivashy.backend.composer.dto.track; + +public record PlaylistTrackResponse( + Long trackId, + String title, + String artist, + String audioPath, + Integer durationSeconds, + String fileName) { +} diff --git a/src/main/java/com/bivashy/backend/composer/model/TrackPlaylist.java b/src/main/java/com/bivashy/backend/composer/model/TrackPlaylist.java index 5f1fb9c..7791354 100644 --- a/src/main/java/com/bivashy/backend/composer/model/TrackPlaylist.java +++ b/src/main/java/com/bivashy/backend/composer/model/TrackPlaylist.java @@ -1,5 +1,7 @@ package com.bivashy.backend.composer.model; +import java.math.BigDecimal; + import com.bivashy.backend.composer.model.key.PlaylistTrackId; import jakarta.persistence.Column; @@ -24,8 +26,8 @@ public class TrackPlaylist { @Column(name = "track_id", nullable = false) private Long trackId; - @Column(name = "order_index", nullable = false) - private Long order; + @Column(name = "order_index", nullable = false, precision = 1000, scale = 500) + private BigDecimal order; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "playlist_id", insertable = false, updatable = false) @@ -38,7 +40,7 @@ public class TrackPlaylist { TrackPlaylist() { } - public TrackPlaylist(Long playlistId, Long trackId, Long order) { + public TrackPlaylist(Long playlistId, Long trackId, BigDecimal order) { this.playlistId = playlistId; this.trackId = trackId; this.order = order; @@ -52,10 +54,14 @@ public class TrackPlaylist { return trackId; } - public Long getOrder() { + public BigDecimal getOrder() { return order; } + public void setOrder(BigDecimal order) { + this.order = order; + } + public Playlist getPlaylist() { return playlist; } diff --git a/src/main/java/com/bivashy/backend/composer/repository/TrackMetadataRepository.java b/src/main/java/com/bivashy/backend/composer/repository/TrackMetadataRepository.java index fe8b689..9b7386d 100644 --- a/src/main/java/com/bivashy/backend/composer/repository/TrackMetadataRepository.java +++ b/src/main/java/com/bivashy/backend/composer/repository/TrackMetadataRepository.java @@ -1,5 +1,7 @@ package com.bivashy.backend.composer.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -7,4 +9,5 @@ import com.bivashy.backend.composer.model.TrackMetadata; @Repository public interface TrackMetadataRepository extends JpaRepository { + Optional findByTrackId(Long trackId); } diff --git a/src/main/java/com/bivashy/backend/composer/repository/TrackPlaylistRepository.java b/src/main/java/com/bivashy/backend/composer/repository/TrackPlaylistRepository.java index ac385bd..0b11178 100644 --- a/src/main/java/com/bivashy/backend/composer/repository/TrackPlaylistRepository.java +++ b/src/main/java/com/bivashy/backend/composer/repository/TrackPlaylistRepository.java @@ -1,9 +1,23 @@ package com.bivashy.backend.composer.repository; +import java.math.BigDecimal; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import com.bivashy.backend.composer.model.TrackPlaylist; import com.bivashy.backend.composer.model.key.PlaylistTrackId; public interface TrackPlaylistRepository extends JpaRepository { + List findByPlaylistIdOrderByOrderAsc(Long playlistId); + + @Query("SELECT COALESCE(MAX(tp.order), 0) FROM TrackPlaylist tp WHERE tp.playlistId = :playlistId") + BigDecimal findMaxOrderByPlaylistId(@Param("playlistId") Long playlistId); + + @Query("SELECT MIN(tp.order) FROM TrackPlaylist tp WHERE tp.playlistId = :playlistId AND tp.order > :order") + BigDecimal findNextOrderByPlaylistIdAndOrderGreaterThan(@Param("playlistId") Long playlistId, + @Param("order") BigDecimal order); + } diff --git a/src/main/java/com/bivashy/backend/composer/service/AudioBlobStorageService.java b/src/main/java/com/bivashy/backend/composer/service/AudioBlobStorageService.java index c4e1b02..558506a 100644 --- a/src/main/java/com/bivashy/backend/composer/service/AudioBlobStorageService.java +++ b/src/main/java/com/bivashy/backend/composer/service/AudioBlobStorageService.java @@ -2,6 +2,7 @@ package com.bivashy.backend.composer.service; import java.io.IOException; import java.io.InputStream; +import java.util.Map; import org.springframework.http.MediaType; @@ -10,10 +11,14 @@ public interface AudioBlobStorageService { String store(byte[] data); + String store(InputStream inputStream, Map metadata); + + String store(byte[] data, Map metadata); + byte[] readRaw(String path) throws IOException; Blob read(String path); - public record Blob(InputStream stream, MediaType contentType) { + public record Blob(InputStream stream, MediaType contentType, Map metadata) { } } diff --git a/src/main/java/com/bivashy/backend/composer/service/AudioS3StorageService.java b/src/main/java/com/bivashy/backend/composer/service/AudioS3StorageService.java index 1161f29..1b96815 100644 --- a/src/main/java/com/bivashy/backend/composer/service/AudioS3StorageService.java +++ b/src/main/java/com/bivashy/backend/composer/service/AudioS3StorageService.java @@ -3,6 +3,7 @@ package com.bivashy.backend.composer.service; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Map; import java.util.UUID; import org.apache.tika.Tika; @@ -37,17 +38,28 @@ public class AudioS3StorageService implements AudioBlobStorageService { @Override public String store(InputStream inputStream) { - return store(new ByteArrayInputStream(DEFAULT_BUFFER).readAllBytes()); + return store(inputStream, Map.of()); } @Override public String store(byte[] data) { + return store(data, Map.of()); + } + + @Override + public String store(InputStream inputStream, Map metadata) { + return store(new ByteArrayInputStream(DEFAULT_BUFFER).readAllBytes(), metadata); + } + + @Override + public String store(byte[] data, Map metadata) { String objectKey = newObjectName(); String contentType = detectContentType(data); s3Client.putObject(PutObjectRequest.builder() .bucket(bucket) .key(objectKey) .contentType(contentType) + .metadata(metadata) .build(), RequestBody.fromBytes(data)); return String.join("/", bucket, objectKey); } @@ -73,7 +85,7 @@ public class AudioS3StorageService implements AudioBlobStorageService { } catch (InvalidMediaTypeException e) { logger.error("invalid media type", e); } - return new Blob(response, mediaType); + return new Blob(response, mediaType, response.response().metadata()); } private String detectContentType(byte[] data) { diff --git a/src/main/java/com/bivashy/backend/composer/service/MetadataParseService.java b/src/main/java/com/bivashy/backend/composer/service/MetadataParseService.java index 3f1aef2..2652720 100644 --- a/src/main/java/com/bivashy/backend/composer/service/MetadataParseService.java +++ b/src/main/java/com/bivashy/backend/composer/service/MetadataParseService.java @@ -2,32 +2,68 @@ package com.bivashy.backend.composer.service; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import org.springframework.stereotype.Service; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.github.kokorin.jaffree.ffprobe.FFprobe; import com.github.kokorin.jaffree.ffprobe.FFprobeResult; -import com.github.kokorin.jaffree.ffprobe.Format; -import com.github.kokorin.jaffree.ffprobe.PipeInput; @Service public class MetadataParseService { + private final ObjectMapper ffprobeObjectMapper; + + public MetadataParseService() { + var result = new ObjectMapper(); + result.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + result.setSerializationInclusion(JsonInclude.Include.NON_NULL); + this.ffprobeObjectMapper = result; + } + public Optional extractMetadata(InputStream input) throws IOException { + Path tempFile = Files.createTempFile("metadata-file", ""); + Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING); FFprobeResult result = FFprobe.atPath() .setShowFormat(true) - .setInput(PipeInput.pumpFrom(input)) + .setShowStreams(true) + .setInput(tempFile) .execute(); - Format format = result.getFormat(); + Files.deleteIfExists(tempFile); - if (format == null) { - return Optional.empty(); - } + var format = Optional.ofNullable(result.getFormat()); - return Optional.of(new Metadata(format.getTag("title"), format.getTag("artist"), format.getDuration())); + Optional formatDuration = format.map(f -> f.getDuration()); + List> streamDuration = Optional.ofNullable(result.getStreams()) + .map(streams -> streams.stream() + .map(s -> s.getDuration()) + .map(Optional::ofNullable).toList()) + .orElse(List.of()); + + var foundDuration = Stream.of(Collections.singletonList(formatDuration), streamDuration) + .flatMap(Collection::stream) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst(); + + var jsonResult = ffprobeObjectMapper.writeValueAsString(result); + + var title = format.map(f -> f.getTag("title")).orElse(null); + var artist = format.map(f -> f.getTag("artist")).orElse(null); + + return Optional.of(new Metadata(title, artist, foundDuration.orElse(0f), jsonResult)); } - public static record Metadata(String title, String artist, Float durationSeconds) { + public static record Metadata(String title, String artist, Float durationSeconds, String rawJson) { } } diff --git a/src/main/java/com/bivashy/backend/composer/service/TrackMetadataService.java b/src/main/java/com/bivashy/backend/composer/service/TrackMetadataService.java new file mode 100644 index 0000000..befc7d5 --- /dev/null +++ b/src/main/java/com/bivashy/backend/composer/service/TrackMetadataService.java @@ -0,0 +1,29 @@ +package com.bivashy.backend.composer.service; + +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import com.bivashy.backend.composer.model.Track; +import com.bivashy.backend.composer.model.TrackMetadata; +import com.bivashy.backend.composer.repository.TrackMetadataRepository; + +@Service +public class TrackMetadataService { + private final TrackMetadataRepository trackMetadataRepository; + + public TrackMetadataService(TrackMetadataRepository trackMetadataRepository) { + this.trackMetadataRepository = trackMetadataRepository; + } + + public TrackMetadata createTrackMetadata(Track track, String title, String fileName, String audioPath, + String artist, String thumbnailPath, int durationSeconds) { + return trackMetadataRepository.save( + new TrackMetadata(track, title, fileName, audioPath, artist, thumbnailPath, durationSeconds)); + } + + public TrackMetadata getTrackMetadata(Long trackId) { + return trackMetadataRepository.findByTrackId(trackId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Metadata not found")); + } +} diff --git a/src/main/java/com/bivashy/backend/composer/service/TrackPlaylistService.java b/src/main/java/com/bivashy/backend/composer/service/TrackPlaylistService.java new file mode 100644 index 0000000..9d4ab34 --- /dev/null +++ b/src/main/java/com/bivashy/backend/composer/service/TrackPlaylistService.java @@ -0,0 +1,67 @@ +package com.bivashy.backend.composer.service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.bivashy.backend.composer.model.TrackPlaylist; +import com.bivashy.backend.composer.model.key.PlaylistTrackId; +import com.bivashy.backend.composer.repository.TrackPlaylistRepository; + +@Service +public class TrackPlaylistService { + private final TrackPlaylistRepository repository; + + public TrackPlaylistService(TrackPlaylistRepository repository) { + this.repository = repository; + } + + public List getPlaylistTracks(Long playlistId) { + return repository.findByPlaylistIdOrderByOrderAsc(playlistId); + } + + public void insertTrackAtEnd(Long playlistId, Long trackId) { + addTrack(playlistId, trackId, null); + } + + private BigDecimal findIntermediateOrder(BigDecimal prev, BigDecimal next) { + if (prev == null && next == null) { + return BigDecimal.ONE; + } else if (prev == null) { + return next.divide(BigDecimal.valueOf(2), 1000, RoundingMode.HALF_UP); + } else if (next == null) { + return prev.add(BigDecimal.ONE); + } else { + return prev.add(next).divide(BigDecimal.valueOf(2), 1000, RoundingMode.HALF_UP); + } + } + + private void addTrack(Long playlistId, Long trackId, BigDecimal afterOrder) { + BigDecimal newOrder; + if (afterOrder == null) { + // Insert at the end + BigDecimal maxOrder = repository.findMaxOrderByPlaylistId(playlistId); + newOrder = findIntermediateOrder(maxOrder, null); + } else { + // Insert after a specific track + BigDecimal nextOrder = repository.findNextOrderByPlaylistIdAndOrderGreaterThan(playlistId, afterOrder); + newOrder = findIntermediateOrder(afterOrder, nextOrder); + } + TrackPlaylist trackPlaylist = new TrackPlaylist(playlistId, trackId, newOrder); + repository.save(trackPlaylist); + } + + public void reorderTrack(Long playlistId, Long trackId, BigDecimal afterOrder) { + TrackPlaylist trackPlaylist = repository.findById(new PlaylistTrackId(playlistId, trackId)) + .orElseThrow(() -> new RuntimeException("TrackPlaylist not found")); + + BigDecimal nextOrder = repository.findNextOrderByPlaylistIdAndOrderGreaterThan(playlistId, afterOrder); + BigDecimal newOrder = findIntermediateOrder(afterOrder, nextOrder); + + trackPlaylist.setOrder(newOrder); + repository.save(trackPlaylist); + } + +} diff --git a/src/main/java/com/bivashy/backend/composer/service/TrackService.java b/src/main/java/com/bivashy/backend/composer/service/TrackService.java index 91bd3bd..aacdfd2 100644 --- a/src/main/java/com/bivashy/backend/composer/service/TrackService.java +++ b/src/main/java/com/bivashy/backend/composer/service/TrackService.java @@ -1,88 +1,88 @@ package com.bivashy.backend.composer.service; import java.io.IOException; -import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; import com.bivashy.backend.composer.dto.track.AddLocalTrackRequest; +import com.bivashy.backend.composer.dto.track.PlaylistTrackResponse; import com.bivashy.backend.composer.dto.track.TrackResponse; -import com.bivashy.backend.composer.model.SourceType; import com.bivashy.backend.composer.model.SourceTypes; import com.bivashy.backend.composer.model.Track; import com.bivashy.backend.composer.model.TrackMetadata; -import com.bivashy.backend.composer.model.TrackPlaylist; import com.bivashy.backend.composer.model.TrackSource; import com.bivashy.backend.composer.model.User; -import com.bivashy.backend.composer.repository.SourceTypeRepository; -import com.bivashy.backend.composer.repository.TrackMetadataRepository; -import com.bivashy.backend.composer.repository.TrackPlaylistRepository; import com.bivashy.backend.composer.repository.TrackRepository; -import com.bivashy.backend.composer.repository.TrackSourceRepository; import com.bivashy.backend.composer.service.MetadataParseService.Metadata; @Service public class TrackService { - private final AudioBlobStorageService s3Service; - private final TrackSourceRepository trackSourceRepository; private final TrackRepository trackRepository; - private final TrackMetadataRepository trackMetadataRepository; - private final TrackPlaylistRepository trackPlaylistRepository; - private final SourceTypeRepository sourceTypeRepository; + private final TrackSourceService trackSourceService; + private final TrackMetadataService trackMetadataService; + private final TrackPlaylistService trackPlaylistService; private final MetadataParseService metadataParseService; - public TrackService(AudioBlobStorageService s3Service, TrackSourceRepository trackSourceRepository, - TrackRepository trackRepository, TrackMetadataRepository trackMetadataRepository, - TrackPlaylistRepository trackPlaylistRepository, - SourceTypeRepository sourceTypeRepository, MetadataParseService metadataParseService) { - this.s3Service = s3Service; - this.trackSourceRepository = trackSourceRepository; + public TrackService(TrackRepository trackRepository, + TrackSourceService trackSourceService, + TrackMetadataService trackMetadataService, + TrackPlaylistService trackPlaylistService, + MetadataParseService metadataParseService) { this.trackRepository = trackRepository; - this.trackMetadataRepository = trackMetadataRepository; - this.trackPlaylistRepository = trackPlaylistRepository; - this.sourceTypeRepository = sourceTypeRepository; + this.trackSourceService = trackSourceService; + this.trackMetadataService = trackMetadataService; + this.trackPlaylistService = trackPlaylistService; this.metadataParseService = metadataParseService; } public TrackResponse addLocalTrack(User user, Long playlistId, AddLocalTrackRequest request) throws IOException { - String audioPath = s3Service.store(request.source().getBytes()); - Optional metadata = metadataParseService.extractMetadata(request.source().getInputStream()); + String ffprobeJson = metadata.map(Metadata::rawJson).orElse("{}"); - Optional possibleSourceType = sourceTypeRepository.findByName(SourceTypes.FILE); - if (possibleSourceType.isEmpty()) { - throw new IllegalStateException("cannot find source type " + SourceTypes.FILE); - } - SourceType sourceType = possibleSourceType.get(); - TrackSource trackSource = new TrackSource(audioPath, sourceType, LocalDateTime.now()); - trackSource = trackSourceRepository.save(trackSource); + TrackSource trackSource = trackSourceService.createTrackSource( + request.source().getBytes(), ffprobeJson, SourceTypes.FILE); - Track track = new Track(trackSource); - track = trackRepository.save(track); + Track track = trackRepository.save(new Track(trackSource)); - String fileName = request.source().getOriginalFilename(); - String title = metadata.map(m -> m.title()).orElse(fileName); - String artist = metadata.map(m -> m.artist()).orElse(null); - String thumbnailPath = null; // TODO:? - int durationSeconds = metadata.map(m -> m.durationSeconds()).map(Float::intValue).orElse(0); - - TrackMetadata trackMetadata = new TrackMetadata(track, title, fileName, audioPath, - artist, thumbnailPath, - durationSeconds); - trackMetadata = trackMetadataRepository.save(trackMetadata); - - // TODO: use linked list instead of int order? - TrackPlaylist playlistTrack = new TrackPlaylist(playlistId, track.getId(), /* order */ 0L); - playlistTrack = trackPlaylistRepository.save(playlistTrack); + String fileName = fileNameWithoutExtension(request.source().getOriginalFilename()); + String title = metadata.map(Metadata::title).orElse(fileName); + String artist = metadata.map(Metadata::artist).orElse(null); + int durationSeconds = metadata.map(Metadata::durationSeconds).map(Float::intValue).orElse(0); + trackMetadataService.createTrackMetadata( + track, title, fileName, trackSource.getSourceUrl(), artist, null, durationSeconds); + trackPlaylistService.insertTrackAtEnd(playlistId, track.getId()); return new TrackResponse( track.getId(), - trackMetadata.getTitle(), - trackMetadata.getArtist(), - trackMetadata.getAudioPath(), - trackMetadata.getDurationSeconds(), - trackMetadata.getFileName()); + title, + artist, + trackSource.getSourceUrl(), + durationSeconds, + fileName); } + public List getPlaylistTracks(User user, Long playlistId) { + return trackPlaylistService.getPlaylistTracks(playlistId).stream() + .map(pt -> { + Track track = trackRepository.findById(pt.getTrackId()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Track not found")); + TrackMetadata metadata = trackMetadataService.getTrackMetadata(track.getId()); + return new PlaylistTrackResponse( + track.getId(), + metadata.getTitle(), + metadata.getArtist(), + metadata.getAudioPath(), + metadata.getDurationSeconds(), + metadata.getFileName()); + }) + .toList(); + } + + private String fileNameWithoutExtension(String fileName) { + return fileName.replaceFirst("[.][^.]+$", ""); + } } diff --git a/src/main/java/com/bivashy/backend/composer/service/TrackSourceService.java b/src/main/java/com/bivashy/backend/composer/service/TrackSourceService.java new file mode 100644 index 0000000..f9827e1 --- /dev/null +++ b/src/main/java/com/bivashy/backend/composer/service/TrackSourceService.java @@ -0,0 +1,33 @@ +package com.bivashy.backend.composer.service; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.springframework.stereotype.Service; + +import com.bivashy.backend.composer.model.SourceType; +import com.bivashy.backend.composer.model.TrackSource; +import com.bivashy.backend.composer.repository.SourceTypeRepository; +import com.bivashy.backend.composer.repository.TrackSourceRepository; + +@Service +public class TrackSourceService { + private final TrackSourceRepository trackSourceRepository; + private final SourceTypeRepository sourceTypeRepository; + private final AudioBlobStorageService s3Service; + + public TrackSourceService(TrackSourceRepository trackSourceRepository, + SourceTypeRepository sourceTypeRepository, + AudioBlobStorageService s3Service) { + this.trackSourceRepository = trackSourceRepository; + this.sourceTypeRepository = sourceTypeRepository; + this.s3Service = s3Service; + } + + public TrackSource createTrackSource(byte[] audioBytes, String ffprobeJson, String sourceType) { + String audioPath = s3Service.store(audioBytes, Map.of("ffprobe", ffprobeJson)); + SourceType type = sourceTypeRepository.findByName(sourceType) + .orElseThrow(() -> new IllegalStateException("Source type not found: " + sourceType)); + return trackSourceRepository.save(new TrackSource(audioPath, type, LocalDateTime.now())); + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b4b4fcf..cee3873 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -11,6 +11,10 @@ spring: access-key: ${S3_ACCESS_KEY} secret-key: ${S3_SECRET_KEY} bucket: ${S3_BUCKET} + servlet: + multipart: + max-file-size: 8096MB + max-request-size: 8096MB logging: level: diff --git a/src/main/resources/db/migration/V1_10__create_base_tables.sql b/src/main/resources/db/migration/V1_10__create_base_tables.sql index 1cdd35e..61ec98f 100644 --- a/src/main/resources/db/migration/V1_10__create_base_tables.sql +++ b/src/main/resources/db/migration/V1_10__create_base_tables.sql @@ -64,11 +64,11 @@ CREATE TABLE IF NOT EXISTS "playlist" ( CREATE TABLE IF NOT EXISTS "playlist_track" ( "playlist_id" bigint NOT NULL, "track_id" bigint NOT NULL, - "order_index" bigint NOT NULL, - CONSTRAINT "pk_playlist_track" PRIMARY KEY ("playlist_id", "track_id"), - CONSTRAINT "fk_playlist_track_playlist_id" + "order_index" numeric NOT NULL, + CONSTRAINT "pk_playlist_track_new" PRIMARY KEY ("playlist_id", "track_id"), + CONSTRAINT "fk_playlist_track_playlist_id_new" FOREIGN KEY ("playlist_id") REFERENCES "playlist" ("id"), - CONSTRAINT "fk_playlist_track_track_id" + CONSTRAINT "fk_playlist_track_track_id_new" FOREIGN KEY ("track_id") REFERENCES "track" ("id") );