feat: add Navidrome client with Subsonic API sync
Add the Navidrome client module that connects to a Navidrome server via the Subsonic API, fetches artist and album data, and syncs it into the local SQLite database. - Add go-subsonic dependency for Subsonic API communication - Create internal/navidrome/client.go with NavidromeClient wrapper - NewClient constructor with token-based auth - Ping health check with HTTP status validation - GetArtists fetches all artists via getArtists endpoint - GetArtistAlbums fetches albums per artist via getArtist endpoint - Create internal/navidrome/sync.go with sync orchestration - SyncArtists upserts artists into artist_settings table - SyncAlbums fetches and stores albums for monitored artists - Add local_albums table (migration 003) with FK to artist_settings - Add LocalAlbum CRUD operations in internal/database/local_albums.go - Full test coverage: 19 tests across client and sync packages - All tests pass, go vet and go fmt clean
This commit is contained in:
@@ -57,6 +57,11 @@ func (db *DB) Conn() *sql.DB {
|
||||
return db.conn
|
||||
}
|
||||
|
||||
// Begin starts a new database transaction.
|
||||
func (db *DB) Begin() (*sql.Tx, error) {
|
||||
return db.conn.Begin()
|
||||
}
|
||||
|
||||
// migrate runs all pending schema migrations in order.
|
||||
func (db *DB) migrate() error {
|
||||
// Create the migrations tracking table first, unconditionally.
|
||||
@@ -94,7 +99,16 @@ func (db *DB) migrate() error {
|
||||
);`,
|
||||
},
|
||||
{
|
||||
name: "003_create_notifications_sent",
|
||||
name: "003_create_local_albums",
|
||||
sql: `CREATE TABLE IF NOT EXISTS local_albums (
|
||||
id TEXT PRIMARY KEY,
|
||||
artist_id TEXT NOT NULL REFERENCES artist_settings(id),
|
||||
title TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_local_albums_artist_id ON local_albums(artist_id);`,
|
||||
},
|
||||
{
|
||||
name: "004_create_notifications_sent",
|
||||
sql: `CREATE TABLE IF NOT EXISTS notifications_sent (
|
||||
rgid TEXT NOT NULL REFERENCES external_releases(rgid),
|
||||
sent_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -145,7 +159,6 @@ func (db *DB) isMigrationApplied(name string) (bool, error) {
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
|
||||
// ArtistSettings represents a row in the artist_settings table.
|
||||
type ArtistSettings struct {
|
||||
ID string `json:"id"`
|
||||
@@ -155,6 +168,13 @@ type ArtistSettings struct {
|
||||
Monitored bool `json:"monitored"`
|
||||
}
|
||||
|
||||
// LocalAlbum represents a row in the local_albums table.
|
||||
type LocalAlbum struct {
|
||||
ID string `json:"id"`
|
||||
ArtistID string `json:"artist_id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// ExternalRelease represents a row in the external_releases table.
|
||||
type ExternalRelease struct {
|
||||
RGID string `json:"rgid"`
|
||||
|
||||
Reference in New Issue
Block a user