navidrome/db/migrations/20250701010108_add_multi_library_support.go
Deluan 295900e6f7 feat: add multi-library support to tag repository system
Implemented comprehensive library filtering for tag repositories to support the multi-library feature. This change ensures that users only see tags from libraries they have access to, while admin users can see all tags.

Key changes:
- Enhanced TagRepository.Add() method to accept libraryID parameter for proper library association
- Updated baseTagRepository to implement library-aware queries with proper joins
- Added library_tag table integration for per-library tag statistics
- Implemented user permission-based filtering through user_library associations
- Added comprehensive test coverage for library filtering scenarios
- Updated UI data provider to include tag filtering by selected libraries
- Modified scanner to pass library ID when adding tags during folder processing

The implementation maintains backward compatibility while providing proper isolation between libraries for tag-based operations like genres and other metadata tags.
2025-07-16 18:10:15 -04:00

120 lines
4.3 KiB
Go

package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigrationContext(upAddMultiLibrarySupport, downAddMultiLibrarySupport)
}
func upAddMultiLibrarySupport(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, `
-- Create user_library association table
CREATE TABLE user_library (
user_id VARCHAR(255) NOT NULL,
library_id INTEGER NOT NULL,
PRIMARY KEY (user_id, library_id),
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE,
FOREIGN KEY (library_id) REFERENCES library(id) ON DELETE CASCADE
);
-- Create indexes for performance
CREATE INDEX idx_user_library_user_id ON user_library(user_id);
CREATE INDEX idx_user_library_library_id ON user_library(library_id);
-- Populate with existing users having access to library ID 1 (existing setup)
-- Admin users get access to all libraries, regular users get access to library 1
INSERT INTO user_library (user_id, library_id)
SELECT u.id, 1
FROM user u;
-- Add total_duration column to library table
ALTER TABLE library ADD COLUMN total_duration real DEFAULT 0;
UPDATE library SET total_duration = (
SELECT IFNULL(SUM(duration),0) from album where album.library_id = library.id and missing = 0
);
-- Add default_new_users column to library table
ALTER TABLE library ADD COLUMN default_new_users boolean DEFAULT false;
-- Set library ID 1 (default library) as default for new users
UPDATE library SET default_new_users = true WHERE id = 1;
-- Add stats column to library_artist junction table for per-library artist statistics
ALTER TABLE library_artist ADD COLUMN stats text DEFAULT '{}';
-- Migrate existing global artist stats to per-library format in library_artist table
-- For each library_artist association, copy the artist's global stats
UPDATE library_artist
SET stats = (
SELECT COALESCE(artist.stats, '{}')
FROM artist
WHERE artist.id = library_artist.artist_id
);
-- Remove stats column from artist table to eliminate duplication
-- Stats are now stored per-library in library_artist table
ALTER TABLE artist DROP COLUMN stats;
-- Create library_tag table for per-library tag statistics
CREATE TABLE library_tag (
tag_id VARCHAR NOT NULL,
library_id INTEGER NOT NULL,
album_count INTEGER DEFAULT 0 NOT NULL,
media_file_count INTEGER DEFAULT 0 NOT NULL,
PRIMARY KEY (tag_id, library_id),
FOREIGN KEY (tag_id) REFERENCES tag(id) ON DELETE CASCADE,
FOREIGN KEY (library_id) REFERENCES library(id) ON DELETE CASCADE
);
-- Create indexes for optimal query performance
CREATE INDEX idx_library_tag_tag_id ON library_tag(tag_id);
CREATE INDEX idx_library_tag_library_id ON library_tag(library_id);
-- Migrate existing tag stats to per-library format in library_tag table
-- For existing installations, copy current global stats to library ID 1 (default library)
INSERT INTO library_tag (tag_id, library_id, album_count, media_file_count)
SELECT t.id, 1, t.album_count, t.media_file_count
FROM tag t
WHERE EXISTS (SELECT 1 FROM library WHERE id = 1);
-- Remove global stats from tag table as they are now per-library
ALTER TABLE tag DROP COLUMN album_count;
ALTER TABLE tag DROP COLUMN media_file_count;
`)
return err
}
func downAddMultiLibrarySupport(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, `
-- Restore stats column to artist table before removing from library_artist
ALTER TABLE artist ADD COLUMN stats text DEFAULT '{}';
-- Restore global stats by aggregating from library_artist (simplified approach)
-- In a real rollback scenario, this might need more sophisticated logic
UPDATE artist
SET stats = (
SELECT COALESCE(la.stats, '{}')
FROM library_artist la
WHERE la.artist_id = artist.id
LIMIT 1
);
ALTER TABLE library_artist DROP COLUMN IF EXISTS stats;
DROP INDEX IF EXISTS idx_user_library_library_id;
DROP INDEX IF EXISTS idx_user_library_user_id;
DROP TABLE IF EXISTS user_library;
ALTER TABLE library DROP COLUMN IF EXISTS total_duration;
ALTER TABLE library DROP COLUMN IF EXISTS default_new_users;
-- Drop library_tag table and its indexes
DROP INDEX IF EXISTS idx_library_tag_library_id;
DROP INDEX IF EXISTS idx_library_tag_tag_id;
DROP TABLE IF EXISTS library_tag;
`)
return err
}