images: implemented ListImages handler

This commit is contained in:
Tigor Hutasuhut 2024-08-25 22:10:57 +07:00
parent 0fec03adbc
commit 9e1a192f93
3 changed files with 107 additions and 23 deletions

View file

@ -50,35 +50,30 @@ type ImageListRequest struct {
// - Author's url // - Author's url
var imagesFTSBM25 = fmt.Sprintf("bm25(%s, 25, 20, 10, 5, 3, 3)", ImagesFts5.TableName()) var imagesFTSBM25 = fmt.Sprintf("bm25(%s, 25, 20, 10, 5, 3, 3)", ImagesFts5.TableName())
func (request ImageListRequest) Statement() Statement { func (request ImageListRequest) Statement() SelectStatement {
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() cond := request.WhereExpression()
from := Images
if len(request.Search) > 0 { if len(request.Search) > 0 {
cond.AND(ImagesFts5.ImagesFts5.EQ(String(request.Search))) cond.AND(ImagesFts5.ImagesFts5.EQ(String(request.Search)))
from.INNER_JOIN(ImagesFts5, Images.PostName.EQ(ImagesFts5.PostName)) stmt := SELECT(
stmt := SELECT(Images.AllColumns).FROM(from).WHERE(cond) Images.AllColumns, Devices.Slug, Devices.Name,
).
FROM(
Images.
INNER_JOIN(Devices, Devices.Slug.EQ(Images.Device)).
INNER_JOIN(ImagesFts5, Images.PostName.EQ(ImagesFts5.PostName)),
).
WHERE(cond)
if request.Limit > 0 { if request.Limit > 0 {
stmt.LIMIT(request.Limit) stmt.LIMIT(request.Limit)
} }
return stmt.ORDER_BY(RawString(imagesFTSBM25).DESC()) return stmt.ORDER_BY(RawString(imagesFTSBM25).DESC())
} }
stmt := SELECT(Images.AllColumns).FROM(from).WHERE(cond) stmt := SELECT(
Images.AllColumns, Devices.Slug, Devices.Name,
).
FROM(Images.INNER_JOIN(Devices, Devices.Slug.EQ(Images.Device))).
WHERE(cond)
if request.Limit > 0 { if request.Limit > 0 {
stmt.LIMIT(request.Limit) stmt.LIMIT(request.Limit)
} }
@ -132,10 +127,15 @@ func (request ImageListRequest) WhereExpression() BoolExpression {
return cond return cond
} }
type ImageListResponse struct {
model.Images
DeviceName string
}
// ImageList list images by request. // ImageList list images by request.
// //
// Results are always grouped by device then grouped by subreddit after sorting. // 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) { func (api *API) ImageList(ctx context.Context, request ImageListRequest) (images []ImageListResponse, err error) {
ctx, span := tracer.Start(ctx, "ImageList") ctx, span := tracer.Start(ctx, "ImageList")
defer func() { telemetry.EndWithStatus(span, err) }() defer func() { telemetry.EndWithStatus(span, err) }()

View file

@ -1,7 +1,9 @@
package server package server
import ( import (
"cmp"
"context" "context"
"slices"
"connectrpc.com/connect" "connectrpc.com/connect"
"github.com/tigorlazuardi/bluemage/go/api" "github.com/tigorlazuardi/bluemage/go/api"
@ -17,6 +19,13 @@ type ImageHandler struct {
var imageConverter = converter.ImageConverterImpl{} var imageConverter = converter.ImageConverterImpl{}
func (im *ImageHandler) ListImages(ctx context.Context, request *connect.Request[images.ListImagesRequest]) (*connect.Response[images.ListImagesResponse], error) { func (im *ImageHandler) ListImages(ctx context.Context, request *connect.Request[images.ListImagesRequest]) (*connect.Response[images.ListImagesResponse], error) {
if request.Msg.Limit < 1 {
request.Msg.Limit = 50
}
if request.Msg.Limit > 1000 {
request.Msg.Limit = 1000
}
listRequest := imageConverter.ProtoListImagesRequestToAPIImagesRequest(request.Msg) listRequest := imageConverter.ProtoListImagesRequestToAPIImagesRequest(request.Msg)
list, err := im.API.ImageList(ctx, listRequest) list, err := im.API.ImageList(ctx, listRequest)
@ -24,9 +33,19 @@ func (im *ImageHandler) ListImages(ctx context.Context, request *connect.Request
return nil, errs.IntoConnectError(err) return nil, errs.IntoConnectError(err)
} }
_ = list group := jetImagesToProtoGroupedByDeviceImages(list)
resp := &images.ListImagesResponse{
Devices: group,
}
if request.Msg.After > 0 && len(group) >= int(request.Msg.Limit) {
resp.After = &list[len(list)-1].CreatedAt
}
panic("not implemented") // TODO: Implement if request.Msg.Before > 0 && len(group) >= int(request.Msg.Limit) {
resp.Before = &list[0].CreatedAt
}
return connect.NewResponse(resp), nil
} }
func (im *ImageHandler) DeleteImages(ctx context.Context, request *connect.Request[images.DeleteImagesRequest]) (*connect.Response[images.DeleteImagesResponse], error) { func (im *ImageHandler) DeleteImages(ctx context.Context, request *connect.Request[images.DeleteImagesRequest]) (*connect.Response[images.DeleteImagesResponse], error) {
@ -36,3 +55,62 @@ func (im *ImageHandler) DeleteImages(ctx context.Context, request *connect.Reque
func (im *ImageHandler) GetImage(ctx context.Context, request *connect.Request[images.GetImageRequest]) (*connect.Response[images.GetImageResponse], error) { func (im *ImageHandler) GetImage(ctx context.Context, request *connect.Request[images.GetImageRequest]) (*connect.Response[images.GetImageResponse], error) {
panic("not implemented") // TODO: Implement panic("not implemented") // TODO: Implement
} }
func jetImagesToProtoGroupedByDeviceImages(jetImages []api.ImageListResponse) (out []*images.GroupedByDeviceImages) {
if jetImages == nil {
return nil
}
if len(jetImages) == 0 {
return []*images.GroupedByDeviceImages{}
}
type device struct {
slug string
name string
}
grouped := map[device]map[string][]*images.Image{}
for _, jetImage := range jetImages {
device := device{
slug: jetImage.Device,
name: jetImage.DeviceName,
}
subreddit := jetImage.Subreddit
if grouped[device] == nil {
grouped[device] = map[string][]*images.Image{}
}
if grouped[device][subreddit] == nil {
grouped[device][subreddit] = []*images.Image{}
}
grouped[device][subreddit] = append(grouped[device][subreddit], imageConverter.JetImageToProtoImage(jetImage.Images))
}
out = make([]*images.GroupedByDeviceImages, 0, len(grouped))
for device, subreddits := range grouped {
group := &images.GroupedByDeviceImages{
Slug: device.slug,
Name: device.name,
Subreddits: make([]*images.GroupedBySubredditImages, 0, len(subreddits)),
}
for subreddit, img := range subreddits {
group.Subreddits = append(group.Subreddits, &images.GroupedBySubredditImages{
Subreddit: subreddit,
Images: img,
})
}
slices.SortFunc(group.Subreddits, func(a, b *images.GroupedBySubredditImages) int {
return cmp.Compare(a.Subreddit, b.Subreddit)
})
out = append(out, group)
}
slices.SortFunc(out, func(a, b *images.GroupedByDeviceImages) int {
return cmp.Compare(a.Name, b.Name)
})
return out
}

View file

@ -71,6 +71,12 @@ message ListImagesRequest {
message ListImagesResponse { message ListImagesResponse {
repeated GroupedByDeviceImages devices = 1; repeated GroupedByDeviceImages devices = 1;
// after is set if there are more images
// after the last given image.
optional int64 after = 2; optional int64 after = 2;
// before is set if there are more images
// before the first image in the result.
optional int64 before = 3; optional int64 before = 3;
} }