From ef372cecf3613b287d6b7da6c349eed0847d98ee Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 6 Apr 2026 18:07:49 -0400 Subject: [PATCH] torrent security: opt-in gate + visibility gating to prevent accidental seeding - Add torrent_opt_in boolean to mps_product (default false) - Gate all torrent seeding (upload trigger, backfill) on torrent_opt_in AND visibility==1 - Suppress magnet/torrent display for all users when product is private or unlisted - Add permanence warning and explicit consent checkbox on product edit page - backfill_status endpoint counts only opted-in public products - Alembic migration: 0ee5654cfe7d --- make_post_sell/models/product.py | 8 +++- ...dd_torrent_opt_in_column_to_mps_product.py | 37 +++++++++++++++ make_post_sell/templates/product_edit.j2 | 31 ++++++++++--- make_post_sell/views/content.py | 14 +++++- make_post_sell/views/product.py | 45 +++++++++++++++++-- make_post_sell/views/shop.py | 11 ++++- 6 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 make_post_sell/scripts/alembic/versions/0ee5654cfe7d_add_torrent_opt_in_column_to_mps_product.py diff --git a/make_post_sell/models/product.py b/make_post_sell/models/product.py index f223ffe..99975b6 100644 --- a/make_post_sell/models/product.py +++ b/make_post_sell/models/product.py @@ -109,12 +109,18 @@ class Product(RBase, Base): total_file_bytes = Column(BigInteger, nullable=False, default=0) - # Optional magnet link for BitTorrent distribution (set when shop.torrent_enabled) + # Optional magnet link for BitTorrent distribution. + # Only generated when torrent_opt_in=True AND visibility=1 (public). torrent_magnet_link = Column(UnicodeText, nullable=True) # CDN URL of the .torrent file (set alongside torrent_magnet_link) torrent_file_url = Column(UnicodeText, nullable=True) + # Explicit per-product opt-in to BitTorrent seeding. + # False by default — owner must consciously enable per product. + # Once seeded, files persist in the swarm even if this is later disabled. + torrent_opt_in = Column(Boolean, nullable=False, server_default="0", default=False) + # a denormalized json field to relate products to bundles, and vice versa. json_bundle_metadata = Column( UnicodeText, default=DEFAULT_BUNDLE_METADATA, nullable=False diff --git a/make_post_sell/scripts/alembic/versions/0ee5654cfe7d_add_torrent_opt_in_column_to_mps_product.py b/make_post_sell/scripts/alembic/versions/0ee5654cfe7d_add_torrent_opt_in_column_to_mps_product.py new file mode 100644 index 0000000..d8641a4 --- /dev/null +++ b/make_post_sell/scripts/alembic/versions/0ee5654cfe7d_add_torrent_opt_in_column_to_mps_product.py @@ -0,0 +1,37 @@ +"""add torrent_opt_in column to mps_product + +Revision ID: 0ee5654cfe7d +Revises: 4f4d7c147437 +Create Date: 2026-04-06 18:03:53.663932 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0ee5654cfe7d' +down_revision = '4f4d7c147437' +branch_labels = None +depends_on = None + + +def _column_exists(table, column): + from alembic import op as _op + from sqlalchemy import inspect + bind = _op.get_bind() + insp = inspect(bind) + return column in [c["name"] for c in insp.get_columns(table)] + + +def upgrade(): + if not _column_exists('mps_product', 'torrent_opt_in'): + op.add_column( + 'mps_product', + sa.Column('torrent_opt_in', sa.Boolean(), server_default='0', nullable=False), + ) + + +def downgrade(): + if _column_exists('mps_product', 'torrent_opt_in'): + op.drop_column('mps_product', 'torrent_opt_in') diff --git a/make_post_sell/templates/product_edit.j2 b/make_post_sell/templates/product_edit.j2 index d4e634e..7702e67 100644 --- a/make_post_sell/templates/product_edit.j2 +++ b/make_post_sell/templates/product_edit.j2 @@ -289,11 +289,28 @@ Your cover (thumbnail1) will show up on search pages.
{% if torrent_enabled %} -