520be53901
- Add proper Alembic initial migration (0001_initial_schema.py) - Migrate refresh tokens from JSON file to SQLite (RefreshTokenTable) - Remove Neko-Sama provider entirely (redirects to Gupy, not a host) - Fix provider health check always showing UNKNOWN - Run check_all_health() on startup - Fix POST /providers/health/check background task bug - Add HTMX refresh after manual health check trigger - Fix anime search relevance scoring with MIN_RELEVANCE_THRESHOLD=0.5 - Replace bare 'except:' with 'except Exception:' across codebase - Add Playwright E2E test suite (12 tests, auth setup, helpers) - Fix toast container blocking clicks via pointer-events: none - Remove obsolete Jest/Vite test files and config - Clean up obsolete test_watchlist scripts - Update sonarr model comment for active providers
210 lines
11 KiB
Python
210 lines
11 KiB
Python
"""Initial schema
|
|
|
|
Revision ID: 0001_initial_schema
|
|
Revises:
|
|
Create Date: 2026-05-12 08:00:00.000000
|
|
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision: str = '0001_initial_schema'
|
|
down_revision: Union[str, None] = None
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# ### commands auto generated by Alembic - please adjust! ###
|
|
op.create_table(
|
|
'users',
|
|
sa.Column('username', sa.String(), nullable=False),
|
|
sa.Column('email', sa.String(), nullable=True),
|
|
sa.Column('full_name', sa.String(), nullable=True),
|
|
sa.Column('is_active', sa.Boolean(), nullable=False),
|
|
sa.Column('is_admin', sa.Boolean(), nullable=False),
|
|
sa.Column('id', sa.String(), nullable=False),
|
|
sa.Column('hashed_password', sa.String(), nullable=False),
|
|
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
sa.Column('last_login', sa.DateTime(), nullable=True),
|
|
sa.PrimaryKeyConstraint('id'),
|
|
sa.UniqueConstraint('username')
|
|
)
|
|
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=False)
|
|
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
|
|
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
|
|
|
|
op.create_table(
|
|
'app_settings',
|
|
sa.Column('default_lang', sa.String(), nullable=False),
|
|
sa.Column('theme', sa.String(), nullable=False),
|
|
sa.Column('disabled_providers_json', sa.String(), nullable=False),
|
|
sa.Column('recommendations_filter', sa.String(), nullable=False),
|
|
sa.Column('releases_filter', sa.String(), nullable=False),
|
|
sa.Column('anime_enabled', sa.Boolean(), nullable=False),
|
|
sa.Column('series_enabled', sa.Boolean(), nullable=False),
|
|
sa.Column('download_dir', sa.String(), nullable=False),
|
|
sa.Column('id', sa.String(), nullable=False),
|
|
sa.Column('user_id', sa.String(), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
sa.PrimaryKeyConstraint('id'),
|
|
sa.UniqueConstraint('user_id')
|
|
)
|
|
op.create_index(op.f('ix_app_settings_id'), 'app_settings', ['id'], unique=False)
|
|
op.create_index(op.f('ix_app_settings_user_id'), 'app_settings', ['user_id'], unique=True)
|
|
|
|
op.create_table(
|
|
'favorites',
|
|
sa.Column('anime_id', sa.String(), nullable=False),
|
|
sa.Column('title', sa.String(), nullable=False),
|
|
sa.Column('url', sa.String(), nullable=False),
|
|
sa.Column('provider', sa.String(), nullable=False),
|
|
sa.Column('poster_url', sa.String(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
|
sa.Column('id', sa.String(), nullable=False),
|
|
sa.Column('user_id', sa.String(), nullable=False),
|
|
sa.Column('metadata_json', sa.String(), nullable=True),
|
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
op.create_index(op.f('ix_favorites_anime_id'), 'favorites', ['anime_id'], unique=False)
|
|
op.create_index(op.f('ix_favorites_id'), 'favorites', ['id'], unique=False)
|
|
op.create_index(op.f('ix_favorites_title'), 'favorites', ['title'], unique=False)
|
|
op.create_index(op.f('ix_favorites_user_id'), 'favorites', ['user_id'], unique=False)
|
|
|
|
op.create_table(
|
|
'refresh_tokens',
|
|
sa.Column('token_id', sa.String(), nullable=False),
|
|
sa.Column('username', sa.String(), nullable=False),
|
|
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
sa.Column('expires_at', sa.DateTime(), nullable=True),
|
|
sa.Column('revoked', sa.Boolean(), nullable=False),
|
|
sa.Column('revoked_at', sa.DateTime(), nullable=True),
|
|
sa.Column('id', sa.String(), nullable=False),
|
|
sa.PrimaryKeyConstraint('id'),
|
|
sa.UniqueConstraint('token_id')
|
|
)
|
|
op.create_index(op.f('ix_refresh_tokens_id'), 'refresh_tokens', ['id'], unique=False)
|
|
op.create_index(op.f('ix_refresh_tokens_token_id'), 'refresh_tokens', ['token_id'], unique=True)
|
|
op.create_index(op.f('ix_refresh_tokens_username'), 'refresh_tokens', ['username'], unique=False)
|
|
|
|
op.create_table(
|
|
'sonarr_config',
|
|
sa.Column('webhook_enabled', sa.Boolean(), nullable=False),
|
|
sa.Column('webhook_secret', sa.String(), nullable=True),
|
|
sa.Column('auto_download_enabled', sa.Boolean(), nullable=False),
|
|
sa.Column('default_language', sa.String(), nullable=False),
|
|
sa.Column('default_quality', sa.String(), nullable=True),
|
|
sa.Column('default_provider', sa.String(), nullable=False),
|
|
sa.Column('verify_hmac', sa.Boolean(), nullable=False),
|
|
sa.Column('log_webhooks', sa.Boolean(), nullable=False),
|
|
sa.Column('id', sa.String(), nullable=False),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
op.create_index(op.f('ix_sonarr_config_id'), 'sonarr_config', ['id'], unique=False)
|
|
|
|
op.create_table(
|
|
'sonarr_mappings',
|
|
sa.Column('sonarr_series_id', sa.Integer(), nullable=False),
|
|
sa.Column('sonarr_title', sa.String(), nullable=False),
|
|
sa.Column('anime_provider', sa.String(), nullable=False),
|
|
sa.Column('anime_url', sa.String(), nullable=False),
|
|
sa.Column('anime_title', sa.String(), nullable=False),
|
|
sa.Column('lang', sa.String(), nullable=False),
|
|
sa.Column('quality_preference', sa.String(), nullable=True),
|
|
sa.Column('auto_download', sa.Boolean(), nullable=False),
|
|
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
|
sa.Column('id', sa.String(), nullable=False),
|
|
sa.Column('user_id', sa.String(), nullable=False),
|
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
sa.PrimaryKeyConstraint('id'),
|
|
sa.UniqueConstraint('sonarr_series_id')
|
|
)
|
|
op.create_index(op.f('ix_sonarr_mappings_id'), 'sonarr_mappings', ['id'], unique=False)
|
|
op.create_index(op.f('ix_sonarr_mappings_sonarr_series_id'), 'sonarr_mappings', ['sonarr_series_id'], unique=True)
|
|
op.create_index(op.f('ix_sonarr_mappings_user_id'), 'sonarr_mappings', ['user_id'], unique=False)
|
|
|
|
op.create_table(
|
|
'watchlist_items',
|
|
sa.Column('anime_title', sa.String(), nullable=False),
|
|
sa.Column('anime_url', sa.String(), nullable=False),
|
|
sa.Column('provider_id', sa.String(), nullable=False),
|
|
sa.Column('lang', sa.String(), nullable=False),
|
|
sa.Column('last_checked', sa.DateTime(), nullable=True),
|
|
sa.Column('last_episode_downloaded', sa.Integer(), nullable=False),
|
|
sa.Column('total_episodes', sa.Integer(), nullable=True),
|
|
sa.Column('auto_download', sa.Boolean(), nullable=False),
|
|
sa.Column('quality_preference', sa.String(), nullable=False),
|
|
sa.Column('status', sa.String(), nullable=False),
|
|
sa.Column('poster_image', sa.String(), nullable=True),
|
|
sa.Column('cover_image', sa.String(), nullable=True),
|
|
sa.Column('synopsis', sa.String(), nullable=True),
|
|
sa.Column('genres_json', sa.String(), nullable=True),
|
|
sa.Column('added_at', sa.DateTime(), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
|
sa.Column('id', sa.String(), nullable=False),
|
|
sa.Column('user_id', sa.String(), nullable=False),
|
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
op.create_index(op.f('ix_watchlist_items_anime_title'), 'watchlist_items', ['anime_title'], unique=False)
|
|
op.create_index(op.f('ix_watchlist_items_id'), 'watchlist_items', ['id'], unique=False)
|
|
op.create_index(op.f('ix_watchlist_items_user_id'), 'watchlist_items', ['user_id'], unique=False)
|
|
|
|
op.create_table(
|
|
'watchlist_settings',
|
|
sa.Column('check_interval_hours', sa.Integer(), nullable=False),
|
|
sa.Column('auto_download_enabled', sa.Boolean(), nullable=False),
|
|
sa.Column('max_concurrent_auto_downloads', sa.Integer(), nullable=False),
|
|
sa.Column('notify_on_new_episodes', sa.Boolean(), nullable=False),
|
|
sa.Column('include_completed_anime', sa.Boolean(), nullable=False),
|
|
sa.Column('id', sa.String(), nullable=False),
|
|
sa.Column('user_id', sa.String(), nullable=False),
|
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
op.create_index(op.f('ix_watchlist_settings_id'), 'watchlist_settings', ['id'], unique=False)
|
|
op.create_index(op.f('ix_watchlist_settings_user_id'), 'watchlist_settings', ['user_id'], unique=False)
|
|
# ### end Alembic commands ###
|
|
|
|
|
|
def downgrade() -> None:
|
|
# ### commands auto generated by Alembic - please adjust! ###
|
|
op.drop_index(op.f('ix_watchlist_settings_user_id'), table_name='watchlist_settings')
|
|
op.drop_index(op.f('ix_watchlist_settings_id'), table_name='watchlist_settings')
|
|
op.drop_table('watchlist_settings')
|
|
op.drop_index(op.f('ix_watchlist_items_user_id'), table_name='watchlist_items')
|
|
op.drop_index(op.f('ix_watchlist_items_id'), table_name='watchlist_items')
|
|
op.drop_index(op.f('ix_watchlist_items_anime_title'), table_name='watchlist_items')
|
|
op.drop_table('watchlist_items')
|
|
op.drop_index(op.f('ix_sonarr_mappings_user_id'), table_name='sonarr_mappings')
|
|
op.drop_index(op.f('ix_sonarr_mappings_sonarr_series_id'), table_name='sonarr_mappings')
|
|
op.drop_index(op.f('ix_sonarr_mappings_id'), table_name='sonarr_mappings')
|
|
op.drop_table('sonarr_mappings')
|
|
op.drop_index(op.f('ix_sonarr_config_id'), table_name='sonarr_config')
|
|
op.drop_table('sonarr_config')
|
|
op.drop_index(op.f('ix_refresh_tokens_username'), table_name='refresh_tokens')
|
|
op.drop_index(op.f('ix_refresh_tokens_token_id'), table_name='refresh_tokens')
|
|
op.drop_index(op.f('ix_refresh_tokens_id'), table_name='refresh_tokens')
|
|
op.drop_table('refresh_tokens')
|
|
op.drop_index(op.f('ix_favorites_user_id'), table_name='favorites')
|
|
op.drop_index(op.f('ix_favorites_title'), table_name='favorites')
|
|
op.drop_index(op.f('ix_favorites_id'), table_name='favorites')
|
|
op.drop_index(op.f('ix_favorites_anime_id'), table_name='favorites')
|
|
op.drop_table('favorites')
|
|
op.drop_index(op.f('ix_app_settings_user_id'), table_name='app_settings')
|
|
op.drop_index(op.f('ix_app_settings_id'), table_name='app_settings')
|
|
op.drop_table('app_settings')
|
|
op.drop_index(op.f('ix_users_username'), table_name='users')
|
|
op.drop_index(op.f('ix_users_id'), table_name='users')
|
|
op.drop_index(op.f('ix_users_email'), table_name='users')
|
|
op.drop_table('users')
|
|
# ### end Alembic commands ###
|