diff --git a/internal/database/release.go b/internal/database/release.go index 590672a..26ee396 100644 --- a/internal/database/release.go +++ b/internal/database/release.go @@ -283,3 +283,33 @@ FROM "release";` return &rls, nil } + +func (repo *ReleaseRepo) Delete(ctx context.Context) error { + tx, err := repo.db.BeginTx(ctx, nil) + if err != nil { + return err + } + + defer tx.Rollback() + + _, err = tx.ExecContext(ctx, `DELETE FROM "release"`) + if err != nil { + log.Error().Stack().Err(err).Msg("error deleting all releases") + return err + } + + _, err = tx.ExecContext(ctx, `DELETE FROM release_action_status`) + if err != nil { + log.Error().Stack().Err(err).Msg("error deleting all release_action_status") + return err + } + + err = tx.Commit() + if err != nil { + log.Error().Stack().Err(err).Msg("error deleting all releases") + return err + + } + + return nil +} diff --git a/internal/domain/release.go b/internal/domain/release.go index 2d7ecf3..bfa6738 100644 --- a/internal/domain/release.go +++ b/internal/domain/release.go @@ -31,6 +31,7 @@ type ReleaseRepo interface { GetActionStatusByReleaseID(ctx context.Context, releaseID int64) ([]ReleaseActionStatus, error) Stats(ctx context.Context) (*ReleaseStats, error) StoreReleaseActionStatus(ctx context.Context, actionStatus *ReleaseActionStatus) error + Delete(ctx context.Context) error } type Release struct { diff --git a/internal/http/encoder.go b/internal/http/encoder.go index 80eb41a..9efef21 100644 --- a/internal/http/encoder.go +++ b/internal/http/encoder.go @@ -21,6 +21,14 @@ func (e encoder) StatusResponse(ctx context.Context, w http.ResponseWriter, resp } } +func (e encoder) StatusNoContent(w http.ResponseWriter) { + w.WriteHeader(http.StatusNoContent) +} + func (e encoder) StatusNotFound(ctx context.Context, w http.ResponseWriter) { w.WriteHeader(http.StatusNotFound) } + +func (e encoder) StatusInternalError(w http.ResponseWriter) { + w.WriteHeader(http.StatusInternalServerError) +} diff --git a/internal/http/release.go b/internal/http/release.go index 4cc347a..472225f 100644 --- a/internal/http/release.go +++ b/internal/http/release.go @@ -14,6 +14,7 @@ type releaseService interface { Find(ctx context.Context, query domain.ReleaseQueryParams) (res []domain.Release, nextCursor int64, count int64, err error) GetIndexerOptions(ctx context.Context) ([]string, error) Stats(ctx context.Context) (*domain.ReleaseStats, error) + Delete(ctx context.Context) error } type releaseHandler struct { @@ -32,6 +33,7 @@ func (h releaseHandler) Routes(r chi.Router) { r.Get("/", h.findReleases) r.Get("/stats", h.getStats) r.Get("/indexers", h.getIndexerOptions) + r.Delete("/all", h.deleteReleases) } func (h releaseHandler) findReleases(w http.ResponseWriter, r *http.Request) { @@ -135,3 +137,13 @@ func (h releaseHandler) getStats(w http.ResponseWriter, r *http.Request) { h.encoder.StatusResponse(r.Context(), w, stats, http.StatusOK) } + +func (h releaseHandler) deleteReleases(w http.ResponseWriter, r *http.Request) { + err := h.service.Delete(r.Context()) + if err != nil { + h.encoder.StatusInternalError(w) + return + } + + h.encoder.StatusNoContent(w) +} diff --git a/internal/release/service.go b/internal/release/service.go index 023b020..4aa516f 100644 --- a/internal/release/service.go +++ b/internal/release/service.go @@ -16,6 +16,7 @@ type Service interface { Store(ctx context.Context, release *domain.Release) error StoreReleaseActionStatus(ctx context.Context, actionStatus *domain.ReleaseActionStatus) error Process(release domain.Release) error + Delete(ctx context.Context) error } type service struct { @@ -44,12 +45,7 @@ func (s *service) GetIndexerOptions(ctx context.Context) ([]string, error) { } func (s *service) Stats(ctx context.Context) (*domain.ReleaseStats, error) { - stats, err := s.repo.Stats(ctx) - if err != nil { - return nil, err - } - - return stats, nil + return s.repo.Stats(ctx) } func (s *service) Store(ctx context.Context, release *domain.Release) error { @@ -62,12 +58,7 @@ func (s *service) Store(ctx context.Context, release *domain.Release) error { } func (s *service) StoreReleaseActionStatus(ctx context.Context, actionStatus *domain.ReleaseActionStatus) error { - err := s.repo.StoreReleaseActionStatus(ctx, actionStatus) - if err != nil { - return err - } - - return nil + return s.repo.StoreReleaseActionStatus(ctx, actionStatus) } func (s *service) Process(release domain.Release) error { @@ -88,3 +79,7 @@ func (s *service) Process(release domain.Release) error { return nil } + +func (s *service) Delete(ctx context.Context) error { + return s.repo.Delete(ctx) +} diff --git a/web/src/api/APIClient.ts b/web/src/api/APIClient.ts index a662287..6198260 100644 --- a/web/src/api/APIClient.ts +++ b/web/src/api/APIClient.ts @@ -119,6 +119,7 @@ export const APIClient = { return appClient.Get(`api/release?${params.toString()}`) }, indexerOptions: () => appClient.Get(`api/release/indexers`), - stats: () => appClient.Get("api/release/stats") + stats: () => appClient.Get("api/release/stats"), + delete: () => appClient.Delete(`api/release/all`), } }; \ No newline at end of file diff --git a/web/src/screens/Settings.tsx b/web/src/screens/Settings.tsx index dc30855..e884d79 100644 --- a/web/src/screens/Settings.tsx +++ b/web/src/screens/Settings.tsx @@ -1,4 +1,4 @@ -import {CogIcon, DownloadIcon, KeyIcon} from '@heroicons/react/outline' +import {CogIcon, CollectionIcon, DownloadIcon, KeyIcon} from '@heroicons/react/outline' import {NavLink, Route, Switch as RouteSwitch, useLocation, useRouteMatch} from "react-router-dom"; import { classNames } from "../utils"; @@ -7,12 +7,14 @@ import { IrcSettings } from "./settings/Irc"; import ApplicationSettings from "./settings/Application"; import DownloadClientSettings from "./settings/DownloadClient"; import { RegexPlayground } from './settings/RegexPlayground'; +import ReleaseSettings from "./settings/Releases"; const subNavigation = [ {name: 'Application', href: '', icon: CogIcon, current: true}, {name: 'Indexers', href: 'indexers', icon: KeyIcon, current: false}, {name: 'IRC', href: 'irc', icon: KeyIcon, current: false}, {name: 'Clients', href: 'clients', icon: DownloadIcon, current: false}, + {name: 'Releases', href: 'releases', icon: CollectionIcon, current: false}, // {name: 'Regex Playground', href: 'regex-playground', icon: CogIcon, current: false} // {name: 'Actions', href: 'actions', icon: PlayIcon, current: false}, // {name: 'Rules', href: 'rules', icon: ClipboardCheckIcon, current: false}, @@ -91,6 +93,10 @@ export default function Settings() { + + + + {/* */} diff --git a/web/src/screens/settings/Releases.tsx b/web/src/screens/settings/Releases.tsx new file mode 100644 index 0000000..eb227d8 --- /dev/null +++ b/web/src/screens/settings/Releases.tsx @@ -0,0 +1,81 @@ +import { useRef } from "react"; +import { useMutation } from "react-query"; +import { toast } from "react-hot-toast"; + +import { APIClient } from "../../api/APIClient"; +import Toast from "../../components/notifications/Toast"; +import { queryClient } from "../../App"; +import { useToggle } from "../../hooks/hooks"; +import { DeleteModal } from "../../components/modals"; + +function ReleaseSettings() { + const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false); + const deleteMutation = useMutation(() => APIClient.release.delete(), { + onSuccess: () => { + toast.custom((t) => ( + + )); + + // Invalidate filters just in case, most likely not necessary but can't hurt. + queryClient.invalidateQueries("releases"); + + toggleDeleteModal() + } + }) + + const deleteAction = () => { + deleteMutation.mutate() + } + + const cancelModalButtonRef = useRef(null); + + return ( +
+ + +
+
+

Releases

+

+ Release settings. Reset state. +

+
+
+ +
+
+
+ +
+

Danger Zone

+
+ +
    +
    +

    + Delete all releases +

    + +
    +
+
+
+
+ + ) +} + +export default ReleaseSettings; \ No newline at end of file