From e52048a06c2dabc6e5011bf97e3214a387f65617 Mon Sep 17 00:00:00 2001 From: DevReaper0 Date: Wed, 23 Oct 2024 15:24:17 -0500 Subject: [PATCH] Added some general database tests. --- README.md | 22 +- config/config.go | 4 +- db/database_test.go | 1406 +++++++++++++++++++++++++++++++++++++++++++ db/sqlite.go | 1361 ++++++++++++++++++++++++++++++++++++----- db/sqlite_test.go | 205 ------- 5 files changed, 2651 insertions(+), 347 deletions(-) create mode 100644 db/database_test.go delete mode 100644 db/sqlite_test.go diff --git a/README.md b/README.md index 90ecb3b..ebd5ab2 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,25 @@ Libra is a new, open, and extensible music service. Libra does what you want, ho ## Setup Steps -Before anything else, you need to create the database. Install PostgreSQL and run the following commands: +Before anything else, you need to set up the database. Install one of the following databases and follow its steps: + +
+SQLite + +No additional steps are needed to use SQLite. +
+ +
+ +PostgreSQL + +To create the PostgreSQL database, run the following commands: ```bash sudo -u postgres createuser -P libra sudo -u postgres createdb -O libra -E UTF-8 libra ``` +
Dependencies: @@ -17,6 +30,13 @@ Dependencies: - `yt-dlp` and `ytmusicapi` for the YouTube source - `FFmpeg` +## Development + +To run all tests, run `make test`. +If you don't have every supported database type installed, you can skip database tests by setting the following environment variables: +- `SKIP_SQLITE_TESTS` +- `SKIP_POSTGRES_TESTS` + ## Roadmap In the future, the project aims for the following: diff --git a/config/config.go b/config/config.go index c621d14..bcf3b51 100644 --- a/config/config.go +++ b/config/config.go @@ -162,7 +162,7 @@ func LoadConfig() (conf Config) { viper.SetConfigType("yaml") viper.AddConfigPath(".") - if err := readDefaultConfig(); err != nil { + if err := LoadDefaultConfig(); err != nil { log.Fatal().Msg("Failed to read default config") return } @@ -182,7 +182,7 @@ func LoadConfig() (conf Config) { return } -func readDefaultConfig() error { +func LoadDefaultConfig() error { return viper.ReadConfig(strings.NewReader(defaultConfig)) } diff --git a/db/database_test.go b/db/database_test.go new file mode 100644 index 0000000..67cf7ca --- /dev/null +++ b/db/database_test.go @@ -0,0 +1,1406 @@ +package db + +import ( + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/LibraMusic/LibraCore/config" + "github.com/LibraMusic/LibraCore/types" +) + +var testCases = []struct { + name string + dbFunc func(t *testing.T) Database +}{ + {"SQLite", setupSQLiteTestDB}, + {"PostgreSQL", setupPostgresTestDB}, +} + +func setupSQLiteTestDB(t *testing.T) Database { + if os.Getenv("SKIP_SQLITE_TESTS") != "" { + t.Skip("Skipping SQLite tests") + } + + config.Conf.Database.SQLite.Path = ":memory:" + + db, err := ConnectSQLite() + if err != nil { + t.Fatalf("Failed to connect to SQLite: %v", err) + } + return db +} + +func setupPostgresTestDB(t *testing.T) Database { + if os.Getenv("SKIP_POSTGRES_TESTS") != "" { + t.Skip("Skipping PostgreSQL tests") + } + + config.Conf.Database.PostgreSQL.Host = "localhost" + config.Conf.Database.PostgreSQL.User = "libra" + config.Conf.Database.PostgreSQL.Pass = "password" + config.Conf.Database.PostgreSQL.DBName = "libra" + config.Conf.Database.PostgreSQL.Params = "?sslmode=disable" + + db, err := ConnectPostgreSQL() + if err != nil { + t.Fatalf("Failed to connect to PostgreSQL: %v", err) + } + return db +} + +func TestDatabaseAddTrack(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + track := types.Track{ + ID: "1", + UserID: "user1", + ISRC: "ISRC123", + Title: "Test Track", + ArtistIDs: []string{"artist1"}, + AlbumIDs: []string{"album1"}, + PrimaryAlbumID: "album1", + TrackNumber: 1, + Duration: 180, + Description: "Test Description", + ReleaseDate: "2023-01-01", + Lyrics: map[string]string{"en": "Test Lyrics"}, + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + ContentSource: "mock::1", + MetadataSource: "mock::1", + LyricSources: map[string]types.LinkedSource{"en": "mock::1"}, + } + + err := db.AddTrack(track) + assert.NoError(t, err) + + retrievedTrack, err := db.GetTrack("1") + assert.NoError(t, err) + assert.Equal(t, track, retrievedTrack) + }) + } +} + +func TestDatabaseGetTracks(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + track1 := types.Track{ + ID: "1", + UserID: "user1", + ISRC: "ISRC123", + Title: "Test Track 1", + ArtistIDs: []string{"artist1"}, + AlbumIDs: []string{"album1"}, + PrimaryAlbumID: "album1", + TrackNumber: 1, + Duration: 180, + Description: "Test Description 1", + ReleaseDate: "2023-01-01", + Lyrics: map[string]string{"en": "Test Lyrics 1"}, + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + ContentSource: "mock::1", + MetadataSource: "mock::1", + LyricSources: map[string]types.LinkedSource{"en": "mock::1"}, + } + + track2 := types.Track{ + ID: "2", + UserID: "user1", + ISRC: "ISRC124", + Title: "Test Track 2", + ArtistIDs: []string{"artist2"}, + AlbumIDs: []string{"album2"}, + PrimaryAlbumID: "album2", + TrackNumber: 2, + Duration: 200, + Description: "Test Description 2", + ReleaseDate: "2023-01-02", + Lyrics: map[string]string{"en": "Test Lyrics 2"}, + ListenCount: 200, + FavoriteCount: 100, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag3", "tag4"}, + AdditionalMeta: map[string]interface{}{"key": "value2"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item2"}, + ContentSource: "mock::2", + MetadataSource: "mock::2", + LyricSources: map[string]types.LinkedSource{"en": "mock::2"}, + } + + track3 := types.Track{ + ID: "3", + UserID: "user2", + ISRC: "ISRC125", + Title: "Test Track 3", + ArtistIDs: []string{"artist3"}, + AlbumIDs: []string{"album3"}, + PrimaryAlbumID: "album3", + TrackNumber: 3, + Duration: 300, + Description: "Test Description 3", + ReleaseDate: "2023-01-03", + Lyrics: map[string]string{"en": "Test Lyrics 3"}, + ListenCount: 300, + FavoriteCount: 150, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag5", "tag6"}, + AdditionalMeta: map[string]interface{}{"key": "value3"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item3"}, + ContentSource: "mock::3", + MetadataSource: "mock::3", + LyricSources: map[string]types.LinkedSource{"en": "mock::3"}, + } + + err := db.AddTrack(track1) + assert.NoError(t, err) + err = db.AddTrack(track2) + assert.NoError(t, err) + err = db.AddTrack(track3) + assert.NoError(t, err) + + tracks, err := db.GetAllTracks() + assert.NoError(t, err) + assert.Len(t, tracks, 3) + assert.Contains(t, tracks, track1) + assert.Contains(t, tracks, track2) + assert.Contains(t, tracks, track3) + + tracks, err = db.GetTracks("user1") + assert.NoError(t, err) + assert.Len(t, tracks, 2) + assert.Contains(t, tracks, track1) + assert.Contains(t, tracks, track2) + + tracks, err = db.GetTracks("user2") + assert.NoError(t, err) + assert.Len(t, tracks, 1) + assert.Contains(t, tracks, track3) + }) + } +} + +func TestDatabaseUpdateTrack(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + track := types.Track{ + ID: "1", + UserID: "user1", + ISRC: "ISRC123", + Title: "Test Track", + ArtistIDs: []string{"artist1"}, + AlbumIDs: []string{"album1"}, + PrimaryAlbumID: "album1", + TrackNumber: 1, + Duration: 180, + Description: "Test Description", + ReleaseDate: "2023-01-01", + Lyrics: map[string]string{"en": "Test Lyrics"}, + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + ContentSource: "mock::1", + MetadataSource: "mock::1", + LyricSources: map[string]types.LinkedSource{"en": "mock::1"}, + } + + err := db.AddTrack(track) + assert.NoError(t, err) + + track.Title = "Updated Test Track" + err = db.UpdateTrack(track) + assert.NoError(t, err) + + retrievedTrack, err := db.GetTrack("1") + assert.NoError(t, err) + assert.Equal(t, "Updated Test Track", retrievedTrack.Title) + }) + } +} + +func TestDatabaseDeleteTrack(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + track := types.Track{ + ID: "1", + UserID: "user1", + ISRC: "ISRC123", + Title: "Test Track", + ArtistIDs: []string{"artist1"}, + AlbumIDs: []string{"album1"}, + PrimaryAlbumID: "album1", + TrackNumber: 1, + Duration: 180, + Description: "Test Description", + ReleaseDate: "2023-01-01", + Lyrics: map[string]string{"en": "Test Lyrics"}, + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + ContentSource: "mock::1", + MetadataSource: "mock::1", + LyricSources: map[string]types.LinkedSource{"en": "mock::1"}, + } + + err := db.AddTrack(track) + assert.NoError(t, err) + + err = db.DeleteTrack("1") + assert.NoError(t, err) + + _, err = db.GetTrack("1") + assert.Error(t, err) + }) + } +} + +func TestDatabaseAddAlbum(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + album := types.Album{ + ID: "1", + UserID: "user1", + UPC: "UPC123", + Title: "Test Album", + ArtistIDs: []string{"artist1"}, + TrackIDs: []string{"track1"}, + Description: "Test Description", + ReleaseDate: "2023-01-01", + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + MetadataSource: "mock::1", + } + + err := db.AddAlbum(album) + assert.NoError(t, err) + + retrievedAlbum, err := db.GetAlbum("1") + assert.NoError(t, err) + assert.Equal(t, album, retrievedAlbum) + }) + } +} + +func TestDatabaseGetAlbums(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + album1 := types.Album{ + ID: "1", + UserID: "user1", + UPC: "UPC123", + Title: "Test Album 1", + ArtistIDs: []string{"artist1"}, + TrackIDs: []string{"track1"}, + Description: "Test Description 1", + ReleaseDate: "2023-01-01", + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + MetadataSource: "mock::1", + } + + album2 := types.Album{ + ID: "2", + UserID: "user1", + UPC: "UPC124", + Title: "Test Album 2", + ArtistIDs: []string{"artist2"}, + TrackIDs: []string{"track2"}, + Description: "Test Description 2", + ReleaseDate: "2023-01-02", + ListenCount: 200, + FavoriteCount: 100, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag3", "tag4"}, + AdditionalMeta: map[string]interface{}{"key": "value2"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item2"}, + MetadataSource: "mock::2", + } + + album3 := types.Album{ + ID: "3", + UserID: "user2", + UPC: "UPC125", + Title: "Test Album 3", + ArtistIDs: []string{"artist3"}, + TrackIDs: []string{"track3"}, + Description: "Test Description 3", + ReleaseDate: "2023-01-03", + ListenCount: 300, + FavoriteCount: 150, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag5", "tag6"}, + AdditionalMeta: map[string]interface{}{"key": "value3"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item3"}, + MetadataSource: "mock::3", + } + + err := db.AddAlbum(album1) + assert.NoError(t, err) + err = db.AddAlbum(album2) + assert.NoError(t, err) + err = db.AddAlbum(album3) + assert.NoError(t, err) + + albums, err := db.GetAllAlbums() + assert.NoError(t, err) + assert.Len(t, albums, 3) + assert.Contains(t, albums, album1) + assert.Contains(t, albums, album2) + assert.Contains(t, albums, album3) + + albums, err = db.GetAlbums("user1") + assert.NoError(t, err) + assert.Len(t, albums, 2) + assert.Contains(t, albums, album1) + assert.Contains(t, albums, album2) + + albums, err = db.GetAlbums("user2") + assert.NoError(t, err) + assert.Len(t, albums, 1) + assert.Contains(t, albums, album3) + }) + } +} + +func TestDatabaseUpdateAlbum(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + album := types.Album{ + ID: "1", + UserID: "user1", + UPC: "UPC123", + Title: "Test Album", + ArtistIDs: []string{"artist1"}, + TrackIDs: []string{"track1"}, + Description: "Test Description", + ReleaseDate: "2023-01-01", + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + MetadataSource: "mock::1", + } + + err := db.AddAlbum(album) + assert.NoError(t, err) + + album.Title = "Updated Test Album" + err = db.UpdateAlbum(album) + assert.NoError(t, err) + + retrievedAlbum, err := db.GetAlbum("1") + assert.NoError(t, err) + assert.Equal(t, "Updated Test Album", retrievedAlbum.Title) + }) + } +} + +func TestDatabaseDeleteAlbum(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + album := types.Album{ + ID: "1", + UserID: "user1", + UPC: "UPC123", + Title: "Test Album", + ArtistIDs: []string{"artist1"}, + TrackIDs: []string{"track1"}, + Description: "Test Description", + ReleaseDate: "2023-01-01", + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + MetadataSource: "mock::1", + } + + err := db.AddAlbum(album) + assert.NoError(t, err) + + err = db.DeleteAlbum("1") + assert.NoError(t, err) + + _, err = db.GetAlbum("1") + assert.Error(t, err) + }) + } +} + +func TestDatabaseAddVideo(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + video := types.Video{ + ID: "1", + UserID: "user1", + Title: "Test Video", + ArtistIDs: []string{"artist1"}, + Duration: 300, + Description: "Test Description", + ReleaseDate: "2023-01-01", + Subtitles: map[string]string{"en": "Test Subtitles"}, + WatchCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + ContentSource: "mock::1", + MetadataSource: "mock::1", + LyricSources: map[string]types.LinkedSource{"en": "mock::1"}, + } + + err := db.AddVideo(video) + assert.NoError(t, err) + + retrievedVideo, err := db.GetVideo("1") + assert.NoError(t, err) + assert.Equal(t, video, retrievedVideo) + }) + } +} + +func TestDatabaseGetVideos(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + video1 := types.Video{ + ID: "1", + UserID: "user1", + Title: "Test Video 1", + ArtistIDs: []string{"artist1"}, + Duration: 300, + Description: "Test Description 1", + ReleaseDate: "2023-01-01", + Subtitles: map[string]string{"en": "Test Subtitles 1"}, + WatchCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + ContentSource: "mock::1", + MetadataSource: "mock::1", + LyricSources: map[string]types.LinkedSource{"en": "mock::1"}, + } + + video2 := types.Video{ + ID: "2", + UserID: "user1", + Title: "Test Video 2", + ArtistIDs: []string{"artist2"}, + Duration: 400, + Description: "Test Description 2", + ReleaseDate: "2023-01-02", + Subtitles: map[string]string{"en": "Test Subtitles 2"}, + WatchCount: 200, + FavoriteCount: 100, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag3", "tag4"}, + AdditionalMeta: map[string]interface{}{"key": "value2"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item2"}, + ContentSource: "mock::2", + MetadataSource: "mock::2", + LyricSources: map[string]types.LinkedSource{"en": "mock::2"}, + } + + video3 := types.Video{ + ID: "3", + UserID: "user2", + Title: "Test Video 3", + ArtistIDs: []string{"artist3"}, + Duration: 500, + Description: "Test Description 3", + ReleaseDate: "2023-01-03", + Subtitles: map[string]string{"en": "Test Subtitles 3"}, + WatchCount: 300, + FavoriteCount: 150, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag5", "tag6"}, + AdditionalMeta: map[string]interface{}{"key": "value3"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item3"}, + ContentSource: "mock::3", + MetadataSource: "mock::3", + LyricSources: map[string]types.LinkedSource{"en": "mock::3"}, + } + + err := db.AddVideo(video1) + assert.NoError(t, err) + err = db.AddVideo(video2) + assert.NoError(t, err) + err = db.AddVideo(video3) + assert.NoError(t, err) + + videos, err := db.GetAllVideos() + assert.NoError(t, err) + assert.Len(t, videos, 3) + assert.Contains(t, videos, video1) + assert.Contains(t, videos, video2) + assert.Contains(t, videos, video3) + + videos, err = db.GetVideos("user1") + assert.NoError(t, err) + assert.Len(t, videos, 2) + assert.Contains(t, videos, video1) + assert.Contains(t, videos, video2) + + videos, err = db.GetVideos("user2") + assert.NoError(t, err) + assert.Len(t, videos, 1) + assert.Contains(t, videos, video3) + }) + } +} + +func TestDatabaseUpdateVideo(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + video := types.Video{ + ID: "1", + UserID: "user1", + Title: "Test Video", + ArtistIDs: []string{"artist1"}, + Duration: 300, + Description: "Test Description", + ReleaseDate: "2023-01-01", + Subtitles: map[string]string{"en": "Test Subtitles"}, + WatchCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + ContentSource: "mock::1", + MetadataSource: "mock::1", + LyricSources: map[string]types.LinkedSource{"en": "mock::1"}, + } + + err := db.AddVideo(video) + assert.NoError(t, err) + + video.Title = "Updated Test Video" + err = db.UpdateVideo(video) + assert.NoError(t, err) + + retrievedVideo, err := db.GetVideo("1") + assert.NoError(t, err) + assert.Equal(t, "Updated Test Video", retrievedVideo.Title) + }) + } +} + +func TestDatabaseDeleteVideo(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + video := types.Video{ + ID: "1", + UserID: "user1", + Title: "Test Video", + ArtistIDs: []string{"artist1"}, + Duration: 300, + Description: "Test Description", + ReleaseDate: "2023-01-01", + Subtitles: map[string]string{"en": "Test Subtitles"}, + WatchCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + ContentSource: "mock::1", + MetadataSource: "mock::1", + LyricSources: map[string]types.LinkedSource{"en": "mock::1"}, + } + + err := db.AddVideo(video) + assert.NoError(t, err) + + err = db.DeleteVideo("1") + assert.NoError(t, err) + + _, err = db.GetVideo("1") + assert.Error(t, err) + }) + } +} + +func TestDatabaseAddArtist(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + artist := types.Artist{ + ID: "1", + UserID: "user1", + Name: "Test Artist", + AlbumIDs: []string{"album1"}, + TrackIDs: []string{"track1"}, + Description: "Test Description", + CreationDate: "2023-01-01", + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + MetadataSource: "mock::1", + } + + err := db.AddArtist(artist) + assert.NoError(t, err) + + retrievedArtist, err := db.GetArtist("1") + assert.NoError(t, err) + assert.Equal(t, artist, retrievedArtist) + }) + } +} + +func TestDatabaseGetArtists(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + artist1 := types.Artist{ + ID: "1", + UserID: "user1", + Name: "Test Artist 1", + AlbumIDs: []string{"album1"}, + TrackIDs: []string{"track1"}, + Description: "Test Description 1", + CreationDate: "2023-01-01", + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + MetadataSource: "mock::1", + } + + artist2 := types.Artist{ + ID: "2", + UserID: "user1", + Name: "Test Artist 2", + AlbumIDs: []string{"album2"}, + TrackIDs: []string{"track2"}, + Description: "Test Description 2", + CreationDate: "2023-01-02", + ListenCount: 200, + FavoriteCount: 100, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag3", "tag4"}, + AdditionalMeta: map[string]interface{}{"key": "value2"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item2"}, + MetadataSource: "mock::2", + } + + artist3 := types.Artist{ + ID: "3", + UserID: "user2", + Name: "Test Artist 3", + AlbumIDs: []string{"album3"}, + TrackIDs: []string{"track3"}, + Description: "Test Description 3", + CreationDate: "2023-01-03", + ListenCount: 300, + FavoriteCount: 150, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag5", "tag6"}, + AdditionalMeta: map[string]interface{}{"key": "value3"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item3"}, + MetadataSource: "mock::3", + } + + err := db.AddArtist(artist1) + assert.NoError(t, err) + err = db.AddArtist(artist2) + assert.NoError(t, err) + err = db.AddArtist(artist3) + assert.NoError(t, err) + + artists, err := db.GetAllArtists() + assert.NoError(t, err) + assert.Len(t, artists, 3) + assert.Contains(t, artists, artist1) + assert.Contains(t, artists, artist2) + assert.Contains(t, artists, artist3) + + artists, err = db.GetArtists("user1") + assert.NoError(t, err) + assert.Len(t, artists, 2) + assert.Contains(t, artists, artist1) + assert.Contains(t, artists, artist2) + + artists, err = db.GetArtists("user2") + assert.NoError(t, err) + assert.Len(t, artists, 1) + assert.Contains(t, artists, artist3) + }) + } +} + +func TestDatabaseUpdateArtist(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + artist := types.Artist{ + ID: "1", + UserID: "user1", + Name: "Test Artist", + AlbumIDs: []string{"album1"}, + TrackIDs: []string{"track1"}, + Description: "Test Description", + CreationDate: "2023-01-01", + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + MetadataSource: "mock::1", + } + + err := db.AddArtist(artist) + assert.NoError(t, err) + + artist.Name = "Updated Test Artist" + err = db.UpdateArtist(artist) + assert.NoError(t, err) + + retrievedArtist, err := db.GetArtist("1") + assert.NoError(t, err) + assert.Equal(t, "Updated Test Artist", retrievedArtist.Name) + }) + } +} + +func TestDatabaseDeleteArtist(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + artist := types.Artist{ + ID: "1", + UserID: "user1", + Name: "Test Artist", + AlbumIDs: []string{"album1"}, + TrackIDs: []string{"track1"}, + Description: "Test Description", + CreationDate: "2023-01-01", + ListenCount: 100, + FavoriteCount: 50, + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + LinkedItemIDs: []string{"item1"}, + MetadataSource: "mock::1", + } + + err := db.AddArtist(artist) + assert.NoError(t, err) + + err = db.DeleteArtist("1") + assert.NoError(t, err) + + _, err = db.GetArtist("1") + assert.Error(t, err) + }) + } +} + +func TestDatabaseAddPlaylist(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + playlist := types.Playlist{ + ID: "1", + UserID: "user1", + Title: "Test Playlist", + TrackIDs: []string{"track1", "track2"}, + ListenCount: 100, + FavoriteCount: 50, + Description: "Test Description", + CreationDate: "2023-01-01", + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + MetadataSource: "mock::1", + } + + err := db.AddPlaylist(playlist) + assert.NoError(t, err) + + retrievedPlaylist, err := db.GetPlaylist("1") + assert.NoError(t, err) + assert.Equal(t, playlist, retrievedPlaylist) + }) + } +} + +func TestDatabaseGetPlaylists(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + playlist1 := types.Playlist{ + ID: "1", + UserID: "user1", + Title: "Test Playlist 1", + TrackIDs: []string{"track1", "track2"}, + ListenCount: 100, + FavoriteCount: 50, + Description: "Test Description 1", + CreationDate: "2023-01-01", + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + MetadataSource: "mock::1", + } + + playlist2 := types.Playlist{ + ID: "2", + UserID: "user1", + Title: "Test Playlist 2", + TrackIDs: []string{"track3", "track4"}, + ListenCount: 200, + FavoriteCount: 100, + Description: "Test Description 2", + CreationDate: "2023-01-02", + AdditionDate: time.Now().Unix(), + Tags: []string{"tag3", "tag4"}, + AdditionalMeta: map[string]interface{}{"key": "value2"}, + Permissions: map[string]string{"read": "all"}, + MetadataSource: "mock::2", + } + + playlist3 := types.Playlist{ + ID: "3", + UserID: "user2", + Title: "Test Playlist 3", + TrackIDs: []string{"track5", "track6"}, + ListenCount: 300, + FavoriteCount: 150, + Description: "Test Description 3", + CreationDate: "2023-01-03", + AdditionDate: time.Now().Unix(), + Tags: []string{"tag5", "tag6"}, + AdditionalMeta: map[string]interface{}{"key": "value3"}, + Permissions: map[string]string{"read": "all"}, + MetadataSource: "mock::3", + } + + err := db.AddPlaylist(playlist1) + assert.NoError(t, err) + err = db.AddPlaylist(playlist2) + assert.NoError(t, err) + err = db.AddPlaylist(playlist3) + assert.NoError(t, err) + + playlists, err := db.GetAllPlaylists() + assert.NoError(t, err) + assert.Len(t, playlists, 3) + assert.Contains(t, playlists, playlist1) + assert.Contains(t, playlists, playlist2) + assert.Contains(t, playlists, playlist3) + + playlists, err = db.GetPlaylists("user1") + assert.NoError(t, err) + assert.Len(t, playlists, 2) + assert.Contains(t, playlists, playlist1) + assert.Contains(t, playlists, playlist2) + + playlists, err = db.GetPlaylists("user2") + assert.NoError(t, err) + assert.Len(t, playlists, 1) + assert.Contains(t, playlists, playlist3) + }) + } +} + +func TestDatabaseUpdatePlaylist(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + playlist := types.Playlist{ + ID: "1", + UserID: "user1", + Title: "Test Playlist", + TrackIDs: []string{"track1", "track2"}, + ListenCount: 100, + FavoriteCount: 50, + Description: "Test Description", + CreationDate: "2023-01-01", + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + MetadataSource: "mock::1", + } + + err := db.AddPlaylist(playlist) + assert.NoError(t, err) + + playlist.Title = "Updated Test Playlist" + err = db.UpdatePlaylist(playlist) + assert.NoError(t, err) + + retrievedPlaylist, err := db.GetPlaylist("1") + assert.NoError(t, err) + assert.Equal(t, "Updated Test Playlist", retrievedPlaylist.Title) + }) + } +} + +func TestDatabaseDeletePlaylist(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + playlist := types.Playlist{ + ID: "1", + UserID: "user1", + Title: "Test Playlist", + TrackIDs: []string{"track1", "track2"}, + ListenCount: 100, + FavoriteCount: 50, + Description: "Test Description", + CreationDate: "2023-01-01", + AdditionDate: time.Now().Unix(), + Tags: []string{"tag1", "tag2"}, + AdditionalMeta: map[string]interface{}{"key": "value"}, + Permissions: map[string]string{"read": "all"}, + MetadataSource: "mock::1", + } + + err := db.AddPlaylist(playlist) + assert.NoError(t, err) + + err = db.DeletePlaylist("1") + assert.NoError(t, err) + + _, err = db.GetPlaylist("1") + assert.Error(t, err) + }) + } +} + +func TestDatabaseAddUser(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + user := types.User{ + ID: "1", + Username: "testuser", + Email: "testuser@example.com", + PasswordHash: "hashedpassword", + DisplayName: "Test User", + Description: "Test Description", + ListenedTo: map[string]int{"track1": 1}, + Favorites: []string{"track1", "track2"}, + PublicViewCount: 100, + CreationDate: time.Now().Unix(), + Permissions: map[string]string{"admin": "true"}, + LinkedArtistID: "artist1", + LinkedSources: map[string]string{"source1": "link1"}, + } + + err := db.CreateUser(user) + assert.NoError(t, err) + + retrievedUser, err := db.GetUser("1") + assert.NoError(t, err) + assert.Equal(t, user, retrievedUser) + }) + } +} + +func TestDatabaseGetUsers(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + user1 := types.User{ + ID: "1", + Username: "testuser1", + Email: "testuser1@example.com", + PasswordHash: "hashedpassword1", + DisplayName: "Test User 1", + Description: "Test Description 1", + ListenedTo: map[string]int{"track1": 1}, + Favorites: []string{"track1", "track2"}, + PublicViewCount: 100, + CreationDate: time.Now().Unix(), + Permissions: map[string]string{"admin": "true"}, + LinkedArtistID: "artist1", + LinkedSources: map[string]string{"source1": "link1"}, + } + + user2 := types.User{ + ID: "2", + Username: "testuser2", + Email: "testuser2@example.com", + PasswordHash: "hashedpassword2", + DisplayName: "Test User 2", + Description: "Test Description 2", + ListenedTo: map[string]int{"track2": 2}, + Favorites: []string{"track3", "track4"}, + PublicViewCount: 200, + CreationDate: time.Now().Unix(), + Permissions: map[string]string{"admin": "false"}, + LinkedArtistID: "artist2", + LinkedSources: map[string]string{"source2": "link2"}, + } + + err := db.CreateUser(user1) + assert.NoError(t, err) + err = db.CreateUser(user2) + assert.NoError(t, err) + + users, err := db.GetUsers() + assert.NoError(t, err) + assert.Len(t, users, 2) + assert.Contains(t, users, user1) + assert.Contains(t, users, user2) + }) + } +} + +func TestDatabaseUpdateUser(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + user := types.User{ + ID: "1", + Username: "testuser", + Email: "testuser@example.com", + PasswordHash: "hashedpassword", + DisplayName: "Test User", + Description: "Test Description", + ListenedTo: map[string]int{"track1": 1}, + Favorites: []string{"track1", "track2"}, + PublicViewCount: 100, + CreationDate: time.Now().Unix(), + Permissions: map[string]string{"admin": "true"}, + LinkedArtistID: "artist1", + LinkedSources: map[string]string{"source1": "link1"}, + } + + err := db.CreateUser(user) + assert.NoError(t, err) + + user.DisplayName = "Updated Test User" + err = db.UpdateUser(user) + assert.NoError(t, err) + + retrievedUser, err := db.GetUser("1") + assert.NoError(t, err) + assert.Equal(t, "Updated Test User", retrievedUser.DisplayName) + }) + } +} + +func TestDatabaseDeleteUser(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + user := types.User{ + ID: "1", + Username: "testuser", + Email: "testuser@example.com", + PasswordHash: "hashedpassword", + DisplayName: "Test User", + Description: "Test Description", + ListenedTo: map[string]int{"track1": 1}, + Favorites: []string{"track1", "track2"}, + PublicViewCount: 100, + CreationDate: time.Now().Unix(), + Permissions: map[string]string{"admin": "true"}, + LinkedArtistID: "artist1", + LinkedSources: map[string]string{"source1": "link1"}, + } + + err := db.CreateUser(user) + assert.NoError(t, err) + + err = db.DeleteUser("1") + assert.NoError(t, err) + + _, err = db.GetUser("1") + assert.Error(t, err) + }) + } +} + +func TestDatabaseUsernameExists(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + user := types.User{ + ID: "1", + Username: "testuser", + Email: "testuser@example.com", + PasswordHash: "hashedpassword", + DisplayName: "Test User", + Description: "Test Description", + ListenedTo: map[string]int{"track1": 1}, + Favorites: []string{"track1", "track2"}, + PublicViewCount: 100, + CreationDate: time.Now().Unix(), + Permissions: map[string]string{"admin": "true"}, + LinkedArtistID: "artist1", + LinkedSources: map[string]string{"source1": "link1"}, + } + + err := db.CreateUser(user) + assert.NoError(t, err) + + exists, err := db.UsernameExists("testuser") + assert.NoError(t, err) + assert.True(t, exists) + + exists, err = db.UsernameExists("nonexistentuser") + assert.NoError(t, err) + assert.False(t, exists) + }) + } +} + +func TestDatabaseEmailExists(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + user := types.User{ + ID: "1", + Username: "testuser", + Email: "testuser@example.com", + PasswordHash: "hashedpassword", + DisplayName: "Test User", + Description: "Test Description", + ListenedTo: map[string]int{"track1": 1}, + Favorites: []string{"track1", "track2"}, + PublicViewCount: 100, + CreationDate: time.Now().Unix(), + Permissions: map[string]string{"admin": "true"}, + LinkedArtistID: "artist1", + LinkedSources: map[string]string{"source1": "link1"}, + } + + err := db.CreateUser(user) + assert.NoError(t, err) + + exists, err := db.EmailExists("testuser@example.com") + assert.NoError(t, err) + assert.True(t, exists) + + exists, err = db.EmailExists("nonexistent@example.com") + assert.NoError(t, err) + assert.False(t, exists) + }) + } +} + +func TestDatabaseBlacklistToken(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + token := "testtoken" + expiration := time.Now().Add(1 * time.Hour) + + err := db.BlacklistToken(token, expiration) + assert.NoError(t, err) + + isBlacklisted, err := db.IsTokenBlacklisted(token) + assert.NoError(t, err) + assert.True(t, isBlacklisted) + }) + } +} + +func TestDatabaseCleanExpiredTokens(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + token := "expiredtoken" + expiration := time.Now().Add(-1 * time.Hour) + + err := db.BlacklistToken(token, expiration) + assert.NoError(t, err) + + err = db.CleanExpiredTokens() + assert.NoError(t, err) + + isBlacklisted, err := db.IsTokenBlacklisted(token) + assert.NoError(t, err) + assert.False(t, isBlacklisted) + }) + } +} + +func TestDatabaseIsTokenBlacklisted(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := tc.dbFunc(t) + assert.NotNil(t, db) + defer db.Close() + + token := "testtoken" + expiration := time.Now().Add(1 * time.Hour) + + err := db.BlacklistToken(token, expiration) + assert.NoError(t, err) + + isBlacklisted, err := db.IsTokenBlacklisted(token) + assert.NoError(t, err) + assert.True(t, isBlacklisted) + + nonExistentToken := "nonexistenttoken" + isBlacklisted, err = db.IsTokenBlacklisted(nonExistentToken) + assert.NoError(t, err) + assert.False(t, isBlacklisted) + }) + } +} diff --git a/db/sqlite.go b/db/sqlite.go index ff493d8..fca1d98 100644 --- a/db/sqlite.go +++ b/db/sqlite.go @@ -73,7 +73,7 @@ func (db *SQLiteDatabase) createTracksTable() error { lyrics TEXT, -- JSON listen_count INTEGER, favorite_count INTEGER, - addition_date INTEGER, -- BIGINT replaced with INTEGER + addition_date INTEGER, tags TEXT, -- JSON (json array) additional_meta BLOB, -- JSONB (json object) permissions TEXT, -- JSON (json object) @@ -87,33 +87,128 @@ func (db *SQLiteDatabase) createTracksTable() error { } func (db *SQLiteDatabase) createAlbumsTable() error { - logging.Error().Msg("unimplemented") - return nil + _, err := db.sqlDB.Exec(` + CREATE TABLE IF NOT EXISTS albums ( + id TEXT PRIMARY KEY, + user_id TEXT, + upc TEXT, + title TEXT, + artist_ids TEXT, -- JSON (json array) + track_ids TEXT, -- JSON (json array) + description TEXT, + release_date TEXT, + listen_count INTEGER, + favorite_count INTEGER, + addition_date INTEGER, + tags TEXT, -- JSON (json array) + additional_meta BLOB, -- JSONB (json object) + permissions TEXT, -- JSON (json object) + linked_item_ids TEXT, -- JSON (json array) + metadata_source TEXT + ); + `) + return err } func (db *SQLiteDatabase) createVideosTable() error { - logging.Error().Msg("unimplemented") - return nil + _, err := db.sqlDB.Exec(` + CREATE TABLE IF NOT EXISTS videos ( + id TEXT PRIMARY KEY, + user_id TEXT, + title TEXT, + artist_ids TEXT, -- JSON (json array) + duration INTEGER, + description TEXT, + release_date TEXT, + subtitles TEXT, -- JSON (json object) + watch_count INTEGER, + favorite_count INTEGER, + addition_date INTEGER, + tags TEXT, -- JSON (json array) + additional_meta BLOB, -- JSONB (json object) + permissions TEXT, -- JSON (json object) + linked_item_ids TEXT, -- JSON (json array) + content_source TEXT, + metadata_source TEXT, + lyric_sources TEXT -- JSON (json object) + ); + `) + return err } func (db *SQLiteDatabase) createArtistsTable() error { - logging.Error().Msg("unimplemented") - return nil + _, err := db.sqlDB.Exec(` + CREATE TABLE IF NOT EXISTS artists ( + id TEXT PRIMARY KEY, + user_id TEXT, + name TEXT, + album_ids TEXT, -- JSON (json array) + track_ids TEXT, -- JSON (json array) + description TEXT, + creation_date TEXT, + listen_count INTEGER, + favorite_count INTEGER, + addition_date INTEGER, + tags TEXT, -- JSON (json array) + additional_meta BLOB, -- JSONB (json object) + permissions TEXT, -- JSON (json object) + linked_item_ids TEXT, -- JSON (json array) + metadata_source TEXT + ); + `) + return err } func (db *SQLiteDatabase) createPlaylistsTable() error { - logging.Error().Msg("unimplemented") - return nil + _, err := db.sqlDB.Exec(` + CREATE TABLE IF NOT EXISTS playlists ( + id TEXT PRIMARY KEY, + user_id TEXT, + title TEXT, + track_ids TEXT, -- JSON (json array) + listen_count INTEGER, + favorite_count INTEGER, + description TEXT, + creation_date TEXT, + addition_date INTEGER, + tags TEXT, -- JSON (json array) + additional_meta BLOB, -- JSONB (json object) + permissions TEXT, -- JSON (json object) + metadata_source TEXT + ); + `) + return err } func (db *SQLiteDatabase) createUsersTable() error { - logging.Error().Msg("unimplemented") - return nil + _, err := db.sqlDB.Exec(` + CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL, + email TEXT NOT NULL, + password_hash TEXT NOT NULL, + display_name TEXT, + description TEXT, + listened_to BLOB, -- JSONB (json object) + favorites TEXT, -- JSON (json array) + public_view_count INTEGER, + creation_date TEXT, + permissions TEXT, -- JSON (json object) + linked_artist_id TEXT, + linked_sources TEXT -- JSON (json object) + ); + `) + return err } func (db *SQLiteDatabase) createBlacklistedTokensTable() error { - logging.Error().Msg("unimplemented") - return nil + _, err := db.sqlDB.Exec(` + CREATE TABLE IF NOT EXISTS blacklisted_tokens ( + token TEXT PRIMARY KEY, + expiration TEXT + ); + `) + return err } func (db *SQLiteDatabase) Close() error { @@ -407,176 +502,1164 @@ func (db *SQLiteDatabase) DeleteTrack(id string) error { } func (db *SQLiteDatabase) GetAllAlbums() ([]types.Album, error) { - logging.Error().Msg("unimplemented") - return nil, nil + var albums []types.Album + rows, err := db.sqlDB.Query("SELECT * FROM albums;") + if err != nil { + return albums, err + } + defer rows.Close() + + for rows.Next() { + album := types.Album{} + var artistIDs, trackIDs, tags, linkedItemIDs string + var additionalMeta, permissions string + + err = rows.Scan( + &album.ID, &album.UserID, &album.UPC, &album.Title, + &artistIDs, &trackIDs, &album.Description, &album.ReleaseDate, + &album.ListenCount, &album.FavoriteCount, &album.AdditionDate, + &tags, &additionalMeta, &permissions, &linkedItemIDs, + &album.MetadataSource, + ) + if err != nil { + return albums, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(artistIDs), &album.ArtistIDs); err != nil { + return albums, errors.New("failed to parse artist_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(trackIDs), &album.TrackIDs); err != nil { + return albums, errors.New("failed to parse track_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &album.Tags); err != nil { + return albums, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedItemIDs), &album.LinkedItemIDs); err != nil { + return albums, errors.New("failed to parse linked_item_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &album.AdditionalMeta); err != nil { + return albums, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &album.Permissions); err != nil { + return albums, errors.New("failed to parse permissions: " + err.Error()) + } + + albums = append(albums, album) + } + + if err = rows.Err(); err != nil { + return albums, err + } + + return albums, err } func (db *SQLiteDatabase) GetAlbums(userID string) ([]types.Album, error) { - logging.Error().Msg("unimplemented") - return nil, nil -} + var albums []types.Album + rows, err := db.sqlDB.Query("SELECT * FROM albums WHERE user_id = ?;", userID) + if err != nil { + return albums, err + } -func (db *SQLiteDatabase) GetAlbum(id string) (types.Album, error) { - logging.Error().Msg("unimplemented") - return types.Album{}, nil -} + for rows.Next() { + album := types.Album{} + var artistIDs, trackIDs, tags, linkedItemIDs string + var additionalMeta, permissions string -func (db *SQLiteDatabase) AddAlbum(album types.Album) error { - logging.Error().Msg("unimplemented") - return nil -} + err = rows.Scan( + &album.ID, &album.UserID, &album.UPC, &album.Title, + &artistIDs, &trackIDs, &album.Description, &album.ReleaseDate, + &album.ListenCount, &album.FavoriteCount, &album.AdditionDate, + &tags, &additionalMeta, &permissions, &linkedItemIDs, + &album.MetadataSource, + ) + if err != nil { + return albums, err + } -func (db *SQLiteDatabase) UpdateAlbum(album types.Album) error { - logging.Error().Msg("unimplemented") - return nil -} + // Parse JSON fields + if err = json.Unmarshal([]byte(artistIDs), &album.ArtistIDs); err != nil { + return albums, errors.New("failed to parse artist_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(trackIDs), &album.TrackIDs); err != nil { + return albums, errors.New("failed to parse track_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &album.Tags); err != nil { + return albums, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedItemIDs), &album.LinkedItemIDs); err != nil { + return albums, errors.New("failed to parse linked_item_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &album.AdditionalMeta); err != nil { + return albums, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &album.Permissions); err != nil { + return albums, errors.New("failed to parse permissions: " + err.Error()) + } -func (db *SQLiteDatabase) DeleteAlbum(id string) error { - logging.Error().Msg("unimplemented") - return nil -} + albums = append(albums, album) + } -func (db *SQLiteDatabase) GetAllVideos() ([]types.Video, error) { - logging.Error().Msg("unimplemented") - return nil, nil -} + if err = rows.Err(); err != nil { + return albums, err + } -func (db *SQLiteDatabase) GetVideos(userID string) ([]types.Video, error) { - logging.Error().Msg("unimplemented") - return nil, nil + return albums, err } -func (db *SQLiteDatabase) GetVideo(id string) (types.Video, error) { - logging.Error().Msg("unimplemented") - return types.Video{}, nil -} +func (db *SQLiteDatabase) GetAlbum(id string) (types.Album, error) { + album := types.Album{} + var artistIDs, trackIDs, tags, linkedItemIDs string + var additionalMeta, permissions string -func (db *SQLiteDatabase) AddVideo(video types.Video) error { - logging.Error().Msg("unimplemented") - return nil -} + row := db.sqlDB.QueryRow("SELECT * FROM albums WHERE id = ?;", id) + err := row.Scan( + &album.ID, &album.UserID, &album.UPC, &album.Title, + &artistIDs, &trackIDs, &album.Description, &album.ReleaseDate, + &album.ListenCount, &album.FavoriteCount, &album.AdditionDate, + &tags, &additionalMeta, &permissions, &linkedItemIDs, + &album.MetadataSource, + ) + if err != nil { + return album, err + } -func (db *SQLiteDatabase) UpdateVideo(video types.Video) error { - logging.Error().Msg("unimplemented") - return nil -} + // Parse JSON fields + if err = json.Unmarshal([]byte(artistIDs), &album.ArtistIDs); err != nil { + return album, errors.New("failed to parse artist_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(trackIDs), &album.TrackIDs); err != nil { + return album, errors.New("failed to parse track_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &album.Tags); err != nil { + return album, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedItemIDs), &album.LinkedItemIDs); err != nil { + return album, errors.New("failed to parse linked_item_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &album.AdditionalMeta); err != nil { + return album, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &album.Permissions); err != nil { + return album, errors.New("failed to parse permissions: " + err.Error()) + } -func (db *SQLiteDatabase) DeleteVideo(id string) error { - logging.Error().Msg("unimplemented") - return nil + return album, nil } -func (db *SQLiteDatabase) GetAllArtists() ([]types.Artist, error) { - logging.Error().Msg("unimplemented") - return nil, nil -} +func (db *SQLiteDatabase) AddAlbum(album types.Album) error { + // Convert JSON fields to strings + artistIDs, err := json.Marshal(album.ArtistIDs) + if err != nil { + return errors.New("failed to marshal artist_ids: " + err.Error()) + } + trackIDs, err := json.Marshal(album.TrackIDs) + if err != nil { + return errors.New("failed to marshal track_ids: " + err.Error()) + } + tags, err := json.Marshal(album.Tags) + if err != nil { + return errors.New("failed to marshal tags: " + err.Error()) + } + linkedItemIDs, err := json.Marshal(album.LinkedItemIDs) + if err != nil { + return errors.New("failed to marshal linked_item_ids: " + err.Error()) + } + additionalMeta, err := json.Marshal(album.AdditionalMeta) + if err != nil { + return errors.New("failed to marshal additional_meta: " + err.Error()) + } + permissions, err := json.Marshal(album.Permissions) + if err != nil { + return errors.New("failed to marshal permissions: " + err.Error()) + } -func (db *SQLiteDatabase) GetArtists(userID string) ([]types.Artist, error) { - logging.Error().Msg("unimplemented") - return nil, nil -} + _, err = db.sqlDB.Exec(` + INSERT INTO albums ( + id, user_id, upc, title, artist_ids, track_ids, description, release_date, listen_count, favorite_count, addition_date, tags, additional_meta, permissions, linked_item_ids, metadata_source + ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ); + `, + album.ID, album.UserID, album.UPC, album.Title, string(artistIDs), string(trackIDs), + album.Description, album.ReleaseDate, album.ListenCount, album.FavoriteCount, + album.AdditionDate, string(tags), string(additionalMeta), string(permissions), + string(linkedItemIDs), album.MetadataSource, + ) -func (db *SQLiteDatabase) GetArtist(id string) (types.Artist, error) { - logging.Error().Msg("unimplemented") - return types.Artist{}, nil + return err } -func (db *SQLiteDatabase) AddArtist(artist types.Artist) error { - logging.Error().Msg("unimplemented") - return nil -} +func (db *SQLiteDatabase) UpdateAlbum(album types.Album) error { + // Convert JSON fields to strings + artistIDs, err := json.Marshal(album.ArtistIDs) + if err != nil { + return errors.New("failed to marshal artist_ids: " + err.Error()) + } + trackIDs, err := json.Marshal(album.TrackIDs) + if err != nil { + return errors.New("failed to marshal track_ids: " + err.Error()) + } + tags, err := json.Marshal(album.Tags) + if err != nil { + return errors.New("failed to marshal tags: " + err.Error()) + } + linkedItemIDs, err := json.Marshal(album.LinkedItemIDs) + if err != nil { + return errors.New("failed to marshal linked_item_ids: " + err.Error()) + } + additionalMeta, err := json.Marshal(album.AdditionalMeta) + if err != nil { + return errors.New("failed to marshal additional_meta: " + err.Error()) + } + permissions, err := json.Marshal(album.Permissions) + if err != nil { + return errors.New("failed to marshal permissions: " + err.Error()) + } -func (db *SQLiteDatabase) UpdateArtist(artist types.Artist) error { - logging.Error().Msg("unimplemented") - return nil -} + _, err = db.sqlDB.Exec(` + UPDATE albums + SET user_id=?, upc=?, title=?, artist_ids=?, track_ids=?, description=?, + release_date=?, listen_count=?, favorite_count=?, addition_date=?, tags=?, + additional_meta=?, permissions=?, linked_item_ids=?, metadata_source=? + WHERE id=?; + `, + album.UserID, album.UPC, album.Title, string(artistIDs), string(trackIDs), + album.Description, album.ReleaseDate, album.ListenCount, album.FavoriteCount, + album.AdditionDate, string(tags), string(additionalMeta), string(permissions), + string(linkedItemIDs), album.MetadataSource, album.ID, + ) -func (db *SQLiteDatabase) DeleteArtist(id string) error { - logging.Error().Msg("unimplemented") - return nil + return err } -func (db *SQLiteDatabase) GetAllPlaylists() ([]types.Playlist, error) { - logging.Error().Msg("unimplemented") - return nil, nil +func (db *SQLiteDatabase) DeleteAlbum(id string) error { + _, err := db.sqlDB.Exec("DELETE FROM albums WHERE id = ?;", id) + return err } -func (db *SQLiteDatabase) GetPlaylists(userID string) ([]types.Playlist, error) { - logging.Error().Msg("unimplemented") - return nil, nil -} +func (db *SQLiteDatabase) GetAllVideos() ([]types.Video, error) { + var videos []types.Video + rows, err := db.sqlDB.Query("SELECT * FROM videos;") + if err != nil { + return videos, err + } -func (db *SQLiteDatabase) GetPlaylist(id string) (types.Playlist, error) { - logging.Error().Msg("unimplemented") - return types.Playlist{}, nil -} + for rows.Next() { + video := types.Video{} + var artistIDs, tags, linkedItemIDs string + var additionalMeta, permissions, subtitles, lyricSources string -func (db *SQLiteDatabase) AddPlaylist(playlist types.Playlist) error { - logging.Error().Msg("unimplemented") - return nil -} + err = rows.Scan( + &video.ID, &video.UserID, &video.Title, &artistIDs, + &video.Duration, &video.Description, &video.ReleaseDate, &subtitles, + &video.WatchCount, &video.FavoriteCount, &video.AdditionDate, + &tags, &additionalMeta, &permissions, &linkedItemIDs, + &video.ContentSource, &video.MetadataSource, &lyricSources, + ) + if err != nil { + return videos, err + } -func (db *SQLiteDatabase) UpdatePlaylist(playlist types.Playlist) error { - logging.Error().Msg("unimplemented") - return nil -} + // Parse JSON fields + if err = json.Unmarshal([]byte(artistIDs), &video.ArtistIDs); err != nil { + return videos, errors.New("failed to parse artist_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &video.Tags); err != nil { + return videos, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedItemIDs), &video.LinkedItemIDs); err != nil { + return videos, errors.New("failed to parse linked_item_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &video.AdditionalMeta); err != nil { + return videos, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &video.Permissions); err != nil { + return videos, errors.New("failed to parse permissions: " + err.Error()) + } + if err = json.Unmarshal([]byte(subtitles), &video.Subtitles); err != nil { + return videos, errors.New("failed to parse subtitles: " + err.Error()) + } + if err = json.Unmarshal([]byte(lyricSources), &video.LyricSources); err != nil { + return videos, errors.New("failed to parse lyric_sources: " + err.Error()) + } -func (db *SQLiteDatabase) DeletePlaylist(id string) error { - logging.Error().Msg("unimplemented") - return nil -} + videos = append(videos, video) + } -func (db *SQLiteDatabase) GetUsers() ([]types.User, error) { - logging.Error().Msg("unimplemented") - return nil, nil -} + if err = rows.Err(); err != nil { + return videos, err + } -func (db *SQLiteDatabase) GetUser(id string) (types.User, error) { - logging.Error().Msg("unimplemented") - return types.User{}, nil + return videos, err } -func (db *SQLiteDatabase) GetUserByUsername(username string) (types.User, error) { - logging.Error().Msg("unimplemented") - return types.User{}, nil -} +func (db *SQLiteDatabase) GetVideos(userID string) ([]types.Video, error) { + var videos []types.Video + rows, err := db.sqlDB.Query("SELECT * FROM videos WHERE user_id = ?;", userID) + if err != nil { + return videos, err + } -func (db *SQLiteDatabase) CreateUser(user types.User) error { - logging.Error().Msg("unimplemented") - return nil -} + for rows.Next() { + video := types.Video{} + var artistIDs, tags, linkedItemIDs string + var additionalMeta, permissions, subtitles, lyricSources string -func (db *SQLiteDatabase) UpdateUser(user types.User) error { - logging.Error().Msg("unimplemented") - return nil -} + err = rows.Scan( + &video.ID, &video.UserID, &video.Title, &artistIDs, + &video.Duration, &video.Description, &video.ReleaseDate, &subtitles, + &video.WatchCount, &video.FavoriteCount, &video.AdditionDate, + &tags, &additionalMeta, &permissions, &linkedItemIDs, + &video.ContentSource, &video.MetadataSource, &lyricSources, + ) + if err != nil { + return videos, err + } -func (db *SQLiteDatabase) DeleteUser(id string) error { - logging.Error().Msg("unimplemented") - return nil -} + // Parse JSON fields + if err = json.Unmarshal([]byte(artistIDs), &video.ArtistIDs); err != nil { + return videos, errors.New("failed to parse artist_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &video.Tags); err != nil { + return videos, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedItemIDs), &video.LinkedItemIDs); err != nil { + return videos, errors.New("failed to parse linked_item_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &video.AdditionalMeta); err != nil { + return videos, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &video.Permissions); err != nil { + return videos, errors.New("failed to parse permissions: " + err.Error()) + } + if err = json.Unmarshal([]byte(subtitles), &video.Subtitles); err != nil { + return videos, errors.New("failed to parse subtitles: " + err.Error()) + } + if err = json.Unmarshal([]byte(lyricSources), &video.LyricSources); err != nil { + return videos, errors.New("failed to parse lyric_sources: " + err.Error()) + } -func (db *SQLiteDatabase) UsernameExists(username string) (bool, error) { - logging.Error().Msg("unimplemented") - return false, nil -} + videos = append(videos, video) + } -func (db *SQLiteDatabase) EmailExists(email string) (bool, error) { - logging.Error().Msg("unimplemented") - return false, nil + if err = rows.Err(); err != nil { + return videos, err + } + + return videos, err } -func (db *SQLiteDatabase) BlacklistToken(token string, expiration time.Time) error { - logging.Error().Msg("unimplemented") - return nil +func (db *SQLiteDatabase) GetVideo(id string) (types.Video, error) { + video := types.Video{} + var artistIDs, tags, linkedItemIDs string + var additionalMeta, permissions, subtitles, lyricSources string + + row := db.sqlDB.QueryRow("SELECT * FROM videos WHERE id = ?;", id) + err := row.Scan( + &video.ID, &video.UserID, &video.Title, &artistIDs, + &video.Duration, &video.Description, &video.ReleaseDate, &subtitles, + &video.WatchCount, &video.FavoriteCount, &video.AdditionDate, + &tags, &additionalMeta, &permissions, &linkedItemIDs, + &video.ContentSource, &video.MetadataSource, &lyricSources, + ) + if err != nil { + return video, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(artistIDs), &video.ArtistIDs); err != nil { + return video, errors.New("failed to parse artist_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &video.Tags); err != nil { + return video, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedItemIDs), &video.LinkedItemIDs); err != nil { + return video, errors.New("failed to parse linked_item_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &video.AdditionalMeta); err != nil { + return video, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &video.Permissions); err != nil { + return video, errors.New("failed to parse permissions: " + err.Error()) + } + if err = json.Unmarshal([]byte(subtitles), &video.Subtitles); err != nil { + return video, errors.New("failed to parse subtitles: " + err.Error()) + } + if err = json.Unmarshal([]byte(lyricSources), &video.LyricSources); err != nil { + return video, errors.New("failed to parse lyric_sources: " + err.Error()) + } + + return video, nil } -func (db *SQLiteDatabase) CleanExpiredTokens() error { - logging.Error().Msg("unimplemented") - return nil +func (db *SQLiteDatabase) AddVideo(video types.Video) error { + // Convert JSON fields to strings + artistIDs, err := json.Marshal(video.ArtistIDs) + if err != nil { + return errors.New("failed to marshal artist_ids: " + err.Error()) + } + tags, err := json.Marshal(video.Tags) + if err != nil { + return errors.New("failed to marshal tags: " + err.Error()) + } + linkedItemIDs, err := json.Marshal(video.LinkedItemIDs) + if err != nil { + return errors.New("failed to marshal linked_item_ids: " + err.Error()) + } + additionalMeta, err := json.Marshal(video.AdditionalMeta) + if err != nil { + return errors.New("failed to marshal additional_meta: " + err.Error()) + } + permissions, err := json.Marshal(video.Permissions) + if err != nil { + return errors.New("failed to marshal permissions: " + err.Error()) + } + subtitles, err := json.Marshal(video.Subtitles) + if err != nil { + return errors.New("failed to marshal subtitles: " + err.Error()) + } + lyricSources, err := json.Marshal(video.LyricSources) + if err != nil { + return errors.New("failed to marshal lyric_sources: " + err.Error()) + } + + _, err = db.sqlDB.Exec(` + INSERT INTO videos ( + id, user_id, title, artist_ids, duration, description, release_date, subtitles, watch_count, favorite_count, addition_date, tags, additional_meta, permissions, linked_item_ids, content_source, metadata_source, lyric_sources + ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ); + `, + video.ID, video.UserID, video.Title, string(artistIDs), video.Duration, + video.Description, video.ReleaseDate, string(subtitles), video.WatchCount, + video.FavoriteCount, video.AdditionDate, string(tags), string(additionalMeta), + string(permissions), string(linkedItemIDs), video.ContentSource, + video.MetadataSource, string(lyricSources), + ) + + return err +} + +func (db *SQLiteDatabase) UpdateVideo(video types.Video) error { + // Convert JSON fields to strings + artistIDs, err := json.Marshal(video.ArtistIDs) + if err != nil { + return errors.New("failed to marshal artist_ids: " + err.Error()) + } + tags, err := json.Marshal(video.Tags) + if err != nil { + return errors.New("failed to marshal tags: " + err.Error()) + } + linkedItemIDs, err := json.Marshal(video.LinkedItemIDs) + if err != nil { + return errors.New("failed to marshal linked_item_ids: " + err.Error()) + } + additionalMeta, err := json.Marshal(video.AdditionalMeta) + if err != nil { + return errors.New("failed to marshal additional_meta: " + err.Error()) + } + permissions, err := json.Marshal(video.Permissions) + if err != nil { + return errors.New("failed to marshal permissions: " + err.Error()) + } + subtitles, err := json.Marshal(video.Subtitles) + if err != nil { + return errors.New("failed to marshal subtitles: " + err.Error()) + } + lyricSources, err := json.Marshal(video.LyricSources) + if err != nil { + return errors.New("failed to marshal lyric_sources: " + err.Error()) + } + + _, err = db.sqlDB.Exec(` + UPDATE videos + SET user_id=?, title=?, artist_ids=?, duration=?, description=?, release_date=?, + subtitles=?, watch_count=?, favorite_count=?, addition_date=?, tags=?, + additional_meta=?, permissions=?, linked_item_ids=?, content_source=?, + metadata_source=?, lyric_sources=? + WHERE id=?; + `, + video.UserID, video.Title, string(artistIDs), video.Duration, video.Description, + video.ReleaseDate, string(subtitles), video.WatchCount, video.FavoriteCount, + video.AdditionDate, string(tags), string(additionalMeta), string(permissions), + string(linkedItemIDs), video.ContentSource, video.MetadataSource, + string(lyricSources), video.ID, + ) + + return err +} + +func (db *SQLiteDatabase) DeleteVideo(id string) error { + _, err := db.sqlDB.Exec("DELETE FROM videos WHERE id = ?;", id) + return err +} + +/* +CREATE TABLE IF NOT EXISTS artists ( + id TEXT PRIMARY KEY, + user_id TEXT, + name TEXT, + album_ids TEXT, -- JSON (json array) + track_ids TEXT, -- JSON (json array) + description TEXT, + creation_date TEXT, + listen_count INTEGER, + favorite_count INTEGER, + addition_date INTEGER, + tags TEXT, -- JSON (json array) + additional_meta BLOB, -- JSONB (json object) + permissions TEXT, -- JSON (json object) + linked_item_ids TEXT, -- JSON (json array) + metadata_source TEXT +); +*/ + +func (db *SQLiteDatabase) GetAllArtists() ([]types.Artist, error) { + var artists []types.Artist + rows, err := db.sqlDB.Query("SELECT * FROM artists;") + if err != nil { + return artists, err + } + + for rows.Next() { + artist := types.Artist{} + var albumIDs, trackIDs, tags, linkedItemIDs string + var additionalMeta, permissions string + + err = rows.Scan( + &artist.ID, &artist.UserID, &artist.Name, &albumIDs, &trackIDs, + &artist.Description, &artist.CreationDate, &artist.ListenCount, + &artist.FavoriteCount, &artist.AdditionDate, &tags, + &additionalMeta, &permissions, &linkedItemIDs, &artist.MetadataSource, + ) + if err != nil { + return artists, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(albumIDs), &artist.AlbumIDs); err != nil { + return artists, errors.New("failed to parse album_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(trackIDs), &artist.TrackIDs); err != nil { + return artists, errors.New("failed to parse track_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &artist.Tags); err != nil { + return artists, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedItemIDs), &artist.LinkedItemIDs); err != nil { + return artists, errors.New("failed to parse linked_item_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &artist.AdditionalMeta); err != nil { + return artists, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &artist.Permissions); err != nil { + return artists, errors.New("failed to parse permissions: " + err.Error()) + } + + artists = append(artists, artist) + } + + if err = rows.Err(); err != nil { + return artists, err + } + + return artists, err +} + +func (db *SQLiteDatabase) GetArtists(userID string) ([]types.Artist, error) { + var artists []types.Artist + rows, err := db.sqlDB.Query("SELECT * FROM artists WHERE user_id = ?;", userID) + if err != nil { + return artists, err + } + + for rows.Next() { + artist := types.Artist{} + var albumIDs, trackIDs, tags, linkedItemIDs string + var additionalMeta, permissions string + + err = rows.Scan( + &artist.ID, &artist.UserID, &artist.Name, &albumIDs, &trackIDs, + &artist.Description, &artist.CreationDate, &artist.ListenCount, + &artist.FavoriteCount, &artist.AdditionDate, &tags, + &additionalMeta, &permissions, &linkedItemIDs, &artist.MetadataSource, + ) + if err != nil { + return artists, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(albumIDs), &artist.AlbumIDs); err != nil { + return artists, errors.New("failed to parse album_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(trackIDs), &artist.TrackIDs); err != nil { + return artists, errors.New("failed to parse track_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &artist.Tags); err != nil { + return artists, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedItemIDs), &artist.LinkedItemIDs); err != nil { + return artists, errors.New("failed to parse linked_item_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &artist.AdditionalMeta); err != nil { + return artists, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &artist.Permissions); err != nil { + return artists, errors.New("failed to parse permissions: " + err.Error()) + } + + artists = append(artists, artist) + } + + if err = rows.Err(); err != nil { + return artists, err + } + + return artists, err +} + +func (db *SQLiteDatabase) GetArtist(id string) (types.Artist, error) { + artist := types.Artist{} + var albumIDs, trackIDs, tags, linkedItemIDs string + var additionalMeta, permissions string + + row := db.sqlDB.QueryRow("SELECT * FROM artists WHERE id = ?;", id) + err := row.Scan( + &artist.ID, &artist.UserID, &artist.Name, &albumIDs, &trackIDs, + &artist.Description, &artist.CreationDate, &artist.ListenCount, + &artist.FavoriteCount, &artist.AdditionDate, &tags, + &additionalMeta, &permissions, &linkedItemIDs, &artist.MetadataSource, + ) + if err != nil { + return artist, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(albumIDs), &artist.AlbumIDs); err != nil { + return artist, errors.New("failed to parse album_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(trackIDs), &artist.TrackIDs); err != nil { + return artist, errors.New("failed to parse track_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &artist.Tags); err != nil { + return artist, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedItemIDs), &artist.LinkedItemIDs); err != nil { + return artist, errors.New("failed to parse linked_item_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &artist.AdditionalMeta); err != nil { + return artist, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &artist.Permissions); err != nil { + return artist, errors.New("failed to parse permissions: " + err.Error()) + } + + return artist, nil +} + +func (db *SQLiteDatabase) AddArtist(artist types.Artist) error { + // Convert JSON fields to strings + albumIDs, err := json.Marshal(artist.AlbumIDs) + if err != nil { + return errors.New("failed to marshal album_ids: " + err.Error()) + } + trackIDs, err := json.Marshal(artist.TrackIDs) + if err != nil { + return errors.New("failed to marshal track_ids: " + err.Error()) + } + tags, err := json.Marshal(artist.Tags) + if err != nil { + return errors.New("failed to marshal tags: " + err.Error()) + } + linkedItemIDs, err := json.Marshal(artist.LinkedItemIDs) + if err != nil { + return errors.New("failed to marshal linked_item_ids: " + err.Error()) + } + additionalMeta, err := json.Marshal(artist.AdditionalMeta) + if err != nil { + return errors.New("failed to marshal additional_meta: " + err.Error()) + } + permissions, err := json.Marshal(artist.Permissions) + if err != nil { + return errors.New("failed to marshal permissions: " + err.Error()) + } + + _, err = db.sqlDB.Exec(` + INSERT INTO artists ( + id, user_id, name, album_ids, track_ids, description, creation_date, listen_count, favorite_count, addition_date, tags, additional_meta, permissions, linked_item_ids, metadata_source + ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ); + `, + artist.ID, artist.UserID, artist.Name, string(albumIDs), string(trackIDs), + artist.Description, artist.CreationDate, artist.ListenCount, artist.FavoriteCount, + artist.AdditionDate, string(tags), string(additionalMeta), string(permissions), + string(linkedItemIDs), artist.MetadataSource, + ) + + return err +} + +func (db *SQLiteDatabase) UpdateArtist(artist types.Artist) error { + // Convert JSON fields to strings + albumIDs, err := json.Marshal(artist.AlbumIDs) + if err != nil { + return errors.New("failed to marshal album_ids: " + err.Error()) + } + trackIDs, err := json.Marshal(artist.TrackIDs) + if err != nil { + return errors.New("failed to marshal track_ids: " + err.Error()) + } + tags, err := json.Marshal(artist.Tags) + if err != nil { + return errors.New("failed to marshal tags: " + err.Error()) + } + linkedItemIDs, err := json.Marshal(artist.LinkedItemIDs) + if err != nil { + return errors.New("failed to marshal linked_item_ids: " + err.Error()) + } + additionalMeta, err := json.Marshal(artist.AdditionalMeta) + if err != nil { + return errors.New("failed to marshal additional_meta: " + err.Error()) + } + permissions, err := json.Marshal(artist.Permissions) + if err != nil { + return errors.New("failed to marshal permissions: " + err.Error()) + } + + _, err = db.sqlDB.Exec(` + UPDATE artists + SET user_id=?, name=?, album_ids=?, track_ids=?, description=?, creation_date=?, + listen_count=?, favorite_count=?, addition_date=?, tags=?, additional_meta=?, + permissions=?, linked_item_ids=?, metadata_source=? + WHERE id=?; + `, + artist.UserID, artist.Name, string(albumIDs), string(trackIDs), + artist.Description, artist.CreationDate, artist.ListenCount, artist.FavoriteCount, + artist.AdditionDate, string(tags), string(additionalMeta), string(permissions), + string(linkedItemIDs), artist.MetadataSource, artist.ID, + ) + + return err +} + +func (db *SQLiteDatabase) DeleteArtist(id string) error { + _, err := db.sqlDB.Exec("DELETE FROM artists WHERE id = ?;", id) + return err +} + +func (db *SQLiteDatabase) GetAllPlaylists() ([]types.Playlist, error) { + var playlists []types.Playlist + rows, err := db.sqlDB.Query("SELECT * FROM playlists;") + if err != nil { + return playlists, err + } + + for rows.Next() { + playlist := types.Playlist{} + var trackIDs, tags string + var additionalMeta, permissions string + + err = rows.Scan( + &playlist.ID, &playlist.UserID, &playlist.Title, &trackIDs, + &playlist.ListenCount, &playlist.FavoriteCount, &playlist.Description, + &playlist.CreationDate, &playlist.AdditionDate, &tags, + &additionalMeta, &permissions, &playlist.MetadataSource, + ) + if err != nil { + return playlists, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(trackIDs), &playlist.TrackIDs); err != nil { + return playlists, errors.New("failed to parse track_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &playlist.Tags); err != nil { + return playlists, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &playlist.AdditionalMeta); err != nil { + return playlists, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &playlist.Permissions); err != nil { + return playlists, errors.New("failed to parse permissions: " + err.Error()) + } + + playlists = append(playlists, playlist) + } + + if err = rows.Err(); err != nil { + return playlists, err + } + + return playlists, err +} + +func (db *SQLiteDatabase) GetPlaylists(userID string) ([]types.Playlist, error) { + var playlists []types.Playlist + rows, err := db.sqlDB.Query("SELECT * FROM playlists WHERE user_id = ?;", userID) + if err != nil { + return playlists, err + } + + for rows.Next() { + playlist := types.Playlist{} + var trackIDs, tags string + var additionalMeta, permissions string + + err = rows.Scan( + &playlist.ID, &playlist.UserID, &playlist.Title, &trackIDs, + &playlist.ListenCount, &playlist.FavoriteCount, &playlist.Description, + &playlist.CreationDate, &playlist.AdditionDate, &tags, + &additionalMeta, &permissions, &playlist.MetadataSource, + ) + if err != nil { + return playlists, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(trackIDs), &playlist.TrackIDs); err != nil { + return playlists, errors.New("failed to parse track_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &playlist.Tags); err != nil { + return playlists, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &playlist.AdditionalMeta); err != nil { + return playlists, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &playlist.Permissions); err != nil { + return playlists, errors.New("failed to parse permissions: " + err.Error()) + } + + playlists = append(playlists, playlist) + } + + if err = rows.Err(); err != nil { + return playlists, err + } + + return playlists, err +} + +func (db *SQLiteDatabase) GetPlaylist(id string) (types.Playlist, error) { + playlist := types.Playlist{} + var trackIDs, tags string + var additionalMeta, permissions string + + row := db.sqlDB.QueryRow("SELECT * FROM playlists WHERE id = ?;", id) + err := row.Scan( + &playlist.ID, &playlist.UserID, &playlist.Title, &trackIDs, + &playlist.ListenCount, &playlist.FavoriteCount, &playlist.Description, + &playlist.CreationDate, &playlist.AdditionDate, &tags, + &additionalMeta, &permissions, &playlist.MetadataSource, + ) + if err != nil { + return playlist, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(trackIDs), &playlist.TrackIDs); err != nil { + return playlist, errors.New("failed to parse track_ids: " + err.Error()) + } + if err = json.Unmarshal([]byte(tags), &playlist.Tags); err != nil { + return playlist, errors.New("failed to parse tags: " + err.Error()) + } + if err = json.Unmarshal([]byte(additionalMeta), &playlist.AdditionalMeta); err != nil { + return playlist, errors.New("failed to parse additional_meta: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &playlist.Permissions); err != nil { + return playlist, errors.New("failed to parse permissions: " + err.Error()) + } + + return playlist, nil +} + +func (db *SQLiteDatabase) AddPlaylist(playlist types.Playlist) error { + // Convert JSON fields to strings + trackIDs, err := json.Marshal(playlist.TrackIDs) + if err != nil { + return errors.New("failed to marshal track_ids: " + err.Error()) + } + tags, err := json.Marshal(playlist.Tags) + if err != nil { + return errors.New("failed to marshal tags: " + err.Error()) + } + additionalMeta, err := json.Marshal(playlist.AdditionalMeta) + if err != nil { + return errors.New("failed to marshal additional_meta: " + err.Error()) + } + permissions, err := json.Marshal(playlist.Permissions) + if err != nil { + return errors.New("failed to marshal permissions: " + err.Error()) + } + + _, err = db.sqlDB.Exec(` + INSERT INTO playlists ( + id, user_id, title, track_ids, listen_count, favorite_count, description, + creation_date, addition_date, tags, additional_meta, permissions, metadata_source + ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ); + `, + playlist.ID, playlist.UserID, playlist.Title, string(trackIDs), + playlist.ListenCount, playlist.FavoriteCount, playlist.Description, + playlist.CreationDate, playlist.AdditionDate, string(tags), + string(additionalMeta), string(permissions), playlist.MetadataSource, + ) + + return err +} + +func (db *SQLiteDatabase) UpdatePlaylist(playlist types.Playlist) error { + // Convert JSON fields to strings + trackIDs, err := json.Marshal(playlist.TrackIDs) + if err != nil { + return errors.New("failed to marshal track_ids: " + err.Error()) + } + tags, err := json.Marshal(playlist.Tags) + if err != nil { + return errors.New("failed to marshal tags: " + err.Error()) + } + additionalMeta, err := json.Marshal(playlist.AdditionalMeta) + if err != nil { + return errors.New("failed to marshal additional_meta: " + err.Error()) + } + permissions, err := json.Marshal(playlist.Permissions) + if err != nil { + return errors.New("failed to marshal permissions: " + err.Error()) + } + + _, err = db.sqlDB.Exec(` + UPDATE playlists + SET user_id=?, title=?, track_ids=?, listen_count=?, favorite_count=?, description=?, + creation_date=?, addition_date=?, tags=?, additional_meta=?, permissions=?, + metadata_source=? + WHERE id=?; + `, + playlist.UserID, playlist.Title, string(trackIDs), + playlist.ListenCount, playlist.FavoriteCount, playlist.Description, + playlist.CreationDate, playlist.AdditionDate, string(tags), + string(additionalMeta), string(permissions), playlist.MetadataSource, + playlist.ID, + ) + + return err +} + +func (db *SQLiteDatabase) DeletePlaylist(id string) error { + _, err := db.sqlDB.Exec("DELETE FROM playlists WHERE id = ?;", id) + return err +} + +func (db *SQLiteDatabase) GetUsers() ([]types.User, error) { + var users []types.User + rows, err := db.sqlDB.Query("SELECT * FROM users;") + if err != nil { + return users, err + } + + for rows.Next() { + user := types.User{} + var listenedTo, favorites, permissions, linkedSources string + + err = rows.Scan( + &user.ID, &user.Username, &user.Email, &user.PasswordHash, + &user.DisplayName, &user.Description, &listenedTo, &favorites, + &user.PublicViewCount, &user.CreationDate, &permissions, + &user.LinkedArtistID, &linkedSources, + ) + if err != nil { + return users, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(listenedTo), &user.ListenedTo); err != nil { + return users, errors.New("failed to parse listened_to: " + err.Error()) + } + if err = json.Unmarshal([]byte(favorites), &user.Favorites); err != nil { + return users, errors.New("failed to parse favorites: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &user.Permissions); err != nil { + return users, errors.New("failed to parse permissions: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedSources), &user.LinkedSources); err != nil { + return users, errors.New("failed to parse linked_sources: " + err.Error()) + } + + users = append(users, user) + } + + if err = rows.Err(); err != nil { + return users, err + } + + return users, err +} + +func (db *SQLiteDatabase) GetUser(id string) (types.User, error) { + user := types.User{} + var listenedTo, favorites, permissions, linkedSources string + + row := db.sqlDB.QueryRow("SELECT * FROM users WHERE id = ?;", id) + err := row.Scan( + &user.ID, &user.Username, &user.Email, &user.PasswordHash, + &user.DisplayName, &user.Description, &listenedTo, &favorites, + &user.PublicViewCount, &user.CreationDate, &permissions, + &user.LinkedArtistID, &linkedSources, + ) + if err != nil { + return user, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(listenedTo), &user.ListenedTo); err != nil { + return user, errors.New("failed to parse listened_to: " + err.Error()) + } + if err = json.Unmarshal([]byte(favorites), &user.Favorites); err != nil { + return user, errors.New("failed to parse favorites: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &user.Permissions); err != nil { + return user, errors.New("failed to parse permissions: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedSources), &user.LinkedSources); err != nil { + return user, errors.New("failed to parse linked_sources: " + err.Error()) + } + + return user, nil +} + +func (db *SQLiteDatabase) GetUserByUsername(username string) (types.User, error) { + user := types.User{} + var listenedTo, favorites, permissions, linkedSources string + + row := db.sqlDB.QueryRow("SELECT * FROM users WHERE username = ?;", username) + err := row.Scan( + &user.ID, &user.Username, &user.Email, &user.PasswordHash, + &user.DisplayName, &user.Description, &listenedTo, &favorites, + &user.PublicViewCount, &user.CreationDate, &permissions, + &user.LinkedArtistID, &linkedSources, + ) + if err != nil { + return user, err + } + + // Parse JSON fields + if err = json.Unmarshal([]byte(listenedTo), &user.ListenedTo); err != nil { + return user, errors.New("failed to parse listened_to: " + err.Error()) + } + if err = json.Unmarshal([]byte(favorites), &user.Favorites); err != nil { + return user, errors.New("failed to parse favorites: " + err.Error()) + } + if err = json.Unmarshal([]byte(permissions), &user.Permissions); err != nil { + return user, errors.New("failed to parse permissions: " + err.Error()) + } + if err = json.Unmarshal([]byte(linkedSources), &user.LinkedSources); err != nil { + return user, errors.New("failed to parse linked_sources: " + err.Error()) + } + + return user, nil +} + +func (db *SQLiteDatabase) CreateUser(user types.User) error { + // Convert JSON fields to strings + listenedTo, err := json.Marshal(user.ListenedTo) + if err != nil { + return errors.New("failed to marshal listened_to: " + err.Error()) + } + favorites, err := json.Marshal(user.Favorites) + if err != nil { + return errors.New("failed to marshal favorites: " + err.Error()) + } + permissions, err := json.Marshal(user.Permissions) + if err != nil { + return errors.New("failed to marshal permissions: " + err.Error()) + } + linkedSources, err := json.Marshal(user.LinkedSources) + if err != nil { + return errors.New("failed to marshal linked_sources: " + err.Error()) + } + + _, err = db.sqlDB.Exec(` + INSERT INTO users ( + id, username, email, password_hash, display_name, description, listened_to, + favorites, public_view_count, creation_date, permissions, linked_artist_id, + linked_sources + ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ); + `, + user.ID, user.Username, user.Email, user.PasswordHash, user.DisplayName, + user.Description, string(listenedTo), string(favorites), user.PublicViewCount, + user.CreationDate, string(permissions), user.LinkedArtistID, string(linkedSources), + ) + + return err +} + +func (db *SQLiteDatabase) UpdateUser(user types.User) error { + // Convert JSON fields to strings + listenedTo, err := json.Marshal(user.ListenedTo) + if err != nil { + return errors.New("failed to marshal listened_to: " + err.Error()) + } + favorites, err := json.Marshal(user.Favorites) + if err != nil { + return errors.New("failed to marshal favorites: " + err.Error()) + } + permissions, err := json.Marshal(user.Permissions) + if err != nil { + return errors.New("failed to marshal permissions: " + err.Error()) + } + linkedSources, err := json.Marshal(user.LinkedSources) + if err != nil { + return errors.New("failed to marshal linked_sources: " + err.Error()) + } + + _, err = db.sqlDB.Exec(` + UPDATE users + SET username=?, email=?, password_hash=?, display_name=?, description=?, listened_to=?, + favorites=?, public_view_count=?, creation_date=?, permissions=?, linked_artist_id=?, + linked_sources=? + WHERE id=?; + `, + user.Username, user.Email, user.PasswordHash, user.DisplayName, user.Description, + string(listenedTo), string(favorites), user.PublicViewCount, user.CreationDate, + string(permissions), user.LinkedArtistID, string(linkedSources), user.ID, + ) + + return err +} + +func (db *SQLiteDatabase) DeleteUser(id string) error { + _, err := db.sqlDB.Exec("DELETE FROM users WHERE id = ?;", id) + return err +} + +func (db *SQLiteDatabase) UsernameExists(username string) (bool, error) { + var exists bool + err := db.sqlDB.QueryRow("SELECT EXISTS(SELECT 1 FROM users WHERE username = ?);", username).Scan(&exists) + return exists, err +} + +func (db *SQLiteDatabase) EmailExists(email string) (bool, error) { + var exists bool + err := db.sqlDB.QueryRow("SELECT EXISTS(SELECT 1 FROM users WHERE email = ?);", email).Scan(&exists) + return exists, err +} + +func (db *SQLiteDatabase) BlacklistToken(token string, expiration time.Time) error { + _, err := db.sqlDB.Exec("INSERT INTO blacklisted_tokens (token, expiration) VALUES (?, ?);", token, expiration.Format(time.DateTime)) + return err +} + +func (db *SQLiteDatabase) CleanExpiredTokens() error { + _, err := db.sqlDB.Exec("DELETE FROM blacklisted_tokens WHERE expiration < datetime('now');") + return err } func (db *SQLiteDatabase) IsTokenBlacklisted(token string) (bool, error) { - logging.Error().Msg("unimplemented") - return false, nil + var exists bool + err := db.sqlDB.QueryRow("SELECT EXISTS(SELECT 1 FROM blacklisted_tokens WHERE token = ?);", token).Scan(&exists) + return exists, err } diff --git a/db/sqlite_test.go b/db/sqlite_test.go deleted file mode 100644 index 9e8e0ac..0000000 --- a/db/sqlite_test.go +++ /dev/null @@ -1,205 +0,0 @@ -package db - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/LibraMusic/LibraCore/types" -) - -func setupSQLiteTestDB(t *testing.T) *SQLiteDatabase { - db, err := ConnectSQLite() - if err != nil { - t.Fatalf("Failed to connect to SQLite: %v", err) - } - return db -} - -func teardownSQLiteTestDB(db *SQLiteDatabase) { - db.Close() -} - -func TestSQLiteDatabase_AddTrack(t *testing.T) { - db := setupSQLiteTestDB(t) - defer teardownSQLiteTestDB(db) - - track := types.Track{ - ID: "1", - UserID: "user1", - ISRC: "ISRC123", - Title: "Test Track", - ArtistIDs: []string{"artist1"}, - AlbumIDs: []string{"album1"}, - PrimaryAlbumID: "album1", - TrackNumber: 1, - Duration: 180, - Description: "Test Description", - ReleaseDate: "2023-01-01", - Lyrics: map[string]string{"en": "Test Lyrics"}, - ListenCount: 100, - FavoriteCount: 50, - AdditionDate: time.Now().Unix(), - Tags: []string{"tag1", "tag2"}, - AdditionalMeta: map[string]interface{}{"key": "value"}, - Permissions: map[string]string{"read": "all"}, - LinkedItemIDs: []string{"item1"}, - ContentSource: "source", - MetadataSource: "metadata", - LyricSources: map[string]types.LinkedSource{}, - } - - err := db.AddTrack(track) - assert.NoError(t, err) - - retrievedTrack, err := db.GetTrack("1") - assert.NoError(t, err) - assert.Equal(t, track, retrievedTrack) -} - -func TestSQLiteDatabase_GetTracks(t *testing.T) { - db := setupSQLiteTestDB(t) - defer teardownSQLiteTestDB(db) - - track1 := types.Track{ - ID: "1", - UserID: "user1", - ISRC: "ISRC123", - Title: "Test Track 1", - ArtistIDs: []string{"artist1"}, - AlbumIDs: []string{"album1"}, - PrimaryAlbumID: "album1", - TrackNumber: 1, - Duration: 180, - Description: "Test Description 1", - ReleaseDate: "2023-01-01", - Lyrics: map[string]string{"en": "Test Lyrics 1"}, - ListenCount: 100, - FavoriteCount: 50, - AdditionDate: time.Now().Unix(), - Tags: []string{"tag1", "tag2"}, - AdditionalMeta: map[string]interface{}{"key": "value"}, - Permissions: map[string]string{"read": "all"}, - LinkedItemIDs: []string{"item1"}, - ContentSource: "source", - MetadataSource: "metadata", - LyricSources: map[string]types.LinkedSource{}, - } - - track2 := types.Track{ - ID: "2", - UserID: "user1", - ISRC: "ISRC124", - Title: "Test Track 2", - ArtistIDs: []string{"artist2"}, - AlbumIDs: []string{"album2"}, - PrimaryAlbumID: "album2", - TrackNumber: 2, - Duration: 200, - Description: "Test Description 2", - ReleaseDate: "2023-01-02", - Lyrics: map[string]string{"en": "Test Lyrics 2"}, - ListenCount: 200, - FavoriteCount: 100, - AdditionDate: time.Now().Unix(), - Tags: []string{"tag3", "tag4"}, - AdditionalMeta: map[string]interface{}{"key": "value2"}, - Permissions: map[string]string{"read": "all"}, - LinkedItemIDs: []string{"item2"}, - ContentSource: "source2", - MetadataSource: "metadata2", - LyricSources: map[string]types.LinkedSource{}, - } - - err := db.AddTrack(track1) - assert.NoError(t, err) - err = db.AddTrack(track2) - assert.NoError(t, err) - - tracks, err := db.GetTracks("user1") - assert.NoError(t, err) - assert.Len(t, tracks, 2) - assert.Contains(t, tracks, track1) - assert.Contains(t, tracks, track2) -} - -func TestSQLiteDatabase_UpdateTrack(t *testing.T) { - db := setupSQLiteTestDB(t) - defer teardownSQLiteTestDB(db) - - track := types.Track{ - ID: "1", - UserID: "user1", - ISRC: "ISRC123", - Title: "Test Track", - ArtistIDs: []string{"artist1"}, - AlbumIDs: []string{"album1"}, - PrimaryAlbumID: "album1", - TrackNumber: 1, - Duration: 180, - Description: "Test Description", - ReleaseDate: "2023-01-01", - Lyrics: map[string]string{"en": "Test Lyrics"}, - ListenCount: 100, - FavoriteCount: 50, - AdditionDate: time.Now().Unix(), - Tags: []string{"tag1", "tag2"}, - AdditionalMeta: map[string]interface{}{"key": "value"}, - Permissions: map[string]string{"read": "all"}, - LinkedItemIDs: []string{"item1"}, - ContentSource: "source", - MetadataSource: "metadata", - LyricSources: map[string]types.LinkedSource{}, - } - - err := db.AddTrack(track) - assert.NoError(t, err) - - track.Title = "Updated Test Track" - err = db.UpdateTrack(track) - assert.NoError(t, err) - - retrievedTrack, err := db.GetTrack("1") - assert.NoError(t, err) - assert.Equal(t, "Updated Test Track", retrievedTrack.Title) -} - -func TestSQLiteDatabase_DeleteTrack(t *testing.T) { - db := setupSQLiteTestDB(t) - defer teardownSQLiteTestDB(db) - - track := types.Track{ - ID: "1", - UserID: "user1", - ISRC: "ISRC123", - Title: "Test Track", - ArtistIDs: []string{"artist1"}, - AlbumIDs: []string{"album1"}, - PrimaryAlbumID: "album1", - TrackNumber: 1, - Duration: 180, - Description: "Test Description", - ReleaseDate: "2023-01-01", - Lyrics: map[string]string{"en": "Test Lyrics"}, - ListenCount: 100, - FavoriteCount: 50, - AdditionDate: time.Now().Unix(), - Tags: []string{"tag1", "tag2"}, - AdditionalMeta: map[string]interface{}{"key": "value"}, - Permissions: map[string]string{"read": "all"}, - LinkedItemIDs: []string{"item1"}, - ContentSource: "source", - MetadataSource: "metadata", - LyricSources: map[string]types.LinkedSource{}, - } - - err := db.AddTrack(track) - assert.NoError(t, err) - - err = db.DeleteTrack("1") - assert.NoError(t, err) - - _, err = db.GetTrack("1") - assert.Error(t, err) -}