Initial commit

This commit is contained in:
2025-11-09 03:18:49 +05:00
commit a59c1a7a01
33 changed files with 1688 additions and 0 deletions

View File

@ -0,0 +1,13 @@
package com.bivashy.backend.composer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ComposerApplication {
public static void main(String[] args) {
SpringApplication.run(ComposerApplication.class, args);
}
}

View File

@ -0,0 +1,40 @@
package com.bivashy.backend.composer.auth;
import java.util.Collection;
import java.util.Collections;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class CustomUserDetails implements UserDetails {
private final long id;
private final String username;
private final String password;
public CustomUserDetails(long id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO: Authority implementation
return Collections.emptyList();
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
public long getId() {
return id;
}
}

View File

@ -0,0 +1,25 @@
package com.bivashy.backend.composer.config;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration
public class CORSConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:3000"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

View File

@ -0,0 +1,18 @@
package com.bivashy.backend.composer.config;
import org.modelmapper.ModelMapper;
import org.modelmapper.config.Configuration.AccessLevel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldAccessLevel(AccessLevel.PRIVATE)
.setFieldMatchingEnabled(true);
return modelMapper;
}
}

View File

@ -0,0 +1,47 @@
package com.bivashy.backend.composer.config;
import java.util.Optional;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import com.bivashy.backend.composer.auth.CustomUserDetails;
import com.bivashy.backend.composer.model.User;
import com.bivashy.backend.composer.repository.UserRepository;
import com.bivashy.backend.composer.service.CustomUserDetailsService;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
// TODO: Temporary to test API, remove after testing
.csrf(c -> c.disable());
return http.build();
}
@Bean
public CustomUserDetailsService customUserDetailsService(UserRepository repository) {
CustomUserDetails defaultUser = new CustomUserDetails(1, "user", passwordEncoder().encode("password"));
Optional<User> user = repository.findById(defaultUser.getId());
if (user.isEmpty()) {
repository.save(new User(defaultUser.getUsername()));
}
return new CustomUserDetailsService(defaultUser);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,34 @@
package com.bivashy.backend.composer.controller;
import java.util.List;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.bivashy.backend.composer.auth.CustomUserDetails;
import com.bivashy.backend.composer.dto.playlist.PlaylistCreateDTO;
import com.bivashy.backend.composer.dto.playlist.PlaylistReadDTO;
import com.bivashy.backend.composer.service.PlaylistService;
@RestController
public class PlaylistController {
private final PlaylistService service;
public PlaylistController(PlaylistService service) {
this.service = service;
}
@GetMapping("/playlists")
public List<PlaylistReadDTO> playlists(@AuthenticationPrincipal CustomUserDetails user) {
return service.findPlaylists(user.getId());
}
@PostMapping("/playlist")
public PlaylistReadDTO createPlaylist(@AuthenticationPrincipal CustomUserDetails user,
@RequestBody PlaylistCreateDTO playlist) {
return service.createPlaylist(user.getId(), playlist);
}
}

View File

@ -0,0 +1,22 @@
package com.bivashy.backend.composer.converter;
import org.springframework.stereotype.Component;
import com.bivashy.backend.composer.dto.playlist.PlaylistCreateDTO;
import com.bivashy.backend.composer.dto.playlist.PlaylistReadDTO;
import com.bivashy.backend.composer.model.Playlist;
import com.bivashy.backend.composer.model.User;
@Component
public class PlaylistConverter {
public PlaylistReadDTO convertToRead(Playlist playlist) {
return new PlaylistReadDTO(playlist.getId(), playlist.getOwner().getId(), playlist.getTitle(),
playlist.getCreatedAt(),
playlist.getUpdatedAt());
}
public Playlist convertFromCreate(long userId, PlaylistCreateDTO playlist) {
return new Playlist(new User(userId), playlist.title());
}
}

View File

@ -0,0 +1,4 @@
package com.bivashy.backend.composer.dto.playlist;
public record PlaylistCreateDTO(String title) {
}

View File

@ -0,0 +1,14 @@
package com.bivashy.backend.composer.dto.playlist;
import java.time.LocalDateTime;
public record PlaylistReadDTO(
long id,
long ownerId,
String title,
LocalDateTime createdAt,
LocalDateTime updatedAt) {
public PlaylistReadDTO withUserId(long userId) {
return new PlaylistReadDTO(this.id, userId, this.title, this.createdAt, this.updatedAt);
}
}

View File

@ -0,0 +1,79 @@
package com.bivashy.backend.composer.model;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
@Entity
@Table(name = "playlist")
public class Playlist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "owner_id", nullable = false)
private User owner;
@Column(nullable = false, length = 500)
private String title;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@ManyToMany
@JoinTable(name = "playlist_track", joinColumns = @JoinColumn(name = "playlist_id"), inverseJoinColumns = @JoinColumn(name = "track_id"))
@OrderBy("order ASC")
private Set<Track> tracks = new HashSet<>();
Playlist() {
}
public Playlist(User owner, String title) {
this.owner = owner;
this.title = title;
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
public Long getId() {
return id;
}
public User getOwner() {
return owner;
}
public String getTitle() {
return title;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public Set<Track> getTracks() {
return tracks;
}
}

View File

@ -0,0 +1,47 @@
package com.bivashy.backend.composer.model;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
@Entity
@Table(name = "source_provider")
public class SourceProvider {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 500)
private String name;
@OneToMany(mappedBy = "provider", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<SourceType> sourceTypes = new HashSet<>();
SourceProvider() {
}
public SourceProvider(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Set<SourceType> getSourceTypes() {
return sourceTypes;
}
}

View File

@ -0,0 +1,58 @@
package com.bivashy.backend.composer.model;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
@Entity
@Table(name = "source_type")
public class SourceType {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "provider_id", nullable = false)
private SourceProvider provider;
@Column(nullable = false, length = 500)
private String name;
@OneToMany(mappedBy = "sourceType", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<TrackSource> trackSources = new HashSet<>();
SourceType() {
}
public SourceType(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public SourceProvider getProvider() {
return provider;
}
public String getName() {
return name;
}
public Set<TrackSource> getTrackSources() {
return trackSources;
}
}

View File

@ -0,0 +1,65 @@
package com.bivashy.backend.composer.model;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
@Entity
@Table(name = "track")
public class Track {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "source_id", nullable = false)
private TrackSource source;
@OneToMany(mappedBy = "track", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<TrackMetadata> metadata = new HashSet<>();
@OneToMany(mappedBy = "track", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<TrackVersion> versions = new HashSet<>();
@ManyToMany(mappedBy = "tracks")
private Set<Playlist> playlists = new HashSet<>();
Track() {
}
public Track(TrackSource source) {
this.source = source;
}
public Long getId() {
return id;
}
public TrackSource getSource() {
return source;
}
public Set<TrackMetadata> getMetadata() {
return metadata;
}
public Set<TrackVersion> getVersions() {
return versions;
}
public Set<Playlist> getPlaylists() {
return playlists;
}
}

View File

@ -0,0 +1,91 @@
package com.bivashy.backend.composer.model;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
@Entity
@Table(name = "track_metadata")
public class TrackMetadata {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "track_id", nullable = false)
private Track track;
@Column(nullable = false, length = 500)
private String title;
@Column(name = "audio_path", nullable = false, length = 500)
private String audioPath;
@Column(length = 500)
private String artist;
@Column(name = "thumbnail_path", length = 500)
private String thumbnailPath;
@Column(name = "duration_seconds")
private Integer durationSeconds;
@OneToMany(mappedBy = "metadata", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<TrackVersion> versions = new HashSet<>();
TrackMetadata() {
}
public TrackMetadata(Track track, String title, String audioPath, String artist, String thumbnailPath,
Integer durationSeconds) {
this.track = track;
this.title = title;
this.audioPath = audioPath;
this.artist = artist;
this.thumbnailPath = thumbnailPath;
this.durationSeconds = durationSeconds;
}
public Long getId() {
return id;
}
public Track getTrack() {
return track;
}
public String getTitle() {
return title;
}
public String getAudioPath() {
return audioPath;
}
public String getArtist() {
return artist;
}
public String getThumbnailPath() {
return thumbnailPath;
}
public Integer getDurationSeconds() {
return durationSeconds;
}
public Set<TrackVersion> getVersions() {
return versions;
}
}

View File

@ -0,0 +1,91 @@
package com.bivashy.backend.composer.model;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
@Entity
@Table(name = "track_source")
public class TrackSource {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "source_url", nullable = false, length = 500)
private String sourceUrl;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "source_type_id", nullable = false)
private SourceType sourceType;
@Column(name = "last_fetched_at", nullable = false)
private LocalDateTime lastFetchedAt;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "source", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Track> tracks = new HashSet<>();
@OneToMany(mappedBy = "source", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<TrackVersion> trackVersions = new HashSet<>();
TrackSource() {
}
public TrackSource(String sourceUrl, SourceType sourceType, LocalDateTime lastFetchedAt) {
this.sourceUrl = sourceUrl;
this.sourceType = sourceType;
this.lastFetchedAt = lastFetchedAt;
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
public Long getId() {
return id;
}
public String getSourceUrl() {
return sourceUrl;
}
public SourceType getSourceType() {
return sourceType;
}
public LocalDateTime getLastFetchedAt() {
return lastFetchedAt;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public Set<Track> getTracks() {
return tracks;
}
public Set<TrackVersion> getTrackVersions() {
return trackVersions;
}
}

View File

@ -0,0 +1,67 @@
package com.bivashy.backend.composer.model;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Entity
@Table(name = "track_version")
public class TrackVersion {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "track_id", nullable = false)
private Track track;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "metadata_id", nullable = false)
private TrackMetadata metadata;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "source_id", nullable = false)
private TrackSource source;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
TrackVersion() {
}
public TrackVersion(Track track, TrackMetadata metadata, TrackSource source) {
this.track = track;
this.metadata = metadata;
this.source = source;
this.createdAt = LocalDateTime.now();
}
public Long getId() {
return id;
}
public Track getTrack() {
return track;
}
public TrackMetadata getMetadata() {
return metadata;
}
public TrackSource getSource() {
return source;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
}

View File

@ -0,0 +1,70 @@
package com.bivashy.backend.composer.model;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 500)
private String name;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Playlist> playlists = new HashSet<>();
User() {
}
public User(Long id) {
this.id = id;
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
public User(String name) {
this.name = name;
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public Set<Playlist> getPlaylists() {
return playlists;
}
}

View File

@ -0,0 +1,18 @@
package com.bivashy.backend.composer.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.bivashy.backend.composer.model.Playlist;
@Repository
public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
Optional<Playlist> findById(Long id);
Optional<Playlist> findByOwnerId(long id);
List<Playlist> findAllByOwnerId(long id);
}

View File

@ -0,0 +1,13 @@
package com.bivashy.backend.composer.repository;
import com.bivashy.backend.composer.model.User;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findById(long id);
}

View File

@ -0,0 +1,23 @@
package com.bivashy.backend.composer.service;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.bivashy.backend.composer.auth.CustomUserDetails;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final CustomUserDetails defaultUser;
public CustomUserDetailsService(CustomUserDetails defaultUser) {
this.defaultUser = defaultUser;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return defaultUser;
}
}

View File

@ -0,0 +1,34 @@
package com.bivashy.backend.composer.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.bivashy.backend.composer.converter.PlaylistConverter;
import com.bivashy.backend.composer.dto.playlist.PlaylistCreateDTO;
import com.bivashy.backend.composer.dto.playlist.PlaylistReadDTO;
import com.bivashy.backend.composer.model.Playlist;
import com.bivashy.backend.composer.repository.PlaylistRepository;
@Service
public class PlaylistService {
private final PlaylistRepository repository;
private final PlaylistConverter converter;
public PlaylistService(PlaylistRepository repository, PlaylistConverter converter) {
this.repository = repository;
this.converter = converter;
}
public PlaylistReadDTO createPlaylist(long userId, PlaylistCreateDTO playlist) {
Playlist result = repository.save(converter.convertFromCreate(userId, playlist));
return converter.convertToRead(result);
}
public List<PlaylistReadDTO> findPlaylists(long userId) {
return repository.findAllByOwnerId(userId)
.stream()
.map(converter::convertToRead)
.toList();
}
}

View File

@ -0,0 +1,14 @@
spring:
application:
name: composer
datasource:
driver-class-name: org.postgresql.Driver
password: password
url: jdbc:postgresql://postgres:5432/db
username: user
logging:
level:
org:
springframework:
security: DEBUG

View File

@ -0,0 +1,85 @@
CREATE TABLE IF NOT EXISTS "users" (
"id" bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
"name" varchar(500) NOT NULL,
"created_at" timestamp NOT NULL DEFAULT NOW(),
"updated_at" timestamp NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS "source_provider" (
"id" bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
"name" varchar(500) NOT NULL
);
CREATE TABLE IF NOT EXISTS "source_type" (
"id" bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
"provider_id" bigint NOT NULL,
"name" varchar(500) NOT NULL,
CONSTRAINT "fk_source_type_provider_id"
FOREIGN KEY ("provider_id") REFERENCES "source_provider" ("id")
);
CREATE TABLE IF NOT EXISTS "track_source" (
"id" bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
"source_url" varchar(500) NOT NULL,
"source_type_id" bigint NOT NULL,
"last_fetched_at" timestamp NOT NULL DEFAULT NOW(),
"created_at" timestamp NOT NULL DEFAULT NOW(),
"updated_at" timestamp NOT NULL DEFAULT NOW(),
CONSTRAINT "fk_track_source_source_type_id"
FOREIGN KEY ("source_type_id") REFERENCES "source_type" ("id")
);
CREATE TABLE IF NOT EXISTS "track" (
"id" bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
"source_id" bigint NOT NULL,
CONSTRAINT "fk_track_source_id"
FOREIGN KEY ("source_id") REFERENCES "track_source" ("id")
);
CREATE TABLE IF NOT EXISTS "track_metadata" (
"id" bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
"track_id" bigint NOT NULL,
"title" varchar(500) NOT NULL,
"audio_path" varchar(500) NOT NULL,
"artist" varchar(500),
"thumbnail_path" varchar(500),
"duration_seconds" integer,
CONSTRAINT "fk_track_metadata_track_id"
FOREIGN KEY ("track_id") REFERENCES "track" ("id")
);
CREATE TABLE IF NOT EXISTS "playlist" (
"id" bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
"owner_id" bigint NOT NULL,
"title" varchar(500) NOT NULL,
"created_at" timestamp NOT NULL DEFAULT NOW(),
"updated_at" timestamp NOT NULL DEFAULT NOW(),
CONSTRAINT "fk_playlist_owner_id"
FOREIGN KEY ("owner_id") REFERENCES "users" ("id")
);
CREATE TABLE IF NOT EXISTS "playlist_track" (
"playlist_id" bigint NOT NULL,
"track_id" bigint NOT NULL,
"order" bigint NOT NULL,
CONSTRAINT "pk_playlist_track" PRIMARY KEY ("playlist_id", "track_id"),
CONSTRAINT "fk_playlist_track_playlist_id"
FOREIGN KEY ("playlist_id") REFERENCES "playlist" ("id"),
CONSTRAINT "fk_playlist_track_track_id"
FOREIGN KEY ("track_id") REFERENCES "track" ("id")
);
CREATE TABLE IF NOT EXISTS "track_version" (
"id" bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
"track_id" bigint NOT NULL,
"metadata_id" bigint NOT NULL,
"source_id" bigint NOT NULL,
"created_at" timestamp NOT NULL DEFAULT NOW(),
CONSTRAINT "fk_track_version_track_id"
FOREIGN KEY ("track_id") REFERENCES "track" ("id"),
CONSTRAINT "fk_track_version_metadata_id"
FOREIGN KEY ("metadata_id") REFERENCES "track_metadata" ("id"),
CONSTRAINT "fk_track_version_source_id"
FOREIGN KEY ("source_id") REFERENCES "track_source" ("id")
);

View File

@ -0,0 +1,17 @@
INSERT INTO "source_provider" ("id", "name")
OVERRIDING SYSTEM VALUE
VALUES
(1, 'YOUTUBE'),
(2, 'LOCAL'),
(3, 'EXTERNAL')
ON CONFLICT ("id") DO NOTHING;
INSERT INTO "source_type" ("id", "provider_id", "name")
OVERRIDING SYSTEM VALUE
VALUES
(1, 1, 'VIDEO'),
(2, 1, 'PLAYLIST'),
(3, 2, 'FILE'),
(4, 3, 'URL')
ON CONFLICT ("id") DO NOTHING;

View File

@ -0,0 +1,5 @@
flyway.user=user
flyway.password=password
flyway.schemas=public
flyway.url=jdbc:postgresql://postgres:5432/db
flyway.locations=filesystem:db/migration

View File

@ -0,0 +1,13 @@
package com.bivashy.backend.composer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ComposerApplicationTests {
@Test
void contextLoads() {
}
}