Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions graphql/schema/torrent_content.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ input VideoSourceFacetInput {
filter: [VideoSource]
}

input SizeRangeInput {
min: Int
max: Int
}

input TorrentContentFacetsInput {
contentType: ContentTypeFacetInput
torrentSource: TorrentSourceFacetInput
Expand All @@ -74,6 +79,7 @@ input TorrentContentFacetsInput {
releaseYear: ReleaseYearFacetInput
videoResolution: VideoResolutionFacetInput
videoSource: VideoSourceFacetInput
sizeRange: SizeRangeInput
}

type ContentTypeAgg {
Expand Down
53 changes: 53 additions & 0 deletions internal/database/search/criteria_torrent_content_size.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package search

import (
"fmt"
"strings"
"gorm.io/gorm"
"github.com/bitmagnet-io/bitmagnet/internal/database/query"
"github.com/bitmagnet-io/bitmagnet/internal/maps"
)

type SizeRangeCriteria struct {
MinBytes *int64
MaxBytes *int64
Key string
}

func (c SizeRangeCriteria) Apply(q *gorm.DB) (*gorm.DB, error) {
// If no min or max specified, return the query as is
if c.MinBytes == nil && c.MaxBytes == nil {
return q, nil
}

if c.MinBytes != nil {
q = q.Where(fmt.Sprintf("%s >= ?", c.Key), *c.MinBytes)
}

if c.MaxBytes != nil {
q = q.Where(fmt.Sprintf("%s <= ?", c.Key), *c.MaxBytes)
}

return q, nil
}

func (c SizeRangeCriteria) Raw(ctx query.DbContext) (query.RawCriteria, error) {
conditions := make([]string, 0, 2)
args := make([]interface{}, 0, 2)

if c.MinBytes != nil {
conditions = append(conditions, fmt.Sprintf("%s >= ?", c.Key))
args = append(args, *c.MinBytes)
}

if c.MaxBytes != nil {
conditions = append(conditions, fmt.Sprintf("%s <= ?", c.Key))
args = append(args, *c.MaxBytes)
}

return query.RawCriteria{
Query: strings.Join(conditions, " AND "),
Args: args,
Joins: maps.NewInsertMap[string, struct{}](),
}, nil
}
131 changes: 131 additions & 0 deletions internal/database/search/criteria_torrent_content_size_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package search

import (
"context"
"testing"

"github.com/bitmagnet-io/bitmagnet/internal/database/dao"
"github.com/bitmagnet-io/bitmagnet/internal/database/query"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestSizeRangeCriteria_Raw(t *testing.T) {
// Define test cases
tests := []struct {
name string
minBytes *int64
maxBytes *int64
key string
expectedQuery string
expectedArgs []interface{}
}{
{
name: "no min or max specified",
minBytes: nil,
maxBytes: nil,
key: "torrent_contents.size",
expectedQuery: "",
expectedArgs: []interface{}{},
},
{
name: "only min specified",
minBytes: int64Ptr(1024),
maxBytes: nil,
key: "torrent_contents.size",
expectedQuery: "torrent_contents.size >= ?",
expectedArgs: []interface{}{int64(1024)},
},
{
name: "only max specified",
minBytes: nil,
maxBytes: int64Ptr(1048576),
key: "torrent_contents.size",
expectedQuery: "torrent_contents.size <= ?",
expectedArgs: []interface{}{int64(1048576)},
},
{
name: "both min and max specified",
minBytes: int64Ptr(1024),
maxBytes: int64Ptr(1048576),
key: "torrent_contents.size",
expectedQuery: "torrent_contents.size >= ? AND torrent_contents.size <= ?",
expectedArgs: []interface{}{int64(1024), int64(1048576)},
},
{
name: "different column name",
minBytes: int64Ptr(1024),
maxBytes: int64Ptr(1048576),
key: "torrents.size",
expectedQuery: "torrents.size >= ? AND torrents.size <= ?",
expectedArgs: []interface{}{int64(1024), int64(1048576)},
},
{
name: "zero min value",
minBytes: int64Ptr(0),
maxBytes: int64Ptr(1048576),
key: "torrent_contents.size",
expectedQuery: "torrent_contents.size >= ? AND torrent_contents.size <= ?",
expectedArgs: []interface{}{int64(0), int64(1048576)},
},
{
name: "min greater than max",
minBytes: int64Ptr(2048),
maxBytes: int64Ptr(1024),
key: "torrent_contents.size",
expectedQuery: "torrent_contents.size >= ? AND torrent_contents.size <= ?",
expectedArgs: []interface{}{int64(2048), int64(1024)},
},
{
name: "large size values",
minBytes: int64Ptr(1_000_000_000), // 1GB
maxBytes: int64Ptr(1_000_000_000_000), // 1TB
key: "torrent_contents.size",
expectedQuery: "torrent_contents.size >= ? AND torrent_contents.size <= ?",
expectedArgs: []interface{}{int64(1_000_000_000), int64(1_000_000_000_000)},
},
}

// Run tests
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Arrange
criteria := SizeRangeCriteria{
MinBytes: tt.minBytes,
MaxBytes: tt.maxBytes,
Key: tt.key,
}
dbContext := &mockDBContext{}

// Act
rawCriteria, err := criteria.Raw(dbContext)

// Assert
require.NoError(t, err)
assert.Equal(t, tt.expectedQuery, rawCriteria.Query)
assert.Equal(t, tt.expectedArgs, rawCriteria.Args)
assert.NotNil(t, rawCriteria.Joins)
assert.Empty(t, rawCriteria.Joins.Entries(), "No joins should be required for size criteria")
})
}
}

// Helper function to create a pointer to an int64
func int64Ptr(v int64) *int64 {
return &v
}

// Mock DbContext for testing Raw method
type mockDBContext struct{}

func (m *mockDBContext) Query() *dao.Query {
return nil
}

func (m *mockDBContext) TableName() string {
return ""
}

func (m *mockDBContext) NewSubQuery(ctx context.Context) query.SubQuery {
return nil
}
74 changes: 73 additions & 1 deletion internal/gql/gql.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions internal/gql/gqlmodel/gen/model.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 23 additions & 1 deletion internal/gql/gqlmodel/torrent_content.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,29 @@ func (t TorrentContentQuery) Search(
qFacets = append(qFacets, videoSourceFacet(*videoSource))
}
options = append(options, q.WithFacet(qFacets...))

// Handle size range filters
if sizeRange, ok := input.Facets.SizeRange.ValueOK(); ok {
sizeCriteria := search.SizeRangeCriteria{
Key: "torrent_contents.size",
}

if min, minOk := sizeRange.Min.ValueOK(); minOk {
minSize := int64(*min)
sizeCriteria.MinBytes = &minSize
}

if max, maxOk := sizeRange.Max.ValueOK(); maxOk {
maxSize := int64(*max)
sizeCriteria.MaxBytes = &maxSize
}

if sizeCriteria.MinBytes != nil || sizeCriteria.MaxBytes != nil {
options = append(options, q.Where(sizeCriteria))
}
}
}

if infoHashes, ok := input.InfoHashes.ValueOK(); ok {
options = append(options, q.Where(search.TorrentContentInfoHashCriteria(infoHashes...)))
}
Expand Down Expand Up @@ -273,4 +295,4 @@ func transformTorrentContentAggregations(aggs q.Aggregations) (gen.TorrentConten
a.VideoSource = agg
}
return a, nil
}
}
Loading