Files
evo-sync/web/routes/catalog.py

266 lines
9.5 KiB
Python
Raw Normal View History

from datetime import datetime, timezone
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy import func
from sqlalchemy.orm import Session
from web.auth.session import get_current_user, get_viewed_user
from web.config import settings
from web.database import get_db
from web.models.connections import CachedGroup, CachedProduct, CachedStore, SyncConfig, SyncFilter
from web.templates_env import templates
router = APIRouter()
def _get_or_create_sync_config(db: Session, user_id: int) -> SyncConfig:
cfg = db.query(SyncConfig).filter_by(user_id=user_id).first()
if not cfg:
cfg = SyncConfig(user_id=user_id, is_enabled=True)
db.add(cfg)
db.flush()
return cfg
def _enabled_store_ids(db: Session, user_id: int) -> set[str] | None:
"""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 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()
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 filters not yet seeded (all enabled)."""
cfg = db.query(SyncConfig).filter_by(user_id=user_id).first()
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()
return {f.entity_id for f in filters}
def _render(request: Request, template: str, ctx: dict) -> HTMLResponse:
ctx["request"] = request
ctx.setdefault("jivosite_widget_id", settings.JIVOSITE_WIDGET_ID)
return templates.TemplateResponse(ctx.pop("request"), template, ctx)
@router.get("/catalog/stores")
async def catalog_stores(request: Request, db: Session = Depends(get_db)):
try:
real_user, viewed_user = get_viewed_user(request, db)
except Exception:
return RedirectResponse("/login", 303)
stores = (
db.query(CachedStore)
.filter(CachedStore.user_id == viewed_user.id)
.order_by(CachedStore.name)
.all()
)
enabled_ids = _enabled_store_ids(db, viewed_user.id)
return _render(request, "catalog/stores.html", {
"user": real_user,
"viewed_user": viewed_user if viewed_user.id != real_user.id else None,
"stores": stores,
"enabled_ids": enabled_ids,
"refresh_interval": settings.CATALOG_REFRESH_INTERVAL_SECONDS,
})
@router.get("/catalog/stores/{store_evotor_id}/groups")
async def catalog_groups(store_evotor_id: str, request: Request, db: Session = Depends(get_db)):
try:
real_user, viewed_user = get_viewed_user(request, db)
except Exception:
return RedirectResponse("/login", 303)
store = (
db.query(CachedStore)
.filter(CachedStore.user_id == viewed_user.id, CachedStore.evotor_id == store_evotor_id)
.first()
)
if not store:
return RedirectResponse("/catalog/stores", 303)
groups = (
db.query(CachedGroup)
.filter(CachedGroup.user_id == viewed_user.id, CachedGroup.store_evotor_id == store_evotor_id)
.order_by(CachedGroup.name)
.all()
)
enabled_ids = _enabled_group_ids(db, viewed_user.id, store_evotor_id)
counts_q = (
db.query(CachedProduct.group_evotor_id, func.count().label("cnt"))
.filter(CachedProduct.user_id == viewed_user.id, CachedProduct.store_evotor_id == store_evotor_id)
.group_by(CachedProduct.group_evotor_id)
.all()
)
product_counts = {row.group_evotor_id: row.cnt for row in counts_q}
return _render(request, "catalog/groups.html", {
"user": real_user,
"viewed_user": viewed_user if viewed_user.id != real_user.id else None,
"store": store, "groups": groups,
"enabled_ids": enabled_ids,
"product_counts": product_counts,
})
@router.get("/catalog/stores/{store_evotor_id}/products")
async def catalog_products(store_evotor_id: str, request: Request, db: Session = Depends(get_db)):
try:
real_user, viewed_user = get_viewed_user(request, db)
except Exception:
return RedirectResponse("/login", 303)
store = (
db.query(CachedStore)
.filter(CachedStore.user_id == viewed_user.id, CachedStore.evotor_id == store_evotor_id)
.first()
)
if not store:
return RedirectResponse("/catalog/stores", 303)
group_id = request.query_params.get("group")
q = db.query(CachedProduct).filter(
CachedProduct.user_id == viewed_user.id,
CachedProduct.store_evotor_id == store_evotor_id,
)
if group_id:
q = q.filter(CachedProduct.group_evotor_id == group_id)
products = q.order_by(CachedProduct.name).all()
groups = (
db.query(CachedGroup)
.filter(CachedGroup.user_id == viewed_user.id, CachedGroup.store_evotor_id == store_evotor_id)
.order_by(CachedGroup.name)
.all()
)
group_map = {g.evotor_id: g.name for g in groups}
return _render(request, "catalog/products.html", {
"user": real_user,
"viewed_user": viewed_user if viewed_user.id != real_user.id else None,
"store": store,
"products": products,
"groups": groups,
"group_id": group_id,
"group_map": group_map,
})
@router.post("/catalog/stores/{store_evotor_id}/toggle")
async def catalog_store_toggle(store_evotor_id: str, request: Request, db: Session = Depends(get_db)):
try:
user = get_current_user(request, db)
except Exception:
return RedirectResponse("/login", 303)
cfg = _get_or_create_sync_config(db, user.id)
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 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:
# Currently disabled → 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),
))
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)
@router.post("/catalog/stores/{store_evotor_id}/groups/{group_evotor_id}/toggle")
async def catalog_group_toggle(
store_evotor_id: str, group_evotor_id: str,
request: Request, db: Session = Depends(get_db),
):
try:
user = get_current_user(request, db)
except Exception:
return RedirectResponse("/login", 303)
cfg = _get_or_create_sync_config(db, user.id)
existing = db.query(SyncFilter).filter_by(
sync_config_id=cfg.id, entity_type="group", filter_mode="include",
parent_entity_id=store_evotor_id,
).all()
existing_ids = {f.entity_id for f in existing}
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,
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(
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)