diff --git a/internal/database/release.go b/internal/database/release.go index 9288c3d..af2cf94 100644 --- a/internal/database/release.go +++ b/internal/database/release.go @@ -97,19 +97,19 @@ func (repo *ReleaseRepo) StoreReleaseActionStatus(ctx context.Context, status *d return nil } -func (repo *ReleaseRepo) Find(ctx context.Context, params domain.ReleaseQueryParams) ([]*domain.Release, int64, int64, error) { +func (repo *ReleaseRepo) Find(ctx context.Context, params domain.ReleaseQueryParams) (*domain.FindReleasesResponse, error) { tx, err := repo.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted}) if err != nil { - return nil, 0, 0, errors.Wrap(err, "error begin transaction") + return nil, errors.Wrap(err, "error begin transaction") } defer tx.Rollback() - releases, nextCursor, total, err := repo.findReleases(ctx, tx, params) + resp, err := repo.findReleases(ctx, tx, params) if err != nil { - return nil, nextCursor, total, err + return nil, err } - return releases, nextCursor, total, nil + return resp, nil } var reservedSearch = map[string]*regexp.Regexp{ @@ -126,7 +126,7 @@ var reservedSearch = map[string]*regexp.Regexp{ "r.filter": regexp.MustCompile(`(?i)(?:` + `filter` + `:)(?P'.*?'|".*?"|\S+)`), } -func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain.ReleaseQueryParams) ([]*domain.Release, int64, int64, error) { +func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain.ReleaseQueryParams) (*domain.FindReleasesResponse, error) { whereQueryBuilder := sq.And{} if params.Cursor > 0 { whereQueryBuilder = append(whereQueryBuilder, sq.Lt{"r.id": params.Cursor}) @@ -172,7 +172,7 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain whereQuery, _, err := whereQueryBuilder.ToSql() if err != nil { - return nil, 0, 0, errors.Wrap(err, "error building wherequery") + return nil, errors.Wrap(err, "error building wherequery") } subQueryBuilder := repo.db.squirrel. @@ -206,53 +206,57 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain subQuery, subArgs, err := subQueryBuilder.ToSql() if err != nil { - return nil, 0, 0, errors.Wrap(err, "error building subquery") + return nil, errors.Wrap(err, "error building subquery") } queryBuilder := repo.db.squirrel. - Select("r.id", "r.filter_status", "r.rejections", "r.indexer", "r.filter", "r.protocol", "r.info_url", "r.download_url", "r.title", "r.torrent_name", "r.size", "r.category", "r.season", "r.episode", "r.year", "r.resolution", "r.source", "r.codec", "r.container", "r.release_group", "r.timestamp", + Select("r.id", "r.filter_status", "r.rejections", "r.indexer", "i.id", "i.name", "i.identifier_external", "r.filter", "r.protocol", "r.info_url", "r.download_url", "r.title", "r.torrent_name", "r.size", "r.category", "r.season", "r.episode", "r.year", "r.resolution", "r.source", "r.codec", "r.container", "r.release_group", "r.timestamp", "ras.id", "ras.status", "ras.action", "ras.action_id", "ras.type", "ras.client", "ras.filter", "ras.filter_id", "ras.release_id", "ras.rejections", "ras.timestamp"). Column(sq.Alias(countQuery, "page_total")). From("release r"). OrderBy("r.id DESC"). Where("r.id IN ("+subQuery+")", subArgs...). - LeftJoin("release_action_status ras ON r.id = ras.release_id") + LeftJoin("release_action_status ras ON r.id = ras.release_id"). + LeftJoin("indexer i ON r.indexer = i.identifier") query, args, err := queryBuilder.ToSql() if err != nil { - return nil, 0, 0, errors.Wrap(err, "error building query") + return nil, errors.Wrap(err, "error building query") } repo.log.Trace().Str("database", "release.find").Msgf("query: '%v', args: '%v'", query, args) - res := make([]*domain.Release, 0) + resp := &domain.FindReleasesResponse{ + Data: make([]*domain.Release, 0), + TotalCount: 0, + NextCursor: 0, + } rows, err := tx.QueryContext(ctx, query, args...) if err != nil { - return nil, 0, 0, errors.Wrap(err, "error executing query") + return resp, errors.Wrap(err, "error executing query") } defer rows.Close() if err := rows.Err(); err != nil { - return res, 0, 0, errors.Wrap(err, "error rows findreleases") + return resp, errors.Wrap(err, "error rows findreleases") } - var countItems int64 = 0 - for rows.Next() { var rls domain.Release var ras domain.ReleaseActionStatus - var rlsindexer, rlsfilter, infoUrl, downloadUrl, codec sql.NullString + var rlsIndexer, rlsIndexerName, rlsIndexerExternalName, rlsFilter, infoUrl, downloadUrl, codec sql.NullString + var rlsIndexerID sql.NullInt64 var rasId, rasFilterId, rasReleaseId, rasActionId sql.NullInt64 var rasStatus, rasAction, rasType, rasClient, rasFilter sql.NullString var rasRejections []sql.NullString var rasTimestamp sql.NullTime - if err := rows.Scan(&rls.ID, &rls.FilterStatus, pq.Array(&rls.Rejections), &rlsindexer, &rlsfilter, &rls.Protocol, &infoUrl, &downloadUrl, &rls.Title, &rls.TorrentName, &rls.Size, &rls.Category, &rls.Season, &rls.Episode, &rls.Year, &rls.Resolution, &rls.Source, &codec, &rls.Container, &rls.Group, &rls.Timestamp, &rasId, &rasStatus, &rasAction, &rasActionId, &rasType, &rasClient, &rasFilter, &rasFilterId, &rasReleaseId, pq.Array(&rasRejections), &rasTimestamp, &countItems); err != nil { - return res, 0, 0, errors.Wrap(err, "error scanning row") + if err := rows.Scan(&rls.ID, &rls.FilterStatus, pq.Array(&rls.Rejections), &rlsIndexer, &rlsIndexerID, &rlsIndexerName, &rlsIndexerExternalName, &rlsFilter, &rls.Protocol, &infoUrl, &downloadUrl, &rls.Title, &rls.TorrentName, &rls.Size, &rls.Category, &rls.Season, &rls.Episode, &rls.Year, &rls.Resolution, &rls.Source, &codec, &rls.Container, &rls.Group, &rls.Timestamp, &rasId, &rasStatus, &rasAction, &rasActionId, &rasType, &rasClient, &rasFilter, &rasFilterId, &rasReleaseId, pq.Array(&rasRejections), &rasTimestamp, &resp.TotalCount); err != nil { + return resp, errors.Wrap(err, "error scanning row") } //for _, codec := range codecs { @@ -277,21 +281,25 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain } idx := 0 - for ; idx < len(res); idx++ { - if res[idx].ID != rls.ID { + for ; idx < len(resp.Data); idx++ { + if resp.Data[idx].ID != rls.ID { continue } - res[idx].ActionStatus = append(res[idx].ActionStatus, ras) + resp.Data[idx].ActionStatus = append(resp.Data[idx].ActionStatus, ras) break } - if idx != len(res) { + if idx != len(resp.Data) { continue } - rls.Indexer.Identifier = rlsindexer.String - rls.FilterName = rlsfilter.String + rls.Indexer.Identifier = rlsIndexer.String + rls.Indexer.ID = int(rlsIndexerID.Int64) + rls.Indexer.Name = rlsIndexerName.String + rls.Indexer.IdentifierExternal = rlsIndexerExternalName.String + + rls.FilterName = rlsFilter.String rls.ActionStatus = make([]domain.ReleaseActionStatus, 0) rls.InfoURL = infoUrl.String rls.DownloadURL = downloadUrl.String @@ -302,31 +310,15 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain rls.ActionStatus = append(rls.ActionStatus, ras) } - res = append(res, &rls) + resp.Data = append(resp.Data, &rls) } - nextCursor := int64(0) - if len(res) > 0 { - lastID := res[len(res)-1].ID - nextCursor = lastID + if len(resp.Data) > 0 { + lastID := resp.Data[len(resp.Data)-1].ID + resp.NextCursor = lastID } - return res, nextCursor, countItems, nil -} - -func (repo *ReleaseRepo) FindRecent(ctx context.Context) ([]*domain.Release, error) { - tx, err := repo.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted}) - if err != nil { - return nil, errors.Wrap(err, "error begin transaction") - } - defer tx.Rollback() - - releases, _, _, err := repo.findReleases(ctx, tx, domain.ReleaseQueryParams{Limit: 10}) - if err != nil { - return nil, err - } - - return releases, nil + return resp, nil } func (repo *ReleaseRepo) GetIndexerOptions(ctx context.Context) ([]string, error) { diff --git a/internal/database/release_test.go b/internal/database/release_test.go index b31fe25..779b912 100644 --- a/internal/database/release_test.go +++ b/internal/database/release_test.go @@ -231,12 +231,12 @@ func TestReleaseRepo_Find(t *testing.T) { Search: "", } - releases, nextCursor, total, err := repo.Find(context.Background(), queryParams) + resp, err := repo.Find(context.Background(), queryParams) // Verify - assert.NotNil(t, releases) - assert.NotEqual(t, int64(0), total) - assert.True(t, nextCursor >= 0) + assert.NotNil(t, resp) + assert.NotEqual(t, int64(0), resp.TotalCount) + assert.True(t, resp.NextCursor >= 0) // Cleanup _ = repo.Delete(context.Background(), &domain.DeleteReleaseRequest{OlderThan: 0}) @@ -281,11 +281,11 @@ func TestReleaseRepo_FindRecent(t *testing.T) { err = repo.Store(context.Background(), mockData) assert.NoError(t, err) - releases, err := repo.FindRecent(context.Background()) + resp, err := repo.Find(context.Background(), domain.ReleaseQueryParams{Limit: 10}) // Verify - assert.NotNil(t, releases) - assert.Lenf(t, releases, 1, "Expected 1 release, got %d", len(releases)) + assert.NotNil(t, resp.Data) + assert.Lenf(t, resp.Data, 1, "Expected 1 release, got %d", len(resp.Data)) // Cleanup _ = repo.Delete(context.Background(), &domain.DeleteReleaseRequest{OlderThan: 0}) diff --git a/internal/domain/release.go b/internal/domain/release.go index b632468..92de407 100644 --- a/internal/domain/release.go +++ b/internal/domain/release.go @@ -31,8 +31,7 @@ import ( type ReleaseRepo interface { Store(ctx context.Context, release *Release) error - Find(ctx context.Context, params ReleaseQueryParams) (res []*Release, nextCursor int64, count int64, err error) - FindRecent(ctx context.Context) ([]*Release, error) + Find(ctx context.Context, params ReleaseQueryParams) (*FindReleasesResponse, error) Get(ctx context.Context, req *GetReleaseRequest) (*Release, error) GetIndexerOptions(ctx context.Context) ([]string, error) Stats(ctx context.Context) (*ReleaseStats, error) @@ -271,6 +270,12 @@ type ReleaseQueryParams struct { Search string } +type FindReleasesResponse struct { + Data []*Release `json:"data"` + TotalCount uint64 `json:"count"` + NextCursor int64 `json:"next_cursor"` +} + type ReleaseActionRetryReq struct { ReleaseId int ActionStatusId int diff --git a/internal/http/release.go b/internal/http/release.go index 3639315..2d36f75 100644 --- a/internal/http/release.go +++ b/internal/http/release.go @@ -18,8 +18,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) + Find(ctx context.Context, query domain.ReleaseQueryParams) (*domain.FindReleasesResponse, error) Get(ctx context.Context, req *domain.GetReleaseRequest) (*domain.Release, error) GetIndexerOptions(ctx context.Context) ([]string, error) Stats(ctx context.Context) (*domain.ReleaseStats, error) @@ -128,7 +127,7 @@ func (h releaseHandler) findReleases(w http.ResponseWriter, r *http.Request) { Search: search, } - releases, nextCursor, count, err := h.service.Find(r.Context(), query) + resp, err := h.service.Find(r.Context(), query) if err != nil { h.encoder.StatusResponse(w, http.StatusInternalServerError, map[string]any{ "code": "INTERNAL_SERVER_ERROR", @@ -137,21 +136,11 @@ func (h releaseHandler) findReleases(w http.ResponseWriter, r *http.Request) { return } - ret := struct { - Data []*domain.Release `json:"data"` - NextCursor int64 `json:"next_cursor"` - Count int64 `json:"count"` - }{ - Data: releases, - NextCursor: nextCursor, - Count: count, - } - - h.encoder.StatusResponse(w, http.StatusOK, ret) + h.encoder.StatusResponse(w, http.StatusOK, resp) } func (h releaseHandler) findRecentReleases(w http.ResponseWriter, r *http.Request) { - releases, err := h.service.FindRecent(r.Context()) + resp, err := h.service.Find(r.Context(), domain.ReleaseQueryParams{Limit: 10}) if err != nil { h.encoder.StatusResponse(w, http.StatusInternalServerError, map[string]any{ "code": "INTERNAL_SERVER_ERROR", @@ -160,13 +149,7 @@ func (h releaseHandler) findRecentReleases(w http.ResponseWriter, r *http.Reques return } - ret := struct { - Data []*domain.Release `json:"data"` - }{ - Data: releases, - } - - h.encoder.StatusResponse(w, http.StatusOK, ret) + h.encoder.StatusResponse(w, http.StatusOK, resp) } func (h releaseHandler) getReleaseByID(w http.ResponseWriter, r *http.Request) { diff --git a/internal/release/service.go b/internal/release/service.go index 0f9eaff..7c47ed1 100644 --- a/internal/release/service.go +++ b/internal/release/service.go @@ -19,8 +19,7 @@ import ( ) type Service interface { - Find(ctx context.Context, query domain.ReleaseQueryParams) (res []*domain.Release, nextCursor int64, count int64, err error) - FindRecent(ctx context.Context) ([]*domain.Release, error) + Find(ctx context.Context, query domain.ReleaseQueryParams) (*domain.FindReleasesResponse, error) Get(ctx context.Context, req *domain.GetReleaseRequest) (*domain.Release, error) GetActionStatus(ctx context.Context, req *domain.GetReleaseActionStatusRequest) (*domain.ReleaseActionStatus, error) GetIndexerOptions(ctx context.Context) ([]string, error) @@ -58,14 +57,10 @@ func NewService(log logger.Logger, repo domain.ReleaseRepo, actionSvc action.Ser } } -func (s *service) Find(ctx context.Context, query domain.ReleaseQueryParams) (res []*domain.Release, nextCursor int64, count int64, err error) { +func (s *service) Find(ctx context.Context, query domain.ReleaseQueryParams) (*domain.FindReleasesResponse, error) { return s.repo.Find(ctx, query) } -func (s *service) FindRecent(ctx context.Context) (res []*domain.Release, err error) { - return s.repo.FindRecent(ctx) -} - func (s *service) Get(ctx context.Context, req *domain.GetReleaseRequest) (*domain.Release, error) { return s.repo.Get(ctx, req) } diff --git a/web/src/components/data-table/Cells.tsx b/web/src/components/data-table/Cells.tsx index 7b0c165..fb84c33 100644 --- a/web/src/components/data-table/Cells.tsx +++ b/web/src/components/data-table/Cells.tsx @@ -110,23 +110,23 @@ export const AgeCell = ({value}: CellProps) => ( ); -export const IndexerCell = ({value}: CellProps) => ( -
- ) => ( +
+ - {value} + {props.row.original.indexer.name ? props.row.original.indexer.name : props.row.original.indexer.identifier} - -
+
+
); export const TitleCell = ({value}: CellProps) => ( diff --git a/web/src/screens/dashboard/ActivityTable.tsx b/web/src/screens/dashboard/ActivityTable.tsx index a445b79..7c4b695 100644 --- a/web/src/screens/dashboard/ActivityTable.tsx +++ b/web/src/screens/dashboard/ActivityTable.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ -import React, { Suspense, useState } from "react"; +import React, { useState } from "react"; import { useSuspenseQuery } from "@tanstack/react-query"; import { useTable, @@ -18,8 +18,8 @@ import { EmptyListState } from "@components/emptystates"; import * as Icons from "@components/Icons"; import * as DataTable from "@components/data-table"; import { RandomLinuxIsos } from "@utils"; -import { RingResizeSpinner } from "@components/Icons"; import { ReleasesLatestQueryOptions } from "@api/queries"; +import { IndexerCell } from "@components/data-table"; // This is a custom filter UI for selecting // a unique option from a list @@ -166,28 +166,6 @@ function Table({ columns, data }: TableProps) { ); } -export const RecentActivityTable = () => { - return ( -
-

- Recent activity -

-
- - -
- } - > - {/**/} - - -
- - ) -} - export const ActivityTable = () => { const columns = React.useMemo(() => [ { @@ -208,7 +186,7 @@ export const ActivityTable = () => { { Header: "Indexer", accessor: "indexer.identifier", - Cell: DataTable.TitleCell, + Cell: IndexerCell, Filter: SelectColumnFilter, filter: "includes" } @@ -275,80 +253,3 @@ export const ActivityTable = () => { ); }; - -export const ActivityTableContent = () => { - const columns = React.useMemo(() => [ - { - Header: "Age", - accessor: "timestamp", - Cell: DataTable.AgeCell - }, - { - Header: "Release", - accessor: "name", - Cell: DataTable.TitleCell - }, - { - Header: "Actions", - accessor: "action_status", - Cell: DataTable.ReleaseStatusCell - }, - { - Header: "Indexer", - accessor: "indexer.identifier", - Cell: DataTable.TitleCell, - Filter: SelectColumnFilter, - filter: "includes" - } - ] as Column[], []); - - const { isLoading, data } = useSuspenseQuery(ReleasesLatestQueryOptions()); - - const [modifiedData, setModifiedData] = useState([]); - const [showLinuxIsos, setShowLinuxIsos] = useState(false); - - if (isLoading) { - return ( - - ); - } - - const toggleReleaseNames = () => { - setShowLinuxIsos(!showLinuxIsos); - if (!showLinuxIsos && data && data.data) { - const randomNames = RandomLinuxIsos(data.data.length); - const newData: Release[] = data.data.map((item, index) => ({ - ...item, - name: `${randomNames[index]}.iso`, - indexer: { - id: 0, - name: index % 2 === 0 ? "distrowatch" : "linuxtracker", - identifier: index % 2 === 0 ? "distrowatch" : "linuxtracker", - identifier_external: index % 2 === 0 ? "distrowatch" : "linuxtracker", - }, - })); - setModifiedData(newData); - } - }; - - const displayData = showLinuxIsos ? modifiedData : (data?.data ?? []); - - return ( - <> - - - - - ); -};