"""endpoint commands queue + cached MTConnect state. Adds the endpointcommands queue (one WebUI-issued, server-allowlisted action per row, claimed and finalized by the agent long-poll) and two cached MTConnect run-state columns on endpointstates so the WebUI toggle can show a real state between command polls. Idempotent: create_table_if_missing guards the new table, and the column adds check the live schema first so a db.create_all() site (which already built the columns) re-runs clean on strict MySQL 8. """ from alembic import op from shopdb.plugins.alembic_template import ( create_index_if_missing, create_table_if_missing, ) import sqlalchemy as sa # revision identifiers, used by Alembic. revision = 'endpoints0002commands' down_revision = 'endpoints0001baseline' branch_labels = None depends_on = None def _has_column(table_name, column_name): from sqlalchemy import inspect inspector = inspect(op.get_bind()) if not inspector.has_table(table_name): return False return any(col['name'] == column_name for col in inspector.get_columns(table_name)) def upgrade(): if not _has_column('endpointstates', 'mtconnectrunning'): op.add_column('endpointstates', sa.Column('mtconnectrunning', sa.Boolean(), nullable=True)) if not _has_column('endpointstates', 'mtconnectcheckedat'): op.add_column('endpointstates', sa.Column('mtconnectcheckedat', sa.DateTime(), nullable=True)) create_table_if_missing( 'endpointcommands', sa.Column('endpointcommandid', sa.Integer(), nullable=False), sa.Column('assetid', sa.Integer(), nullable=False), sa.Column('action', sa.String(length=64), nullable=False), sa.Column('status', sa.String(length=20), nullable=False), sa.Column('requestedby', sa.Integer(), nullable=True), sa.Column('resultmessage', sa.Text(), nullable=True), sa.Column('resultjson', sa.Text(length=16777215), nullable=True), sa.Column('createdat', sa.DateTime(), nullable=False), sa.Column('claimedat', sa.DateTime(), nullable=True), sa.Column('completedat', sa.DateTime(), nullable=True), sa.ForeignKeyConstraint(['assetid'], ['assets.assetid'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('endpointcommandid'), ) create_index_if_missing('ix_endpointcommands_assetid', 'endpointcommands', ['assetid']) create_index_if_missing('ix_endpointcommands_status', 'endpointcommands', ['status']) create_index_if_missing('idx_endpointcommands_assetstatus', 'endpointcommands', ['assetid', 'status']) def downgrade(): op.drop_index('idx_endpointcommands_assetstatus', table_name='endpointcommands') op.drop_index('ix_endpointcommands_status', table_name='endpointcommands') op.drop_index('ix_endpointcommands_assetid', table_name='endpointcommands') op.drop_table('endpointcommands') if _has_column('endpointstates', 'mtconnectcheckedat'): op.drop_column('endpointstates', 'mtconnectcheckedat') if _has_column('endpointstates', 'mtconnectrunning'): op.drop_column('endpointstates', 'mtconnectrunning')