Files
Mist/backend/src/mist/db/migrations/versions/0001_initial.py
T
goddard bfd6771a9a
admin-web / build (push) Successful in 22s
backend / test (push) Failing after 52s
mistpipe / test (push) Successful in 10s
admin-web / build-and-push (push) Failing after 5s
backend / build-and-push (push) Has been skipped
Initial Mist scaffold
Successor to the Josh Steam prototypes. Single-VM Docker Compose stack with
the load-bearing core/ logic ported from JoshSteam CDN with bug fixes.

Contents:
- backend/  FastAPI + Celery (same image, two entrypoints)
            core/  hdiff, librsync, chain_replay, manifest, compression,
                   discord, steam, unrealpak, paths
            api/   auth, catalog, admin, builds (skeletons) + downloads (real)
            worker/  Celery factory replacing the missing prototype Tasks/__init__.py
            db/    SQLAlchemy models + Alembic initial migration
- admin-web/  SvelteKit + Tailwind skeleton
- client/    Tauri 2 + Svelte skeleton (Mist placeholder UI)
- mistpipe/  click-based admin CLI with subcommand stubs
- docs/      ARCHITECTURE, DECISIONS (9 ADRs), RUNBOOK
- docker-compose.yml + dev overlay + .github/workflows

Bugs fixed during port:
- Routes/download.py:2 stray backslash on import line
- Utils/celery.py inspect.reserved() missing parens + double active() typo
- Hardcoded OneDrive/Desktop paths replaced with pydantic-settings config
- Discord webhook URL + RabbitMQ password moved to env vars
- Missing Tasks/__init__.py reconstructed as worker/__init__.py

Out of scope for this commit: route bodies, UI screens, mistpipe subcommand
bodies, real image builds.
2026-06-07 19:39:25 -04:00

92 lines
3.8 KiB
Python

"""Initial schema — users, games, versions, build_jobs.
Revision ID: 0001
Revises:
Create Date: 2026-06-07
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "0001"
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
"users",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("username", sa.String(64), nullable=False, unique=True),
sa.Column("password_hash", sa.String(255), nullable=False),
sa.Column("is_admin", sa.Boolean(), nullable=False, server_default=sa.text("false")),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
)
op.create_index("ix_users_username", "users", ["username"], unique=True)
op.create_table(
"games",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("title", sa.String(255), nullable=False, unique=True),
sa.Column("app_id", sa.Integer(), nullable=True),
sa.Column("description_override", sa.Text(), nullable=True),
sa.Column("header_image_override", sa.String(1024), nullable=True),
sa.Column("is_private", sa.Boolean(), nullable=False, server_default=sa.text("false")),
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
)
op.create_index("ix_games_title", "games", ["title"], unique=True)
op.create_index("ix_games_is_private", "games", ["is_private"])
op.create_index("ix_games_deleted_at", "games", ["deleted_at"])
op.create_table(
"versions",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("game_id", sa.Integer(), sa.ForeignKey("games.id", ondelete="CASCADE"), nullable=False),
sa.Column("version_string", sa.String(64), nullable=False),
sa.Column("ordinal", sa.Integer(), nullable=False),
sa.Column("manifest_hash", sa.String(64), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
sa.UniqueConstraint("game_id", "version_string", name="uq_versions_game_id_version_string"),
)
op.create_index("ix_versions_game_id_ordinal", "versions", ["game_id", "ordinal"])
build_job_kind = sa.Enum(
"import_new_game",
"push_update",
"generate_direct_update",
"generate_indirect_update",
"prepare_full_game",
name="buildjobkind",
)
build_job_state = sa.Enum("pending", "running", "success", "failure", name="buildjobstate")
op.create_table(
"build_jobs",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("celery_task_id", sa.String(128), nullable=True),
sa.Column("game_id", sa.Integer(), sa.ForeignKey("games.id", ondelete="SET NULL"), nullable=True),
sa.Column("kind", build_job_kind, nullable=False),
sa.Column("state", build_job_state, nullable=False, server_default="pending"),
sa.Column("detail", sa.Text(), nullable=True),
sa.Column("error", sa.Text(), nullable=True),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
)
op.create_index("ix_build_jobs_celery_task_id", "build_jobs", ["celery_task_id"])
def downgrade() -> None:
op.drop_table("build_jobs")
op.execute("DROP TYPE IF EXISTS buildjobstate")
op.execute("DROP TYPE IF EXISTS buildjobkind")
op.drop_table("versions")
op.drop_table("games")
op.drop_table("users")