feat: implement activity pagination

This commit is contained in:
biplavbarua 2026-01-10 03:45:21 +05:30
parent da2dd80e2f
commit b962f0c159
6 changed files with 127 additions and 12 deletions

View File

@ -15,22 +15,39 @@ import (
)
func (s *APIV1Service) ListActivities(ctx context.Context, request *v1pb.ListActivitiesRequest) (*v1pb.ListActivitiesResponse, error) {
// Set default page size if not specified
pageSize := request.PageSize
if pageSize <= 0 || pageSize > 1000 {
pageSize = 100
var limit, offset int
if request.PageToken != "" {
var pageToken v1pb.PageToken
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)
}
// TODO: Implement pagination with page_token and use pageSize for limiting
// 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
activities, err := s.Store.ListActivities(ctx, &store.FindActivity{})
if limit <= 0 {
limit = DefaultPageSize
}
limitPlusOne := limit + 1
activities, err := s.Store.ListActivities(ctx, &store.FindActivity{
Limit: &limitPlusOne,
Offset: &offset,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list activities: %v", err)
}
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 {
activityMessage, err := s.convertActivityFromStore(ctx, activity)
if err != nil {
@ -40,8 +57,8 @@ func (s *APIV1Service) ListActivities(ctx context.Context, request *v1pb.ListAct
}
return &v1pb.ListActivitiesResponse{
Activities: activityMessages,
// TODO: Implement next_page_token for pagination
Activities: activityMessages,
NextPageToken: nextPageToken,
}, nil
}

View 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)
}

View File

@ -42,6 +42,10 @@ type Activity struct {
type FindActivity struct {
ID *int32
Type *ActivityType
// Pagination
Limit *int
Offset *int
}
func (s *Store) CreateActivity(ctx context.Context, create *Activity) (*Activity, error) {

View File

@ -2,8 +2,10 @@ package mysql
import (
"context"
"fmt"
"strings"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
@ -56,6 +58,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"
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...)
if err != nil {
return nil, err

View File

@ -2,8 +2,10 @@ package postgres
import (
"context"
"fmt"
"strings"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
@ -44,6 +46,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"
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...)
if err != nil {
return nil, err

View File

@ -2,8 +2,10 @@ package sqlite
import (
"context"
"fmt"
"strings"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
@ -46,6 +48,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"
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...)
if err != nil {
return nil, err