Add Alembic for database migrations and initial migration scripts
- Created alembic.ini configuration file for Alembic migrations. - Added README file for Alembic with a brief description. - Implemented env.py for Alembic to manage database migrations. - Created script.py.mako template for migration scripts. - Added initial migration script to create database tables. - Created a migration script to add initial plan and playlist data. - Updated database initialization to run Alembic migrations. - Enhanced credit service to automatically recharge user credits based on their plan. - Implemented delete_task method in scheduler service to remove scheduled tasks. - Updated scheduler API to reflect task deletion instead of cancellation. - Added CLI tool for managing database migrations. - Updated tests to cover new functionality for task deletion and credit recharge. - Updated pyproject.toml and lock files to include Alembic as a dependency.
This commit is contained in:
222
alembic/versions/7aa9892ceff3_initial_migration.py
Normal file
222
alembic/versions/7aa9892ceff3_initial_migration.py
Normal file
@@ -0,0 +1,222 @@
|
||||
"""Initial migration
|
||||
|
||||
Revision ID: 7aa9892ceff3
|
||||
Revises:
|
||||
Create Date: 2025-09-16 13:16:58.233360
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '7aa9892ceff3'
|
||||
down_revision: Union[str, Sequence[str], None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('plan',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('code', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('credits', sa.Integer(), nullable=False),
|
||||
sa.Column('max_credits', sa.Integer(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_plan_code'), 'plan', ['code'], unique=True)
|
||||
op.create_table('sound',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('filename', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('duration', sa.Integer(), nullable=False),
|
||||
sa.Column('size', sa.Integer(), nullable=False),
|
||||
sa.Column('hash', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('normalized_filename', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('normalized_duration', sa.Integer(), nullable=True),
|
||||
sa.Column('normalized_size', sa.Integer(), nullable=True),
|
||||
sa.Column('normalized_hash', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('thumbnail', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('play_count', sa.Integer(), nullable=False),
|
||||
sa.Column('is_normalized', sa.Boolean(), nullable=False),
|
||||
sa.Column('is_music', sa.Boolean(), nullable=False),
|
||||
sa.Column('is_deletable', sa.Boolean(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('hash', name='uq_sound_hash')
|
||||
)
|
||||
op.create_table('user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('plan_id', sa.Integer(), nullable=False),
|
||||
sa.Column('role', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('picture', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('password_hash', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=False),
|
||||
sa.Column('credits', sa.Integer(), nullable=False),
|
||||
sa.Column('api_token', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('api_token_expires_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('refresh_token_hash', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('refresh_token_expires_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['plan_id'], ['plan.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('api_token'),
|
||||
sa.UniqueConstraint('email')
|
||||
)
|
||||
op.create_table('credit_transaction',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('action_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('amount', sa.Integer(), nullable=False),
|
||||
sa.Column('balance_before', sa.Integer(), nullable=False),
|
||||
sa.Column('balance_after', sa.Integer(), nullable=False),
|
||||
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('success', sa.Boolean(), nullable=False),
|
||||
sa.Column('metadata_json', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('extraction',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('service', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('service_id', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('sound_id', sa.Integer(), nullable=True),
|
||||
sa.Column('url', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('track', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('artist', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('album', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('genre', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('error', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['sound_id'], ['sound.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('playlist',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('genre', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('is_main', sa.Boolean(), nullable=False),
|
||||
sa.Column('is_current', sa.Boolean(), nullable=False),
|
||||
sa.Column('is_deletable', sa.Boolean(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name')
|
||||
)
|
||||
op.create_table('scheduled_task',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
||||
sa.Column('task_type', sa.Enum('CREDIT_RECHARGE', 'PLAY_SOUND', 'PLAY_PLAYLIST', name='tasktype'), nullable=False),
|
||||
sa.Column('status', sa.Enum('PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'CANCELLED', name='taskstatus'), nullable=False),
|
||||
sa.Column('scheduled_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('timezone', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('recurrence_type', sa.Enum('NONE', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY', 'CRON', name='recurrencetype'), nullable=False),
|
||||
sa.Column('cron_expression', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('recurrence_count', sa.Integer(), nullable=True),
|
||||
sa.Column('executions_count', sa.Integer(), nullable=False),
|
||||
sa.Column('parameters', sa.JSON(), nullable=True),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('last_executed_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('next_execution_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('error_message', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=False),
|
||||
sa.Column('expires_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('sound_played',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('sound_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['sound_id'], ['sound.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('user_oauth',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('provider', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('provider_user_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('picture', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('provider', 'provider_user_id', name='uq_user_oauth_provider_user_id')
|
||||
)
|
||||
op.create_table('favorite',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('sound_id', sa.Integer(), nullable=True),
|
||||
sa.Column('playlist_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['playlist_id'], ['playlist.id'], ),
|
||||
sa.ForeignKeyConstraint(['sound_id'], ['sound.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('user_id', 'playlist_id', name='uq_favorite_user_playlist'),
|
||||
sa.UniqueConstraint('user_id', 'sound_id', name='uq_favorite_user_sound')
|
||||
)
|
||||
op.create_table('playlist_sound',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('playlist_id', sa.Integer(), nullable=False),
|
||||
sa.Column('sound_id', sa.Integer(), nullable=False),
|
||||
sa.Column('position', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['playlist_id'], ['playlist.id'], ),
|
||||
sa.ForeignKeyConstraint(['sound_id'], ['sound.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('playlist_id', 'position', name='uq_playlist_sound_playlist_position'),
|
||||
sa.UniqueConstraint('playlist_id', 'sound_id', name='uq_playlist_sound_playlist_sound')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('playlist_sound')
|
||||
op.drop_table('favorite')
|
||||
op.drop_table('user_oauth')
|
||||
op.drop_table('sound_played')
|
||||
op.drop_table('scheduled_task')
|
||||
op.drop_table('playlist')
|
||||
op.drop_table('extraction')
|
||||
op.drop_table('credit_transaction')
|
||||
op.drop_table('user')
|
||||
op.drop_table('sound')
|
||||
op.drop_index(op.f('ix_plan_code'), table_name='plan')
|
||||
op.drop_table('plan')
|
||||
# ### end Alembic commands ###
|
||||
Reference in New Issue
Block a user