From 6e12654f6abe557a777de7b1b6f093228bec2303 Mon Sep 17 00:00:00 2001 From: soup Date: Sun, 17 Dec 2023 21:18:26 +0100 Subject: [PATCH] feat(filters): show enabled and disabled actions in list view (#1304) * feat(filters): reflect enabled actions * dont store release unless enabled action found * store the release after the delay * add new parameter to FindByFilterID method --- .gitignore | 1 + internal/action/service.go | 6 ++--- internal/database/action.go | 10 +++++--- internal/database/action_test.go | 8 +++---- internal/database/filter.go | 11 ++++++--- internal/domain/action.go | 2 +- internal/domain/filter.go | 1 + internal/filter/service.go | 4 ++-- internal/release/service.go | 41 ++++++++++++++++---------------- web/src/screens/filters/List.tsx | 19 ++++++++------- web/src/types/Filter.d.ts | 1 + 11 files changed, 59 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 3610550..39f4dba 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ bin/ dist/ .run/ tmp/ +.golangci.yml # Preserve files !.gitkeep diff --git a/internal/action/service.go b/internal/action/service.go index a97c54d..5332144 100644 --- a/internal/action/service.go +++ b/internal/action/service.go @@ -20,7 +20,7 @@ type Service interface { Store(ctx context.Context, action domain.Action) (*domain.Action, error) List(ctx context.Context) ([]domain.Action, error) Get(ctx context.Context, req *domain.GetActionRequest) (*domain.Action, error) - FindByFilterID(ctx context.Context, filterID int) ([]*domain.Action, error) + FindByFilterID(ctx context.Context, filterID int, active *bool) ([]*domain.Action, error) Delete(ctx context.Context, req *domain.DeleteActionRequest) error DeleteByFilterID(ctx context.Context, filterID int) error ToggleEnabled(actionID int) error @@ -76,8 +76,8 @@ func (s *service) Get(ctx context.Context, req *domain.GetActionRequest) (*domai return a, nil } -func (s *service) FindByFilterID(ctx context.Context, filterID int) ([]*domain.Action, error) { - return s.repo.FindByFilterID(ctx, filterID) +func (s *service) FindByFilterID(ctx context.Context, filterID int, active *bool) ([]*domain.Action, error) { + return s.repo.FindByFilterID(ctx, filterID, active) } func (s *service) Delete(ctx context.Context, req *domain.DeleteActionRequest) error { diff --git a/internal/database/action.go b/internal/database/action.go index be3d655..e5dc256 100644 --- a/internal/database/action.go +++ b/internal/database/action.go @@ -30,7 +30,7 @@ func NewActionRepo(log logger.Logger, db *DB, clientRepo domain.DownloadClientRe } } -func (r *ActionRepo) FindByFilterID(ctx context.Context, filterID int) ([]*domain.Action, error) { +func (r *ActionRepo) FindByFilterID(ctx context.Context, filterID int, active *bool) ([]*domain.Action, error) { tx, err := r.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted}) if err != nil { return nil, err @@ -38,7 +38,7 @@ func (r *ActionRepo) FindByFilterID(ctx context.Context, filterID int) ([]*domai defer tx.Rollback() - actions, err := r.findByFilterID(ctx, tx, filterID) + actions, err := r.findByFilterID(ctx, tx, filterID, active) if err != nil { return nil, err } @@ -59,7 +59,7 @@ func (r *ActionRepo) FindByFilterID(ctx context.Context, filterID int) ([]*domai return actions, nil } -func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) ([]*domain.Action, error) { +func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int, active *bool) ([]*domain.Action, error) { queryBuilder := r.db.squirrel. Select( "id", @@ -95,6 +95,10 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) ( From("action"). Where(sq.Eq{"filter_id": filterID}) + if active != nil { + queryBuilder = queryBuilder.Where(sq.Eq{"enabled": *active}) + } + query, args, err := queryBuilder.ToSql() if err != nil { return nil, errors.Wrap(err, "error building query") diff --git a/internal/database/action_test.go b/internal/database/action_test.go index cfc43bf..53eb8fd 100644 --- a/internal/database/action_test.go +++ b/internal/database/action_test.go @@ -214,7 +214,7 @@ func TestActionRepo_FindByFilterID(t *testing.T) { assert.NoError(t, err) // Actual test for FindByFilterID - actions, err := repo.FindByFilterID(context.Background(), createdFilters[0].ID) + actions, err := repo.FindByFilterID(context.Background(), createdFilters[0].ID, nil) assert.NoError(t, err) assert.NotNil(t, actions) assert.Equal(t, 1, len(actions)) @@ -235,7 +235,7 @@ func TestActionRepo_FindByFilterID(t *testing.T) { assert.NotNil(t, createdFilters) // Actual test for FindByFilterID - actions, err := repo.FindByFilterID(context.Background(), createdFilters[0].ID) + actions, err := repo.FindByFilterID(context.Background(), createdFilters[0].ID, nil) assert.NoError(t, err) assert.Equal(t, 0, len(actions)) @@ -244,7 +244,7 @@ func TestActionRepo_FindByFilterID(t *testing.T) { }) t.Run(fmt.Sprintf("FindByFilterID_Succeeds_With_Invalid_FilterID [%s]", dbType), func(t *testing.T) { - actions, err := repo.FindByFilterID(context.Background(), 9999) // 9999 is an invalid filter ID + actions, err := repo.FindByFilterID(context.Background(), 9999, nil) // 9999 is an invalid filter ID assert.NoError(t, err) assert.NotNil(t, actions) assert.Equal(t, 0, len(actions)) @@ -254,7 +254,7 @@ func TestActionRepo_FindByFilterID(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) defer cancel() - actions, err := repo.FindByFilterID(ctx, 1) + actions, err := repo.FindByFilterID(ctx, 1, nil) assert.Error(t, err) assert.Nil(t, actions) }) diff --git a/internal/database/filter.go b/internal/database/filter.go index 058a12c..c21bae0 100644 --- a/internal/database/filter.go +++ b/internal/database/filter.go @@ -57,6 +57,12 @@ func (r *FilterRepo) find(ctx context.Context, tx *Tx, params domain.FilterQuery From("action a"). Where("a.filter_id = f.id") + actionEnabledCountQuery := r.db.squirrel. + Select("COUNT(*)"). + From("action a"). + Where("a.filter_id = f.id"). + Where("a.enabled = '1'") + queryBuilder := r.db.squirrel. Select( "f.id", @@ -68,6 +74,7 @@ func (r *FilterRepo) find(ctx context.Context, tx *Tx, params domain.FilterQuery ). Distinct(). Column(sq.Alias(actionCountQuery, "action_count")). + Column(sq.Alias(actionEnabledCountQuery, "actions_enabled_count")). LeftJoin("filter_indexer fi ON f.id = fi.filter_id"). LeftJoin("indexer i ON i.id = fi.indexer_id"). From("filter f") @@ -89,7 +96,6 @@ func (r *FilterRepo) find(ctx context.Context, tx *Tx, params domain.FilterQuery for _, v := range params.Filters.Indexers { filter = append(filter, sq.Eq{"i.identifier": v}) } - queryBuilder = queryBuilder.Where(filter) } @@ -102,14 +108,13 @@ func (r *FilterRepo) find(ctx context.Context, tx *Tx, params domain.FilterQuery if err != nil { return nil, errors.Wrap(err, "error executing query") } - defer rows.Close() var filters []domain.Filter for rows.Next() { var f domain.Filter - if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &f.Priority, &f.CreatedAt, &f.UpdatedAt, &f.ActionsCount); err != nil { + if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &f.Priority, &f.CreatedAt, &f.UpdatedAt, &f.ActionsCount, &f.ActionsEnabledCount); err != nil { return nil, errors.Wrap(err, "error scanning row") } diff --git a/internal/domain/action.go b/internal/domain/action.go index 74dbf13..720e24e 100644 --- a/internal/domain/action.go +++ b/internal/domain/action.go @@ -14,7 +14,7 @@ import ( type ActionRepo interface { Store(ctx context.Context, action Action) (*Action, error) StoreFilterActions(ctx context.Context, filterID int64, actions []*Action) ([]*Action, error) - FindByFilterID(ctx context.Context, filterID int) ([]*Action, error) + FindByFilterID(ctx context.Context, filterID int, active *bool) ([]*Action, error) List(ctx context.Context) ([]Action, error) Get(ctx context.Context, req *GetActionRequest) (*Action, error) Delete(ctx context.Context, req *DeleteActionRequest) error diff --git a/internal/domain/filter.go b/internal/domain/filter.go index b1c5d21..387ae4f 100644 --- a/internal/domain/filter.go +++ b/internal/domain/filter.go @@ -133,6 +133,7 @@ type Filter struct { ExceptDescription string `json:"except_description,omitempty"` UseRegexDescription bool `json:"use_regex_description,omitempty"` ActionsCount int `json:"actions_count"` + ActionsEnabledCount int `json:"actions_enabled_count"` Actions []*Action `json:"actions,omitempty"` External []FilterExternal `json:"external,omitempty"` Indexers []Indexer `json:"indexers"` diff --git a/internal/filter/service.go b/internal/filter/service.go index e4cedcc..4a9c474 100644 --- a/internal/filter/service.go +++ b/internal/filter/service.go @@ -118,7 +118,7 @@ func (s *service) FindByID(ctx context.Context, filterID int) (*domain.Filter, e return nil, err } - actions, err := s.actionRepo.FindByFilterID(ctx, filter.ID) + actions, err := s.actionRepo.FindByFilterID(ctx, filter.ID, nil) if err != nil { s.log.Error().Err(err).Msgf("could not find filter actions for filter id: %v", filter.ID) } @@ -691,7 +691,7 @@ func (s *service) webhook(ctx context.Context, external domain.FilterExternal, r } // add header to req - req.Header.Add(http.CanonicalHeaderKey(h[0]), h[1]) + req.Header.Add(h[0], h[1]) // go already canonicalizes the provided header key. } } diff --git a/internal/release/service.go b/internal/release/service.go index d0c736e..c88fa61 100644 --- a/internal/release/service.go +++ b/internal/release/service.go @@ -162,6 +162,27 @@ func (s *service) processFilters(ctx context.Context, filters []*domain.Filter, l.Info().Msgf("Matched '%s' (%s) for %s", release.TorrentName, release.FilterName, release.Indexer) + // found matching filter, lets find the filter actions and attach + active := true + actions, err := s.actionSvc.FindByFilterID(ctx, f.ID, &active) + if err != nil { + s.log.Error().Err(err).Msgf("release.Process: error finding actions for filter: %s", f.Name) + return err + } + + // if no actions, continue to next filter + if len(actions) == 0 { + s.log.Warn().Msgf("release.Process: no active actions found for filter '%s', trying next one..", f.Name) + continue + } + + // sleep for the delay period specified in the filter before running actions + delay := release.Filter.Delay + if delay > 0 { + l.Debug().Msgf("release.Process: delaying processing of '%s' (%s) for %s by %d seconds as specified in the filter", release.TorrentName, release.FilterName, release.Indexer, delay) + time.Sleep(time.Duration(delay) * time.Second) + } + // save release here to only save those with rejections from actions instead of all releases if release.ID == 0 { release.FilterStatus = domain.ReleaseStatusFilterApproved @@ -172,26 +193,6 @@ func (s *service) processFilters(ctx context.Context, filters []*domain.Filter, } } - // found matching filter, lets find the filter actions and attach - actions, err := s.actionSvc.FindByFilterID(ctx, f.ID) - if err != nil { - s.log.Error().Err(err).Msgf("release.Process: error finding actions for filter: %s", f.Name) - return err - } - - // if no actions, continue to next filter - if len(actions) == 0 { - s.log.Warn().Msgf("release.Process: no actions found for filter '%s', trying next one..", f.Name) - return nil - } - - // sleep for the delay period specified in the filter before running actions - delay := release.Filter.Delay - if delay > 0 { - l.Debug().Msgf("release.Process: delaying processing of '%s' (%s) for %s by %d seconds as specified in the filter", release.TorrentName, release.FilterName, release.Indexer, delay) - time.Sleep(time.Duration(delay) * time.Second) - } - var rejections []string // run actions (watchFolder, test, exec, qBittorrent, Deluge, arr etc.) diff --git a/web/src/screens/filters/List.tsx b/web/src/screens/filters/List.tsx index 8ae6492..4503bc6 100644 --- a/web/src/screens/filters/List.tsx +++ b/web/src/screens/filters/List.tsx @@ -645,19 +645,20 @@ function FilterListItem({ filter, values, idx }: FilterListItemProps) { to={`${filter.id.toString()}/actions`} className="flex items-center cursor-pointer hover:text-black dark:hover:text-gray-300" > - - Actions: {filter.actions_count} + + Actions: {filter.actions_enabled_count}/{filter.actions_count} - {!filter.actions_count && ( - - - - )} } > - {!filter.actions_count ? ( - <>{"You need to setup an action in the filter otherwise you will not get any snatches."} + {filter.actions_count === 0 ? ( + <> + {"No actions defined. Set up actions to enable snatching."} + + ) : filter.actions_enabled_count === 0 ? ( + <> + {"You need to enable at least one action in the filter otherwise you will not get any snatches."} + ) : null} diff --git a/web/src/types/Filter.d.ts b/web/src/types/Filter.d.ts index 4831308..6987ca9 100644 --- a/web/src/types/Filter.d.ts +++ b/web/src/types/Filter.d.ts @@ -68,6 +68,7 @@ interface Filter { tags_match_logic: string; except_tags_match_logic: string; actions_count: number; + actions_enabled_count: number; actions: Action[]; indexers: Indexer[]; external: ExternalFilter[];