feat(lists): integrate Omegabrr (#1885)

* feat(lists): integrate Omegabrr

* feat(lists): add missing lists index

* feat(lists): add db repo

* feat(lists): add db migrations

* feat(lists): labels

* feat(lists): url lists and more arrs

* fix(lists): db migrations client_id wrong type

* fix(lists): db fields

* feat(lists): create list form wip

* feat(lists): show in list and create

* feat(lists): update and delete

* feat(lists): trigger via webhook

* feat(lists): add webhook handler

* fix(arr): encode json to pointer

* feat(lists): rename endpoint to lists

* feat(lists): fetch tags from arr

* feat(lists): process plaintext lists

* feat(lists): add background refresh job

* run every 6th hour with a random start delay between 1-35 seconds

* feat(lists): refresh on save and improve logging

* feat(lists): cast arr client to pointer

* feat(lists): improve error handling

* feat(lists): reset shows field with match release

* feat(lists): filter opts all lists

* feat(lists): trigger on update if enabled

* feat(lists): update option for lists

* feat(lists): show connected filters in list

* feat(lists): missing listSvc dep

* feat(lists): cleanup

* feat(lists): typo arr list

* feat(lists): radarr include original

* feat(lists): rename ExcludeAlternateTitle to IncludeAlternateTitle

* fix(lists): arr client type conversion to pointer

* fix(actions): only log panic recover if err not nil

* feat(lists): show spinner on save

* feat(lists): show icon in filters list

* feat(lists): change icon color in filters list

* feat(lists): delete relations on filter delete
This commit is contained in:
ze0s 2024-12-25 13:23:37 +01:00 committed by GitHub
parent b68ae334ca
commit 221bc35371
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 5025 additions and 254 deletions

View file

@ -22,6 +22,7 @@ type downloadClientService interface {
Update(ctx context.Context, client *domain.DownloadClient) error
Delete(ctx context.Context, clientID int32) error
Test(ctx context.Context, client domain.DownloadClient) error
GetArrTags(ctx context.Context, id int32) ([]*domain.ArrTag, error)
}
type downloadClientHandler struct {
@ -45,6 +46,8 @@ func (h downloadClientHandler) Routes(r chi.Router) {
r.Route("/{clientID}", func(r chi.Router) {
r.Get("/", h.findByID)
r.Delete("/", h.delete)
r.Get("/arr/tags", h.findArrTagsByID)
})
}
@ -78,6 +81,26 @@ func (h downloadClientHandler) findByID(w http.ResponseWriter, r *http.Request)
h.encoder.StatusResponse(w, http.StatusOK, client)
}
func (h downloadClientHandler) findArrTagsByID(w http.ResponseWriter, r *http.Request) {
clientID, err := strconv.ParseInt(chi.URLParam(r, "clientID"), 10, 32)
if err != nil {
h.encoder.Error(w, err)
return
}
client, err := h.service.GetArrTags(r.Context(), int32(clientID))
if err != nil {
if errors.Is(err, domain.ErrRecordNotFound) {
h.encoder.NotFoundErr(w, errors.New("download client with id %d not found", clientID))
}
h.encoder.Error(w, err)
return
}
h.encoder.StatusResponse(w, http.StatusOK, client)
}
func (h downloadClientHandler) store(w http.ResponseWriter, r *http.Request) {
var data *domain.DownloadClient
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {

124
internal/http/list.go Normal file
View file

@ -0,0 +1,124 @@
package http
import (
"context"
"encoding/json"
"net/http"
"strconv"
"github.com/autobrr/autobrr/internal/domain"
"github.com/go-chi/chi/v5"
)
type listService interface {
Store(ctx context.Context, list *domain.List) error
Update(ctx context.Context, list *domain.List) error
Delete(ctx context.Context, id int64) error
FindByID(ctx context.Context, id int64) (*domain.List, error)
List(ctx context.Context) ([]*domain.List, error)
RefreshList(ctx context.Context, id int64) error
RefreshAll(ctx context.Context) error
RefreshArrLists(ctx context.Context) error
RefreshOtherLists(ctx context.Context) error
}
type listHandler struct {
encoder encoder
listSvc listService
}
func newListHandler(encoder encoder, service listService) *listHandler {
return &listHandler{encoder: encoder, listSvc: service}
}
func (h listHandler) Routes(r chi.Router) {
r.Get("/", h.list)
r.Post("/", h.store)
r.Post("/refresh", h.refreshAll)
r.Route("/{listID}", func(r chi.Router) {
r.Post("/refresh", h.refreshList)
r.Put("/", h.update)
r.Delete("/", h.delete)
})
}
func (h listHandler) list(w http.ResponseWriter, r *http.Request) {
data, err := h.listSvc.List(r.Context())
if err != nil {
h.encoder.Error(w, err)
return
}
h.encoder.StatusResponse(w, http.StatusOK, data)
}
func (h listHandler) store(w http.ResponseWriter, r *http.Request) {
var data *domain.List
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
h.encoder.Error(w, err)
return
}
if err := h.listSvc.Store(r.Context(), data); err != nil {
h.encoder.Error(w, err)
return
}
h.encoder.StatusCreatedData(w, data)
}
func (h listHandler) update(w http.ResponseWriter, r *http.Request) {
var data *domain.List
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
h.encoder.Error(w, err)
return
}
if err := h.listSvc.Update(r.Context(), data); err != nil {
h.encoder.Error(w, err)
return
}
h.encoder.NoContent(w)
}
func (h listHandler) delete(w http.ResponseWriter, r *http.Request) {
listID, err := strconv.Atoi(chi.URLParam(r, "listID"))
if err != nil {
h.encoder.Error(w, err)
return
}
if err := h.listSvc.Delete(r.Context(), int64(listID)); err != nil {
h.encoder.Error(w, err)
return
}
h.encoder.NoContent(w)
}
func (h listHandler) refreshAll(w http.ResponseWriter, r *http.Request) {
if err := h.listSvc.RefreshAll(r.Context()); err != nil {
h.encoder.Error(w, err)
return
}
h.encoder.NoContent(w)
}
func (h listHandler) refreshList(w http.ResponseWriter, r *http.Request) {
listID, err := strconv.Atoi(chi.URLParam(r, "listID"))
if err != nil {
h.encoder.Error(w, err)
return
}
if err := h.listSvc.RefreshList(r.Context(), int64(listID)); err != nil {
h.encoder.Error(w, err)
return
}
h.encoder.NoContent(w)
}

View file

@ -42,13 +42,14 @@ type Server struct {
feedService feedService
indexerService indexerService
ircService ircService
listService listService
notificationService notificationService
proxyService proxyService
releaseService releaseService
updateService updateService
}
func NewServer(log logger.Logger, config *config.AppConfig, sse *sse.Server, db *database.DB, version string, commit string, date string, actionService actionService, apiService apikeyService, authService authService, downloadClientSvc downloadClientService, filterSvc filterService, feedSvc feedService, indexerSvc indexerService, ircSvc ircService, notificationSvc notificationService, proxySvc proxyService, releaseSvc releaseService, updateSvc updateService) Server {
func NewServer(log logger.Logger, config *config.AppConfig, sse *sse.Server, db *database.DB, version string, commit string, date string, actionService actionService, apiService apikeyService, authService authService, downloadClientSvc downloadClientService, filterSvc filterService, feedSvc feedService, indexerSvc indexerService, ircSvc ircService, listSvc listService, notificationSvc notificationService, proxySvc proxyService, releaseSvc releaseService, updateSvc updateService) Server {
return Server{
log: log.With().Str("module", "http").Logger(),
config: config,
@ -68,6 +69,7 @@ func NewServer(log logger.Logger, config *config.AppConfig, sse *sse.Server, db
feedService: feedSvc,
indexerService: indexerSvc,
ircService: ircSvc,
listService: listSvc,
notificationService: notificationSvc,
proxyService: proxySvc,
releaseService: releaseSvc,
@ -142,12 +144,14 @@ func (s Server) Handler() http.Handler {
r.Route("/feeds", newFeedHandler(encoder, s.feedService).Routes)
r.Route("/irc", newIrcHandler(encoder, s.sse, s.ircService).Routes)
r.Route("/indexer", newIndexerHandler(encoder, s.indexerService, s.ircService).Routes)
r.Route("/lists", newListHandler(encoder, s.listService).Routes)
r.Route("/keys", newAPIKeyHandler(encoder, s.apiService).Routes)
r.Route("/logs", newLogsHandler(s.config).Routes)
r.Route("/notification", newNotificationHandler(encoder, s.notificationService).Routes)
r.Route("/proxy", newProxyHandler(encoder, s.proxyService).Routes)
r.Route("/release", newReleaseHandler(encoder, s.releaseService).Routes)
r.Route("/updates", newUpdateHandler(encoder, s.updateService).Routes)
r.Route("/webhook", newWebhookHandler(encoder, s.listService).Routes)
r.HandleFunc("/events", func(w http.ResponseWriter, r *http.Request) {

71
internal/http/webhook.go Normal file
View file

@ -0,0 +1,71 @@
package http
import (
"context"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
)
type webhookHandler struct {
encoder encoder
listSvc listService
}
func newWebhookHandler(encoder encoder, listSvc listService) *webhookHandler {
return &webhookHandler{encoder: encoder, listSvc: listSvc}
}
func (h *webhookHandler) Routes(r chi.Router) {
r.Route("/lists", func(r chi.Router) {
r.Post("/trigger", h.refreshAll)
r.Post("/trigger/arr", h.refreshArr)
r.Post("/trigger/lists", h.refreshLists)
r.Get("/trigger", h.refreshAll)
r.Get("/trigger/arr", h.refreshArr)
r.Get("/trigger/lists", h.refreshLists)
r.Post("/trigger/{listID}", h.refreshByID)
})
}
func (h *webhookHandler) refreshAll(w http.ResponseWriter, r *http.Request) {
go h.listSvc.RefreshAll(context.Background())
h.encoder.NoContent(w)
}
func (h *webhookHandler) refreshByID(w http.ResponseWriter, r *http.Request) {
listID, err := strconv.Atoi(chi.URLParam(r, "listID"))
if err != nil {
h.encoder.Error(w, err)
return
}
if err := h.listSvc.RefreshList(context.Background(), int64(listID)); err != nil {
h.encoder.Error(w, err)
return
}
h.encoder.NoContent(w)
}
func (h *webhookHandler) refreshArr(w http.ResponseWriter, r *http.Request) {
if err := h.listSvc.RefreshArrLists(r.Context()); err != nil {
h.encoder.Error(w, err)
return
}
h.encoder.NoContent(w)
}
func (h *webhookHandler) refreshLists(w http.ResponseWriter, r *http.Request) {
if err := h.listSvc.RefreshOtherLists(r.Context()); err != nil {
h.encoder.Error(w, err)
return
}
h.encoder.NoContent(w)
}