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.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{ pkgs, config, ... }:
|
||||
|
||||
{
|
||||
imports = [ ./music.nix ];
|
||||
|
||||
sops.secrets."postgres_crt" = {
|
||||
sopsFile = ../../secrets/common.yml;
|
||||
|
||||
@@ -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}
|
||||
'';
|
||||
};
|
||||
}
|
||||
+2
-2
@@ -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 :
|
||||
|
||||
Reference in New Issue
Block a user