diff --git a/web/migrations/versions/0011_sync_config_filter_seeded_flags.py b/web/migrations/versions/0011_sync_config_filter_seeded_flags.py new file mode 100644 index 0000000..dc1de98 --- /dev/null +++ b/web/migrations/versions/0011_sync_config_filter_seeded_flags.py @@ -0,0 +1,36 @@ +"""Add store_filters_seeded and group_filters_seeded to sync_configs.""" +revision = "0011" +down_revision = "0010" +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column("sync_configs", sa.Column("store_filters_seeded", sa.Boolean(), nullable=False, server_default="0")) + op.add_column("sync_configs", sa.Column("group_filters_seeded", sa.Boolean(), nullable=False, server_default="0")) + + # Mark existing rows as seeded if they already have filters + op.execute(""" + UPDATE sync_configs sc + SET store_filters_seeded = 1 + WHERE EXISTS ( + SELECT 1 FROM sync_filters sf + WHERE sf.sync_config_id = sc.id AND sf.entity_type = 'store' + ) + """) + op.execute(""" + UPDATE sync_configs sc + SET group_filters_seeded = 1 + WHERE EXISTS ( + SELECT 1 FROM sync_filters sf + WHERE sf.sync_config_id = sc.id AND sf.entity_type = 'group' + ) + """) + + +def downgrade(): + op.drop_column("sync_configs", "group_filters_seeded") + op.drop_column("sync_configs", "store_filters_seeded") diff --git a/web/models/connections.py b/web/models/connections.py index e486187..9df950f 100644 --- a/web/models/connections.py +++ b/web/models/connections.py @@ -59,6 +59,8 @@ class SyncConfig(Base): is_enabled = Column(Boolean, nullable=False, default=False) evo_mirror_enabled = Column(Boolean, nullable=False, default=False) vk_mirror_enabled = Column(Boolean, nullable=False, default=False) + store_filters_seeded = Column(Boolean, nullable=False, default=False) + group_filters_seeded = Column(Boolean, nullable=False, default=False) confirmed_at = Column(DateTime, nullable=True) price_multiplier = Column(Numeric(10, 4), nullable=False, default=1.0) created_at = Column(DateTime, nullable=False, server_default=func.now()) diff --git a/web/routes/catalog.py b/web/routes/catalog.py index 9b728a2..0c63d7f 100644 --- a/web/routes/catalog.py +++ b/web/routes/catalog.py @@ -24,29 +24,25 @@ def _get_or_create_sync_config(db: Session, user_id: int) -> SyncConfig: def _enabled_store_ids(db: Session, user_id: int) -> set[str] | None: - """Return set of enabled store evotor_ids, or None if no filters set (all enabled).""" + """Return set of enabled store evotor_ids, or None if filters not yet seeded (all enabled).""" cfg = db.query(SyncConfig).filter_by(user_id=user_id).first() - if not cfg: + if not cfg or not cfg.store_filters_seeded: return None filters = db.query(SyncFilter).filter_by( sync_config_id=cfg.id, entity_type="store", filter_mode="include" ).all() - if not filters: - return None return {f.entity_id for f in filters} def _enabled_group_ids(db: Session, user_id: int, store_evotor_id: str) -> set[str] | None: - """Return set of enabled group evotor_ids for a store, or None if all enabled.""" + """Return set of enabled group evotor_ids for a store, or None if filters not yet seeded (all enabled).""" cfg = db.query(SyncConfig).filter_by(user_id=user_id).first() - if not cfg: + if not cfg or not cfg.group_filters_seeded: return None filters = db.query(SyncFilter).filter_by( sync_config_id=cfg.id, entity_type="group", filter_mode="include", parent_entity_id=store_evotor_id, ).all() - if not filters: - return None return {f.entity_id for f in filters} @@ -166,37 +162,20 @@ async def catalog_store_toggle(store_evotor_id: str, request: Request, db: Sessi cfg = _get_or_create_sync_config(db, user.id) - # If no filters exist yet, that means all stores are implicitly enabled. - # Toggling one store OFF means we create include-filters for all OTHER stores. existing = db.query(SyncFilter).filter_by( sync_config_id=cfg.id, entity_type="store", filter_mode="include" ).all() existing_ids = {f.entity_id for f in existing} - if store_evotor_id in existing_ids: - # Currently enabled → disable: remove its filter - db.query(SyncFilter).filter_by( - sync_config_id=cfg.id, entity_type="store", - entity_id=store_evotor_id, filter_mode="include", - ).delete() - else: - if not existing_ids: - # First toggle: seed include-filters for all OTHER stores - all_stores = db.query(CachedStore).filter_by(user_id=user.id).all() - now = datetime.now(timezone.utc).replace(tzinfo=None) - for s in all_stores: - if s.evotor_id == store_evotor_id: - continue - db.add(SyncFilter( - sync_config_id=cfg.id, - entity_type="store", - entity_id=s.evotor_id, - entity_name=s.name, - filter_mode="include", - created_at=now, - )) + if cfg.store_filters_seeded: + if store_evotor_id in existing_ids: + # Currently enabled → disable: remove its filter + db.query(SyncFilter).filter_by( + sync_config_id=cfg.id, entity_type="store", + entity_id=store_evotor_id, filter_mode="include", + ).delete() else: - # Re-enable: add its filter back + # Currently disabled → re-enable: add its filter back db.add(SyncFilter( sync_config_id=cfg.id, entity_type="store", @@ -204,6 +183,22 @@ async def catalog_store_toggle(store_evotor_id: str, request: Request, db: Sessi filter_mode="include", created_at=datetime.now(timezone.utc).replace(tzinfo=None), )) + else: + # First toggle ever: seed include-filters for all OTHER stores, mark seeded + all_stores = db.query(CachedStore).filter_by(user_id=user.id).all() + now = datetime.now(timezone.utc).replace(tzinfo=None) + for s in all_stores: + if s.evotor_id == store_evotor_id: + continue + db.add(SyncFilter( + sync_config_id=cfg.id, + entity_type="store", + entity_id=s.evotor_id, + entity_name=s.name, + filter_mode="include", + created_at=now, + )) + cfg.store_filters_seeded = True db.commit() return RedirectResponse("/catalog/stores", 303) @@ -227,30 +222,12 @@ async def catalog_group_toggle( ).all() existing_ids = {f.entity_id for f in existing} - if group_evotor_id in existing_ids: - db.query(SyncFilter).filter_by( - sync_config_id=cfg.id, entity_type="group", - entity_id=group_evotor_id, filter_mode="include", - ).delete() - else: - if not existing_ids: - # First toggle: seed include-filters for all OTHER groups in this store - all_groups = db.query(CachedGroup).filter_by( - user_id=user.id, store_evotor_id=store_evotor_id, - ).all() - now = datetime.now(timezone.utc).replace(tzinfo=None) - for g in all_groups: - if g.evotor_id == group_evotor_id: - continue - db.add(SyncFilter( - sync_config_id=cfg.id, - entity_type="group", - entity_id=g.evotor_id, - entity_name=g.name, - filter_mode="include", - parent_entity_id=store_evotor_id, - created_at=now, - )) + if cfg.group_filters_seeded: + if group_evotor_id in existing_ids: + db.query(SyncFilter).filter_by( + sync_config_id=cfg.id, entity_type="group", + entity_id=group_evotor_id, filter_mode="include", + ).delete() else: db.add(SyncFilter( sync_config_id=cfg.id, @@ -260,6 +237,25 @@ async def catalog_group_toggle( parent_entity_id=store_evotor_id, created_at=datetime.now(timezone.utc).replace(tzinfo=None), )) + else: + # First toggle ever: seed include-filters for all OTHER groups in this store, mark seeded + all_groups = db.query(CachedGroup).filter_by( + user_id=user.id, store_evotor_id=store_evotor_id, + ).all() + now = datetime.now(timezone.utc).replace(tzinfo=None) + for g in all_groups: + if g.evotor_id == group_evotor_id: + continue + db.add(SyncFilter( + sync_config_id=cfg.id, + entity_type="group", + entity_id=g.evotor_id, + entity_name=g.name, + filter_mode="include", + parent_entity_id=store_evotor_id, + created_at=now, + )) + cfg.group_filters_seeded = True db.commit() return RedirectResponse(f"/catalog/stores/{store_evotor_id}/groups", 303)