feat: implement ListActivities pagination (#5468)
This commit is contained in:
parent
c36934f348
commit
def123232a
6 changed files with 127 additions and 12 deletions
|
|
@ -15,22 +15,42 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *APIV1Service) ListActivities(ctx context.Context, request *v1pb.ListActivitiesRequest) (*v1pb.ListActivitiesResponse, error) {
|
func (s *APIV1Service) ListActivities(ctx context.Context, request *v1pb.ListActivitiesRequest) (*v1pb.ListActivitiesResponse, error) {
|
||||||
// Set default page size if not specified
|
var limit, offset int
|
||||||
pageSize := request.PageSize
|
if request.PageToken != "" {
|
||||||
if pageSize <= 0 || pageSize > 1000 {
|
var pageToken v1pb.PageToken
|
||||||
pageSize = 100
|
if err := unmarshalPageToken(request.PageToken, &pageToken); err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "invalid page token: %v", err)
|
||||||
|
}
|
||||||
|
limit = int(pageToken.Limit)
|
||||||
|
offset = int(pageToken.Offset)
|
||||||
|
} else {
|
||||||
|
limit = int(request.PageSize)
|
||||||
}
|
}
|
||||||
|
if limit <= 0 {
|
||||||
// TODO: Implement pagination with page_token and use pageSize for limiting
|
limit = DefaultPageSize
|
||||||
// For now, we'll fetch all activities and the pageSize will be used in future pagination implementation
|
}
|
||||||
_ = pageSize // Acknowledge pageSize variable to avoid linter warning
|
if limit > MaxPageSize {
|
||||||
|
limit = MaxPageSize
|
||||||
activities, err := s.Store.ListActivities(ctx, &store.FindActivity{})
|
}
|
||||||
|
limitPlusOne := limit + 1
|
||||||
|
activities, err := s.Store.ListActivities(ctx, &store.FindActivity{
|
||||||
|
Limit: &limitPlusOne,
|
||||||
|
Offset: &offset,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to list activities: %v", err)
|
return nil, status.Errorf(codes.Internal, "failed to list activities: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var activityMessages []*v1pb.Activity
|
var activityMessages []*v1pb.Activity
|
||||||
|
nextPageToken := ""
|
||||||
|
if len(activities) == limitPlusOne {
|
||||||
|
activities = activities[:limit]
|
||||||
|
nextPageToken, err = getPageToken(limit, offset+limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed to get next page token, error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, activity := range activities {
|
for _, activity := range activities {
|
||||||
activityMessage, err := s.convertActivityFromStore(ctx, activity)
|
activityMessage, err := s.convertActivityFromStore(ctx, activity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -43,8 +63,8 @@ func (s *APIV1Service) ListActivities(ctx context.Context, request *v1pb.ListAct
|
||||||
}
|
}
|
||||||
|
|
||||||
return &v1pb.ListActivitiesResponse{
|
return &v1pb.ListActivitiesResponse{
|
||||||
Activities: activityMessages,
|
Activities: activityMessages,
|
||||||
// TODO: Implement next_page_token for pagination
|
NextPageToken: nextPageToken,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
67
server/router/api/v1/test/activity_service_test.go
Normal file
67
server/router/api/v1/test/activity_service_test.go
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
apiv1 "github.com/usememos/memos/proto/gen/api/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListActivities(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
ts := NewTestService(t)
|
||||||
|
defer ts.Cleanup()
|
||||||
|
|
||||||
|
// Create userOne
|
||||||
|
userOne, err := ts.CreateRegularUser(ctx, "test-user-1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
userOneCtx := ts.CreateUserContext(ctx, userOne.ID)
|
||||||
|
|
||||||
|
// Create userTwo
|
||||||
|
userTwo, err := ts.CreateRegularUser(ctx, "test-user-2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
userTwoCtx := ts.CreateUserContext(ctx, userTwo.ID)
|
||||||
|
|
||||||
|
// UserOne creates a memo
|
||||||
|
memo, err := ts.Service.CreateMemo(userOneCtx, &apiv1.CreateMemoRequest{
|
||||||
|
Memo: &apiv1.Memo{
|
||||||
|
Content: "Base memo",
|
||||||
|
Visibility: apiv1.Visibility_PUBLIC,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// UserTwo creates 15 comments on the memo to generate 15 activities
|
||||||
|
for i := 0; i < 15; i++ {
|
||||||
|
_, err := ts.Service.CreateMemoComment(userTwoCtx, &apiv1.CreateMemoCommentRequest{
|
||||||
|
Name: memo.Name,
|
||||||
|
Comment: &apiv1.Memo{
|
||||||
|
Content: fmt.Sprintf("Comment %d", i),
|
||||||
|
Visibility: apiv1.Visibility_PUBLIC,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List activities with page size 10 (as admin or userOne)
|
||||||
|
// Activities are visible to the receiver (UserOne)
|
||||||
|
resp, err := ts.Service.ListActivities(userOneCtx, &apiv1.ListActivitiesRequest{
|
||||||
|
PageSize: 10,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Activities, 10)
|
||||||
|
require.NotEmpty(t, resp.NextPageToken)
|
||||||
|
|
||||||
|
// List next page
|
||||||
|
resp, err = ts.Service.ListActivities(userOneCtx, &apiv1.ListActivitiesRequest{
|
||||||
|
PageSize: 10,
|
||||||
|
PageToken: resp.NextPageToken,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Activities, 5)
|
||||||
|
require.Empty(t, resp.NextPageToken)
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,10 @@ type Activity struct {
|
||||||
type FindActivity struct {
|
type FindActivity struct {
|
||||||
ID *int32
|
ID *int32
|
||||||
Type *ActivityType
|
Type *ActivityType
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
Limit *int
|
||||||
|
Offset *int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) CreateActivity(ctx context.Context, create *Activity) (*Activity, error) {
|
func (s *Store) CreateActivity(ctx context.Context, create *Activity) (*Activity, error) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
@ -56,6 +57,13 @@ func (d *DB) ListActivities(ctx context.Context, find *store.FindActivity) ([]*s
|
||||||
}
|
}
|
||||||
|
|
||||||
query := "SELECT `id`, `creator_id`, `type`, `level`, `payload`, UNIX_TIMESTAMP(`created_ts`) FROM `activity` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC"
|
query := "SELECT `id`, `creator_id`, `type`, `level`, `payload`, UNIX_TIMESTAMP(`created_ts`) FROM `activity` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC"
|
||||||
|
if find.Limit != nil {
|
||||||
|
query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
|
||||||
|
if find.Offset != nil {
|
||||||
|
query = fmt.Sprintf("%s OFFSET %d", query, *find.Offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := d.db.QueryContext(ctx, query, args...)
|
rows, err := d.db.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
@ -44,6 +45,13 @@ func (d *DB) ListActivities(ctx context.Context, find *store.FindActivity) ([]*s
|
||||||
}
|
}
|
||||||
|
|
||||||
query := "SELECT id, creator_id, type, level, payload, created_ts FROM activity WHERE " + strings.Join(where, " AND ") + " ORDER BY created_ts DESC"
|
query := "SELECT id, creator_id, type, level, payload, created_ts FROM activity WHERE " + strings.Join(where, " AND ") + " ORDER BY created_ts DESC"
|
||||||
|
if find.Limit != nil {
|
||||||
|
query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
|
||||||
|
if find.Offset != nil {
|
||||||
|
query = fmt.Sprintf("%s OFFSET %d", query, *find.Offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := d.db.QueryContext(ctx, query, args...)
|
rows, err := d.db.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package sqlite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
@ -46,6 +47,13 @@ func (d *DB) ListActivities(ctx context.Context, find *store.FindActivity) ([]*s
|
||||||
}
|
}
|
||||||
|
|
||||||
query := "SELECT `id`, `creator_id`, `type`, `level`, `payload`, `created_ts` FROM `activity` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC"
|
query := "SELECT `id`, `creator_id`, `type`, `level`, `payload`, `created_ts` FROM `activity` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC"
|
||||||
|
if find.Limit != nil {
|
||||||
|
query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
|
||||||
|
if find.Offset != nil {
|
||||||
|
query = fmt.Sprintf("%s OFFSET %d", query, *find.Offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := d.db.QueryContext(ctx, query, args...)
|
rows, err := d.db.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue