Files
spotizerr-dev/routes/migrations/v3_0_6.py
2025-08-17 11:45:28 -06:00

211 lines
4.9 KiB
Python

import sqlite3
HISTORY_SQL = """
CREATE TABLE IF NOT EXISTS download_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
download_type TEXT NOT NULL,
title TEXT NOT NULL,
artists TEXT,
timestamp REAL NOT NULL,
status TEXT NOT NULL,
service TEXT,
quality_format TEXT,
quality_bitrate TEXT,
total_tracks INTEGER,
successful_tracks INTEGER,
failed_tracks INTEGER,
skipped_tracks INTEGER,
children_table TEXT,
task_id TEXT,
external_ids TEXT,
metadata TEXT,
release_date TEXT,
genres TEXT,
images TEXT,
owner TEXT,
album_type TEXT,
duration_total_ms INTEGER,
explicit BOOLEAN
);
CREATE INDEX IF NOT EXISTS idx_download_history_timestamp ON download_history(timestamp);
CREATE INDEX IF NOT EXISTS idx_download_history_type_status ON download_history(download_type, status);
CREATE INDEX IF NOT EXISTS idx_download_history_task_id ON download_history(task_id);
CREATE UNIQUE INDEX IF NOT EXISTS uq_download_history_task_type_ids ON download_history(task_id, download_type, external_ids);
"""
WATCH_PLAYLISTS_SQL = """
CREATE TABLE IF NOT EXISTS watched_playlists (
spotify_id TEXT PRIMARY KEY,
name TEXT,
owner_id TEXT,
owner_name TEXT,
total_tracks INTEGER,
link TEXT,
snapshot_id TEXT,
last_checked INTEGER,
added_at INTEGER,
is_active INTEGER DEFAULT 1
);
"""
WATCH_ARTISTS_SQL = """
CREATE TABLE IF NOT EXISTS watched_artists (
spotify_id TEXT PRIMARY KEY,
name TEXT,
link TEXT,
total_albums_on_spotify INTEGER,
last_checked INTEGER,
added_at INTEGER,
is_active INTEGER DEFAULT 1,
genres TEXT,
popularity INTEGER,
image_url TEXT
);
"""
ACCOUNTS_SPOTIFY_SQL = """
CREATE TABLE IF NOT EXISTS spotify (
name TEXT PRIMARY KEY,
region TEXT,
created_at REAL,
updated_at REAL
);
"""
ACCOUNTS_DEEZER_SQL = """
CREATE TABLE IF NOT EXISTS deezer (
name TEXT PRIMARY KEY,
arl TEXT,
region TEXT,
created_at REAL,
updated_at REAL
);
"""
# --- Check functions ---
def _table_columns(conn: sqlite3.Connection, table: str) -> set[str]:
try:
cur = conn.execute(f"PRAGMA table_info({table})")
return {row[1] for row in cur.fetchall()}
except Exception:
return set()
def check_history_3_0_6(conn: sqlite3.Connection) -> bool:
"""Return True if history DB matches v3.0.6 schema for main table."""
cur = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='download_history'"
)
if not cur.fetchone():
return False
required = {
"id",
"download_type",
"title",
"artists",
"timestamp",
"status",
"service",
"quality_format",
"quality_bitrate",
"total_tracks",
"successful_tracks",
"failed_tracks",
"skipped_tracks",
"children_table",
"task_id",
"external_ids",
"metadata",
"release_date",
"genres",
"images",
"owner",
"album_type",
"duration_total_ms",
"explicit",
}
return required.issubset(_table_columns(conn, "download_history"))
def check_watch_playlists_3_0_6(conn: sqlite3.Connection) -> bool:
cur = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='watched_playlists'"
)
if not cur.fetchone():
return False
required = {
"spotify_id",
"name",
"owner_id",
"owner_name",
"total_tracks",
"link",
"snapshot_id",
"last_checked",
"added_at",
"is_active",
}
return required.issubset(_table_columns(conn, "watched_playlists"))
def check_watch_artists_3_0_6(conn: sqlite3.Connection) -> bool:
cur = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='watched_artists'"
)
if not cur.fetchone():
return False
required = {
"spotify_id",
"name",
"link",
"total_albums_on_spotify",
"last_checked",
"added_at",
"is_active",
"genres",
"popularity",
"image_url",
}
return required.issubset(_table_columns(conn, "watched_artists"))
def check_accounts_3_0_6(conn: sqlite3.Connection) -> bool:
"""Return True if accounts DB has both spotify and deezer tables with expected columns."""
# Spotify table
cur = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='spotify'"
)
if not cur.fetchone():
return False
spotify_required = {"name", "region", "created_at", "updated_at"}
if not spotify_required.issubset(_table_columns(conn, "spotify")):
return False
# Deezer table
cur = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='deezer'"
)
if not cur.fetchone():
return False
deezer_required = {"name", "arl", "region", "created_at", "updated_at"}
return deezer_required.issubset(_table_columns(conn, "deezer"))
# --- Update functions ---
def update_history_3_0_6(conn: sqlite3.Connection) -> None:
conn.executescript(HISTORY_SQL)
def update_watch_playlists_3_0_6(conn: sqlite3.Connection) -> None:
conn.executescript(WATCH_PLAYLISTS_SQL)
def update_watch_artists_3_0_6(conn: sqlite3.Connection) -> None:
conn.executescript(WATCH_ARTISTS_SQL)
def update_accounts_3_0_6(conn: sqlite3.Connection) -> None:
conn.executescript(ACCOUNTS_SPOTIFY_SQL)
conn.executescript(ACCOUNTS_DEEZER_SQL)