Initial clean commit

This commit is contained in:
Christian Krakau-Louis
2025-05-13 08:59:02 +00:00
commit 03b982e7c2
113 changed files with 22973 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
"""Add dropbox_export_path column to User model
Revision ID: add_dropbox_export_path
Revises: f83a512b9c47
Create Date: 2025-05-08
"""
import logging
from sqlalchemy import text, inspect
def run_migration():
"""
Add dropbox_export_path column to the User table if it doesn't exist.
"""
try:
from musicround import db
changes_made = False
try:
inspector = inspect(db.engine)
existing_columns = [column['name'] for column in inspector.get_columns('user')]
with db.engine.connect() as conn:
if 'dropbox_export_path' not in existing_columns:
conn.execute(text('ALTER TABLE user ADD COLUMN dropbox_export_path TEXT'))
conn.commit()
changes_made = True
except Exception as e:
logging.error(f"Error in migration: {str(e)}")
return False
if changes_made:
logging.info("Migration completed successfully")
return True
else:
logging.info("No changes were needed")
return None
except ImportError:
import sqlite3
import os
logging.info("Falling back to direct SQLite connection")
try:
db_path = os.environ.get('DATABASE_PATH', '/data/song_data.db')
if not os.path.exists(db_path):
possible_paths = [
'./instance/musicround.db',
'/app/instance/musicround.db',
'/data/song_data.db',
'./song_data.db',
os.path.join(os.path.dirname(os.path.dirname(__file__)), 'instance', 'musicround.db')
]
for path in possible_paths:
if os.path.exists(path):
db_path = path
break
else:
logging.warning(f"Database not found at any of the possible paths")
return False
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(user)")
columns = [column[1] for column in cursor.fetchall()]
if 'dropbox_export_path' not in columns:
cursor.execute("ALTER TABLE user ADD COLUMN dropbox_export_path TEXT")
conn.commit()
conn.close()
logging.info("Migration completed successfully")
return True
else:
conn.close()
logging.info("Column already exists: dropbox_export_path")
return None
except Exception as e:
logging.error(f"Migration add_dropbox_export_path failed: {str(e)}")
import traceback
logging.error(traceback.format_exc())
return False
if __name__ == "__main__":
import logging
logging.basicConfig(level=logging.INFO)
run_migration()
+185
View File
@@ -0,0 +1,185 @@
"""Add Dropbox OAuth fields to User model
Revision ID: f83a512b9c47
Revises: e7c912b4d835
Create Date: 2025-05-08 14:30:45.891234
"""
from alembic import op
import sqlalchemy as sa
import logging
from sqlalchemy import text, inspect
from datetime import datetime
# revision identifiers, used by Alembic.
revision = 'f83a512b9c47'
down_revision = 'e7c912b4d835' # previous migration was add_tag_system
branch_labels = None
depends_on = None
logger = logging.getLogger(__name__)
def upgrade():
# Add Dropbox OAuth columns to user table
op.add_column('user', sa.Column('dropbox_id', sa.String(100), nullable=True))
op.add_column('user', sa.Column('dropbox_token', sa.Text(), nullable=True))
op.add_column('user', sa.Column('dropbox_refresh_token', sa.Text(), nullable=True))
op.add_column('user', sa.Column('dropbox_token_expiry', sa.DateTime(), nullable=True))
def downgrade():
# Remove Dropbox OAuth columns
op.drop_column('user', 'dropbox_id')
op.drop_column('user', 'dropbox_token')
op.drop_column('user', 'dropbox_refresh_token')
op.drop_column('user', 'dropbox_token_expiry')
def run_migration():
"""
Add Dropbox OAuth fields to the User table if they don't exist.
"""
try:
from musicround import db
# Track changes made
changes_made = False
try:
# Connect to the database
inspector = inspect(db.engine)
existing_columns = [column['name'] for column in inspector.get_columns('user')]
# Use connection for executing SQL statements
with db.engine.connect() as conn:
# Add Dropbox OAuth columns
if 'dropbox_id' not in existing_columns:
logger.info("Adding dropbox_id column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN dropbox_id VARCHAR(100)'))
conn.commit()
changes_made = True
logger.info("Added dropbox_id column")
except Exception as e:
logger.error(f"Error adding dropbox_id column: {str(e)}")
if 'dropbox_token' not in existing_columns:
logger.info("Adding dropbox_token column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN dropbox_token TEXT'))
conn.commit()
changes_made = True
logger.info("Added dropbox_token column")
except Exception as e:
logger.error(f"Error adding dropbox_token column: {str(e)}")
if 'dropbox_refresh_token' not in existing_columns:
logger.info("Adding dropbox_refresh_token column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN dropbox_refresh_token TEXT'))
conn.commit()
changes_made = True
logger.info("Added dropbox_refresh_token column")
except Exception as e:
logger.error(f"Error adding dropbox_refresh_token column: {str(e)}")
if 'dropbox_token_expiry' not in existing_columns:
logger.info("Adding dropbox_token_expiry column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN dropbox_token_expiry DATETIME'))
conn.commit()
changes_made = True
logger.info("Added dropbox_token_expiry column")
except Exception as e:
logger.error(f"Error adding dropbox_token_expiry column: {str(e)}")
except Exception as e:
logger.error(f"Error in migration: {str(e)}")
return False # Return False for errors
# Report results
if changes_made:
logger.info("Migration completed successfully")
return True # Changes were made successfully
else:
logger.info("No changes were needed")
return None # No changes were needed (database is already up to date)
except ImportError:
# If we can't import the db object, fall back to SQLite direct connection
import sqlite3
import os
logger.info("Falling back to direct SQLite connection")
# Try to get the database path from environment or standard locations
try:
# Try environment variable
db_path = os.environ.get('DATABASE_PATH', '/data/song_data.db')
# If that doesn't exist, try other possible paths
if not os.path.exists(db_path):
possible_paths = [
'./instance/musicround.db',
'/app/instance/musicround.db',
'/data/song_data.db',
'./song_data.db',
os.path.join(os.path.dirname(os.path.dirname(__file__)), 'instance', 'musicround.db')
]
for path in possible_paths:
if os.path.exists(path):
db_path = path
logger.info(f"Found database at: {db_path}")
break
else:
logger.warning(f"Database not found at any of the possible paths")
return False
# Connect to the database
logger.info(f"Connecting to database at: {db_path}")
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Get existing columns
cursor.execute("PRAGMA table_info(user)")
columns = [column[1] for column in cursor.fetchall()]
# Define the new columns to add
new_columns = [
("dropbox_id", "VARCHAR(100)"),
("dropbox_token", "TEXT"),
("dropbox_refresh_token", "TEXT"),
("dropbox_token_expiry", "DATETIME")
]
# Add each column if it doesn't exist
changes_made = False
for column_name, column_type in new_columns:
if column_name not in columns:
sql = f"ALTER TABLE user ADD COLUMN {column_name} {column_type}"
cursor.execute(sql)
logger.info(f"Added column: {column_name} {column_type}")
changes_made = True
else:
logger.info(f"Column already exists: {column_name}")
# Commit changes and close connection
conn.commit()
conn.close()
if changes_made:
logger.info("Migration completed successfully")
return True
else:
logger.info("No changes were needed")
return None
except Exception as e:
logger.error(f"Migration add_dropbox_oauth failed: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return False
if __name__ == "__main__":
# Set up logging when run directly
logging.basicConfig(level=logging.INFO)
run_migration()
+158
View File
@@ -0,0 +1,158 @@
"""
Migration script to add Google and Authentik OAuth fields to the User table
"""
from datetime import datetime
import logging
from sqlalchemy import text, inspect
logger = logging.getLogger(__name__)
def run_migration():
"""
Add new columns to the User table for Google and Authentik OAuth integration
Returns:
- True: if changes were made successfully
- None: if no changes were needed (already up to date)
- False: if errors occurred
"""
from musicround import db
# Track changes made
changes_made = False
try:
# Connect to the database
inspector = inspect(db.engine)
existing_columns = [column['name'] for column in inspector.get_columns('user')]
# Use connection for executing SQL statements
with db.engine.connect() as conn:
# Add auth_provider column if it doesn't exist
if 'auth_provider' not in existing_columns:
logger.info("Adding auth_provider column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN auth_provider VARCHAR(20)'))
# Set default value for existing rows
conn.execute(text("UPDATE user SET auth_provider = 'local' WHERE auth_provider IS NULL"))
conn.commit()
changes_made = True
logger.info("Added auth_provider column")
except Exception as e:
logger.error(f"Error adding auth_provider column: {str(e)}")
# Add Google OAuth columns
if 'google_id' not in existing_columns:
logger.info("Adding google_id column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN google_id VARCHAR(100)'))
conn.commit()
changes_made = True
logger.info("Added google_id column")
except Exception as e:
logger.error(f"Error adding google_id column: {str(e)}")
if 'google_token' not in existing_columns:
logger.info("Adding google_token column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN google_token TEXT'))
conn.commit()
changes_made = True
logger.info("Added google_token column")
except Exception as e:
logger.error(f"Error adding google_token column: {str(e)}")
if 'google_refresh_token' not in existing_columns:
logger.info("Adding google_refresh_token column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN google_refresh_token TEXT'))
conn.commit()
changes_made = True
logger.info("Added google_refresh_token column")
except Exception as e:
logger.error(f"Error adding google_refresh_token column: {str(e)}")
# Add Authentik OAuth columns
if 'authentik_id' not in existing_columns:
logger.info("Adding authentik_id column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN authentik_id VARCHAR(100)'))
conn.commit()
changes_made = True
logger.info("Added authentik_id column")
except Exception as e:
logger.error(f"Error adding authentik_id column: {str(e)}")
if 'authentik_token' not in existing_columns:
logger.info("Adding authentik_token column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN authentik_token TEXT'))
conn.commit()
changes_made = True
logger.info("Added authentik_token column")
except Exception as e:
logger.error(f"Error adding authentik_token column: {str(e)}")
if 'authentik_refresh_token' not in existing_columns:
logger.info("Adding authentik_refresh_token column")
try:
conn.execute(text('ALTER TABLE user ADD COLUMN authentik_refresh_token TEXT'))
conn.commit()
changes_made = True
logger.info("Added authentik_refresh_token column")
except Exception as e:
logger.error(f"Error adding authentik_refresh_token column: {str(e)}")
# Make password_hash nullable for OAuth-only users
try:
# Due to SQLite limitations, we need to recreate the table to change column nullability
# Check if it's already nullable
is_nullable = False
result = conn.execute(text("PRAGMA table_info('user')"))
columns_info = result.fetchall()
for col in columns_info:
if col[1] == 'password_hash' and col[3] == 0: # 0 means nullable
is_nullable = True
break
if not is_nullable:
logger.info("Modifying password_hash to be nullable")
# Get all column definitions
columns = []
for col_info in columns_info:
name = col_info[1]
type_name = col_info[2]
not_null = "NOT NULL" if col_info[3] == 1 and name != "password_hash" else ""
pk = "PRIMARY KEY" if col_info[5] == 1 else ""
columns.append(f"{name} {type_name} {pk} {not_null}".strip())
# Create a temporary table with the new schema
column_defs = ", ".join(columns)
conn.execute(text(f'CREATE TABLE user_temp ({column_defs})'))
# Copy data from the old table
conn.execute(text('INSERT INTO user_temp SELECT * FROM user'))
# Replace the old table
conn.execute(text('DROP TABLE user'))
conn.execute(text('ALTER TABLE user_temp RENAME TO user'))
conn.commit()
changes_made = True
logger.info("Made password_hash column nullable for OAuth-only users")
else:
logger.info("password_hash is already nullable")
except Exception as e:
logger.error(f"Error modifying password_hash column: {str(e)}")
except Exception as e:
logger.error(f"Error in migration: {str(e)}")
return False # Return False for errors
# Report results
if changes_made:
logger.info("Migration completed successfully")
return True # Changes were made successfully
else:
logger.info("No changes were needed")
return None # No changes were needed (database is already up to date)
+126
View File
@@ -0,0 +1,126 @@
"""Add multiple preview URLs and cover URLs to Song model
Revision ID: d82c9a4f1e56
Revises: a7cb4e9f8d21
Create Date: 2023-06-11 14:23:45.678901
"""
from alembic import op
import sqlalchemy as sa
import sqlite3
import os
import logging
# revision identifiers, used by Alembic.
revision = 'd82c9a4f1e56'
down_revision = 'a7cb4e9f8d21' # replace with your previous migration id
branch_labels = None
depends_on = None
def upgrade():
# Add new columns for preview URLs from different sources
op.add_column('song', sa.Column('spotify_preview_url', sa.String(255), nullable=True))
op.add_column('song', sa.Column('deezer_preview_url', sa.String(255), nullable=True))
op.add_column('song', sa.Column('apple_preview_url', sa.String(255), nullable=True))
op.add_column('song', sa.Column('youtube_preview_url', sa.String(255), nullable=True))
# Add new columns for cover URLs from different services
op.add_column('song', sa.Column('spotify_cover_url', sa.String(255), nullable=True))
op.add_column('song', sa.Column('deezer_cover_url', sa.String(255), nullable=True))
op.add_column('song', sa.Column('apple_cover_url', sa.String(255), nullable=True))
# Add a column for additional data as JSON
op.add_column('song', sa.Column('additional_data', sa.Text(), nullable=True))
def downgrade():
# Remove the new columns
op.drop_column('song', 'spotify_preview_url')
op.drop_column('song', 'deezer_preview_url')
op.drop_column('song', 'apple_preview_url')
op.drop_column('song', 'youtube_preview_url')
op.drop_column('song', 'spotify_cover_url')
op.drop_column('song', 'deezer_cover_url')
op.drop_column('song', 'apple_cover_url')
op.drop_column('song', 'additional_data')
def run_migration():
"""
Add platform-specific preview URL columns to the song table if they don't exist.
"""
logger = logging.getLogger(__name__)
logger.info("Running migration: add_preview_urls")
# Try to get the database path from Flask config first
try:
from flask import current_app
if current_app and current_app.config.get('SQLALCHEMY_DATABASE_URI'):
# Extract path from URI
db_uri = current_app.config['SQLALCHEMY_DATABASE_URI']
if db_uri.startswith('sqlite:///'):
db_path = db_uri[10:] # Remove 'sqlite:///'
logger.info(f"Got database path from Flask config: {db_path}")
else:
# Use DATABASE_PATH if available
db_path = current_app.config.get('DATABASE_PATH', '/data/song_data.db')
logger.info(f"Using DATABASE_PATH: {db_path}")
else:
# If Flask isn't running, try Docker standard path
db_path = '/data/song_data.db'
logger.info(f"Flask not available, using default path: {db_path}")
except Exception as e:
# Fallbacks in case Flask isn't available
logger.warning(f"Could not get path from Flask: {str(e)}")
# Try environment variable
db_path = os.environ.get('DATABASE_PATH', '/data/song_data.db')
logger.info(f"Using DATABASE_PATH from environment: {db_path}")
if not os.path.exists(db_path):
logger.warning(f"Database not found at {db_path}")
return False
try:
# Connect to the database
logger.info(f"Connecting to database at: {db_path}")
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Get existing columns
cursor.execute("PRAGMA table_info(song)")
columns = [column[1] for column in cursor.fetchall()]
# Define the new columns to add
new_columns = [
("spotify_preview_url", "VARCHAR(500)"),
("deezer_preview_url", "VARCHAR(500)"),
("apple_preview_url", "VARCHAR(500)"),
("youtube_preview_url", "VARCHAR(500)"),
("spotify_cover_url", "VARCHAR(500)"),
("deezer_cover_url", "VARCHAR(500)"),
("apple_cover_url", "VARCHAR(500)"),
("additional_data", "TEXT")
]
# Add each column if it doesn't exist
for column_name, column_type in new_columns:
if column_name not in columns:
sql = f"ALTER TABLE song ADD COLUMN {column_name} {column_type}"
cursor.execute(sql)
logger.info(f"Added column: {column_name} {column_type}")
else:
logger.info(f"Column already exists: {column_name}")
# Commit changes and close connection
conn.commit()
conn.close()
logger.info("Migration add_preview_urls completed successfully")
return True
except Exception as e:
logger.error(f"Migration add_preview_urls failed: {str(e)}")
return False
if __name__ == "__main__":
# Set up logging when run directly
logging.basicConfig(level=logging.INFO)
run_migration()
+124
View File
@@ -0,0 +1,124 @@
"""Add new fields to Song model
Revision ID: a7cb4e9f8d21
Revises: previous_revision_id
Create Date: 2023-06-10 12:34:56.789012
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a7cb4e9f8d21'
down_revision = 'previous_revision_id' # replace with your previous migration id
branch_labels = None
depends_on = None
def upgrade():
# Add new columns to the song table
op.add_column('song', sa.Column('isrc', sa.String(50), nullable=True))
op.add_column('song', sa.Column('album_name', sa.String(100), nullable=True))
op.add_column('song', sa.Column('metadata_sources', sa.String(100), nullable=True))
op.add_column('song', sa.Column('import_date', sa.DateTime, nullable=True))
# Create index for ISRC
op.create_index(op.f('ix_song_isrc'), 'song', ['isrc'], unique=False)
# Increase length of existing URL columns
op.alter_column('song', 'preview_url', type_=sa.String(255))
op.alter_column('song', 'cover_url', type_=sa.String(255))
def downgrade():
# Remove the new columns
op.drop_index(op.f('ix_song_isrc'), table_name='song')
op.drop_column('song', 'isrc')
op.drop_column('song', 'album_name')
op.drop_column('song', 'metadata_sources')
op.drop_column('song', 'import_date')
# Restore original column lengths
op.alter_column('song', 'preview_url', type_=sa.String(200))
op.alter_column('song', 'cover_url', type_=sa.String(200))
def run_migration():
"""
Add additional song fields to the song table if they don't exist.
"""
import sqlite3
import os
import logging
logger = logging.getLogger(__name__)
logger.info("Running migration: add_song_fields")
# Try to get the database path from Flask config first
try:
from flask import current_app
if current_app and current_app.config.get('SQLALCHEMY_DATABASE_URI'):
# Extract path from URI
db_uri = current_app.config['SQLALCHEMY_DATABASE_URI']
if db_uri.startswith('sqlite:///'):
db_path = db_uri[10:] # Remove 'sqlite:///'
logger.info(f"Got database path from Flask config: {db_path}")
else:
# Use DATABASE_PATH if available
db_path = current_app.config.get('DATABASE_PATH', '/data/song_data.db')
logger.info(f"Using DATABASE_PATH: {db_path}")
else:
# If Flask isn't running, try Docker standard path
db_path = '/data/song_data.db'
logger.info(f"Flask not available, using default path: {db_path}")
except Exception as e:
# Fallbacks in case Flask isn't available
logger.warning(f"Could not get path from Flask: {str(e)}")
# Try environment variable
db_path = os.environ.get('DATABASE_PATH', '/data/song_data.db')
logger.info(f"Using DATABASE_PATH from environment: {db_path}")
if not os.path.exists(db_path):
logger.warning(f"Database not found at {db_path}")
return False
try:
# Connect to the database
logger.info(f"Connecting to database at: {db_path}")
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Get existing columns
cursor.execute("PRAGMA table_info(song)")
columns = [column[1] for column in cursor.fetchall()]
# Define the new columns to add
new_columns = [
("album_name", "VARCHAR(200)"),
("metadata_sources", "VARCHAR(500)"),
("import_date", "DATETIME"),
("source", "VARCHAR(20)")
]
# Add each column if it doesn't exist
for column_name, column_type in new_columns:
if column_name not in columns:
sql = f"ALTER TABLE song ADD COLUMN {column_name} {column_type}"
cursor.execute(sql)
logger.info(f"Added column: {column_name} {column_type}")
else:
logger.info(f"Column already exists: {column_name}")
# Commit changes and close connection
conn.commit()
conn.close()
logger.info("Migration add_song_fields completed successfully")
return True
except Exception as e:
logger.error(f"Migration add_song_fields failed: {str(e)}")
return False
if __name__ == "__main__":
# Set up logging when run directly
import logging
logging.basicConfig(level=logging.INFO)
run_migration()
+116
View File
@@ -0,0 +1,116 @@
"""
Migration script to add Spotify audio features to the Song model
"""
import sqlite3
import os
import logging
from flask import current_app
def run_migration():
"""
Add Spotify audio features columns to the song table if they don't exist.
This is safe to run multiple times as it checks for column existence.
"""
logger = logging.getLogger(__name__)
logger.info("Running migration: add_spotify_audio_features")
# Try to get the database path from Flask app config
db_path = None
# First check if we can get the path from Flask current_app
if current_app:
# Get the database URI from Flask's config
db_uri = current_app.config.get('SQLALCHEMY_DATABASE_URI')
if db_uri and db_uri.startswith('sqlite:///'):
# Extract the path from the URI
db_path = db_uri.replace('sqlite:///', '')
logger.info(f"Got database path from Flask config: {db_path}")
# If we couldn't get the path from Flask, try the known locations
if not db_path or not os.path.exists(db_path):
# Docker container path based on app configuration in __init__.py
data_dir = '/data'
db_path = os.path.join(data_dir, 'song_data.db') # Path used in Flask app config
# If that doesn't exist, try other possible paths
if not os.path.exists(db_path):
possible_paths = [
'./instance/musicround.db',
'/app/instance/musicround.db',
'/data/song_data.db',
'./song_data.db',
os.path.join(os.path.dirname(os.path.dirname(__file__)), 'instance', 'musicround.db')
]
for path in possible_paths:
if os.path.exists(path):
db_path = path
logger.info(f"Found database at: {db_path}")
break
else:
logger.warning(f"Database not found at any of the possible paths")
return False
try:
# Connect to the database
logger.info(f"Connecting to database at: {db_path}")
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Get existing columns
cursor.execute("PRAGMA table_info(song)")
columns = [column[1] for column in cursor.fetchall()]
logger.info(f"Current columns in the song table: {columns}")
# Define the new columns to add
new_columns = [
("acousticness", "FLOAT"),
("danceability", "FLOAT"),
("energy", "FLOAT"),
("instrumentalness", "FLOAT"),
("key", "INTEGER"),
("liveness", "FLOAT"),
("loudness", "FLOAT"),
("mode", "INTEGER"),
("speechiness", "FLOAT"),
("tempo", "FLOAT"),
("time_signature", "INTEGER"),
("valence", "FLOAT"),
("duration_ms", "INTEGER"),
("analysis_url", "VARCHAR(500)")
]
# Add each column if it doesn't exist
columns_added = 0
for column_name, column_type in new_columns:
if column_name not in columns:
sql = f"ALTER TABLE song ADD COLUMN {column_name} {column_type}"
cursor.execute(sql)
logger.info(f"Added column: {column_name} {column_type}")
columns_added += 1
else:
logger.info(f"Column already exists: {column_name}")
# Commit changes and close connection
conn.commit()
conn.close()
if columns_added > 0:
logger.info(f"Successfully added {columns_added} new columns to the database.")
else:
logger.info("No new columns needed to be added.")
logger.info("Migration add_spotify_audio_features completed successfully")
return True
except Exception as e:
logger.error(f"Migration add_spotify_audio_features failed: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return False
if __name__ == "__main__":
# Set up logging when run directly
logging.basicConfig(level=logging.INFO)
run_migration()
+146
View File
@@ -0,0 +1,146 @@
"""Add tag system for songs
Revision ID: e7c912b4d835
Revises: d82c9a4f1e56
Create Date: 2025-04-22 10:34:56.789012
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'e7c912b4d835'
down_revision = 'd82c9a4f1e56' # previous migration was add_preview_urls
branch_labels = None
depends_on = None
def upgrade():
# Create tag table
op.create_table('tag',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=50), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
# Create song_tag mapping table
op.create_table('song_tag',
sa.Column('song_id', sa.Integer(), nullable=False),
sa.Column('tag_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['song_id'], ['song.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tag_id'], ['tag.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('song_id', 'tag_id')
)
# Create indexes for faster lookups
op.create_index(op.f('ix_song_tag_song_id'), 'song_tag', ['song_id'], unique=False)
op.create_index(op.f('ix_song_tag_tag_id'), 'song_tag', ['tag_id'], unique=False)
def downgrade():
# Drop the indexes first
op.drop_index(op.f('ix_song_tag_tag_id'), table_name='song_tag')
op.drop_index(op.f('ix_song_tag_song_id'), table_name='song_tag')
# Drop the tables
op.drop_table('song_tag')
op.drop_table('tag')
def run_migration():
"""
Add tag and song_tag tables for the tag system if they don't exist.
"""
import sqlite3
import os
import logging
logger = logging.getLogger(__name__)
logger.info("Running migration: add_tag_system")
# Try to get the database path from Flask config first
try:
from flask import current_app
if current_app and current_app.config.get('SQLALCHEMY_DATABASE_URI'):
# Extract path from URI
db_uri = current_app.config['SQLALCHEMY_DATABASE_URI']
if db_uri.startswith('sqlite:///'):
db_path = db_uri[10:] # Remove 'sqlite:///'
logger.info(f"Got database path from Flask config: {db_path}")
else:
# Use DATABASE_PATH if available
db_path = current_app.config.get('DATABASE_PATH', '/data/song_data.db')
logger.info(f"Using DATABASE_PATH: {db_path}")
else:
# If Flask isn't running, try Docker standard path
db_path = '/data/song_data.db'
logger.info(f"Flask not available, using default path: {db_path}")
except Exception as e:
# Fallbacks in case Flask isn't available
logger.warning(f"Could not get path from Flask: {str(e)}")
# Try environment variable
db_path = os.environ.get('DATABASE_PATH', '/data/song_data.db')
logger.info(f"Using DATABASE_PATH from environment: {db_path}")
if not os.path.exists(db_path):
logger.warning(f"Database not found at {db_path}")
return False
try:
# Connect to the database
logger.info(f"Connecting to database at: {db_path}")
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check if tables exist
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='tag'")
tag_table_exists = cursor.fetchone() is not None
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='song_tag'")
song_tag_table_exists = cursor.fetchone() is not None
# Create tag table if it doesn't exist
if not tag_table_exists:
cursor.execute('''
CREATE TABLE tag (
id INTEGER PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
logger.info("Created table: tag")
else:
logger.info("Table already exists: tag")
# Create song_tag table if it doesn't exist
if not song_tag_table_exists:
cursor.execute('''
CREATE TABLE song_tag (
song_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (song_id, tag_id),
FOREIGN KEY (song_id) REFERENCES song (id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tag (id) ON DELETE CASCADE
)
''')
logger.info("Created table: song_tag")
else:
logger.info("Table already exists: song_tag")
# Commit changes and close connection
conn.commit()
conn.close()
logger.info("Migration add_tag_system completed successfully")
return True
except Exception as e:
logger.error(f"Migration add_tag_system failed: {str(e)}")
return False
if __name__ == "__main__":
# Set up logging when run directly
import logging
logging.basicConfig(level=logging.INFO)
run_migration()