From db098efdf5bcd93f59efd27c47dd942c3ce4e1e0 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 24 Sep 2026 22:55:18 -0400 Subject: [PATCH] feat(postgres): add music database service Introduces a dedicated PostgreSQL database and schema for music data, including playlists, tracks, and various analytical views. This service is designed for importing Spotify playlists into YouTube Music. Updates the codeberg-mcp dependency hash to SRI format. Refactors home-manager package names for prettier and neovim-node-client. --- services/codeberg-mcp/default.nix | 5 +- services/postgres/default.nix | 1 + services/postgres/music.nix | 119 ++++++++++++++++++++++++++++++ users/mac/home.nix | 4 +- 4 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 services/postgres/music.nix diff --git a/services/codeberg-mcp/default.nix b/services/codeberg-mcp/default.nix index eac2cef..35482f4 100644 --- a/services/codeberg-mcp/default.nix +++ b/services/codeberg-mcp/default.nix @@ -8,8 +8,9 @@ let owner = "effecet"; repo = "codeberg-mcp"; rev = "f7e2fa06ca097cfa5638cef3768c35072c554fd9"; - # Hash temporaire — à corriger avec nix-prefetch-github après build. - hash = "1glxwx1wvvvck3pmp41yxi56z138wamvp0hf3nbls3zd8w9damqj"; + # Hash base32 nix-prefetch-url -> SRI : depuis Nix 2.x un hash nu n'a plus + # de type et fait échouer TOUTE l'évaluation de la config tower. + hash = "sha256-ElfVEkftD02XHQ6Cu6viaIRvSuw+kFvvmGzvzUPnnb4="; }; # Package Python manuel : le repo n'a pas de build-system valide. # On crée un binaire qui lance server.py avec le bon python. diff --git a/services/postgres/default.nix b/services/postgres/default.nix index c71cfcf..0730b78 100644 --- a/services/postgres/default.nix +++ b/services/postgres/default.nix @@ -1,6 +1,7 @@ { pkgs, config, ... }: { + imports = [ ./music.nix ]; sops.secrets."postgres_crt" = { sopsFile = ../../secrets/common.yml; diff --git a/services/postgres/music.nix b/services/postgres/music.nix new file mode 100644 index 0000000..b15b194 --- /dev/null +++ b/services/postgres/music.nix @@ -0,0 +1,119 @@ +{ pkgs, ... }: + +# Base "music" : export des playlists Spotify de marc (Exportify), importées vers +# YouTube Music. Chaque titre garde ses métadonnées d'origine (artistes, album, +# ISRC, durée) et le video id YouTube Music auquel il a été apparié, ce qui permet +# de requêter les doublons entre playlists, les artistes les plus présents, ou les +# titres sans équivalent trouvé côté YouTube Music. +# +# Rôle dédié "music" (comme les autres services du repo) plutôt que le rôle +# "hermes" : ce n'est pas un besoin interne de l'agent, n'importe quel outil peut +# s'y brancher en trust local via `psql -h 127.0.0.1 -U music -d music`. +let + schema = pkgs.writeText "music-schema.sql" '' + -- Playlists importées (id = id de playlist YouTube Music). + create table if not exists playlist ( + id text primary key, + name text not null, + source_file text, + privacy text not null default 'PRIVATE', + imported_at timestamptz not null default now() + ); + + create table if not exists track ( + id bigint generated by default as identity primary key, + title text not null, + artists text[] not null default '{}', + album text, + album_artists text[], + duration_ms integer, + release_date date, + -- Spotify ne donne que l'annee pour certaines sorties : release_date est + -- alors normalisee au 1er janvier et la precision reelle est conservee ici. + release_date_precision text, + isrc text, + explicit boolean, + spotify_track_id text unique, + youtube_music_video_id text + ); + + -- Pour une base deja creee avant l'ajout d'une colonne (create table if not + -- exists ne touche pas une table existante). + alter table track add column if not exists release_date_precision text; + + -- Ordre exact des titres dans chaque playlist (position = index d'origine). + create table if not exists playlist_track ( + playlist_id text not null references playlist(id) on delete cascade, + position integer not null, + track_id bigint not null references track(id) on delete cascade, + added_at timestamptz, + primary key (playlist_id, position) + ); + + -- Titres likés sur YouTube Music (ex "Liked Songs" de Spotify). + create table if not exists liked_track ( + track_id bigint primary key references track(id) on delete cascade, + liked_at timestamptz + ); + + create index if not exists track_title_lower_idx on track (lower(title)); + create index if not exists track_artists_idx on track using gin (artists); + create index if not exists playlist_track_track_idx on playlist_track (track_id); + + create or replace view v_playlist_stats as + select p.id, p.name, p.privacy, count(pt.track_id) as tracks + from playlist p + left join playlist_track pt on pt.playlist_id = p.id + group by p.id, p.name, p.privacy + order by p.name; + + -- Titres présents dans plusieurs playlists. + create or replace view v_duplicate_tracks as + select t.id, t.title, t.artists, count(distinct pt.playlist_id) as playlists + from track t + join playlist_track pt on pt.track_id = t.id + group by t.id, t.title, t.artists + having count(distinct pt.playlist_id) > 1 + order by playlists desc, t.title; + + create or replace view v_top_artists as + select artist, count(distinct t.id) as tracks + from track t + cross join lateral unnest(t.artists) as artist + group by artist + order by tracks desc; + + -- Titres sans équivalent trouvé sur YouTube Music. + create or replace view v_unmatched_tracks as + select id, title, artists, album + from track + where youtube_music_video_id is null + order by title; + ''; +in { + services.postgresql.ensureDatabases = [ "music" ]; + services.postgresql.ensureUsers = [ + { + name = "music"; + ensureDBOwnership = true; + } + ]; + + # Schéma appliqué à chaque activation. Tout est en IF NOT EXISTS / OR REPLACE, + # donc idempotent : une base vidée ou restaurée se répare toute seule, et une + # base déjà remplie n'est jamais touchée (aucun DROP). + systemd.services.music-schema = { + description = "Applique le schéma de la base music"; + requires = [ "postgresql-setup.service" ]; + after = [ "postgresql-setup.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + ${pkgs.postgresql_17}/bin/psql -h 127.0.0.1 -U music -d music \ + -v ON_ERROR_STOP=1 -q -f ${schema} + ''; + }; +} diff --git a/users/mac/home.nix b/users/mac/home.nix index ae5b8ff..d35035d 100644 --- a/users/mac/home.nix +++ b/users/mac/home.nix @@ -50,14 +50,14 @@ ripgrep # telescope live-grep + vim.health fd # telescope extended stylua # conform lua formatter - nodePackages.prettier # conform prettier + prettier # conform prettier tree-sitter # lspsaga + nvim-treesitter CLI sox # gp.nvim audio pngpaste # img-clip (macOS) findutils # GNU find (fix fzf-lua "illegal option") luarocks # lazy.nvim rocks - nodePackages.neovim # node provider + neovim-node-client # node provider # (nerdfonts.override { fonts = [ "Meslo" ]; }) # ou sur nixpkgs récent :