mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-09 17:44:59 +09:00
GO-4459 Return paginated responses
This commit is contained in:
parent
cee9715f44
commit
24f25ffbe9
4 changed files with 143 additions and 66 deletions
|
@ -96,7 +96,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/objects": {
|
||||
"/search": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
|
@ -112,7 +112,7 @@
|
|||
{
|
||||
"type": "string",
|
||||
"description": "The search term to filter objects by name",
|
||||
"name": "search",
|
||||
"name": "query",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
|
@ -194,7 +194,7 @@
|
|||
"200": {
|
||||
"description": "List of spaces",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.SpacesResponse"
|
||||
"$ref": "#/definitions/api.PaginatedResponse-api_Space"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
|
@ -299,7 +299,7 @@
|
|||
"200": {
|
||||
"description": "List of members",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MembersResponse"
|
||||
"$ref": "#/definitions/api.PaginatedResponse-api_Member"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
|
@ -1188,17 +1188,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"api.MembersResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"members": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Member"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.MessageContent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -1322,6 +1311,55 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"api.PaginatedResponse-api_Member": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Member"
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"$ref": "#/definitions/api.PaginationMeta"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.PaginatedResponse-api_Space": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Space"
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"$ref": "#/definitions/api.PaginationMeta"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.PaginationMeta": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"has_next": {
|
||||
"description": "whether there are more items available",
|
||||
"type": "boolean"
|
||||
},
|
||||
"limit": {
|
||||
"description": "the current limit",
|
||||
"type": "integer"
|
||||
},
|
||||
"offset": {
|
||||
"description": "the current offset",
|
||||
"type": "integer"
|
||||
},
|
||||
"total": {
|
||||
"description": "the total number of items returned",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.Reactions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -1416,17 +1454,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"api.SpacesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"spaces": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Space"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.Text": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -92,12 +92,12 @@ func (a *ApiServer) authTokenHandler(c *gin.Context) {
|
|||
// @Tags spaces
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param offset query int false "The number of items to skip before starting to collect the result set"
|
||||
// @Param limit query int false "The number of items to return" default(100)
|
||||
// @Success 200 {object} SpacesResponse "List of spaces"
|
||||
// @Failure 403 {object} UnauthorizedError "Unauthorized"
|
||||
// @Failure 404 {object} NotFoundError "Resource not found"
|
||||
// @Failure 502 {object} ServerError "Internal server error"
|
||||
// @Param offset query int false "The number of items to skip before starting to collect the result set"
|
||||
// @Param limit query int false "The number of items to return" default(100)
|
||||
// @Success 200 {object} PaginatedResponse[Space] "List of spaces"
|
||||
// @Failure 403 {object} UnauthorizedError "Unauthorized"
|
||||
// @Failure 404 {object} NotFoundError "Resource not found"
|
||||
// @Failure 502 {object} ServerError "Internal server error"
|
||||
// @Router /spaces [get]
|
||||
func (a *ApiServer) getSpacesHandler(c *gin.Context) {
|
||||
offset := c.GetInt("offset")
|
||||
|
@ -131,7 +131,7 @@ func (a *ApiServer) getSpacesHandler(c *gin.Context) {
|
|||
},
|
||||
Keys: []string{"targetSpaceId", "name", "iconEmoji", "iconImage"},
|
||||
Offset: int32(offset),
|
||||
Limit: int32(limit),
|
||||
Limit: int32(limit + 1),
|
||||
})
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
|
@ -158,7 +158,13 @@ func (a *ApiServer) getSpacesHandler(c *gin.Context) {
|
|||
spaces = append(spaces, workspace)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, SpacesResponse{Spaces: spaces})
|
||||
hasNext := false
|
||||
if len(spaces) > limit {
|
||||
hasNext = true
|
||||
spaces = spaces[:limit]
|
||||
}
|
||||
|
||||
respondWithPagination(c, http.StatusOK, spaces, len(spaces), offset, limit, hasNext)
|
||||
}
|
||||
|
||||
// createSpaceHandler creates a new space
|
||||
|
@ -214,13 +220,13 @@ func (a *ApiServer) createSpaceHandler(c *gin.Context) {
|
|||
// @Tags spaces
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param space_id path string true "The ID of the space"
|
||||
// @Param offset query int false "The number of items to skip before starting to collect the result set"
|
||||
// @Param limit query int false "The number of items to return" default(100)
|
||||
// @Success 200 {object} MembersResponse "List of members"
|
||||
// @Failure 403 {object} UnauthorizedError "Unauthorized"
|
||||
// @Failure 404 {object} NotFoundError "Resource not found"
|
||||
// @Failure 502 {object} ServerError "Internal server error"
|
||||
// @Param space_id path string true "The ID of the space"
|
||||
// @Param offset query int false "The number of items to skip before starting to collect the result set"
|
||||
// @Param limit query int false "The number of items to return" default(100)
|
||||
// @Success 200 {object} PaginatedResponse[Member] "List of members"
|
||||
// @Failure 403 {object} UnauthorizedError "Unauthorized"
|
||||
// @Failure 404 {object} NotFoundError "Resource not found"
|
||||
// @Failure 502 {object} ServerError "Internal server error"
|
||||
// @Router /spaces/{space_id}/members [get]
|
||||
func (a *ApiServer) getMembersHandler(c *gin.Context) {
|
||||
spaceId := c.Param("space_id")
|
||||
|
@ -250,7 +256,7 @@ func (a *ApiServer) getMembersHandler(c *gin.Context) {
|
|||
},
|
||||
Keys: []string{"id", "name", "iconEmoji", "iconImage", "identity", "globalName", "participantPermissions"},
|
||||
Offset: int32(offset),
|
||||
Limit: int32(limit),
|
||||
Limit: int32(limit + 1),
|
||||
})
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
|
@ -280,7 +286,13 @@ func (a *ApiServer) getMembersHandler(c *gin.Context) {
|
|||
members = append(members, member)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, MembersResponse{Members: members})
|
||||
hasNext := false
|
||||
if len(members) > limit {
|
||||
hasNext = true
|
||||
members = members[:limit]
|
||||
}
|
||||
|
||||
respondWithPagination(c, http.StatusOK, members, len(members), offset, limit, hasNext)
|
||||
}
|
||||
|
||||
// getObjectsForSpaceHandler retrieves objects in a specific space
|
||||
|
@ -329,7 +341,7 @@ func (a *ApiServer) getObjectsForSpaceHandler(c *gin.Context) {
|
|||
}},
|
||||
Keys: []string{"id", "name", "type", "layout", "iconEmoji", "iconImage", "lastModifiedDate"},
|
||||
Offset: int32(offset),
|
||||
Limit: int32(limit),
|
||||
Limit: int32(limit + 1),
|
||||
})
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
|
@ -372,7 +384,13 @@ func (a *ApiServer) getObjectsForSpaceHandler(c *gin.Context) {
|
|||
objects = append(objects, object)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"objects": objects})
|
||||
hasNext := false
|
||||
if len(objects) > limit {
|
||||
hasNext = true
|
||||
objects = objects[:limit]
|
||||
}
|
||||
|
||||
respondWithPagination(c, http.StatusOK, objects, len(objects), offset, limit, hasNext)
|
||||
}
|
||||
|
||||
// getObjectHandler retrieves a specific object in a space
|
||||
|
@ -543,7 +561,7 @@ func (a *ApiServer) getObjectTypesHandler(c *gin.Context) {
|
|||
},
|
||||
Keys: []string{"id", "uniqueKey", "name", "iconEmoji"},
|
||||
Offset: int32(offset),
|
||||
Limit: int32(limit),
|
||||
Limit: int32(limit + 1),
|
||||
})
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
|
@ -567,7 +585,13 @@ func (a *ApiServer) getObjectTypesHandler(c *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"object_types": objectTypes})
|
||||
hasNext := false
|
||||
if len(objectTypes) > limit {
|
||||
hasNext = true
|
||||
objectTypes = objectTypes[:limit]
|
||||
}
|
||||
|
||||
respondWithPagination(c, http.StatusOK, objectTypes, len(objectTypes), offset, limit, hasNext)
|
||||
}
|
||||
|
||||
// getObjectTypeTemplatesHandler retrieves a list of templates for a specific object type in a space
|
||||
|
@ -628,7 +652,7 @@ func (a *ApiServer) getObjectTypeTemplatesHandler(c *gin.Context) {
|
|||
},
|
||||
Keys: []string{"id", "targetObjectType", "name", "iconEmoji"},
|
||||
Offset: int32(offset),
|
||||
Limit: int32(limit),
|
||||
Limit: int32(limit + 1),
|
||||
})
|
||||
|
||||
if templateObjectsResp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
|
@ -669,25 +693,31 @@ func (a *ApiServer) getObjectTypeTemplatesHandler(c *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"templates": templates})
|
||||
hasNext := false
|
||||
if len(templates) > limit {
|
||||
hasNext = true
|
||||
templates = templates[:limit]
|
||||
}
|
||||
|
||||
respondWithPagination(c, http.StatusOK, templates, len(templates), offset, limit, hasNext)
|
||||
}
|
||||
|
||||
// getObjectsHandler searches and retrieves objects across all the spaces
|
||||
// searchHandler searches and retrieves objects across all the spaces
|
||||
//
|
||||
// @Summary Search and retrieve objects across all the spaces
|
||||
// @Tags search
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param search query string false "The search term to filter objects by name"
|
||||
// @Param query query string false "The search term to filter objects by name"
|
||||
// @Param object_type query string false "Specify object type for search"
|
||||
// @Param offset query int false "The number of items to skip before starting to collect the result set"
|
||||
// @Param limit query int false "The number of items to return" default(100)
|
||||
// @Success 200 {object} map[string][]Object "List of objects"
|
||||
// @Failure 403 {object} UnauthorizedError "Unauthorized"
|
||||
// @Failure 502 {object} ServerError "Internal server error"
|
||||
// @Router /objects [get]
|
||||
func (a *ApiServer) getObjectsHandler(c *gin.Context) {
|
||||
searchTerm := c.Query("search")
|
||||
// @Router /search [get]
|
||||
func (a *ApiServer) searchHandler(c *gin.Context) {
|
||||
searchQuery := c.Query("query")
|
||||
objectType := c.Query("object_type")
|
||||
offset := c.GetInt("offset")
|
||||
limit := c.GetInt("limit")
|
||||
|
@ -748,12 +778,12 @@ func (a *ApiServer) getObjectsHandler(c *gin.Context) {
|
|||
},
|
||||
}
|
||||
|
||||
if searchTerm != "" {
|
||||
if searchQuery != "" {
|
||||
// TODO also include snippet for notes
|
||||
filters = append(filters, &model.BlockContentDataviewFilter{
|
||||
RelationKey: bundle.RelationKeyName.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Like,
|
||||
Value: pbtypes.String(searchTerm),
|
||||
Value: pbtypes.String(searchQuery),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -780,7 +810,7 @@ func (a *ApiServer) getObjectsHandler(c *gin.Context) {
|
|||
}},
|
||||
Keys: []string{"id", "name", "type", "layout", "iconEmoji", "iconImage", "lastModifiedDate"},
|
||||
Offset: int32(offset),
|
||||
Limit: int32(limit),
|
||||
Limit: int32(limit + 1),
|
||||
// TODO split limit between spaces
|
||||
// Limit: paginationLimitPerSpace,
|
||||
// FullText: searchTerm,
|
||||
|
@ -828,11 +858,13 @@ func (a *ApiServer) getObjectsHandler(c *gin.Context) {
|
|||
})
|
||||
|
||||
// TODO: solve global pagination vs per space pagination
|
||||
hasNext := false
|
||||
if len(searchResults) > limit {
|
||||
hasNext = true
|
||||
searchResults = searchResults[:limit]
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"objects": searchResults})
|
||||
respondWithPagination(c, http.StatusOK, searchResults, len(searchResults), offset, limit, hasNext)
|
||||
}
|
||||
|
||||
// getChatMessagesHandler retrieves last chat messages
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
|
@ -239,3 +241,15 @@ func (a *ApiServer) getTags(resp *pb.RpcObjectShowResponse) []Tag {
|
|||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func respondWithPagination[T any](c *gin.Context, statusCode int, data []T, total, offset, limit int, hasNext bool) {
|
||||
c.JSON(statusCode, PaginatedResponse[T]{
|
||||
Data: data,
|
||||
Pagination: PaginationMeta{
|
||||
Total: total,
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
HasNext: hasNext,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
package api
|
||||
|
||||
type PaginationMeta struct {
|
||||
Total int `json:"total"` // the total number of items returned
|
||||
Offset int `json:"offset"` // the current offset
|
||||
Limit int `json:"limit"` // the current limit
|
||||
HasNext bool `json:"has_next"` // whether there are more items available
|
||||
}
|
||||
|
||||
type PaginatedResponse[T any] struct {
|
||||
Data []T `json:"data"`
|
||||
Pagination PaginationMeta `json:"pagination"`
|
||||
}
|
||||
|
||||
type AuthDisplayCodeResponse struct {
|
||||
ChallengeId string `json:"challenge_id" example:"67647f5ecda913e9a2e11b26"`
|
||||
}
|
||||
|
@ -9,10 +21,6 @@ type AuthTokenResponse struct {
|
|||
AppKey string `json:"app_key" example:""`
|
||||
}
|
||||
|
||||
type SpacesResponse struct {
|
||||
Spaces []Space `json:"spaces"`
|
||||
}
|
||||
|
||||
type CreateSpaceResponse struct {
|
||||
SpaceId string `json:"space_id" example:"bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1"`
|
||||
Name string `json:"name" example:"Space Name"`
|
||||
|
@ -37,10 +45,6 @@ type Space struct {
|
|||
NetworkId string `json:"network_id" example:"N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU"`
|
||||
}
|
||||
|
||||
type MembersResponse struct {
|
||||
Members []Member `json:"members"`
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
Type string `json:"type" example:"member"`
|
||||
Id string `json:"id" example:"_participant_bafyreigyfkt6rbv24sbv5aq2hko1bhmv5xxlf22b4bypdu6j7hnphm3psq_23me69r569oi1_AAjEaEwPF4nkEh9AWkqEnzcQ8HziBB4ETjiTpvRCQvWnSMDZ"`
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue