diff --git a/go/api/images_list.go b/go/api/images_list.go new file mode 100644 index 0000000..3069a4b --- /dev/null +++ b/go/api/images_list.go @@ -0,0 +1,109 @@ +package api + +import ( + "context" + "fmt" + + "github.com/tigorlazuardi/bluemage/go/gen/jet/model" + "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() SelectStatement { + cond := Bool(true) + from := Images + + 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))) + } + + stmt := SELECT(Images.AllColumns).FROM(from).WHERE(cond) + if request.Limit > 0 { + stmt.LIMIT(request.Limit) + } + + if len(request.Search) > 0 { + from.INNER_JOIN(ImagesFts5, Images.PostName.EQ(ImagesFts5.PostName)) + cond.AND(ImagesFts5.ImagesFts5.EQ(String(request.Search))) + return stmt.ORDER_BY(RawString(imagesFTSBM25).ASC()) + } + + 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 (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) }() + + return images, err +} diff --git a/go/server/image_handlers.go b/go/server/image_handlers.go new file mode 100644 index 0000000..854da4a --- /dev/null +++ b/go/server/image_handlers.go @@ -0,0 +1,25 @@ +package server + +import ( + "context" + + "connectrpc.com/connect" + "github.com/tigorlazuardi/bluemage/go/api" + images "github.com/tigorlazuardi/bluemage/go/gen/proto/images/v1" +) + +type ImageHandler struct { + API *api.API +} + +func (im *ImageHandler) ListImages(ctx context.Context, request *connect.Request[images.ListImagesRequest]) (*connect.Response[images.ListImagesResponse], error) { + panic("not implemented") // TODO: Implement +} + +func (im *ImageHandler) DeleteImages(ctx context.Context, request *connect.Request[images.DeleteImagesRequest]) (*connect.Response[images.DeleteImagesResponse], error) { + panic("not implemented") // TODO: Implement +} + +func (im *ImageHandler) GetImage(ctx context.Context, request *connect.Request[images.GetImageRequest]) (*connect.Response[images.GetImageResponse], error) { + panic("not implemented") // TODO: Implement +} diff --git a/schemas/migrations/20240808143100_create_images_table.sql b/schemas/migrations/20240808143100_create_images_table.sql index 01c4533..b048a28 100644 --- a/schemas/migrations/20240808143100_create_images_table.sql +++ b/schemas/migrations/20240808143100_create_images_table.sql @@ -34,12 +34,12 @@ CREATE TABLE images( ); CREATE VIRTUAL TABLE images_fts5 USING fts5( + post_author, post_title, post_name, - post_author, image_relative_path, - image_original_url, - thumbnail_relative_path, + post_url, + post_author_url, content='images', content_rowid='id' ); @@ -68,8 +68,11 @@ END; CREATE TRIGGER images_update_fts_insert AFTER INSERT ON images FOR EACH ROW BEGIN - INSERT INTO images_fts5(rowid, post_title, post_name, post_author, image_relative_path, image_original_url, thumbnail_relative_path) - VALUES (new.id, new.post_title, new.post_name, new.post_author, new.image_relative_path, new.image_original_url, new.thumbnail_relative_path); + INSERT INTO images_fts5( + rowid, post_author, post_title, post_name, image_relative_path, post_url, post_author_url + ) VALUES( + new.id, new.post_author, new.post_title, new.post_name, new.image_relative_path, new.post_url, new.post_author_url + ); END; CREATE TRIGGER images_update_fts_delete AFTER DELETE ON images FOR EACH ROW diff --git a/schemas/proto/images/v1/list.proto b/schemas/proto/images/v1/list.proto index 2ead336..0f305b3 100644 --- a/schemas/proto/images/v1/list.proto +++ b/schemas/proto/images/v1/list.proto @@ -15,7 +15,7 @@ message ListImagesRequest { // subreddits filter the images to be fetched belonging to the given subreddits names. // // If empty, images from all subreddits will be fetched. - string subreddit = 1 [(buf.validate.field).string.min_len = 1]; + repeated string subreddits = 1; // devices filter the images to be fetched belonging to the given devices slugs. // // If empty, images from all devices will be fetched. @@ -54,12 +54,10 @@ message ListImagesRequest { // It will look up by following rank and prioritizes matches // based on following fields: // + // - Author's name // - Post Title // - Post ID - // - Author's name - // - Author's ID // - Image filename (url path suffix to get this image from web browser) - // - Image original url // - Post url // - Author's url string search = 8; diff --git a/schemas/proto/images/v1/types.proto b/schemas/proto/images/v1/types.proto index 6ec41b9..1b1a6eb 100644 --- a/schemas/proto/images/v1/types.proto +++ b/schemas/proto/images/v1/types.proto @@ -7,16 +7,21 @@ message Image { string subreddit = 2; string device = 3; string post_title = 4; - string post_url = 5; - int64 post_created = 6; - string post_author = 7; - string post_author_url = 8; - string image_relative_path = 9; - string image_original_url = 10; - int64 image_height = 11; - int64 image_width = 12; - int64 image_size = 13; - bool nsfw = 14; + string post_name = 5; + string post_url = 6; + int64 post_created = 7; + string post_author = 8; + string post_author_url = 9; + string image_relative_path = 10; + string image_original_url = 11; + int64 image_height = 12; + int64 image_width = 13; + int64 image_size = 14; + string thumbnail_relative_path = 15; + bool nsfw = 16; + bool blacklisted = 17; + int64 created_at = 18; + int64 updated_at = 19; } message GroupedByDeviceImages {