mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 16:59:12 +00:00
feat(indexers): test API from settings (#829)
* refactor(indexers): test api clients * feat(indexers): test api connection * fix(indexers): api client tests * refactor: indexer api clients * feat: add Toasts for indexer api tests * fix: failing red tests
This commit is contained in:
parent
fb9dcc23a0
commit
f3cfeed8cd
19 changed files with 475 additions and 191 deletions
|
@ -1,6 +1,8 @@
|
|||
package indexer
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
|
@ -14,15 +16,15 @@ import (
|
|||
)
|
||||
|
||||
type APIService interface {
|
||||
TestConnection(indexer string) (bool, error)
|
||||
GetTorrentByID(indexer string, torrentID string) (*domain.TorrentBasic, error)
|
||||
TestConnection(ctx context.Context, req domain.IndexerTestApiRequest) (bool, error)
|
||||
GetTorrentByID(ctx context.Context, indexer string, torrentID string) (*domain.TorrentBasic, error)
|
||||
AddClient(indexer string, settings map[string]string) error
|
||||
RemoveClient(indexer string) error
|
||||
}
|
||||
|
||||
type apiClient interface {
|
||||
GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
|
||||
TestAPI() (bool, error)
|
||||
GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error)
|
||||
TestAPI(ctx context.Context) (bool, error)
|
||||
}
|
||||
|
||||
type apiService struct {
|
||||
|
@ -37,49 +39,43 @@ func NewAPIService(log logger.Logger) APIService {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *apiService) GetTorrentByID(indexer string, torrentID string) (*domain.TorrentBasic, error) {
|
||||
v, ok := s.apiClients[indexer]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
func (s *apiService) GetTorrentByID(ctx context.Context, indexer string, torrentID string) (*domain.TorrentBasic, error) {
|
||||
client, err := s.getApiClient(indexer)
|
||||
if err != nil {
|
||||
s.log.Error().Stack().Err(err).Msgf("could not get api client for: %s", indexer)
|
||||
return nil, errors.Wrap(err, "could not get torrent via api for indexer: %s", indexer)
|
||||
}
|
||||
|
||||
s.log.Trace().Str("method", "GetTorrentByID").Msgf("'%v' trying to fetch torrent from api", indexer)
|
||||
s.log.Trace().Str("method", "GetTorrentByID").Msgf("%s fetching torrent from api...", indexer)
|
||||
|
||||
t, err := v.GetTorrentByID(torrentID)
|
||||
torrent, err := client.GetTorrentByID(ctx, torrentID)
|
||||
if err != nil {
|
||||
s.log.Error().Stack().Err(err).Msgf("could not get torrent: '%v' from: %v", torrentID, indexer)
|
||||
s.log.Error().Stack().Err(err).Msgf("could not get torrent: %s from: %s", torrentID, indexer)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.log.Trace().Str("method", "GetTorrentByID").Msgf("'%v' successfully fetched torrent from api: %+v", indexer, t)
|
||||
s.log.Trace().Str("method", "GetTorrentByID").Msgf("%s api successfully fetched torrent: %+v", indexer, torrent)
|
||||
|
||||
return t, nil
|
||||
return torrent, nil
|
||||
}
|
||||
|
||||
func (s *apiService) TestConnection(indexer string) (bool, error) {
|
||||
v, ok := s.apiClients[indexer]
|
||||
if !ok {
|
||||
return false, nil
|
||||
func (s *apiService) TestConnection(ctx context.Context, req domain.IndexerTestApiRequest) (bool, error) {
|
||||
client, err := s.getClientForTest(req)
|
||||
if err != nil {
|
||||
return false, errors.New("could not init api client: %s", req.Identifier)
|
||||
}
|
||||
|
||||
t, err := v.TestAPI()
|
||||
success, err := client.TestAPI(ctx)
|
||||
if err != nil {
|
||||
s.log.Error().Err(err).Msgf("error testing connection for api: %v", indexer)
|
||||
s.log.Error().Err(err).Msgf("error testing connection for api: %s", req.Identifier)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
return success, nil
|
||||
}
|
||||
|
||||
func (s *apiService) AddClient(indexer string, settings map[string]string) error {
|
||||
// basic validation
|
||||
if indexer == "" {
|
||||
return errors.New("api.Service.AddClient: validation falied: indexer can't be empty")
|
||||
} else if len(settings) == 0 {
|
||||
return errors.New("api.Service.AddClient: validation falied: settings can't be empty")
|
||||
}
|
||||
|
||||
s.log.Trace().Msgf("api.Service.AddClient: init api client for '%v'", indexer)
|
||||
s.log.Trace().Msgf("api.Service.AddClient: init api client for: %s", indexer)
|
||||
|
||||
// init client
|
||||
switch indexer {
|
||||
|
@ -100,32 +96,82 @@ func (s *apiService) AddClient(indexer string, settings map[string]string) error
|
|||
if !ok || key == "" {
|
||||
return errors.New("api.Service.AddClient: could not initialize ptp client: missing var 'api_key'")
|
||||
}
|
||||
s.apiClients[indexer] = ptp.NewClient("", user, key)
|
||||
s.apiClients[indexer] = ptp.NewClient(user, key)
|
||||
|
||||
case "ggn":
|
||||
key, ok := settings["api_key"]
|
||||
if !ok || key == "" {
|
||||
return errors.New("api.Service.AddClient: could not initialize ggn client: missing var 'api_key'")
|
||||
}
|
||||
s.apiClients[indexer] = ggn.NewClient("", key)
|
||||
s.apiClients[indexer] = ggn.NewClient(key)
|
||||
|
||||
case "redacted":
|
||||
key, ok := settings["api_key"]
|
||||
if !ok || key == "" {
|
||||
return errors.New("api.Service.AddClient: could not initialize red client: missing var 'api_key'")
|
||||
}
|
||||
s.apiClients[indexer] = red.NewClient("", key)
|
||||
s.apiClients[indexer] = red.NewClient(key)
|
||||
|
||||
case "mock":
|
||||
s.apiClients[indexer] = mock.NewMockClient("", "mock")
|
||||
|
||||
default:
|
||||
return errors.New("api.Service.AddClient: could not initialize client: unsupported indexer '%v'", indexer)
|
||||
return errors.New("api.Service.AddClient: could not initialize client: unsupported indexer: %s", indexer)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *apiService) getApiClient(indexer string) (apiClient, error) {
|
||||
client, ok := s.apiClients[indexer]
|
||||
if !ok {
|
||||
return nil, errors.New("could not find api client for: %s", indexer)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (s *apiService) getClientForTest(req domain.IndexerTestApiRequest) (apiClient, error) {
|
||||
// init client
|
||||
switch req.Identifier {
|
||||
case "btn":
|
||||
if req.ApiKey == "" {
|
||||
return nil, errors.New("api.Service.AddClient: could not initialize btn client: missing var 'api_key'")
|
||||
}
|
||||
return btn.NewClient("", req.ApiKey), nil
|
||||
|
||||
case "ptp":
|
||||
if req.ApiUser == "" {
|
||||
return nil, errors.New("api.Service.AddClient: could not initialize ptp client: missing var 'api_user'")
|
||||
}
|
||||
|
||||
if req.ApiKey == "" {
|
||||
return nil, errors.New("api.Service.AddClient: could not initialize ptp client: missing var 'api_key'")
|
||||
}
|
||||
return ptp.NewClient(req.ApiUser, req.ApiKey), nil
|
||||
|
||||
case "ggn":
|
||||
if req.ApiKey == "" {
|
||||
return nil, errors.New("api.Service.AddClient: could not initialize ggn client: missing var 'api_key'")
|
||||
}
|
||||
return ggn.NewClient(req.ApiKey), nil
|
||||
|
||||
case "redacted":
|
||||
if req.ApiKey == "" {
|
||||
return nil, errors.New("api.Service.AddClient: could not initialize red client: missing var 'api_key'")
|
||||
}
|
||||
return red.NewClient(req.ApiKey), nil
|
||||
|
||||
case "mock":
|
||||
return mock.NewMockClient("", "mock"), nil
|
||||
|
||||
default:
|
||||
return nil, errors.New("api.Service.AddClient: could not initialize client: unsupported indexer: %s", req.Identifier)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) RemoveClient(indexer string) error {
|
||||
_, ok := s.apiClients[indexer]
|
||||
if ok {
|
||||
|
|
|
@ -32,13 +32,14 @@ type Service interface {
|
|||
GetIndexersByIRCNetwork(server string) []*domain.IndexerDefinition
|
||||
GetTorznabIndexers() []domain.IndexerDefinition
|
||||
Start() error
|
||||
TestApi(ctx context.Context, req domain.IndexerTestApiRequest) error
|
||||
}
|
||||
|
||||
type service struct {
|
||||
log zerolog.Logger
|
||||
config *domain.Config
|
||||
repo domain.IndexerRepo
|
||||
apiService APIService
|
||||
ApiService APIService
|
||||
scheduler scheduler.Service
|
||||
|
||||
// contains all raw indexer definitions
|
||||
|
@ -60,7 +61,7 @@ func NewService(log logger.Logger, config *domain.Config, repo domain.IndexerRep
|
|||
log: log.With().Str("module", "indexer").Logger(),
|
||||
config: config,
|
||||
repo: repo,
|
||||
apiService: apiService,
|
||||
ApiService: apiService,
|
||||
scheduler: scheduler,
|
||||
lookupIRCServerDefinition: make(map[string]map[string]*domain.IndexerDefinition),
|
||||
torznabIndexers: make(map[string]*domain.IndexerDefinition),
|
||||
|
@ -106,8 +107,7 @@ func (s *service) Update(ctx context.Context, indexer domain.Indexer) (*domain.I
|
|||
}
|
||||
|
||||
// add to indexerInstances
|
||||
err = s.updateIndexer(*i)
|
||||
if err != nil {
|
||||
if err = s.updateIndexer(*i); err != nil {
|
||||
s.log.Error().Err(err).Msgf("failed to add indexer: %v", indexer.Name)
|
||||
return nil, err
|
||||
}
|
||||
|
@ -137,6 +137,10 @@ func (s *service) Delete(ctx context.Context, id int) error {
|
|||
// remove from lookup tables
|
||||
s.removeIndexer(*indexer)
|
||||
|
||||
if err := s.ApiService.RemoveClient(indexer.Identifier); err != nil {
|
||||
s.log.Error().Err(err).Msgf("could not delete indexer api client: %s", indexer.Identifier)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -332,7 +336,7 @@ func (s *service) Start() error {
|
|||
|
||||
// check if it has api and add to api service
|
||||
if indexer.Enabled && indexer.HasApi() {
|
||||
if err := s.apiService.AddClient(indexer.Identifier, indexer.SettingsMap); err != nil {
|
||||
if err := s.ApiService.AddClient(indexer.Identifier, indexer.SettingsMap); err != nil {
|
||||
s.log.Error().Stack().Err(err).Msgf("indexer.start: could not init api client for: '%v'", indexer.Identifier)
|
||||
}
|
||||
}
|
||||
|
@ -382,8 +386,8 @@ func (s *service) addIndexer(indexer domain.Indexer) error {
|
|||
s.mapIRCServerDefinitionLookup(indexerDefinition.IRC.Server, indexerDefinition)
|
||||
|
||||
// check if it has api and add to api service
|
||||
if indexerDefinition.Enabled && indexerDefinition.HasApi() {
|
||||
if err := s.apiService.AddClient(indexerDefinition.Identifier, indexerDefinition.SettingsMap); err != nil {
|
||||
if indexerDefinition.HasApi() {
|
||||
if err := s.ApiService.AddClient(indexerDefinition.Identifier, indexerDefinition.SettingsMap); err != nil {
|
||||
s.log.Error().Stack().Err(err).Msgf("indexer.start: could not init api client for: '%v'", indexer.Identifier)
|
||||
}
|
||||
}
|
||||
|
@ -418,9 +422,9 @@ func (s *service) updateIndexer(indexer domain.Indexer) error {
|
|||
s.mapIRCServerDefinitionLookup(indexerDefinition.IRC.Server, indexerDefinition)
|
||||
|
||||
// check if it has api and add to api service
|
||||
if indexerDefinition.Enabled && indexerDefinition.HasApi() {
|
||||
if err := s.apiService.AddClient(indexerDefinition.Identifier, indexerDefinition.SettingsMap); err != nil {
|
||||
s.log.Error().Stack().Err(err).Msgf("indexer.start: could not init api client for: '%v'", indexer.Identifier)
|
||||
if indexerDefinition.HasApi() {
|
||||
if err := s.ApiService.AddClient(indexerDefinition.Identifier, indexerDefinition.SettingsMap); err != nil {
|
||||
s.log.Error().Stack().Err(err).Msgf("indexer.start: could not init api client for: '%s'", indexer.Identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -616,6 +620,14 @@ func (s *service) getDefinitionByName(name string) *domain.IndexerDefinition {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *service) getMappedDefinitionByName(name string) *domain.IndexerDefinition {
|
||||
if v, ok := s.mappedDefinitions[name]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) stopFeed(indexer string) {
|
||||
// verify indexer is torznab indexer
|
||||
_, ok := s.torznabIndexers[indexer]
|
||||
|
@ -631,3 +643,30 @@ func (s *service) stopFeed(indexer string) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) TestApi(ctx context.Context, req domain.IndexerTestApiRequest) error {
|
||||
indexer, err := s.FindByID(ctx, req.IndexerId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
def := s.getMappedDefinitionByName(indexer.Identifier)
|
||||
if def == nil {
|
||||
return errors.New("could not find definition: %s", indexer.Identifier)
|
||||
}
|
||||
|
||||
if !def.HasApi() {
|
||||
return errors.New("indexer (%s) does not support api", indexer.Identifier)
|
||||
}
|
||||
|
||||
req.Identifier = def.Identifier
|
||||
|
||||
if _, err = s.ApiService.TestConnection(ctx, req); err != nil {
|
||||
s.log.Error().Err(err).Msgf("error testing api for: %s", indexer.Identifier)
|
||||
return err
|
||||
}
|
||||
|
||||
s.log.Info().Msgf("successful api test for: %s", indexer.Identifier)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue