diff --git a/internal/api/service.go b/internal/api/service.go index 5911c1f..d962580 100644 --- a/internal/api/service.go +++ b/internal/api/service.go @@ -67,7 +67,12 @@ func (s *service) Store(ctx context.Context, apiKey *domain.APIKey) error { } func (s *service) Delete(ctx context.Context, key string) error { - err := s.repo.Delete(ctx, key) + _, err := s.repo.GetKey(ctx, key) + if err != nil { + return err + } + + err = s.repo.Delete(ctx, key) if err != nil { return errors.Wrap(err, "could not delete api key: %s", key) } diff --git a/internal/database/feed.go b/internal/database/feed.go index 24f5d22..8d70a1b 100644 --- a/internal/database/feed.go +++ b/internal/database/feed.go @@ -226,9 +226,7 @@ func (r *FeedRepo) Find(ctx context.Context) ([]domain.Feed, error) { func (r *FeedRepo) GetLastRunDataByID(ctx context.Context, id int) (string, error) { queryBuilder := r.db.squirrel. - Select( - "last_run_data", - ). + Select("last_run_data"). From("feed"). Where(sq.Eq{"id": id}) diff --git a/internal/database/release.go b/internal/database/release.go index 11559b9..50c6de2 100644 --- a/internal/database/release.go +++ b/internal/database/release.go @@ -331,7 +331,6 @@ func (repo *ReleaseRepo) FindRecent(ctx context.Context) ([]*domain.Release, err } func (repo *ReleaseRepo) GetIndexerOptions(ctx context.Context) ([]string, error) { - query := `SELECT DISTINCT indexer FROM "release" UNION SELECT DISTINCT identifier indexer FROM indexer;` repo.log.Trace().Str("database", "release.get_indexers").Msgf("query: '%v'", query) @@ -363,7 +362,6 @@ func (repo *ReleaseRepo) GetIndexerOptions(ctx context.Context) ([]string, error } func (repo *ReleaseRepo) GetActionStatusByReleaseID(ctx context.Context, releaseID int64) ([]domain.ReleaseActionStatus, error) { - queryBuilder := repo.db.squirrel. Select("id", "status", "action", "action_id", "type", "client", "filter", "release_id", "rejections", "timestamp"). From("release_action_status"). diff --git a/internal/http/action.go b/internal/http/action.go index 44049b9..20ffaf5 100644 --- a/internal/http/action.go +++ b/internal/http/action.go @@ -38,7 +38,7 @@ func (h actionHandler) Routes(r chi.Router) { r.Get("/", h.getActions) r.Post("/", h.storeAction) - r.Route("/{id}", func(r chi.Router) { + r.Route("/{actionID}", func(r chi.Router) { r.Delete("/", h.deleteAction) r.Put("/", h.updateAction) r.Patch("/toggleEnabled", h.toggleActionEnabled) @@ -56,17 +56,13 @@ func (h actionHandler) getActions(w http.ResponseWriter, r *http.Request) { } func (h actionHandler) storeAction(w http.ResponseWriter, r *http.Request) { - var ( - data domain.Action - ctx = r.Context() - ) - + var data domain.Action if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - action, err := h.service.Store(ctx, data) + action, err := h.service.Store(r.Context(), data) if err != nil { h.encoder.Error(w, err) return @@ -76,17 +72,13 @@ func (h actionHandler) storeAction(w http.ResponseWriter, r *http.Request) { } func (h actionHandler) updateAction(w http.ResponseWriter, r *http.Request) { - var ( - data domain.Action - ctx = r.Context() - ) - + var data domain.Action if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - action, err := h.service.Store(ctx, data) + action, err := h.service.Store(r.Context(), data) if err != nil { h.encoder.Error(w, err) return @@ -96,7 +88,7 @@ func (h actionHandler) updateAction(w http.ResponseWriter, r *http.Request) { } func (h actionHandler) deleteAction(w http.ResponseWriter, r *http.Request) { - actionID, err := parseInt(chi.URLParam(r, "id")) + actionID, err := parseInt(chi.URLParam(r, "actionID")) if err != nil { h.encoder.StatusError(w, http.StatusBadRequest, errors.New("bad param id")) return @@ -111,7 +103,7 @@ func (h actionHandler) deleteAction(w http.ResponseWriter, r *http.Request) { } func (h actionHandler) toggleActionEnabled(w http.ResponseWriter, r *http.Request) { - actionID, err := parseInt(chi.URLParam(r, "id")) + actionID, err := parseInt(chi.URLParam(r, "actionID")) if err != nil { h.encoder.StatusError(w, http.StatusBadRequest, errors.New("bad param id")) return diff --git a/internal/http/apikey.go b/internal/http/apikey.go index 6fef6e3..727c02a 100644 --- a/internal/http/apikey.go +++ b/internal/http/apikey.go @@ -9,6 +9,7 @@ import ( "net/http" "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" "github.com/go-chi/chi/v5" "github.com/go-chi/render" @@ -51,7 +52,6 @@ func (h apikeyHandler) list(w http.ResponseWriter, r *http.Request) { func (h apikeyHandler) store(w http.ResponseWriter, r *http.Request) { var data domain.APIKey - if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return @@ -66,7 +66,14 @@ func (h apikeyHandler) store(w http.ResponseWriter, r *http.Request) { } func (h apikeyHandler) delete(w http.ResponseWriter, r *http.Request) { - if err := h.service.Delete(r.Context(), chi.URLParam(r, "apikey")); err != nil { + apiKey := chi.URLParam(r, "apikey") + + if err := h.service.Delete(r.Context(), apiKey); err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("api key %s not found", apiKey)) + return + } + h.encoder.Error(w, err) return } diff --git a/internal/http/download_client.go b/internal/http/download_client.go index 7e05609..45b2a23 100644 --- a/internal/http/download_client.go +++ b/internal/http/download_client.go @@ -6,17 +6,18 @@ package http import ( "context" "encoding/json" - "errors" "net/http" "strconv" - "github.com/go-chi/chi/v5" - "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" + + "github.com/go-chi/chi/v5" ) type downloadClientService interface { List(ctx context.Context) ([]domain.DownloadClient, error) + FindByID(ctx context.Context, id int32) (*domain.DownloadClient, error) Store(ctx context.Context, client *domain.DownloadClient) error Update(ctx context.Context, client *domain.DownloadClient) error Delete(ctx context.Context, clientID int32) error @@ -40,13 +41,15 @@ func (h downloadClientHandler) Routes(r chi.Router) { r.Post("/", h.store) r.Put("/", h.update) r.Post("/test", h.test) - r.Delete("/{clientID}", h.delete) + + r.Route("/{clientID}", func(r chi.Router) { + r.Get("/", h.findByID) + r.Delete("/", h.delete) + }) } func (h downloadClientHandler) listDownloadClients(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - clients, err := h.service.List(ctx) + clients, err := h.service.List(r.Context()) if err != nil { h.encoder.Error(w, err) return @@ -55,9 +58,28 @@ func (h downloadClientHandler) listDownloadClients(w http.ResponseWriter, r *htt h.encoder.StatusResponse(w, http.StatusOK, clients) } +func (h downloadClientHandler) findByID(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.FindByID(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 { h.encoder.Error(w, err) return @@ -74,7 +96,6 @@ func (h downloadClientHandler) store(w http.ResponseWriter, r *http.Request) { func (h downloadClientHandler) test(w http.ResponseWriter, r *http.Request) { var data domain.DownloadClient - if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return @@ -90,7 +111,6 @@ func (h downloadClientHandler) test(w http.ResponseWriter, r *http.Request) { func (h downloadClientHandler) update(w http.ResponseWriter, r *http.Request) { var data *domain.DownloadClient - if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return @@ -106,20 +126,13 @@ func (h downloadClientHandler) update(w http.ResponseWriter, r *http.Request) { } func (h downloadClientHandler) delete(w http.ResponseWriter, r *http.Request) { - var clientID = chi.URLParam(r, "clientID") - - if clientID == "" { - h.encoder.Error(w, errors.New("no clientID given")) - return - } - - id, err := strconv.ParseInt(clientID, 10, 32) + clientID, err := strconv.ParseInt(chi.URLParam(r, "clientID"), 10, 32) if err != nil { h.encoder.Error(w, err) return } - if err = h.service.Delete(r.Context(), int32(id)); err != nil { + if err = h.service.Delete(r.Context(), int32(clientID)); err != nil { h.encoder.Error(w, err) return } diff --git a/internal/http/encoder.go b/internal/http/encoder.go index 87ff439..87ab708 100644 --- a/internal/http/encoder.go +++ b/internal/http/encoder.go @@ -20,7 +20,7 @@ type statusResponse struct { Status int `json:"status,omitempty"` } -func (e encoder) StatusResponse(w http.ResponseWriter, status int, response interface{}) { +func (e encoder) StatusResponse(w http.ResponseWriter, status int, response any) { if response != nil { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) @@ -52,7 +52,7 @@ func (e encoder) StatusCreated(w http.ResponseWriter) { w.WriteHeader(http.StatusCreated) } -func (e encoder) StatusCreatedData(w http.ResponseWriter, data interface{}) { +func (e encoder) StatusCreatedData(w http.ResponseWriter, data any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusCreated) diff --git a/internal/http/feed.go b/internal/http/feed.go index e5581f3..7c93a3d 100644 --- a/internal/http/feed.go +++ b/internal/http/feed.go @@ -6,6 +6,7 @@ package http import ( "context" "encoding/json" + "github.com/autobrr/autobrr/pkg/errors" "net/http" "strconv" @@ -16,6 +17,7 @@ import ( type feedService interface { Find(ctx context.Context) ([]domain.Feed, error) + FindByID(ctx context.Context, id int) (*domain.Feed, error) Store(ctx context.Context, feed *domain.Feed) error Update(ctx context.Context, feed *domain.Feed) error Delete(ctx context.Context, id int) error @@ -44,6 +46,7 @@ func (h feedHandler) Routes(r chi.Router) { r.Post("/test", h.test) r.Route("/{feedID}", func(r chi.Router) { + r.Get("/", h.findByID) r.Put("/", h.update) r.Delete("/", h.delete) r.Delete("/cache", h.deleteCache) @@ -54,29 +57,44 @@ func (h feedHandler) Routes(r chi.Router) { } func (h feedHandler) find(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - feeds, err := h.service.Find(ctx) + feeds, err := h.service.Find(r.Context()) if err != nil { - h.encoder.StatusNotFound(w) + h.encoder.Error(w, err) return } h.encoder.StatusResponse(w, http.StatusOK, feeds) } -func (h feedHandler) store(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data *domain.Feed - ) +func (h feedHandler) findByID(w http.ResponseWriter, r *http.Request) { + feedID, err := strconv.Atoi(chi.URLParam(r, "feedID")) + if err != nil { + h.encoder.Error(w, err) + return + } + feed, err := h.service.FindByID(r.Context(), feedID) + if err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("could not find feed with id %d", feedID)) + return + } + + h.encoder.Error(w, err) + return + } + + h.encoder.StatusResponse(w, http.StatusOK, feed) +} + +func (h feedHandler) store(w http.ResponseWriter, r *http.Request) { + var data *domain.Feed if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - err := h.service.Store(ctx, data) + err := h.service.Store(r.Context(), data) if err != nil { h.encoder.Error(w, err) return @@ -86,17 +104,13 @@ func (h feedHandler) store(w http.ResponseWriter, r *http.Request) { } func (h feedHandler) test(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data *domain.Feed - ) - + var data *domain.Feed if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - if err := h.service.Test(ctx, data); err != nil { + if err := h.service.Test(r.Context(), data); err != nil { h.encoder.Error(w, err) return } @@ -105,17 +119,13 @@ func (h feedHandler) test(w http.ResponseWriter, r *http.Request) { } func (h feedHandler) update(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data *domain.Feed - ) - + var data *domain.Feed if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - err := h.service.Update(ctx, data) + err := h.service.Update(r.Context(), data) if err != nil { h.encoder.Error(w, err) return @@ -125,16 +135,18 @@ func (h feedHandler) update(w http.ResponseWriter, r *http.Request) { } func (h feedHandler) forceRun(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - feedID := chi.URLParam(r, "feedID") - - id, err := strconv.Atoi(feedID) + feedID, err := strconv.Atoi(chi.URLParam(r, "feedID")) if err != nil { h.encoder.Error(w, err) return } - if err := h.service.ForceRun(ctx, id); err != nil { + if err := h.service.ForceRun(r.Context(), feedID); err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("could not find feed with id %d", feedID)) + return + } + h.encoder.Error(w, err) return } @@ -143,26 +155,27 @@ func (h feedHandler) forceRun(w http.ResponseWriter, r *http.Request) { } func (h feedHandler) toggleEnabled(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - filterID = chi.URLParam(r, "feedID") - data struct { - Enabled bool `json:"enabled"` - } - ) - - id, err := strconv.Atoi(filterID) + feedID, err := strconv.Atoi(chi.URLParam(r, "feedID")) if err != nil { h.encoder.Error(w, err) return } + var data struct { + Enabled bool `json:"enabled"` + } + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - if err := h.service.ToggleEnabled(ctx, id, data.Enabled); err != nil { + if err := h.service.ToggleEnabled(r.Context(), feedID, data.Enabled); err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("could not find feed with id %d", feedID)) + return + } + h.encoder.Error(w, err) return } @@ -171,18 +184,18 @@ func (h feedHandler) toggleEnabled(w http.ResponseWriter, r *http.Request) { } func (h feedHandler) delete(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - filterID = chi.URLParam(r, "feedID") - ) - - id, err := strconv.Atoi(filterID) + feedID, err := strconv.Atoi(chi.URLParam(r, "feedID")) if err != nil { h.encoder.Error(w, err) return } - if err := h.service.Delete(ctx, id); err != nil { + if err := h.service.Delete(r.Context(), feedID); err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("could not find feed with id %d", feedID)) + return + } + h.encoder.Error(w, err) return } @@ -191,18 +204,13 @@ func (h feedHandler) delete(w http.ResponseWriter, r *http.Request) { } func (h feedHandler) deleteCache(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - filterID = chi.URLParam(r, "feedID") - ) - - id, err := strconv.Atoi(filterID) + feedID, err := strconv.Atoi(chi.URLParam(r, "feedID")) if err != nil { h.encoder.Error(w, err) return } - if err := h.service.DeleteFeedCache(ctx, id); err != nil { + if err := h.service.DeleteFeedCache(r.Context(), feedID); err != nil { h.encoder.Error(w, err) return } @@ -211,18 +219,13 @@ func (h feedHandler) deleteCache(w http.ResponseWriter, r *http.Request) { } func (h feedHandler) latestRun(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - filterID = chi.URLParam(r, "feedID") - ) - - id, err := strconv.Atoi(filterID) + feedID, err := strconv.Atoi(chi.URLParam(r, "feedID")) if err != nil { h.encoder.Error(w, err) return } - feed, err := h.service.GetLastRunData(ctx, id) + feed, err := h.service.GetLastRunData(r.Context(), feedID) if err != nil { h.encoder.Error(w, err) return diff --git a/internal/http/filter.go b/internal/http/filter.go index b047387..d0d6ba3 100644 --- a/internal/http/filter.go +++ b/internal/http/filter.go @@ -11,10 +11,10 @@ import ( "strconv" "strings" - "github.com/go-chi/chi/v5" - "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/pkg/errors" + + "github.com/go-chi/chi/v5" ) type filterService interface { @@ -57,8 +57,6 @@ func (h filterHandler) Routes(r chi.Router) { } func (h filterHandler) getFilters(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - params := domain.FilterQueryParams{ Sort: map[string]string{}, Filters: struct { @@ -86,7 +84,7 @@ func (h filterHandler) getFilters(w http.ResponseWriter, r *http.Request) { u, err := url.Parse(r.URL.String()) if err != nil { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]any{ "code": "BAD_REQUEST_PARAMS", "message": "indexer parameter is invalid", }) @@ -95,7 +93,7 @@ func (h filterHandler) getFilters(w http.ResponseWriter, r *http.Request) { vals := u.Query() params.Filters.Indexers = vals["indexer"] - trackers, err := h.service.Find(ctx, params) + trackers, err := h.service.Find(r.Context(), params) if err != nil { h.encoder.Error(w, err) return @@ -105,21 +103,16 @@ func (h filterHandler) getFilters(w http.ResponseWriter, r *http.Request) { } func (h filterHandler) getByID(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - filterID = chi.URLParam(r, "filterID") - ) - - id, err := strconv.Atoi(filterID) + filterID, err := strconv.Atoi(chi.URLParam(r, "filterID")) if err != nil { h.encoder.Error(w, err) return } - filter, err := h.service.FindByID(ctx, id) + filter, err := h.service.FindByID(r.Context(), filterID) if err != nil { if errors.Is(err, domain.ErrRecordNotFound) { - h.encoder.NotFoundErr(w, errors.New("filter with id %d not found", id)) + h.encoder.NotFoundErr(w, errors.New("filter with id %d not found", filterID)) return } @@ -131,20 +124,20 @@ func (h filterHandler) getByID(w http.ResponseWriter, r *http.Request) { } func (h filterHandler) duplicate(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - filterID = chi.URLParam(r, "filterID") - ) - - id, err := strconv.Atoi(filterID) + filterID, err := strconv.Atoi(chi.URLParam(r, "filterID")) if err != nil { h.encoder.Error(w, err) return } - filter, err := h.service.Duplicate(ctx, id) + filter, err := h.service.Duplicate(r.Context(), filterID) if err != nil { - h.encoder.StatusInternalError(w) + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("filter with id %d not found", filterID)) + return + } + + h.encoder.Error(w, err) return } @@ -152,17 +145,13 @@ func (h filterHandler) duplicate(w http.ResponseWriter, r *http.Request) { } func (h filterHandler) store(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data *domain.Filter - ) - + var data *domain.Filter if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - if err := h.service.Store(ctx, data); err != nil { + if err := h.service.Store(r.Context(), data); err != nil { h.encoder.Error(w, err) return } @@ -171,17 +160,13 @@ func (h filterHandler) store(w http.ResponseWriter, r *http.Request) { } func (h filterHandler) update(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data *domain.Filter - ) - + var data *domain.Filter if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - if err := h.service.Update(ctx, data); err != nil { + if err := h.service.Update(r.Context(), data); err != nil { h.encoder.Error(w, err) return } @@ -190,26 +175,20 @@ func (h filterHandler) update(w http.ResponseWriter, r *http.Request) { } func (h filterHandler) updatePartial(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.FilterUpdate - filterID = chi.URLParam(r, "filterID") - ) - - // set id from param and convert to int - id, err := strconv.Atoi(filterID) + var data domain.FilterUpdate + filterID, err := strconv.Atoi(chi.URLParam(r, "filterID")) if err != nil { h.encoder.Error(w, err) return } - data.ID = id + data.ID = filterID if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - if err := h.service.UpdatePartial(ctx, data); err != nil { + if err := h.service.UpdatePartial(r.Context(), data); err != nil { h.encoder.Error(w, err) return } @@ -218,26 +197,22 @@ func (h filterHandler) updatePartial(w http.ResponseWriter, r *http.Request) { } func (h filterHandler) toggleEnabled(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - filterID = chi.URLParam(r, "filterID") - data struct { - Enabled bool `json:"enabled"` - } - ) - - id, err := strconv.Atoi(filterID) + filterID, err := strconv.Atoi(chi.URLParam(r, "filterID")) if err != nil { h.encoder.Error(w, err) return } + var data struct { + Enabled bool `json:"enabled"` + } + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - if err := h.service.ToggleEnabled(ctx, id, data.Enabled); err != nil { + if err := h.service.ToggleEnabled(r.Context(), filterID, data.Enabled); err != nil { h.encoder.Error(w, err) return } @@ -246,19 +221,15 @@ func (h filterHandler) toggleEnabled(w http.ResponseWriter, r *http.Request) { } func (h filterHandler) delete(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - filterID = chi.URLParam(r, "filterID") - ) - - id, err := strconv.Atoi(filterID) + filterID, err := strconv.Atoi(chi.URLParam(r, "filterID")) if err != nil { h.encoder.Error(w, err) return } - if err := h.service.Delete(ctx, id); err != nil { + if err := h.service.Delete(r.Context(), filterID); err != nil { h.encoder.Error(w, err) + return } h.encoder.StatusResponse(w, http.StatusNoContent, nil) diff --git a/internal/http/indexer.go b/internal/http/indexer.go index d5dd268..c399962 100644 --- a/internal/http/indexer.go +++ b/internal/http/indexer.go @@ -10,6 +10,7 @@ import ( "strconv" "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" "github.com/go-chi/chi/v5" ) @@ -18,6 +19,7 @@ type indexerService interface { Store(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) Update(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) List(ctx context.Context) ([]domain.Indexer, error) + FindByID(ctx context.Context, id int) (*domain.Indexer, error) GetAll() ([]*domain.IndexerDefinition, error) GetTemplates() ([]domain.IndexerDefinition, error) Delete(ctx context.Context, id int) error @@ -46,6 +48,7 @@ func (h indexerHandler) Routes(r chi.Router) { r.Get("/options", h.list) r.Route("/{indexerID}", func(r chi.Router) { + r.Get("/", h.findByID) r.Put("/", h.update) r.Delete("/", h.delete) r.Post("/api/test", h.testApi) @@ -65,17 +68,13 @@ func (h indexerHandler) getSchema(w http.ResponseWriter, r *http.Request) { } func (h indexerHandler) store(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.Indexer - ) - + var data domain.Indexer if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - indexer, err := h.service.Store(ctx, data) + indexer, err := h.service.Store(r.Context(), data) if err != nil { h.encoder.Error(w, err) return @@ -85,17 +84,13 @@ func (h indexerHandler) store(w http.ResponseWriter, r *http.Request) { } func (h indexerHandler) update(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.Indexer - ) - + var data domain.Indexer if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - indexer, err := h.service.Update(ctx, data) + indexer, err := h.service.Update(r.Context(), data) if err != nil { h.encoder.Error(w, err) return @@ -105,18 +100,13 @@ func (h indexerHandler) update(w http.ResponseWriter, r *http.Request) { } func (h indexerHandler) delete(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - idParam = chi.URLParam(r, "indexerID") - ) - - id, err := strconv.Atoi(idParam) + indexerID, err := strconv.Atoi(chi.URLParam(r, "indexerID")) if err != nil { h.encoder.Error(w, err) return } - if err := h.service.Delete(ctx, id); err != nil { + if err := h.service.Delete(r.Context(), indexerID); err != nil { h.encoder.Error(w, err) return } @@ -135,9 +125,7 @@ func (h indexerHandler) getAll(w http.ResponseWriter, r *http.Request) { } func (h indexerHandler) list(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - indexers, err := h.service.List(ctx) + indexers, err := h.service.List(r.Context()) if err != nil { h.encoder.Error(w, err) return @@ -146,29 +134,45 @@ func (h indexerHandler) list(w http.ResponseWriter, r *http.Request) { h.encoder.StatusResponse(w, http.StatusOK, indexers) } -func (h indexerHandler) testApi(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - idParam = chi.URLParam(r, "indexerID") - req domain.IndexerTestApiRequest - ) - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - h.encoder.Error(w, err) - return - } - - id, err := strconv.Atoi(idParam) +func (h indexerHandler) findByID(w http.ResponseWriter, r *http.Request) { + indexerID, err := strconv.Atoi(chi.URLParam(r, "indexerID")) if err != nil { h.encoder.Error(w, err) return } - if req.IndexerId == 0 { - req.IndexerId = id + indexer, err := h.service.FindByID(r.Context(), indexerID) + if err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("indexer with id %d not found", indexerID)) + return + } + + h.encoder.Error(w, err) + return } - if err := h.service.TestApi(ctx, req); err != nil { + h.encoder.StatusResponse(w, http.StatusOK, indexer) +} + +func (h indexerHandler) testApi(w http.ResponseWriter, r *http.Request) { + indexerID, err := strconv.Atoi(chi.URLParam(r, "indexerID")) + if err != nil { + h.encoder.Error(w, err) + return + } + + var req domain.IndexerTestApiRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + h.encoder.Error(w, err) + return + } + + if req.IndexerId == 0 { + req.IndexerId = indexerID + } + + if err := h.service.TestApi(r.Context(), req); err != nil { h.encoder.Error(w, err) return } @@ -183,26 +187,22 @@ func (h indexerHandler) testApi(w http.ResponseWriter, r *http.Request) { } func (h indexerHandler) toggleEnabled(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - indexerID = chi.URLParam(r, "indexerID") - data struct { - Enabled bool `json:"enabled"` - } - ) - - id, err := strconv.Atoi(indexerID) + indexerID, err := strconv.Atoi(chi.URLParam(r, "indexerID")) if err != nil { h.encoder.Error(w, err) return } + var data struct { + Enabled bool `json:"enabled"` + } + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - if err := h.service.ToggleEnabled(ctx, id, data.Enabled); err != nil { + if err := h.service.ToggleEnabled(r.Context(), indexerID, data.Enabled); err != nil { h.encoder.Error(w, err) return } diff --git a/internal/http/irc.go b/internal/http/irc.go index 42a7318..68ba490 100644 --- a/internal/http/irc.go +++ b/internal/http/irc.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/autobrr/autobrr/pkg/errors" "net/http" "strconv" "strings" @@ -76,30 +77,29 @@ func (h ircHandler) Routes(r chi.Router) { } func (h ircHandler) listNetworks(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - networks, err := h.service.GetNetworksWithHealth(ctx) + networks, err := h.service.GetNetworksWithHealth(r.Context()) if err != nil { h.encoder.Error(w, err) + return } h.encoder.StatusResponse(w, http.StatusOK, networks) } func (h ircHandler) getNetworkByID(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - networkID = chi.URLParam(r, "networkID") - ) - - id, err := strconv.Atoi(networkID) + networkID, err := strconv.Atoi(chi.URLParam(r, "networkID")) if err != nil { h.encoder.Error(w, err) return } - network, err := h.service.GetNetworkByID(ctx, int64(id)) + network, err := h.service.GetNetworkByID(r.Context(), int64(networkID)) if err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("network with id %d not found", networkID)) + return + } + h.encoder.Error(w, err) return } @@ -108,18 +108,18 @@ func (h ircHandler) getNetworkByID(w http.ResponseWriter, r *http.Request) { } func (h ircHandler) restartNetwork(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - networkID = chi.URLParam(r, "networkID") - ) - - id, err := strconv.Atoi(networkID) + networkID, err := strconv.Atoi(chi.URLParam(r, "networkID")) if err != nil { h.encoder.Error(w, err) return } - if err := h.service.RestartNetwork(ctx, int64(id)); err != nil { + if err := h.service.RestartNetwork(r.Context(), int64(networkID)); err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("network with id %d not found", networkID)) + return + } + h.encoder.Error(w, err) return } @@ -129,7 +129,6 @@ func (h ircHandler) restartNetwork(w http.ResponseWriter, r *http.Request) { func (h ircHandler) storeNetwork(w http.ResponseWriter, r *http.Request) { var data domain.IrcNetwork - if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return @@ -144,17 +143,13 @@ func (h ircHandler) storeNetwork(w http.ResponseWriter, r *http.Request) { } func (h ircHandler) updateNetwork(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.IrcNetwork - ) - + var data domain.IrcNetwork if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - if err := h.service.UpdateNetwork(ctx, &data); err != nil { + if err := h.service.UpdateNetwork(r.Context(), &data); err != nil { h.encoder.Error(w, err) return } @@ -163,26 +158,21 @@ func (h ircHandler) updateNetwork(w http.ResponseWriter, r *http.Request) { } func (h ircHandler) sendCmd(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - networkID = chi.URLParam(r, "networkID") - data domain.SendIrcCmdRequest - ) - - id, err := strconv.Atoi(networkID) + networkID, err := strconv.Atoi(chi.URLParam(r, "networkID")) if err != nil { h.encoder.Error(w, err) return } + var data domain.SendIrcCmdRequest if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - data.NetworkId = int64(id) + data.NetworkId = int64(networkID) - if err := h.service.SendCmd(ctx, &data); err != nil { + if err := h.service.SendCmd(r.Context(), &data); err != nil { h.encoder.Error(w, err) return } @@ -192,49 +182,32 @@ func (h ircHandler) sendCmd(w http.ResponseWriter, r *http.Request) { // announceProcess manually trigger announce process func (h ircHandler) announceProcess(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.IRCManualProcessRequest - ) - - paramNetworkID := chi.URLParam(r, "networkID") - if paramNetworkID == "" { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ - "code": "BAD_REQUEST_PARAMS", - "message": "parameter networkID missing", - }) - return - } - - networkID, err := strconv.Atoi(paramNetworkID) + networkID, err := strconv.Atoi(chi.URLParam(r, "networkID")) if err != nil { h.encoder.Error(w, err) return } - paramChannel := chi.URLParam(r, "channel") - if paramChannel == "" { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ - "code": "BAD_REQUEST_PARAMS", - "message": "parameter channel missing", - }) - return - } - + var data domain.IRCManualProcessRequest if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } data.NetworkId = int64(networkID) - data.Channel = paramChannel + data.Channel = chi.URLParam(r, "channel") // we cant pass # as an url parameter so the frontend has to strip it - if !strings.HasPrefix("#", data.Channel) { + if !strings.HasPrefix(data.Channel, "#") { data.Channel = fmt.Sprintf("#%s", data.Channel) } - if err := h.service.ManualProcessAnnounce(ctx, &data); err != nil { + if err := h.service.ManualProcessAnnounce(r.Context(), &data); err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("network with id %d not found", data.NetworkId)) + return + } + h.encoder.Error(w, err) return } @@ -243,24 +216,19 @@ func (h ircHandler) announceProcess(w http.ResponseWriter, r *http.Request) { } func (h ircHandler) storeChannel(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - networkID = chi.URLParam(r, "networkID") - data domain.IrcChannel - ) - - id, err := strconv.Atoi(networkID) + networkID, err := strconv.Atoi(chi.URLParam(r, "networkID")) if err != nil { h.encoder.Error(w, err) return } + var data domain.IrcChannel if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - if err := h.service.StoreChannel(ctx, int64(id), &data); err != nil { + if err := h.service.StoreChannel(r.Context(), int64(networkID), &data); err != nil { h.encoder.Error(w, err) return } @@ -269,18 +237,18 @@ func (h ircHandler) storeChannel(w http.ResponseWriter, r *http.Request) { } func (h ircHandler) deleteNetwork(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - networkID = chi.URLParam(r, "networkID") - ) - - id, err := strconv.Atoi(networkID) + networkID, err := strconv.Atoi(chi.URLParam(r, "networkID")) if err != nil { h.encoder.Error(w, err) return } - if err := h.service.DeleteNetwork(ctx, int64(id)); err != nil { + if err := h.service.DeleteNetwork(r.Context(), int64(networkID)); err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("network with id %d does not exist", networkID)) + return + } + h.encoder.Error(w, err) return } diff --git a/internal/http/logs.go b/internal/http/logs.go index 353ebe7..cddfe26 100644 --- a/internal/http/logs.go +++ b/internal/http/logs.go @@ -39,7 +39,7 @@ func (h logsHandler) Routes(r chi.Router) { func (h logsHandler) files(w http.ResponseWriter, r *http.Request) { response := LogfilesResponse{ - Files: []logFile{}, + Files: []LogFile{}, Count: 0, } @@ -67,7 +67,7 @@ func (h logsHandler) files(w http.ResponseWriter, r *http.Request) { return err } - response.Files = append(response.Files, logFile{ + response.Files = append(response.Files, LogFile{ Name: d.Name(), SizeBytes: i.Size(), Size: humanize.Bytes(uint64(i.Size())), @@ -218,17 +218,11 @@ func (h logsHandler) downloadFile(w http.ResponseWriter, r *http.Request) { } logFile := chi.URLParam(r, "logFile") - if logFile == "" { + + if !strings.Contains(logFile, ".log") { render.Status(r, http.StatusBadRequest) render.JSON(w, r, errorResponse{ - Message: "empty log file", - Status: http.StatusBadRequest, - }) - return - } else if !strings.Contains(logFile, ".log") { - render.Status(r, http.StatusBadRequest) - render.JSON(w, r, errorResponse{ - Message: "invalid file", + Message: "invalid log file. Must have .log extension", Status: http.StatusBadRequest, }) return @@ -250,7 +244,7 @@ func (h logsHandler) downloadFile(w http.ResponseWriter, r *http.Request) { } } -type logFile struct { +type LogFile struct { Name string `json:"filename"` SizeBytes int64 `json:"size_bytes"` Size string `json:"size"` @@ -258,6 +252,6 @@ type logFile struct { } type LogfilesResponse struct { - Files []logFile `json:"files"` + Files []LogFile `json:"files"` Count int `json:"count"` } diff --git a/internal/http/notification.go b/internal/http/notification.go index e44b0f4..78dc819 100644 --- a/internal/http/notification.go +++ b/internal/http/notification.go @@ -6,6 +6,7 @@ package http import ( "context" "encoding/json" + "github.com/autobrr/autobrr/pkg/errors" "net/http" "strconv" @@ -41,15 +42,14 @@ func (h notificationHandler) Routes(r chi.Router) { r.Post("/test", h.test) r.Route("/{notificationID}", func(r chi.Router) { + r.Get("/", h.findByID) r.Put("/", h.update) r.Delete("/", h.delete) }) } func (h notificationHandler) list(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - list, _, err := h.service.Find(ctx, domain.NotificationQueryParams{}) + list, _, err := h.service.Find(r.Context(), domain.NotificationQueryParams{}) if err != nil { h.encoder.StatusNotFound(w) return @@ -59,17 +59,13 @@ func (h notificationHandler) list(w http.ResponseWriter, r *http.Request) { } func (h notificationHandler) store(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.Notification - ) - + var data domain.Notification if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - filter, err := h.service.Store(ctx, data) + filter, err := h.service.Store(r.Context(), data) if err != nil { h.encoder.Error(w, err) return @@ -78,18 +74,35 @@ func (h notificationHandler) store(w http.ResponseWriter, r *http.Request) { h.encoder.StatusResponse(w, http.StatusCreated, filter) } -func (h notificationHandler) update(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.Notification - ) +func (h notificationHandler) findByID(w http.ResponseWriter, r *http.Request) { + notificationID, err := strconv.Atoi(chi.URLParam(r, "notificationID")) + if err != nil { + h.encoder.Error(w, err) + return + } + notification, err := h.service.FindByID(r.Context(), notificationID) + if err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("notification with id %d not found", notificationID)) + return + } + + h.encoder.Error(w, err) + return + } + + h.encoder.StatusResponse(w, http.StatusNoContent, notification) +} + +func (h notificationHandler) update(w http.ResponseWriter, r *http.Request) { + var data domain.Notification if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - filter, err := h.service.Update(ctx, data) + filter, err := h.service.Update(r.Context(), data) if err != nil { h.encoder.Error(w, err) return @@ -99,14 +112,13 @@ func (h notificationHandler) update(w http.ResponseWriter, r *http.Request) { } func (h notificationHandler) delete(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - notificationID = chi.URLParam(r, "notificationID") - ) + notificationID, err := strconv.Atoi(chi.URLParam(r, "notificationID")) + if err != nil { + h.encoder.Error(w, err) + return + } - id, _ := strconv.Atoi(notificationID) - - if err := h.service.Delete(ctx, id); err != nil { + if err := h.service.Delete(r.Context(), notificationID); err != nil { h.encoder.Error(w, err) return } @@ -115,17 +127,13 @@ func (h notificationHandler) delete(w http.ResponseWriter, r *http.Request) { } func (h notificationHandler) test(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.Notification - ) - + var data domain.Notification if err := json.NewDecoder(r.Body).Decode(&data); err != nil { h.encoder.Error(w, err) return } - if err := h.service.Test(ctx, data); err != nil { + if err := h.service.Test(r.Context(), data); err != nil { h.encoder.Error(w, err) return } diff --git a/internal/http/release.go b/internal/http/release.go index 492c61b..3639315 100644 --- a/internal/http/release.go +++ b/internal/http/release.go @@ -12,6 +12,7 @@ import ( "strconv" "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" "github.com/go-chi/chi/v5" ) @@ -19,6 +20,7 @@ import ( type releaseService interface { Find(ctx context.Context, query domain.ReleaseQueryParams) (res []*domain.Release, nextCursor int64, count int64, err error) FindRecent(ctx context.Context) (res []*domain.Release, err error) + Get(ctx context.Context, req *domain.GetReleaseRequest) (*domain.Release, error) GetIndexerOptions(ctx context.Context) ([]string, error) Stats(ctx context.Context) (*domain.ReleaseStats, error) Delete(ctx context.Context, req *domain.DeleteReleaseRequest) error @@ -45,19 +47,19 @@ func (h releaseHandler) Routes(r chi.Router) { r.Get("/indexers", h.getIndexerOptions) r.Delete("/", h.deleteReleases) - r.Post("/process", h.retryAction) + //r.Post("/process", h.retryAction) - r.Route("/{releaseId}", func(r chi.Router) { - r.Post("/actions/{actionStatusId}/retry", h.retryAction) + r.Route("/{releaseID}", func(r chi.Router) { + r.Get("/", h.getReleaseByID) + r.Post("/actions/{actionStatusID}/retry", h.retryAction) }) } func (h releaseHandler) findReleases(w http.ResponseWriter, r *http.Request) { - limitP := r.URL.Query().Get("limit") limit, err := strconv.Atoi(limitP) if err != nil && limitP != "" { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]any{ "code": "BAD_REQUEST_PARAMS", "message": "limit parameter is invalid", }) @@ -70,7 +72,7 @@ func (h releaseHandler) findReleases(w http.ResponseWriter, r *http.Request) { offsetP := r.URL.Query().Get("offset") offset, err := strconv.Atoi(offsetP) if err != nil && offsetP != "" { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]any{ "code": "BAD_REQUEST_PARAMS", "message": "offset parameter is invalid", }) @@ -82,7 +84,7 @@ func (h releaseHandler) findReleases(w http.ResponseWriter, r *http.Request) { if cursorP != "" { cursor, err = strconv.Atoi(cursorP) if err != nil && cursorP != "" { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]any{ "code": "BAD_REQUEST_PARAMS", "message": "cursor parameter is invalid", }) @@ -92,7 +94,7 @@ func (h releaseHandler) findReleases(w http.ResponseWriter, r *http.Request) { u, err := url.Parse(r.URL.String()) if err != nil { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]any{ "code": "BAD_REQUEST_PARAMS", "message": "indexer parameter is invalid", }) @@ -104,7 +106,7 @@ func (h releaseHandler) findReleases(w http.ResponseWriter, r *http.Request) { pushStatus := r.URL.Query().Get("push_status") if pushStatus != "" { if !domain.ValidReleasePushStatus(pushStatus) { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]any{ "code": "BAD_REQUEST_PARAMS", "message": fmt.Sprintf("push_status parameter is of invalid type: %v", pushStatus), }) @@ -128,7 +130,7 @@ func (h releaseHandler) findReleases(w http.ResponseWriter, r *http.Request) { releases, nextCursor, count, err := h.service.Find(r.Context(), query) if err != nil { - h.encoder.StatusResponse(w, http.StatusInternalServerError, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusInternalServerError, map[string]any{ "code": "INTERNAL_SERVER_ERROR", "message": err.Error(), }) @@ -149,10 +151,9 @@ func (h releaseHandler) findReleases(w http.ResponseWriter, r *http.Request) { } func (h releaseHandler) findRecentReleases(w http.ResponseWriter, r *http.Request) { - releases, err := h.service.FindRecent(r.Context()) if err != nil { - h.encoder.StatusResponse(w, http.StatusInternalServerError, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusInternalServerError, map[string]any{ "code": "INTERNAL_SERVER_ERROR", "message": err.Error(), }) @@ -168,10 +169,31 @@ func (h releaseHandler) findRecentReleases(w http.ResponseWriter, r *http.Reques h.encoder.StatusResponse(w, http.StatusOK, ret) } +func (h releaseHandler) getReleaseByID(w http.ResponseWriter, r *http.Request) { + releaseID, err := strconv.Atoi(chi.URLParam(r, "releaseID")) + if err != nil { + h.encoder.Error(w, err) + return + } + + release, err := h.service.Get(r.Context(), &domain.GetReleaseRequest{Id: releaseID}) + if err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, errors.New("could not find release with id %d", releaseID)) + return + } + + h.encoder.Error(w, err) + return + } + + h.encoder.StatusResponse(w, http.StatusOK, release) +} + func (h releaseHandler) getIndexerOptions(w http.ResponseWriter, r *http.Request) { stats, err := h.service.GetIndexerOptions(r.Context()) if err != nil { - h.encoder.StatusResponse(w, http.StatusInternalServerError, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusInternalServerError, map[string]any{ "code": "INTERNAL_SERVER_ERROR", "message": err.Error(), }) @@ -182,10 +204,9 @@ func (h releaseHandler) getIndexerOptions(w http.ResponseWriter, r *http.Request } func (h releaseHandler) getStats(w http.ResponseWriter, r *http.Request) { - stats, err := h.service.Stats(r.Context()) if err != nil { - h.encoder.StatusResponse(w, http.StatusInternalServerError, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusInternalServerError, map[string]any{ "code": "INTERNAL_SERVER_ERROR", "message": err.Error(), }) @@ -202,7 +223,7 @@ func (h releaseHandler) deleteReleases(w http.ResponseWriter, r *http.Request) { if olderThanParam != "" { duration, err := strconv.Atoi(olderThanParam) if err != nil { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]any{ "code": "BAD_REQUEST_PARAMS", "message": "olderThan parameter is invalid", }) @@ -227,7 +248,7 @@ func (h releaseHandler) deleteReleases(w http.ResponseWriter, r *http.Request) { if _, valid := validStatuses[status]; valid { filteredStatuses = append(filteredStatuses, status) } else { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]any{ "code": "INVALID_RELEASE_STATUS", "message": "releaseStatus contains invalid value", }) @@ -246,7 +267,6 @@ func (h releaseHandler) deleteReleases(w http.ResponseWriter, r *http.Request) { func (h releaseHandler) process(w http.ResponseWriter, r *http.Request) { var req *domain.ReleaseProcessReq - err := json.NewDecoder(r.Body).Decode(&req) if err != nil { h.encoder.Error(w, err) @@ -254,14 +274,14 @@ func (h releaseHandler) process(w http.ResponseWriter, r *http.Request) { } if req.IndexerIdentifier == "" { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]any{ "code": "VALIDATION_ERROR", "message": "field indexer_identifier empty", }) } if len(req.AnnounceLines) == 0 { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ + h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]any{ "code": "VALIDATION_ERROR", "message": "field announce_lines empty", }) @@ -277,47 +297,29 @@ func (h releaseHandler) process(w http.ResponseWriter, r *http.Request) { } func (h releaseHandler) retryAction(w http.ResponseWriter, r *http.Request) { - var ( - req *domain.ReleaseActionRetryReq - err error - ) - - releaseIdParam := chi.URLParam(r, "releaseId") - if releaseIdParam == "" { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ - "code": "BAD_REQUEST_PARAMS", - "message": "parameter releaseId missing", - }) - return - } - - releaseId, err := strconv.Atoi(releaseIdParam) + releaseID, err := strconv.Atoi(chi.URLParam(r, "releaseID")) if err != nil { h.encoder.StatusError(w, http.StatusBadRequest, err) return } - actionStatusIdParam := chi.URLParam(r, "actionStatusId") - if actionStatusIdParam == "" { - h.encoder.StatusResponse(w, http.StatusBadRequest, map[string]interface{}{ - "code": "BAD_REQUEST_PARAMS", - "message": "parameter actionStatusId missing", - }) - return - } - - actionStatusId, err := strconv.Atoi(actionStatusIdParam) + actionStatusId, err := strconv.Atoi(chi.URLParam(r, "actionStatusID")) if err != nil { h.encoder.StatusError(w, http.StatusBadRequest, err) return } - req = &domain.ReleaseActionRetryReq{ - ReleaseId: releaseId, + req := &domain.ReleaseActionRetryReq{ + ReleaseId: releaseID, ActionStatusId: actionStatusId, } if err := h.service.Retry(r.Context(), req); err != nil { + if errors.Is(err, domain.ErrRecordNotFound) { + h.encoder.NotFoundErr(w, err) + return + } + h.encoder.Error(w, err) return } diff --git a/internal/notification/service.go b/internal/notification/service.go index dad9f58..1f570f5 100644 --- a/internal/notification/service.go +++ b/internal/notification/service.go @@ -29,14 +29,14 @@ type Service interface { type service struct { log zerolog.Logger repo domain.NotificationRepo - senders []domain.NotificationSender + senders map[int]domain.NotificationSender } func NewService(log logger.Logger, repo domain.NotificationRepo) Service { s := &service{ log: log.With().Str("module", "notification").Logger(), repo: repo, - senders: []domain.NotificationSender{}, + senders: make(map[int]domain.NotificationSender), } s.registerSenders() @@ -45,53 +45,47 @@ func NewService(log logger.Logger, repo domain.NotificationRepo) Service { } func (s *service) Find(ctx context.Context, params domain.NotificationQueryParams) ([]domain.Notification, int, error) { - n, count, err := s.repo.Find(ctx, params) + notifications, count, err := s.repo.Find(ctx, params) if err != nil { s.log.Error().Err(err).Msgf("could not find notification with params: %+v", params) return nil, 0, err } - return n, count, err + return notifications, count, err } func (s *service) FindByID(ctx context.Context, id int) (*domain.Notification, error) { - n, err := s.repo.FindByID(ctx, id) + notification, err := s.repo.FindByID(ctx, id) if err != nil { s.log.Error().Err(err).Msgf("could not find notification by id: %v", id) return nil, err } - return n, err + return notification, err } -func (s *service) Store(ctx context.Context, n domain.Notification) (*domain.Notification, error) { - _, err := s.repo.Store(ctx, n) +func (s *service) Store(ctx context.Context, notification domain.Notification) (*domain.Notification, error) { + _, err := s.repo.Store(ctx, notification) if err != nil { - s.log.Error().Err(err).Msgf("could not store notification: %+v", n) + s.log.Error().Err(err).Msgf("could not store notification: %+v", notification) return nil, err } - // reset senders - s.senders = []domain.NotificationSender{} - - // re register senders - s.registerSenders() + // register sender + s.registerSender(notification) return nil, nil } -func (s *service) Update(ctx context.Context, n domain.Notification) (*domain.Notification, error) { - _, err := s.repo.Update(ctx, n) +func (s *service) Update(ctx context.Context, notification domain.Notification) (*domain.Notification, error) { + _, err := s.repo.Update(ctx, notification) if err != nil { - s.log.Error().Err(err).Msgf("could not update notification: %+v", n) + s.log.Error().Err(err).Msgf("could not update notification: %+v", notification) return nil, err } - // reset senders - s.senders = []domain.NotificationSender{} - - // re register senders - s.registerSenders() + // register sender + s.registerSender(notification) return nil, nil } @@ -103,42 +97,46 @@ func (s *service) Delete(ctx context.Context, id int) error { return err } - // reset senders - s.senders = []domain.NotificationSender{} - - // re register senders - s.registerSenders() + // delete sender + delete(s.senders, id) return nil } func (s *service) registerSenders() { - senders, err := s.repo.List(context.Background()) + notificationSenders, err := s.repo.List(context.Background()) if err != nil { s.log.Error().Err(err).Msg("could not find notifications") return } - for _, n := range senders { - if n.Enabled { - switch n.Type { - case domain.NotificationTypeDiscord: - s.senders = append(s.senders, NewDiscordSender(s.log, n)) - case domain.NotificationTypeGotify: - s.senders = append(s.senders, NewGotifySender(s.log, n)) - case domain.NotificationTypeLunaSea: - s.senders = append(s.senders, NewLunaSeaSender(s.log, n)) - case domain.NotificationTypeNotifiarr: - s.senders = append(s.senders, NewNotifiarrSender(s.log, n)) - case domain.NotificationTypeNtfy: - s.senders = append(s.senders, NewNtfySender(s.log, n)) - case domain.NotificationTypePushover: - s.senders = append(s.senders, NewPushoverSender(s.log, n)) - case domain.NotificationTypeShoutrrr: - s.senders = append(s.senders, NewShoutrrrSender(s.log, n)) - case domain.NotificationTypeTelegram: - s.senders = append(s.senders, NewTelegramSender(s.log, n)) - } + for _, notificationSender := range notificationSenders { + s.registerSender(notificationSender) + } + + return +} + +// registerSender registers an enabled notification via it's id +func (s *service) registerSender(notification domain.Notification) { + if notification.Enabled { + switch notification.Type { + case domain.NotificationTypeDiscord: + s.senders[notification.ID] = NewDiscordSender(s.log, notification) + case domain.NotificationTypeGotify: + s.senders[notification.ID] = NewGotifySender(s.log, notification) + case domain.NotificationTypeLunaSea: + s.senders[notification.ID] = NewLunaSeaSender(s.log, notification) + case domain.NotificationTypeNotifiarr: + s.senders[notification.ID] = NewNotifiarrSender(s.log, notification) + case domain.NotificationTypeNtfy: + s.senders[notification.ID] = NewNtfySender(s.log, notification) + case domain.NotificationTypePushover: + s.senders[notification.ID] = NewPushoverSender(s.log, notification) + case domain.NotificationTypeShoutrrr: + s.senders[notification.ID] = NewShoutrrrSender(s.log, notification) + case domain.NotificationTypeTelegram: + s.senders[notification.ID] = NewTelegramSender(s.log, notification) } }