feat(10-01): update SQL schema files for multi-library fresh installs
- Create _libraries.sql with libraries table (name, path, created_at) - Add library_id FK column and index to audio_files.sql - Update playlist_tracks.sql with nullable audio_file_id, SET NULL FK, and 6 phantom columns - Add af.library_id to track_metadata VIEW - Regenerate sqlc code for updated schemas - Fix playlist.go to use sql.NullInt64 for nullable audio_file_id
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS libraries (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
path TEXT NOT NULL UNIQUE,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
@@ -10,9 +10,14 @@ CREATE TABLE IF NOT EXISTS audio_files (
|
|||||||
bitrate int NOT NULL DEFAULT 0,
|
bitrate int NOT NULL DEFAULT 0,
|
||||||
file_size int NOT NULL DEFAULT 0,
|
file_size int NOT NULL DEFAULT 0,
|
||||||
basename text NOT NULL DEFAULT '',
|
basename text NOT NULL DEFAULT '',
|
||||||
|
library_id int NOT NULL DEFAULT 0,
|
||||||
FOREIGN KEY(file_type_id) REFERENCES file_types(id),
|
FOREIGN KEY(file_type_id) REFERENCES file_types(id),
|
||||||
FOREIGN KEY(recording_id) REFERENCES recordings(id)
|
FOREIGN KEY(recording_id) REFERENCES recordings(id),
|
||||||
|
FOREIGN KEY(library_id) REFERENCES libraries(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_audio_files_recording_id
|
CREATE INDEX IF NOT EXISTS idx_audio_files_recording_id
|
||||||
ON audio_files(recording_id);
|
ON audio_files(recording_id);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_audio_files_library_id
|
||||||
|
ON audio_files(library_id);
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
CREATE TABLE IF NOT EXISTS playlist_tracks (
|
CREATE TABLE IF NOT EXISTS playlist_tracks (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
playlist_id INTEGER NOT NULL,
|
playlist_id INTEGER NOT NULL,
|
||||||
audio_file_id INTEGER NOT NULL,
|
audio_file_id INTEGER,
|
||||||
position INTEGER NOT NULL,
|
position INTEGER NOT NULL,
|
||||||
|
phantom_title TEXT,
|
||||||
|
phantom_artist TEXT,
|
||||||
|
phantom_album TEXT,
|
||||||
|
phantom_duration_ms INTEGER,
|
||||||
|
phantom_genre TEXT,
|
||||||
|
phantom_cover_art_path TEXT,
|
||||||
FOREIGN KEY(playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
|
FOREIGN KEY(playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY(audio_file_id) REFERENCES audio_files(id) ON DELETE CASCADE
|
FOREIGN KEY(audio_file_id) REFERENCES audio_files(id) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playlist_tracks_playlist_id
|
CREATE INDEX IF NOT EXISTS idx_playlist_tracks_playlist_id
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ SELECT
|
|||||||
af.bit_depth,
|
af.bit_depth,
|
||||||
af.channels,
|
af.channels,
|
||||||
af.bitrate,
|
af.bitrate,
|
||||||
af.file_size
|
af.file_size,
|
||||||
|
af.library_id
|
||||||
FROM audio_files af
|
FROM audio_files af
|
||||||
LEFT JOIN recordings r ON af.recording_id = r.id
|
LEFT JOIN recordings r ON af.recording_id = r.id
|
||||||
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
|
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func (q *Queries) CountAudioFiles(ctx context.Context) (int64, error) {
|
|||||||
|
|
||||||
const createAudioFile = `-- name: CreateAudioFile :one
|
const createAudioFile = `-- name: CreateAudioFile :one
|
||||||
INSERT INTO audio_files (file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
INSERT INTO audio_files (file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
RETURNING id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename
|
RETURNING id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateAudioFileParams struct {
|
type CreateAudioFileParams struct {
|
||||||
@@ -66,6 +66,7 @@ func (q *Queries) CreateAudioFile(ctx context.Context, arg CreateAudioFileParams
|
|||||||
&i.Bitrate,
|
&i.Bitrate,
|
||||||
&i.FileSize,
|
&i.FileSize,
|
||||||
&i.Basename,
|
&i.Basename,
|
||||||
|
&i.LibraryID,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@@ -122,7 +123,7 @@ func (q *Queries) GetAllAudioFilePaths(ctx context.Context) ([]GetAllAudioFilePa
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getAllAudioFiles = `-- name: GetAllAudioFiles :many
|
const getAllAudioFiles = `-- name: GetAllAudioFiles :many
|
||||||
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename FROM audio_files
|
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id FROM audio_files
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetAllAudioFiles(ctx context.Context) ([]AudioFile, error) {
|
func (q *Queries) GetAllAudioFiles(ctx context.Context) ([]AudioFile, error) {
|
||||||
@@ -146,6 +147,7 @@ func (q *Queries) GetAllAudioFiles(ctx context.Context) ([]AudioFile, error) {
|
|||||||
&i.Bitrate,
|
&i.Bitrate,
|
||||||
&i.FileSize,
|
&i.FileSize,
|
||||||
&i.Basename,
|
&i.Basename,
|
||||||
|
&i.LibraryID,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -307,7 +309,7 @@ func (q *Queries) GetAllTracksWithFullMetadata(ctx context.Context) ([]GetAllTra
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getAudioFile = `-- name: GetAudioFile :one
|
const getAudioFile = `-- name: GetAudioFile :one
|
||||||
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename FROM audio_files
|
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id FROM audio_files
|
||||||
WHERE id = ? LIMIT 1
|
WHERE id = ? LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -326,12 +328,13 @@ func (q *Queries) GetAudioFile(ctx context.Context, id int64) (AudioFile, error)
|
|||||||
&i.Bitrate,
|
&i.Bitrate,
|
||||||
&i.FileSize,
|
&i.FileSize,
|
||||||
&i.Basename,
|
&i.Basename,
|
||||||
|
&i.LibraryID,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAudioFileByPath = `-- name: GetAudioFileByPath :one
|
const getAudioFileByPath = `-- name: GetAudioFileByPath :one
|
||||||
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename FROM audio_files
|
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id FROM audio_files
|
||||||
WHERE file_path = ? LIMIT 1
|
WHERE file_path = ? LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -350,6 +353,7 @@ func (q *Queries) GetAudioFileByPath(ctx context.Context, filePath string) (Audi
|
|||||||
&i.Bitrate,
|
&i.Bitrate,
|
||||||
&i.FileSize,
|
&i.FileSize,
|
||||||
&i.Basename,
|
&i.Basename,
|
||||||
|
&i.LibraryID,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@@ -448,7 +452,7 @@ func (q *Queries) GetAudioFilesByReleaseGroup(ctx context.Context, releaseGroupI
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getAudioFilesNeedingMetadata = `-- name: GetAudioFilesNeedingMetadata :many
|
const getAudioFilesNeedingMetadata = `-- name: GetAudioFilesNeedingMetadata :many
|
||||||
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename FROM audio_files
|
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id FROM audio_files
|
||||||
WHERE recording_id = 0
|
WHERE recording_id = 0
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -473,6 +477,7 @@ func (q *Queries) GetAudioFilesNeedingMetadata(ctx context.Context) ([]AudioFile
|
|||||||
&i.Bitrate,
|
&i.Bitrate,
|
||||||
&i.FileSize,
|
&i.FileSize,
|
||||||
&i.Basename,
|
&i.Basename,
|
||||||
|
&i.LibraryID,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type AudioFile struct {
|
|||||||
Bitrate int64
|
Bitrate int64
|
||||||
FileSize int64
|
FileSize int64
|
||||||
Basename string
|
Basename string
|
||||||
|
LibraryID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type CoverArt struct {
|
type CoverArt struct {
|
||||||
@@ -56,6 +57,13 @@ type Genre struct {
|
|||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Library struct {
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type PlayerState struct {
|
type PlayerState struct {
|
||||||
ID int64
|
ID int64
|
||||||
Volume int64
|
Volume int64
|
||||||
@@ -72,10 +80,16 @@ type Playlist struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PlaylistTrack struct {
|
type PlaylistTrack struct {
|
||||||
ID int64
|
ID int64
|
||||||
PlaylistID int64
|
PlaylistID int64
|
||||||
AudioFileID int64
|
AudioFileID sql.NullInt64
|
||||||
Position int64
|
Position int64
|
||||||
|
PhantomTitle sql.NullString
|
||||||
|
PhantomArtist sql.NullString
|
||||||
|
PhantomAlbum sql.NullString
|
||||||
|
PhantomDurationMs sql.NullInt64
|
||||||
|
PhantomGenre sql.NullString
|
||||||
|
PhantomCoverArtPath sql.NullString
|
||||||
}
|
}
|
||||||
|
|
||||||
type Queue struct {
|
type Queue struct {
|
||||||
@@ -155,4 +169,5 @@ type TrackMetadatum struct {
|
|||||||
Channels int64
|
Channels int64
|
||||||
Bitrate int64
|
Bitrate int64
|
||||||
FileSize int64
|
FileSize int64
|
||||||
|
LibraryID int64
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,17 @@ package sqlcgen
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
const addPlaylistTrack = `-- name: AddPlaylistTrack :one
|
const addPlaylistTrack = `-- name: AddPlaylistTrack :one
|
||||||
INSERT INTO playlist_tracks (playlist_id, audio_file_id, position) VALUES (?, ?, ?)
|
INSERT INTO playlist_tracks (playlist_id, audio_file_id, position) VALUES (?, ?, ?)
|
||||||
RETURNING id, playlist_id, audio_file_id, position
|
RETURNING id, playlist_id, audio_file_id, position, phantom_title, phantom_artist, phantom_album, phantom_duration_ms, phantom_genre, phantom_cover_art_path
|
||||||
`
|
`
|
||||||
|
|
||||||
type AddPlaylistTrackParams struct {
|
type AddPlaylistTrackParams struct {
|
||||||
PlaylistID int64
|
PlaylistID int64
|
||||||
AudioFileID int64
|
AudioFileID sql.NullInt64
|
||||||
Position int64
|
Position int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +29,12 @@ func (q *Queries) AddPlaylistTrack(ctx context.Context, arg AddPlaylistTrackPara
|
|||||||
&i.PlaylistID,
|
&i.PlaylistID,
|
||||||
&i.AudioFileID,
|
&i.AudioFileID,
|
||||||
&i.Position,
|
&i.Position,
|
||||||
|
&i.PhantomTitle,
|
||||||
|
&i.PhantomArtist,
|
||||||
|
&i.PhantomAlbum,
|
||||||
|
&i.PhantomDurationMs,
|
||||||
|
&i.PhantomGenre,
|
||||||
|
&i.PhantomCoverArtPath,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@@ -116,7 +123,7 @@ ORDER BY pt.playlist_id, pt.position
|
|||||||
type GetAllPlaylistTracksWithMetadataRow struct {
|
type GetAllPlaylistTracksWithMetadataRow struct {
|
||||||
ID int64
|
ID int64
|
||||||
PlaylistID int64
|
PlaylistID int64
|
||||||
AudioFileID int64
|
AudioFileID sql.NullInt64
|
||||||
Position int64
|
Position int64
|
||||||
FilePath string
|
FilePath string
|
||||||
LengthMilliseconds int64
|
LengthMilliseconds int64
|
||||||
@@ -262,7 +269,7 @@ ORDER BY pt.position
|
|||||||
type GetPlaylistTracksRow struct {
|
type GetPlaylistTracksRow struct {
|
||||||
ID int64
|
ID int64
|
||||||
PlaylistID int64
|
PlaylistID int64
|
||||||
AudioFileID int64
|
AudioFileID sql.NullInt64
|
||||||
Position int64
|
Position int64
|
||||||
FilePath string
|
FilePath string
|
||||||
}
|
}
|
||||||
@@ -326,7 +333,7 @@ ORDER BY pt.position
|
|||||||
type GetPlaylistTracksWithMetadataRow struct {
|
type GetPlaylistTracksWithMetadataRow struct {
|
||||||
ID int64
|
ID int64
|
||||||
PlaylistID int64
|
PlaylistID int64
|
||||||
AudioFileID int64
|
AudioFileID sql.NullInt64
|
||||||
Position int64
|
Position int64
|
||||||
FilePath string
|
FilePath string
|
||||||
LengthMilliseconds int64
|
LengthMilliseconds int64
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package playlist
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -867,7 +868,7 @@ func (s *Service) ImportPlaylist(
|
|||||||
s.db.Ctx,
|
s.db.Ctx,
|
||||||
sqlcgen.AddPlaylistTrackParams{
|
sqlcgen.AddPlaylistTrackParams{
|
||||||
PlaylistID: created.ID,
|
PlaylistID: created.ID,
|
||||||
AudioFileID: audioFile.ID,
|
AudioFileID: sql.NullInt64{Int64: audioFile.ID, Valid: true},
|
||||||
Position: int64(position),
|
Position: int64(position),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1065,7 +1066,7 @@ func (s *Service) restoreSinglePlaylist(
|
|||||||
s.db.Ctx,
|
s.db.Ctx,
|
||||||
sqlcgen.AddPlaylistTrackParams{
|
sqlcgen.AddPlaylistTrackParams{
|
||||||
PlaylistID: playlistID,
|
PlaylistID: playlistID,
|
||||||
AudioFileID: audioFile.ID,
|
AudioFileID: sql.NullInt64{Int64: audioFile.ID, Valid: true},
|
||||||
Position: int64(position),
|
Position: int64(position),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1125,7 +1126,7 @@ func (s *Service) addSingleTrack(
|
|||||||
s.db.Ctx,
|
s.db.Ctx,
|
||||||
sqlcgen.AddPlaylistTrackParams{
|
sqlcgen.AddPlaylistTrackParams{
|
||||||
PlaylistID: playlistID,
|
PlaylistID: playlistID,
|
||||||
AudioFileID: audioFile.ID,
|
AudioFileID: sql.NullInt64{Int64: audioFile.ID, Valid: true},
|
||||||
Position: position,
|
Position: position,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1654,7 +1655,7 @@ func (s *Service) ResolvePhantomTracks(
|
|||||||
s.db.Ctx,
|
s.db.Ctx,
|
||||||
sqlcgen.AddPlaylistTrackParams{
|
sqlcgen.AddPlaylistTrackParams{
|
||||||
PlaylistID: playlistID,
|
PlaylistID: playlistID,
|
||||||
AudioFileID: audioFile.ID,
|
AudioFileID: sql.NullInt64{Int64: audioFile.ID, Valid: true},
|
||||||
Position: nextPos + int64(resolved),
|
Position: nextPos + int64(resolved),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user