diff --git a/app/api/scenes.py b/app/api/scenes.py index 5b00fe3..3d0b915 100644 --- a/app/api/scenes.py +++ b/app/api/scenes.py @@ -167,9 +167,18 @@ def list_scenes( if q: base = base.where(Scene.title_normalized.ilike(f"%{q.lower()}%")) + # Cap rozmiarów filtrów. Bez tego pojedynczy request z setkami studio_slugs + + # dziesiątkami tagów (każdy tag = osobny correlated EXISTS) + ILIKE budował zapytanie, + # które OOM-killer ubijał → PG crash-recovery = ~1s globalnej przerwy (GOON-1M, + # 2026-06-26: 194 studios + 23 tagi). Realny UI nigdy nie wysyła tylu. 422 zamiast + # wywalania bazy. Limity hojne (>> normalne użycie), ale ograniczają złożoność query. + _MAX_STUDIOS, _MAX_TAGS, _MAX_PERFORMERS = 50, 15, 15 + studio_slug_list = _split_csv(studio_slugs) if studio_slug: studio_slug_list.append(studio_slug) + if len(studio_slug_list) > _MAX_STUDIOS: + raise HTTPException(status_code=422, detail=f"too many studio filters (max {_MAX_STUDIOS})") if studio_slug_list: base = base.where( Scene.studio_id.in_( @@ -178,6 +187,8 @@ def list_scenes( ) tag_slug_list = _split_csv(tags) + if len(tag_slug_list) > _MAX_TAGS: + raise HTTPException(status_code=422, detail=f"too many tag filters (max {_MAX_TAGS})") # AND między tagami: scena musi mieć WSZYSTKIE zaznaczone tagi. Każdy slug → osobny # exists() — zaznaczanie kolejnych filtrów zawęża wyniki, jak intuicja użytkownika. # @@ -207,6 +218,8 @@ def list_scenes( ) perf_id_strings = _split_csv(performer_ids) + if len(perf_id_strings) > _MAX_PERFORMERS: + raise HTTPException(status_code=422, detail=f"too many performer filters (max {_MAX_PERFORMERS})") if perf_id_strings: try: perf_ids = [uuid.UUID(s) for s in perf_id_strings]