1
0
Fork 0
mirror of https://github.com/anyproto/anytype-heart.git synced 2025-06-08 05:47:07 +09:00

GO-4459: Refactor pagination middleware

This commit is contained in:
Jannis Metrikat 2025-01-11 18:44:30 +01:00
parent 4b21551fdd
commit 2cb03241f6
No known key found for this signature in database
GPG key ID: B223CAC5AAF85615
2 changed files with 69 additions and 28 deletions

View file

@ -1,13 +1,52 @@
package pagination
import "github.com/gin-gonic/gin"
import (
"fmt"
"net/http"
"strconv"
type Service[T any] interface {
RespondWithPagination(c *gin.Context, statusCode int, data []T, total int, offset int, limit int, hasMore bool)
Paginate(records []T, offset int, limit int) ([]T, bool)
"github.com/gin-gonic/gin"
)
// Config holds pagination configuration options.
type Config struct {
DefaultPage int
DefaultPageSize int
MinPageSize int
MaxPageSize int
}
// RespondWithPagination returns a json response with the paginated data and corresponding metadata
// New creates a Gin middleware for pagination with the provided Config.
func New(cfg Config) gin.HandlerFunc {
return func(c *gin.Context) {
page := getIntQueryParam(c, "offset", cfg.DefaultPage)
size := getIntQueryParam(c, "limit", cfg.DefaultPageSize)
if size < cfg.MinPageSize || size > cfg.MaxPageSize {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": fmt.Sprintf("limit must be between %d and %d", cfg.MinPageSize, cfg.MaxPageSize),
})
return
}
c.Set("offset", page)
c.Set("limit", size)
c.Next()
}
}
// getIntQueryParam retrieves an integer query parameter or falls back to a default value.
func getIntQueryParam(c *gin.Context, key string, defaultValue int) int {
valStr := c.DefaultQuery(key, strconv.Itoa(defaultValue))
val, err := strconv.Atoi(valStr)
if err != nil || val < 0 {
return defaultValue
}
return val
}
// RespondWithPagination sends a paginated JSON response.
func RespondWithPagination[T any](c *gin.Context, statusCode int, data []T, total int, offset int, limit int, hasMore bool) {
c.JSON(statusCode, PaginatedResponse[T]{
Data: data,
@ -20,20 +59,23 @@ func RespondWithPagination[T any](c *gin.Context, statusCode int, data []T, tota
})
}
// Paginate paginates the given records based on the offset and limit
// Paginate slices the records based on the offset and limit, and determines if more records are available.
func Paginate[T any](records []T, offset int, limit int) ([]T, bool) {
total := len(records)
start := offset
end := offset + limit
if start > total {
start = total
if offset < 0 || limit < 1 {
return []T{}, len(records) > 0
}
total := len(records)
if offset > total {
offset = total
}
end := offset + limit
if end > total {
end = total
}
paginated := records[start:end]
paginated := records[offset:end]
hasMore := end < total
return paginated, hasMore

View file

@ -4,11 +4,11 @@ import (
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/webstradev/gin-pagination/v2/pkg/pagination"
"github.com/anyproto/anytype-heart/cmd/api/auth"
"github.com/anyproto/anytype-heart/cmd/api/export"
"github.com/anyproto/anytype-heart/cmd/api/object"
"github.com/anyproto/anytype-heart/cmd/api/pagination"
"github.com/anyproto/anytype-heart/cmd/api/search"
"github.com/anyproto/anytype-heart/cmd/api/space"
)
@ -18,20 +18,19 @@ func (s *Server) NewRouter() *gin.Engine {
router := gin.Default()
// Pagination middleware setup
paginator := pagination.New(
pagination.WithPageText("offset"),
pagination.WithSizeText("limit"),
pagination.WithDefaultPage(0),
pagination.WithDefaultPageSize(100),
pagination.WithMinPageSize(1),
pagination.WithMaxPageSize(1000),
)
paginator := pagination.New(pagination.Config{
DefaultPage: 0,
DefaultPageSize: 100,
MinPageSize: 1,
MaxPageSize: 1000,
})
// Swagger route
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
// API routes
v1 := router.Group("/v1")
v1.Use(paginator)
v1.Use(s.initAccountInfo())
v1.Use(s.ensureAuthenticated())
{
@ -44,20 +43,20 @@ func (s *Server) NewRouter() *gin.Engine {
v1.POST("/spaces/:space_id/objects/export/:format", export.GetSpaceExportHandler(s.exportService))
// Object
v1.GET("/spaces/:space_id/objects", paginator, object.GetObjectsHandler(s.objectService))
v1.GET("/spaces/:space_id/objects", object.GetObjectsHandler(s.objectService))
v1.GET("/spaces/:space_id/objects/:object_id", object.GetObjectHandler(s.objectService))
v1.DELETE("/spaces/:space_id/objects/:object_id", object.DeleteObjectHandler(s.objectService))
v1.GET("/spaces/:space_id/object_types", paginator, object.GetTypesHandler(s.objectService))
v1.GET("/spaces/:space_id/object_types/:typeId/templates", paginator, object.GetTemplatesHandler(s.objectService))
v1.GET("/spaces/:space_id/object_types", object.GetTypesHandler(s.objectService))
v1.GET("/spaces/:space_id/object_types/:typeId/templates", object.GetTemplatesHandler(s.objectService))
v1.POST("/spaces/:space_id/objects", object.CreateObjectHandler(s.objectService))
v1.PUT("/spaces/:space_id/objects/:object_id", object.UpdateObjectHandler(s.objectService))
// Search
v1.GET("/search", paginator, search.SearchHandler(s.searchService))
v1.GET("/search", search.SearchHandler(s.searchService))
// Space
v1.GET("/spaces", paginator, space.GetSpacesHandler(s.spaceService))
v1.GET("/spaces/:space_id/members", paginator, space.GetMembersHandler(s.spaceService))
v1.GET("/spaces", space.GetSpacesHandler(s.spaceService))
v1.GET("/spaces/:space_id/members", space.GetMembersHandler(s.spaceService))
v1.POST("/spaces", space.CreateSpaceHandler(s.spaceService))
}