feat(api): library sources, scanner and catalog (M2)
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
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>
This commit is contained in:
1 parent
ab092def54
commit
c583991d14
24 files changed
+7467
-84
No files matched your search
@@ -0,0 +1,135 @@
|
||||
-- +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;
|
||||
Reference in new issue
Block a user