bfd6771a9a
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.
92 lines
3.8 KiB
Python
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")
|