ci / api / api (push) Successful in 26s
ci / mobile / mobile (Metro-бандл Android) (push) Failing after 22s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 37s
ci / ts / ts (packages + web) (push) Successful in 51s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 53s
- schema for sources, path patterns, media files, artists, albums, tracks, genres (migration 00003) - path patterns (RE2 with named groups, skip), merge with tags by source priority, fallbacks from the file name and folder, name normalization and multi-artist split - scanner: walk, skip unchanged files by size and mtime, ffprobe in 8 workers with a single catalog writer, renames by content hash keep the track, duplicates fold into one track with the best file as primary, missing files are kept 30 days - storage-agent mounts NFS with validated server, export and an option whitelist, no shell; test mount counts audio files - admin API: sources CRUD, connection test, patterns with preview, scan, library status, file errors, host folder picker; scheduled scans and health checks; mounts are restored on start - catalog API: artist, artist albums, album with tracks, track - api image is alpine with ffmpeg for ffprobe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
136 lines
4.3 KiB
SQL
136 lines
4.3 KiB
SQL
-- +goose Up
|
|
CREATE TYPE source_type AS ENUM ('nfs', 'local');
|
|
CREATE TYPE source_status AS ENUM ('pending', 'mounted', 'error', 'disabled');
|
|
CREATE TYPE tag_priority AS ENUM ('tags_first', 'path_first');
|
|
|
|
CREATE TABLE library_sources (
|
|
id uuid PRIMARY KEY,
|
|
name text NOT NULL,
|
|
type source_type NOT NULL,
|
|
nfs_server text,
|
|
nfs_export text,
|
|
nfs_options text NOT NULL DEFAULT 'ro,nfsvers=4.1,soft,timeo=150,retrans=3',
|
|
local_path text,
|
|
tag_priority tag_priority NOT NULL DEFAULT 'tags_first',
|
|
accepts_uploads boolean NOT NULL DEFAULT false,
|
|
write_template text,
|
|
scan_interval interval,
|
|
status source_status NOT NULL DEFAULT 'pending',
|
|
status_message text,
|
|
last_scan_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE path_patterns (
|
|
id uuid PRIMARY KEY,
|
|
source_id uuid NOT NULL REFERENCES library_sources(id) ON DELETE CASCADE,
|
|
position int NOT NULL,
|
|
regex text NOT NULL,
|
|
is_enabled boolean NOT NULL DEFAULT true,
|
|
UNIQUE (source_id, position)
|
|
);
|
|
|
|
CREATE TABLE artists (
|
|
id uuid PRIMARY KEY,
|
|
name text NOT NULL,
|
|
sort_name text NOT NULL,
|
|
name_norm text NOT NULL UNIQUE,
|
|
image_path text,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE albums (
|
|
id uuid PRIMARY KEY,
|
|
title text NOT NULL,
|
|
title_norm text NOT NULL,
|
|
album_artist_id uuid NOT NULL REFERENCES artists(id),
|
|
year int,
|
|
cover_path text,
|
|
cover_colors text[],
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
UNIQUE (album_artist_id, title_norm)
|
|
);
|
|
|
|
CREATE TABLE media_files (
|
|
id uuid PRIMARY KEY,
|
|
source_id uuid NOT NULL REFERENCES library_sources(id) ON DELETE CASCADE,
|
|
rel_path text NOT NULL,
|
|
size_bytes bigint NOT NULL,
|
|
mtime timestamptz NOT NULL,
|
|
content_hash bytea,
|
|
sha256 bytea,
|
|
upload_file_id uuid,
|
|
format text NOT NULL,
|
|
codec text,
|
|
bitrate int,
|
|
sample_rate int,
|
|
bit_depth int,
|
|
channels int,
|
|
duration_ms int NOT NULL,
|
|
track_id uuid,
|
|
missing_since timestamptz,
|
|
scan_error text,
|
|
UNIQUE (source_id, rel_path)
|
|
);
|
|
CREATE INDEX media_files_content_hash_idx ON media_files (content_hash);
|
|
CREATE INDEX media_files_sha256_idx ON media_files (sha256);
|
|
CREATE INDEX media_files_track_idx ON media_files (track_id);
|
|
|
|
CREATE TABLE tracks (
|
|
id uuid PRIMARY KEY,
|
|
title text NOT NULL,
|
|
title_norm text NOT NULL,
|
|
album_id uuid NOT NULL REFERENCES albums(id),
|
|
disc_no int,
|
|
track_no int,
|
|
year int,
|
|
duration_ms int NOT NULL,
|
|
primary_file_id uuid REFERENCES media_files(id) ON DELETE SET NULL,
|
|
explicit boolean,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX tracks_album_idx ON tracks (album_id, disc_no, track_no);
|
|
-- Дубли: тот же альбом, название и длительность ±2 с ищутся по (album_id, title_norm).
|
|
CREATE INDEX tracks_dedup_idx ON tracks (album_id, title_norm);
|
|
ALTER TABLE media_files ADD FOREIGN KEY (track_id) REFERENCES tracks(id) ON DELETE SET NULL;
|
|
|
|
CREATE TABLE track_artists (
|
|
track_id uuid REFERENCES tracks(id) ON DELETE CASCADE,
|
|
artist_id uuid REFERENCES artists(id) ON DELETE CASCADE,
|
|
role text NOT NULL DEFAULT 'main',
|
|
position int NOT NULL,
|
|
PRIMARY KEY (track_id, artist_id, role)
|
|
);
|
|
CREATE INDEX track_artists_artist_idx ON track_artists (artist_id);
|
|
|
|
CREATE TABLE genres (
|
|
id serial PRIMARY KEY,
|
|
name text NOT NULL,
|
|
name_norm text NOT NULL UNIQUE,
|
|
parent_id int REFERENCES genres(id)
|
|
);
|
|
|
|
CREATE TABLE track_genres (
|
|
track_id uuid REFERENCES tracks(id) ON DELETE CASCADE,
|
|
genre_id int REFERENCES genres(id),
|
|
source text NOT NULL,
|
|
weight real NOT NULL DEFAULT 1,
|
|
PRIMARY KEY (track_id, genre_id, source)
|
|
);
|
|
|
|
-- +goose Down
|
|
DROP TABLE track_genres;
|
|
DROP TABLE genres;
|
|
DROP TABLE track_artists;
|
|
ALTER TABLE media_files DROP CONSTRAINT media_files_track_id_fkey;
|
|
DROP TABLE tracks;
|
|
DROP TABLE media_files;
|
|
DROP TABLE albums;
|
|
DROP TABLE artists;
|
|
DROP TABLE path_patterns;
|
|
DROP TABLE library_sources;
|
|
DROP TYPE tag_priority;
|
|
DROP TYPE source_status;
|
|
DROP TYPE source_type;
|