Initial clean commit
This commit is contained in:
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user