package api import ( "context" "fmt" "github.com/tigorlazuardi/bluemage/go/gen/jet/model" "github.com/tigorlazuardi/bluemage/go/pkg/errs" "github.com/tigorlazuardi/bluemage/go/pkg/telemetry" . "github.com/go-jet/jet/v2/sqlite" . "github.com/tigorlazuardi/bluemage/go/gen/jet/table" ) type ImageListRequest struct { Subreddits []string Devices []string Limit int64 After int64 Before int64 OrderBy string Sort Sort Search string NSFW *bool Blacklist *bool } // CREATE VIRTUAL TABLE images_fts5 USING fts5( // // post_author, // post_title, // post_name, // image_relative_path, // post_url, // post_author_url, // content='images', // content_rowid='id' // // ); // // matching list: // // - Author's name // - Post Title // - Post ID // - Author's ID // - Image filename (url path suffix to get this image from web browser) // - Image original url // - Post url // - Author's url var imagesFTSBM25 = fmt.Sprintf("bm25(%s, 25, 20, 10, 5, 3, 3)", ImagesFts5.TableName()) func (request ImageListRequest) Statement() Statement { queriedImages := CTE("queried_images") return WITH( queriedImages.AS(request.SelectStatement()), )( SELECT(queriedImages.AllColumns()). FROM(queriedImages). ORDER_BY( Images.Device.ASC(), Images.Subreddit.ASC(), ), ) } func (request ImageListRequest) SelectStatement() SelectStatement { cond := request.WhereExpression() from := Images if len(request.Search) > 0 { cond.AND(ImagesFts5.ImagesFts5.EQ(String(request.Search))) from.INNER_JOIN(ImagesFts5, Images.PostName.EQ(ImagesFts5.PostName)) stmt := SELECT(Images.AllColumns).FROM(from).WHERE(cond) if request.Limit > 0 { stmt.LIMIT(request.Limit) } return stmt.ORDER_BY(RawString(imagesFTSBM25).DESC()) } stmt := SELECT(Images.AllColumns).FROM(from).WHERE(cond) if request.Limit > 0 { stmt.LIMIT(request.Limit) } if request.OrderBy == "" { return stmt.ORDER_BY(Images.CreatedAt.DESC()) } orderBy := StringColumn(request.OrderBy) if request.Sort == SortDesc { return stmt.ORDER_BY(orderBy.DESC()) } return stmt.ORDER_BY(orderBy.ASC()) } func (request ImageListRequest) WhereExpression() BoolExpression { cond := Bool(true) if len(request.Devices) > 0 { cond.AND(Images.Device.IN(stringSliceExpressions(request.Devices)...)) } if len(request.Subreddits) > 0 { cond.AND(Images.Subreddit.IN(stringSliceExpressions(request.Subreddits)...)) } if request.NSFW != nil { n := *request.NSFW if n { cond.AND(Images.Nsfw.EQ(Int(1))) } else { cond.AND(Images.Nsfw.EQ(Int(0))) } } if request.Blacklist != nil { b := *request.Blacklist if b { cond.AND(Images.Blacklisted.EQ(Int(1))) } else { cond.AND(Images.Blacklisted.EQ(Int(0))) } } if request.After > 0 { cond.AND(Images.CreatedAt.LT_EQ(Int(request.After))) } if request.Before > 0 { cond.AND(Images.CreatedAt.GT_EQ(Int(request.Before))) } return cond } // ImageList list images by request. // // Results are always grouped by device then grouped by subreddit after sorting. func (api *API) ImageList(ctx context.Context, request ImageListRequest) (images []model.Images, err error) { ctx, span := tracer.Start(ctx, "ImageList") defer func() { telemetry.EndWithStatus(span, err) }() stmt := request.Statement() if err := stmt.QueryContext(ctx, api.DB, &images); err != nil { return images, errs.Wrapw(err, "failed to list images", "request", request, "query", stmt.DebugSql(), ) } return images, err } func stringSliceExpressions(s []string) []Expression { var expr []Expression if s != nil { expr = make([]Expression, len(s)) for i, str := range s { expr[i] = String(str) } } return expr }