Initial MinerOps working checkpoint

This commit is contained in:
MinerOps Admin committed 2026-07-22 20:56:41 +00:00
commit 4c854374e0
103 files changed
+2400932

No files matched your search

+173
View File
@@ -0,0 +1,173 @@
"""create initial foundation tables
Revision ID: 0001_foundation
Revises:
Create Date: 2026-07-08
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
revision: str = "0001_foundation"
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:
op.execute('CREATE EXTENSION IF NOT EXISTS "pgcrypto";')
op.create_table(
"roles",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("name", sa.Text(), nullable=False, unique=True),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
)
op.create_table(
"permissions",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("key", sa.Text(), nullable=False, unique=True),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
)
op.create_table(
"role_permissions",
sa.Column("role_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("roles.id", ondelete="CASCADE"), nullable=False),
sa.Column("permission_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("permissions.id", ondelete="CASCADE"), nullable=False),
sa.PrimaryKeyConstraint("role_id", "permission_id"),
)
op.create_table(
"users",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("username", sa.Text(), nullable=False, unique=True),
sa.Column("display_name", sa.Text(), nullable=False),
sa.Column("email", sa.Text(), nullable=True),
sa.Column("password_hash", sa.Text(), nullable=False),
sa.Column("role_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("roles.id"), nullable=False),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.text("true")),
sa.Column("last_login_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
)
op.create_index("idx_users_role_id", "users", ["role_id"])
op.create_index("idx_users_is_active", "users", ["is_active"])
op.create_table(
"sites",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("name", sa.Text(), nullable=False, unique=True),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
)
op.create_table(
"rooms",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("site_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("sites.id", ondelete="CASCADE"), nullable=False),
sa.Column("name", sa.Text(), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("sort_order", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.UniqueConstraint("site_id", "name", name="uq_rooms_site_name"),
)
op.create_index("idx_rooms_site_id", "rooms", ["site_id"])
op.create_table(
"app_settings",
sa.Column("key", sa.Text(), primary_key=True),
sa.Column("value_json", postgresql.JSONB(), nullable=False),
sa.Column("is_sensitive", sa.Boolean(), nullable=False, server_default=sa.text("false")),
sa.Column("updated_by_user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
)
op.execute("""
INSERT INTO roles (name, description) VALUES
('Admin', 'Full system administrator'),
('Technician', 'Technician user for repair and operational tasks'),
('Viewer', 'Read-only dashboard and miner viewer'),
('Auditor', 'Read-only audit and reporting user');
""")
op.execute("""
INSERT INTO permissions (key, description) VALUES
('admin.access', 'Access administrator features'),
('users.manage', 'Create and manage users'),
('miners.view', 'View miners'),
('miners.create', 'Create miners'),
('miners.update', 'Update miners'),
('miners.delete', 'Delete miners'),
('scan_ranges.manage', 'Manage scan ranges'),
('scans.run', 'Run scans'),
('credentials.manage', 'Manage miner credentials'),
('backups.create', 'Create backups'),
('exports.create', 'Create exports'),
('audit.view', 'View audit logs');
""")
op.execute("""
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r
CROSS JOIN permissions p
WHERE r.name = 'Admin';
""")
op.execute("""
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r
JOIN permissions p ON p.key IN (
'miners.view',
'miners.update',
'scans.run',
'exports.create'
)
WHERE r.name = 'Technician';
""")
op.execute("""
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r
JOIN permissions p ON p.key IN ('miners.view')
WHERE r.name = 'Viewer';
""")
op.execute("""
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r
JOIN permissions p ON p.key IN ('miners.view', 'audit.view', 'exports.create')
WHERE r.name = 'Auditor';
""")
op.execute("""
INSERT INTO sites (name, description)
VALUES ('Main Mining Site', 'Default mining site');
""")
def downgrade() -> None:
op.drop_table("app_settings")
op.drop_index("idx_rooms_site_id", table_name="rooms")
op.drop_table("rooms")
op.drop_table("sites")
op.drop_index("idx_users_is_active", table_name="users")
op.drop_index("idx_users_role_id", table_name="users")
op.drop_table("users")
op.drop_table("role_permissions")
op.drop_table("permissions")
op.drop_table("roles")
@@ -0,0 +1,50 @@
"""create audit logs table
Revision ID: 0002_audit_logs
Revises: 0001_foundation
Create Date: 2026-07-08
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
revision: str = "0002_audit_logs"
down_revision: Union[str, None] = "0001_foundation"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
"audit_logs",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("actor_user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True),
sa.Column("action", sa.Text(), nullable=False),
sa.Column("entity_type", sa.Text(), nullable=False),
sa.Column("entity_id", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("old_value_json", postgresql.JSONB(), nullable=True),
sa.Column("new_value_json", postgresql.JSONB(), nullable=True),
sa.Column("related_miner_asset_id", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("ip_address", postgresql.INET(), nullable=True),
sa.Column("user_agent", sa.Text(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
)
op.create_index("idx_audit_logs_actor_user_id", "audit_logs", ["actor_user_id"])
op.create_index("idx_audit_logs_action", "audit_logs", ["action"])
op.create_index("idx_audit_logs_entity", "audit_logs", ["entity_type", "entity_id"])
op.create_index("idx_audit_logs_related_miner", "audit_logs", ["related_miner_asset_id"])
op.create_index("idx_audit_logs_created_at", "audit_logs", ["created_at"])
def downgrade() -> None:
op.drop_index("idx_audit_logs_created_at", table_name="audit_logs")
op.drop_index("idx_audit_logs_related_miner", table_name="audit_logs")
op.drop_index("idx_audit_logs_entity", table_name="audit_logs")
op.drop_index("idx_audit_logs_action", table_name="audit_logs")
op.drop_index("idx_audit_logs_actor_user_id", table_name="audit_logs")
op.drop_table("audit_logs")
@@ -0,0 +1,62 @@
"""create scan ranges table
Revision ID: 0003_scan_ranges
Revises: 0002_audit_logs
Create Date: 2026-07-08
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
revision: str = "0003_scan_ranges"
down_revision: Union[str, None] = "0002_audit_logs"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
"scan_ranges",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("room_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("rooms.id", ondelete="SET NULL"), nullable=True),
sa.Column("name", sa.Text(), nullable=False),
sa.Column("range_type", sa.Text(), nullable=False, server_default=sa.text("'cidr'")),
sa.Column("cidr", sa.Text(), nullable=True),
sa.Column("start_ip", postgresql.INET(), nullable=True),
sa.Column("end_ip", postgresql.INET(), nullable=True),
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.text("true")),
sa.Column("scan_interval_seconds", sa.Integer(), nullable=False, server_default=sa.text("900")),
sa.Column("timeout_ms", sa.Integer(), nullable=False, server_default=sa.text("3000")),
sa.Column("retry_limit", sa.Integer(), nullable=False, server_default=sa.text("1")),
sa.Column("parallelism_limit", sa.Integer(), nullable=False, server_default=sa.text("10")),
sa.Column("stagger_ms", sa.Integer(), nullable=False, server_default=sa.text("250")),
sa.Column("api_port", sa.Integer(), nullable=False, server_default=sa.text("4028")),
sa.Column("last_scan_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("next_scan_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_by_user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.CheckConstraint(
"""
(range_type = 'cidr' AND cidr IS NOT NULL)
OR
(range_type = 'start_end' AND start_ip IS NOT NULL AND end_ip IS NOT NULL)
""",
name="scan_range_has_valid_range",
),
)
op.create_index("idx_scan_ranges_room_id", "scan_ranges", ["room_id"])
op.create_index("idx_scan_ranges_enabled", "scan_ranges", ["enabled"])
op.create_index("idx_scan_ranges_next_scan_at", "scan_ranges", ["next_scan_at"])
def downgrade() -> None:
op.drop_index("idx_scan_ranges_next_scan_at", table_name="scan_ranges")
op.drop_index("idx_scan_ranges_enabled", table_name="scan_ranges")
op.drop_index("idx_scan_ranges_room_id", table_name="scan_ranges")
op.drop_table("scan_ranges")
@@ -0,0 +1,78 @@
"""create scan run and scan result tables
Revision ID: 0004_scan_runs_results
Revises: 0003_scan_ranges
Create Date: 2026-07-08
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
revision: str = "0004_scan_runs_results"
down_revision: Union[str, None] = "0003_scan_ranges"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
"scan_runs",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("scan_range_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("scan_ranges.id", ondelete="SET NULL"), nullable=True),
sa.Column("triggered_by", sa.Text(), nullable=False, server_default=sa.text("'manual'")),
sa.Column("triggered_by_user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("status", sa.Text(), nullable=False, server_default=sa.text("'running'")),
sa.Column("total_ips", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column("scanned_ips", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column("miners_found", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column("errors_count", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column("notes", sa.Text(), nullable=True),
)
op.create_index("idx_scan_runs_scan_range_id", "scan_runs", ["scan_range_id"])
op.create_index("idx_scan_runs_started_at", "scan_runs", ["started_at"])
op.create_index("idx_scan_runs_status", "scan_runs", ["status"])
op.create_table(
"scan_results",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("scan_run_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("scan_runs.id", ondelete="CASCADE"), nullable=False),
sa.Column("ip_address", postgresql.INET(), nullable=False),
sa.Column("port", sa.Integer(), nullable=False, server_default=sa.text("4028")),
sa.Column("detected", sa.Boolean(), nullable=False, server_default=sa.text("false")),
sa.Column("mac_address", sa.Text(), nullable=True),
sa.Column("response_ms", sa.Integer(), nullable=True),
sa.Column("status", sa.Text(), nullable=False, server_default=sa.text("'unknown'")),
sa.Column("error_message", sa.Text(), nullable=True),
sa.Column("raw_summary_json", postgresql.JSONB(), nullable=True),
sa.Column("raw_version_json", postgresql.JSONB(), nullable=True),
sa.Column("raw_pools_json", postgresql.JSONB(), nullable=True),
sa.Column("raw_error_json", postgresql.JSONB(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
)
op.create_index("idx_scan_results_scan_run_id", "scan_results", ["scan_run_id"])
op.create_index("idx_scan_results_ip_address", "scan_results", ["ip_address"])
op.create_index("idx_scan_results_detected", "scan_results", ["detected"])
op.create_index("idx_scan_results_mac_address", "scan_results", [sa.text("lower(mac_address)")])
op.create_index("idx_scan_results_created_at", "scan_results", ["created_at"])
def downgrade() -> None:
op.drop_index("idx_scan_results_created_at", table_name="scan_results")
op.drop_index("idx_scan_results_mac_address", table_name="scan_results")
op.drop_index("idx_scan_results_detected", table_name="scan_results")
op.drop_index("idx_scan_results_ip_address", table_name="scan_results")
op.drop_index("idx_scan_results_scan_run_id", table_name="scan_results")
op.drop_table("scan_results")
op.drop_index("idx_scan_runs_status", table_name="scan_runs")
op.drop_index("idx_scan_runs_started_at", table_name="scan_runs")
op.drop_index("idx_scan_runs_scan_range_id", table_name="scan_runs")
op.drop_table("scan_runs")
@@ -0,0 +1,155 @@
"""create miner inventory tables
Revision ID: 0005_miner_inventory
Revises: 0004_scan_runs_results
Create Date: 2026-07-08
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
revision: str = "0005_miner_inventory"
down_revision: Union[str, None] = "0004_scan_runs_results"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
"miners",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("site_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("sites.id", ondelete="SET NULL"), nullable=True),
sa.Column("room_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("rooms.id", ondelete="SET NULL"), nullable=True),
sa.Column("asset_tag", sa.Text(), nullable=True),
sa.Column("current_mac", sa.Text(), nullable=True),
sa.Column("current_ip", postgresql.INET(), nullable=True),
sa.Column("hostname", sa.Text(), nullable=True),
sa.Column("miner_sn", sa.Text(), nullable=True),
sa.Column("power_sn", sa.Text(), nullable=True),
sa.Column("model", sa.Text(), nullable=True),
sa.Column("miner_type", sa.Text(), nullable=True),
sa.Column("api_version", sa.Text(), nullable=True),
sa.Column("firmware_version", sa.Text(), nullable=True),
sa.Column("platform", sa.Text(), nullable=True),
sa.Column("chip", sa.Text(), nullable=True),
sa.Column("status", sa.Text(), nullable=False, server_default=sa.text("'discovered'")),
sa.Column("notes", sa.Text(), nullable=True),
sa.Column("first_seen_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
)
op.create_index("idx_miners_site_id", "miners", ["site_id"])
op.create_index("idx_miners_room_id", "miners", ["room_id"])
op.create_index("idx_miners_current_ip", "miners", ["current_ip"])
op.create_index("idx_miners_current_mac", "miners", ["current_mac"])
op.create_index("idx_miners_miner_sn", "miners", ["miner_sn"])
op.create_index("idx_miners_status", "miners", ["status"])
op.create_index("idx_miners_last_seen_at", "miners", ["last_seen_at"])
op.create_table(
"miner_network_identities",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("miner_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("miners.id", ondelete="CASCADE"), nullable=False),
sa.Column("mac_address", sa.Text(), nullable=False),
sa.Column("ip_address", postgresql.INET(), nullable=True),
sa.Column("hostname", sa.Text(), nullable=True),
sa.Column("source", sa.Text(), nullable=False, server_default=sa.text("'scanner'")),
sa.Column("is_current", sa.Boolean(), nullable=False, server_default=sa.text("true")),
sa.Column("first_seen_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
)
op.create_index("idx_miner_network_identities_miner_id", "miner_network_identities", ["miner_id"])
op.create_index("idx_miner_network_identities_mac_address", "miner_network_identities", ["mac_address"])
op.create_index("idx_miner_network_identities_ip_address", "miner_network_identities", ["ip_address"])
op.create_index("idx_miner_network_identities_is_current", "miner_network_identities", ["is_current"])
op.create_table(
"miner_hashboards",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("miner_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("miners.id", ondelete="CASCADE"), nullable=False),
sa.Column("slot", sa.Integer(), nullable=False),
sa.Column("pcb_sn", sa.Text(), nullable=True),
sa.Column("chip_data", sa.Text(), nullable=True),
sa.Column("temperature_c", sa.Numeric(), nullable=True),
sa.Column("chip_frequency", sa.Numeric(), nullable=True),
sa.Column("mhs_av", sa.Numeric(), nullable=True),
sa.Column("mhs_1m", sa.Numeric(), nullable=True),
sa.Column("mhs_15m", sa.Numeric(), nullable=True),
sa.Column("hs_rt", sa.Numeric(), nullable=True),
sa.Column("factory_ghs", sa.Numeric(), nullable=True),
sa.Column("effective_chips", sa.Integer(), nullable=True),
sa.Column("upfreq_complete", sa.Integer(), nullable=True),
sa.Column("raw_json", postgresql.JSONB(), nullable=True),
sa.Column("first_seen_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.UniqueConstraint("miner_id", "slot", name="uq_miner_hashboards_miner_slot"),
)
op.create_index("idx_miner_hashboards_miner_id", "miner_hashboards", ["miner_id"])
op.create_index("idx_miner_hashboards_pcb_sn", "miner_hashboards", ["pcb_sn"])
op.create_table(
"miner_status_snapshots",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("miner_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("miners.id", ondelete="CASCADE"), nullable=False),
sa.Column("observed_ip", postgresql.INET(), nullable=True),
sa.Column("observed_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("mhs_av", sa.Numeric(), nullable=True),
sa.Column("mhs_1m", sa.Numeric(), nullable=True),
sa.Column("mhs_15m", sa.Numeric(), nullable=True),
sa.Column("hs_rt", sa.Numeric(), nullable=True),
sa.Column("power_w", sa.Numeric(), nullable=True),
sa.Column("power_rate_w_per_th", sa.Numeric(), nullable=True),
sa.Column("env_temp_c", sa.Numeric(), nullable=True),
sa.Column("chip_temp_min_c", sa.Numeric(), nullable=True),
sa.Column("chip_temp_max_c", sa.Numeric(), nullable=True),
sa.Column("chip_temp_avg_c", sa.Numeric(), nullable=True),
sa.Column("fan_speed_in_rpm", sa.Integer(), nullable=True),
sa.Column("fan_speed_out_rpm", sa.Integer(), nullable=True),
sa.Column("uptime_seconds", sa.BigInteger(), nullable=True),
sa.Column("hash_stable", sa.Boolean(), nullable=True),
sa.Column("pool_rejected_percent", sa.Numeric(), nullable=True),
sa.Column("raw_summary_json", postgresql.JSONB(), nullable=True),
sa.Column("raw_version_json", postgresql.JSONB(), nullable=True),
sa.Column("raw_miner_info_json", postgresql.JSONB(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
)
op.create_index("idx_miner_status_snapshots_miner_id", "miner_status_snapshots", ["miner_id"])
op.create_index("idx_miner_status_snapshots_observed_at", "miner_status_snapshots", ["observed_at"])
def downgrade() -> None:
op.drop_index("idx_miner_status_snapshots_observed_at", table_name="miner_status_snapshots")
op.drop_index("idx_miner_status_snapshots_miner_id", table_name="miner_status_snapshots")
op.drop_table("miner_status_snapshots")
op.drop_index("idx_miner_hashboards_pcb_sn", table_name="miner_hashboards")
op.drop_index("idx_miner_hashboards_miner_id", table_name="miner_hashboards")
op.drop_table("miner_hashboards")
op.drop_index("idx_miner_network_identities_is_current", table_name="miner_network_identities")
op.drop_index("idx_miner_network_identities_ip_address", table_name="miner_network_identities")
op.drop_index("idx_miner_network_identities_mac_address", table_name="miner_network_identities")
op.drop_index("idx_miner_network_identities_miner_id", table_name="miner_network_identities")
op.drop_table("miner_network_identities")
op.drop_index("idx_miners_last_seen_at", table_name="miners")
op.drop_index("idx_miners_status", table_name="miners")
op.drop_index("idx_miners_miner_sn", table_name="miners")
op.drop_index("idx_miners_current_mac", table_name="miners")
op.drop_index("idx_miners_current_ip", table_name="miners")
op.drop_index("idx_miners_room_id", table_name="miners")
op.drop_index("idx_miners_site_id", table_name="miners")
op.drop_table("miners")