feat(filters): improve list view with filtering (#465)

This commit is contained in:
ze0s 2022-09-22 11:54:17 +02:00 committed by GitHub
parent 63d4c21e54
commit f5faf066a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 576 additions and 94 deletions

View file

@ -3,6 +3,8 @@ package database
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/autobrr/autobrr/internal/domain"
@ -26,6 +28,97 @@ func NewFilterRepo(log logger.Logger, db *DB) domain.FilterRepo {
}
}
func (r *FilterRepo) Find(ctx context.Context, params domain.FilterQueryParams) ([]domain.Filter, error) {
tx, err := r.db.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return nil, errors.Wrap(err, "error begin transaction")
}
defer tx.Rollback()
filters, err := r.find(ctx, tx, params)
if err != nil {
return nil, err
}
if err = tx.Commit(); err != nil {
return nil, errors.Wrap(err, "error commit transaction find releases")
}
return filters, nil
}
func (r *FilterRepo) find(ctx context.Context, tx *Tx, params domain.FilterQueryParams) ([]domain.Filter, error) {
actionCountQuery := r.db.squirrel.
Select("COUNT(*)").
From("action a").
Where("a.filter_id = f.id")
queryBuilder := r.db.squirrel.
Select(
"f.id",
"f.enabled",
"f.name",
"f.priority",
"f.created_at",
"f.updated_at",
).
Distinct().
Column(sq.Alias(actionCountQuery, "action_count")).
LeftJoin("filter_indexer fi ON f.id = fi.filter_id").
LeftJoin("indexer i ON i.id = fi.indexer_id").
From("filter f")
if params.Search != "" {
queryBuilder = queryBuilder.Where("f.name LIKE ?", fmt.Sprint("%", params.Search, "%"))
}
if len(params.Sort) > 0 {
for field, order := range params.Sort {
queryBuilder = queryBuilder.OrderBy(fmt.Sprintf("f.%v %v", field, strings.ToUpper(order)))
}
} else {
queryBuilder = queryBuilder.OrderBy("f.name ASC")
}
if params.Filters.Indexers != nil {
filter := sq.And{}
for _, v := range params.Filters.Indexers {
filter = append(filter, sq.Eq{"i.identifier": v})
}
queryBuilder = queryBuilder.Where(filter)
}
query, args, err := queryBuilder.ToSql()
if err != nil {
return nil, errors.Wrap(err, "error building query")
}
rows, err := tx.QueryContext(ctx, query, args...)
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 {
return nil, errors.Wrap(err, "error scanning row")
}
filters = append(filters, f)
}
if err := rows.Err(); err != nil {
return nil, errors.Wrap(err, "row error")
}
return filters, nil
}
func (r *FilterRepo) ListFilters(ctx context.Context) ([]domain.Filter, error) {
actionCountQuery := r.db.squirrel.
Select("COUNT(*)").
@ -37,6 +130,7 @@ func (r *FilterRepo) ListFilters(ctx context.Context) ([]domain.Filter, error) {
"f.id",
"f.enabled",
"f.name",
"f.priority",
"f.created_at",
"f.updated_at",
).
@ -60,7 +154,7 @@ func (r *FilterRepo) ListFilters(ctx context.Context) ([]domain.Filter, error) {
for rows.Next() {
var f domain.Filter
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &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); err != nil {
return nil, errors.Wrap(err, "error scanning row")
}

View file

@ -20,6 +20,7 @@ https://autodl-community.github.io/autodl-irssi/configuration/filter/
type FilterRepo interface {
FindByID(ctx context.Context, filterID int) (*Filter, error)
FindByIndexerIdentifier(indexer string) ([]Filter, error)
Find(ctx context.Context, params FilterQueryParams) ([]Filter, error)
ListFilters(ctx context.Context) ([]Filter, error)
Store(ctx context.Context, filter Filter) (*Filter, error)
Update(ctx context.Context, filter Filter) (*Filter, error)
@ -49,6 +50,14 @@ const (
FilterMaxDownloadsEver FilterMaxDownloadsUnit = "EVER"
)
type FilterQueryParams struct {
Sort map[string]string
Filters struct {
Indexers []string
}
Search string
}
type Filter struct {
ID int `json:"id"`
Name string `json:"name"`
@ -58,7 +67,7 @@ type Filter struct {
MinSize string `json:"min_size,omitempty"`
MaxSize string `json:"max_size,omitempty"`
Delay int `json:"delay,omitempty"`
Priority int32 `json:"priority,omitempty"`
Priority int32 `json:"priority"`
MaxDownloads int `json:"max_downloads,omitempty"`
MaxDownloadsUnit FilterMaxDownloadsUnit `json:"max_downloads_unit,omitempty"`
MatchReleases string `json:"match_releases,omitempty"`

View file

@ -24,6 +24,7 @@ import (
type Service interface {
FindByID(ctx context.Context, filterID int) (*domain.Filter, error)
FindByIndexerIdentifier(indexer string) ([]domain.Filter, error)
Find(ctx context.Context, params domain.FilterQueryParams) ([]domain.Filter, error)
CheckFilter(f domain.Filter, release *domain.Release) (bool, error)
ListFilters(ctx context.Context) ([]domain.Filter, error)
Store(ctx context.Context, filter domain.Filter) (*domain.Filter, error)
@ -52,6 +53,29 @@ func NewService(log logger.Logger, repo domain.FilterRepo, actionRepo domain.Act
}
}
func (s *service) Find(ctx context.Context, params domain.FilterQueryParams) ([]domain.Filter, error) {
// get filters
filters, err := s.repo.Find(ctx, params)
if err != nil {
s.log.Error().Err(err).Msgf("could not find list filters")
return nil, err
}
ret := make([]domain.Filter, 0)
for _, filter := range filters {
indexers, err := s.indexerSvc.FindByFilterID(ctx, filter.ID)
if err != nil {
return ret, err
}
filter.Indexers = indexers
ret = append(ret, filter)
}
return ret, nil
}
func (s *service) ListFilters(ctx context.Context) ([]domain.Filter, error) {
// get filters
filters, err := s.repo.ListFilters(ctx)

View file

@ -4,7 +4,9 @@ import (
"context"
"encoding/json"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/go-chi/chi/v5"
@ -14,6 +16,7 @@ import (
type filterService interface {
ListFilters(ctx context.Context) ([]domain.Filter, error)
FindByID(ctx context.Context, filterID int) (*domain.Filter, error)
Find(ctx context.Context, params domain.FilterQueryParams) ([]domain.Filter, error)
Store(ctx context.Context, filter domain.Filter) (*domain.Filter, error)
Delete(ctx context.Context, filterID int) error
Update(ctx context.Context, filter domain.Filter) (*domain.Filter, error)
@ -48,7 +51,43 @@ func (h filterHandler) Routes(r chi.Router) {
func (h filterHandler) getFilters(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
trackers, err := h.service.ListFilters(ctx)
params := domain.FilterQueryParams{
Sort: map[string]string{},
Filters: struct {
Indexers []string
}{},
Search: "",
}
sort := r.URL.Query().Get("sort")
if sort != "" && strings.Contains(sort, "-") {
field := ""
order := ""
s := strings.Split(sort, "-")
if s[0] == "name" || s[0] == "priority" {
field = s[0]
}
if s[1] == "asc" || s[1] == "desc" {
order = s[1]
}
params.Sort[field] = order
}
u, err := url.Parse(r.URL.String())
if err != nil {
h.encoder.StatusResponse(r.Context(), w, map[string]interface{}{
"code": "BAD_REQUEST_PARAMS",
"message": "indexer parameter is invalid",
}, http.StatusBadRequest)
return
}
vals := u.Query()
params.Filters.Indexers = vals["indexer"]
trackers, err := h.service.Find(ctx, params)
if err != nil {
//
h.encoder.Error(w, err)