fix: disabling last store/group no longer resets all to enabled

Added store_filters_seeded / group_filters_seeded flags to SyncConfig.
_enabled_*_ids now returns None (all enabled) only before first toggle,
not when the filter table is empty due to all being disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mguschin
2026-05-13 14:36:14 +03:00
parent ebcca2a699
commit 1729ff9b7b
3 changed files with 91 additions and 57 deletions

View File

@@ -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")

View File

@@ -59,6 +59,8 @@ class SyncConfig(Base):
is_enabled = Column(Boolean, nullable=False, default=False) is_enabled = Column(Boolean, nullable=False, default=False)
evo_mirror_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) 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) confirmed_at = Column(DateTime, nullable=True)
price_multiplier = Column(Numeric(10, 4), nullable=False, default=1.0) price_multiplier = Column(Numeric(10, 4), nullable=False, default=1.0)
created_at = Column(DateTime, nullable=False, server_default=func.now()) created_at = Column(DateTime, nullable=False, server_default=func.now())

View File

@@ -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: 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() 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 return None
filters = db.query(SyncFilter).filter_by( filters = db.query(SyncFilter).filter_by(
sync_config_id=cfg.id, entity_type="store", filter_mode="include" sync_config_id=cfg.id, entity_type="store", filter_mode="include"
).all() ).all()
if not filters:
return None
return {f.entity_id for f in filters} return {f.entity_id for f in filters}
def _enabled_group_ids(db: Session, user_id: int, store_evotor_id: str) -> set[str] | None: 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() 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 return None
filters = db.query(SyncFilter).filter_by( filters = db.query(SyncFilter).filter_by(
sync_config_id=cfg.id, entity_type="group", filter_mode="include", sync_config_id=cfg.id, entity_type="group", filter_mode="include",
parent_entity_id=store_evotor_id, parent_entity_id=store_evotor_id,
).all() ).all()
if not filters:
return None
return {f.entity_id for f in filters} return {f.entity_id for f in filters}
@@ -166,13 +162,12 @@ async def catalog_store_toggle(store_evotor_id: str, request: Request, db: Sessi
cfg = _get_or_create_sync_config(db, user.id) 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( existing = db.query(SyncFilter).filter_by(
sync_config_id=cfg.id, entity_type="store", filter_mode="include" sync_config_id=cfg.id, entity_type="store", filter_mode="include"
).all() ).all()
existing_ids = {f.entity_id for f in existing} existing_ids = {f.entity_id for f in existing}
if cfg.store_filters_seeded:
if store_evotor_id in existing_ids: if store_evotor_id in existing_ids:
# Currently enabled → disable: remove its filter # Currently enabled → disable: remove its filter
db.query(SyncFilter).filter_by( db.query(SyncFilter).filter_by(
@@ -180,8 +175,16 @@ async def catalog_store_toggle(store_evotor_id: str, request: Request, db: Sessi
entity_id=store_evotor_id, filter_mode="include", entity_id=store_evotor_id, filter_mode="include",
).delete() ).delete()
else: else:
if not existing_ids: # Currently disabled → re-enable: add its filter back
# First toggle: seed include-filters for all OTHER stores db.add(SyncFilter(
sync_config_id=cfg.id,
entity_type="store",
entity_id=store_evotor_id,
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() all_stores = db.query(CachedStore).filter_by(user_id=user.id).all()
now = datetime.now(timezone.utc).replace(tzinfo=None) now = datetime.now(timezone.utc).replace(tzinfo=None)
for s in all_stores: for s in all_stores:
@@ -195,15 +198,7 @@ async def catalog_store_toggle(store_evotor_id: str, request: Request, db: Sessi
filter_mode="include", filter_mode="include",
created_at=now, created_at=now,
)) ))
else: cfg.store_filters_seeded = True
# Re-enable: add its filter back
db.add(SyncFilter(
sync_config_id=cfg.id,
entity_type="store",
entity_id=store_evotor_id,
filter_mode="include",
created_at=datetime.now(timezone.utc).replace(tzinfo=None),
))
db.commit() db.commit()
return RedirectResponse("/catalog/stores", 303) return RedirectResponse("/catalog/stores", 303)
@@ -227,14 +222,23 @@ async def catalog_group_toggle(
).all() ).all()
existing_ids = {f.entity_id for f in existing} existing_ids = {f.entity_id for f in existing}
if cfg.group_filters_seeded:
if group_evotor_id in existing_ids: if group_evotor_id in existing_ids:
db.query(SyncFilter).filter_by( db.query(SyncFilter).filter_by(
sync_config_id=cfg.id, entity_type="group", sync_config_id=cfg.id, entity_type="group",
entity_id=group_evotor_id, filter_mode="include", entity_id=group_evotor_id, filter_mode="include",
).delete() ).delete()
else: else:
if not existing_ids: db.add(SyncFilter(
# First toggle: seed include-filters for all OTHER groups in this store sync_config_id=cfg.id,
entity_type="group",
entity_id=group_evotor_id,
filter_mode="include",
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( all_groups = db.query(CachedGroup).filter_by(
user_id=user.id, store_evotor_id=store_evotor_id, user_id=user.id, store_evotor_id=store_evotor_id,
).all() ).all()
@@ -251,15 +255,7 @@ async def catalog_group_toggle(
parent_entity_id=store_evotor_id, parent_entity_id=store_evotor_id,
created_at=now, created_at=now,
)) ))
else: cfg.group_filters_seeded = True
db.add(SyncFilter(
sync_config_id=cfg.id,
entity_type="group",
entity_id=group_evotor_id,
filter_mode="include",
parent_entity_id=store_evotor_id,
created_at=datetime.now(timezone.utc).replace(tzinfo=None),
))
db.commit() db.commit()
return RedirectResponse(f"/catalog/stores/{store_evotor_id}/groups", 303) return RedirectResponse(f"/catalog/stores/{store_evotor_id}/groups", 303)