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

GO-5645: Add ensureFilters middleware to set filters context for listing endpoints

This commit is contained in:
Jannis Metrikat 2025-05-23 18:26:13 +02:00
parent 45a39daef2
commit b12c66c712
No known key found for this signature in database
GPG key ID: B223CAC5AAF85615
4 changed files with 201 additions and 59 deletions

View file

@ -14,7 +14,10 @@ import (
"github.com/anyproto/anytype-heart/core/api/util"
"github.com/anyproto/anytype-heart/core/event"
"github.com/anyproto/anytype-heart/pb"
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
"github.com/anyproto/anytype-heart/pkg/lib/logging"
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
"github.com/anyproto/anytype-heart/util/pbtypes"
)
const ApiVersion = "2025-05-20"
@ -28,7 +31,7 @@ var (
)
// ensureMetadataHeader is a middleware that ensures the metadata header is set.
func (s *Server) ensureMetadataHeader() gin.HandlerFunc {
func ensureMetadataHeader() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Anytype-Version", ApiVersion)
c.Next()
@ -84,7 +87,7 @@ func (s *Server) ensureAuthenticated(mw apicore.ClientCommands) gin.HandlerFunc
}
// ensureAnalyticsEvent is a middleware that ensures broadcasting an analytics event after a successful request.
func (s *Server) ensureAnalyticsEvent(code string, eventService apicore.EventService) gin.HandlerFunc {
func ensureAnalyticsEvent(code string, eventService apicore.EventService) gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
@ -125,3 +128,29 @@ func ensureRateLimit(rate float64, burst int, isRateLimitDisabled bool) gin.Hand
c.Next()
}
}
// ensureFilters is a middleware that ensures the filters are set in the context.
func ensureFilters() gin.HandlerFunc {
filterDefs := []struct {
Param string
RelationKey string
Condition model.BlockContentDataviewFilterCondition
}{
{bundle.RelationKeyName.String(), bundle.RelationKeyName.String(), model.BlockContentDataviewFilter_Like},
}
return func(c *gin.Context) {
var filters []*model.BlockContentDataviewFilter
for _, def := range filterDefs {
if v := c.Query(def.Param); v != "" {
filters = append(filters, &model.BlockContentDataviewFilter{
RelationKey: def.RelationKey,
Condition: def.Condition,
Value: pbtypes.String(v),
})
}
}
c.Set("filters", filters)
c.Next()
}
}

View file

@ -18,8 +18,7 @@ import (
func TestEnsureMetadataHeader(t *testing.T) {
t.Run("sets correct header", func(t *testing.T) {
// given
fx := newFixture(t)
middleware := fx.ensureMetadataHeader()
middleware := ensureMetadataHeader()
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
@ -145,10 +144,9 @@ func TestEnsureAnalyticsEvent(t *testing.T) {
fx := newFixture(t)
code := "test-code"
fx.eventMock.On("Broadcast", mock.AnythingOfType("*pb.Event")).Return()
srv := &Server{}
mw := srv.ensureAnalyticsEvent(code, fx.eventMock)
middleware := ensureAnalyticsEvent(code, fx.eventMock)
router := gin.New()
router.Use(mw)
router.Use(middleware)
router.GET("/test", func(c *gin.Context) {
c.String(http.StatusAccepted, "OK")
})

View file

@ -22,7 +22,6 @@ const (
)
// NewRouter builds and returns a *gin.Engine with all routes configured.
func (s *Server) NewRouter(mw apicore.ClientCommands, eventService apicore.EventService) *gin.Engine {
isDebug := os.Getenv("ANYTYPE_API_DEBUG") == "1"
if !isDebug {
@ -31,7 +30,7 @@ func (s *Server) NewRouter(mw apicore.ClientCommands, eventService apicore.Event
router := gin.New()
router.Use(gin.Recovery())
router.Use(s.ensureMetadataHeader())
router.Use(ensureMetadataHeader())
if isDebug {
router.Use(gin.Logger())
@ -44,7 +43,7 @@ func (s *Server) NewRouter(mw apicore.ClientCommands, eventService apicore.Event
MaxPageSize: maxPageSize,
})
// Shared ratelimiter with option to disable it through env var
// Shared ratelimiter with the option to disable it through env var
isRateLimitDisabled := os.Getenv("ANYTYPE_API_DISABLE_RATE_LIMIT") == "1"
writeRateLimitMW := ensureRateLimit(maxWriteRequestsPerSecond, maxBurstRequests, isRateLimitDisabled)
@ -77,9 +76,8 @@ func (s *Server) NewRouter(mw apicore.ClientCommands, eventService apicore.Event
{
// TO BE DEPRECATED
authGroup.POST("/auth/display_code", handler.DisplayCodeHandler(s.service))
// TO BE DEPRECATED
authGroup.POST("/auth/token", handler.TokenHandler(s.service))
// UPDATED ROUTES
authGroup.POST("/auth/challenges", handler.CreateChallengeHandler(s.service))
authGroup.POST("/auth/api_keys", handler.CreateApiKeyHandler(s.service))
}
@ -96,58 +94,187 @@ func (s *Server) NewRouter(mw apicore.ClientCommands, eventService apicore.Event
// v1.DELETE("/spaces/:space_id/objects/:object_id/blocks/:block_id", writeRateLimitMW, object.DeleteBlockHandler(s.service))
// List
v1.GET("/spaces/:space_id/lists/:list_id/views", s.ensureAnalyticsEvent("ListGetViews", eventService), handler.GetListViewsHandler(s.service))
v1.GET("/spaces/:space_id/lists/:list_id/views/:view_id/objects", s.ensureAnalyticsEvent("ListGetObjects", eventService), handler.GetObjectsInListHandler(s.service))
v1.POST("/spaces/:space_id/lists/:list_id/objects", writeRateLimitMW, s.ensureAnalyticsEvent("ListAddObject", eventService), handler.AddObjectsToListHandler(s.service))
v1.DELETE("/spaces/:space_id/lists/:list_id/objects/:object_id", writeRateLimitMW, s.ensureAnalyticsEvent("ListRemoveObject", eventService), handler.RemoveObjectFromListHandler(s.service))
v1.GET("/spaces/:space_id/lists/:list_id/views",
ensureAnalyticsEvent("ListGetViews", eventService),
handler.GetListViewsHandler(s.service),
)
v1.GET("/spaces/:space_id/lists/:list_id/views/:view_id/objects",
ensureFilters(),
ensureAnalyticsEvent("ListGetObjects", eventService),
handler.GetObjectsInListHandler(s.service),
)
v1.POST("/spaces/:space_id/lists/:list_id/objects",
writeRateLimitMW,
ensureAnalyticsEvent("ListAddObject", eventService),
handler.AddObjectsToListHandler(s.service),
)
v1.DELETE("/spaces/:space_id/lists/:list_id/objects/:object_id",
writeRateLimitMW,
ensureAnalyticsEvent("ListRemoveObject", eventService),
handler.RemoveObjectFromListHandler(s.service),
)
// Member
v1.GET("/spaces/:space_id/members", s.ensureAnalyticsEvent("MemberList", eventService), handler.ListMembersHandler(s.service))
v1.GET("/spaces/:space_id/members/:member_id", s.ensureAnalyticsEvent("MemberOpen", eventService), handler.GetMemberHandler(s.service))
v1.GET("/spaces/:space_id/members",
ensureFilters(),
ensureAnalyticsEvent("MemberList", eventService),
handler.ListMembersHandler(s.service),
)
v1.GET("/spaces/:space_id/members/:member_id",
ensureAnalyticsEvent("MemberOpen", eventService),
handler.GetMemberHandler(s.service),
)
// TODO: renable when granular permissions are implementeds
// v1.PATCH("/spaces/:space_id/members/:member_id", writeRateLimitMW, space.UpdateMemberHandler(s.service))
// v1.PATCH("/spaces/:space_id/members/:member_id",
// writeRateLimitMW,
// handler.UpdateMemberHandler(s.service),
// )
// Object
v1.GET("/spaces/:space_id/objects", s.ensureAnalyticsEvent("ObjectList", eventService), handler.ListObjectsHandler(s.service))
v1.GET("/spaces/:space_id/objects/:object_id", s.ensureAnalyticsEvent("ObjectOpen", eventService), handler.GetObjectHandler(s.service))
v1.POST("/spaces/:space_id/objects", writeRateLimitMW, s.ensureAnalyticsEvent("ObjectCreate", eventService), handler.CreateObjectHandler(s.service))
v1.PATCH("/spaces/:space_id/objects/:object_id", writeRateLimitMW, s.ensureAnalyticsEvent("ObjectUpdate", eventService), handler.UpdateObjectHandler(s.service))
v1.DELETE("/spaces/:space_id/objects/:object_id", writeRateLimitMW, s.ensureAnalyticsEvent("ObjectDelete", eventService), handler.DeleteObjectHandler(s.service))
v1.GET("/spaces/:space_id/objects",
ensureFilters(),
ensureAnalyticsEvent("ObjectList", eventService),
handler.ListObjectsHandler(s.service),
)
v1.GET("/spaces/:space_id/objects/:object_id",
ensureAnalyticsEvent("ObjectOpen", eventService),
handler.GetObjectHandler(s.service),
)
v1.POST("/spaces/:space_id/objects",
writeRateLimitMW,
ensureAnalyticsEvent("ObjectCreate", eventService),
handler.CreateObjectHandler(s.service),
)
v1.PATCH("/spaces/:space_id/objects/:object_id",
writeRateLimitMW,
ensureAnalyticsEvent("ObjectUpdate", eventService),
handler.UpdateObjectHandler(s.service),
)
v1.DELETE("/spaces/:space_id/objects/:object_id",
writeRateLimitMW,
ensureAnalyticsEvent("ObjectDelete", eventService),
handler.DeleteObjectHandler(s.service),
)
// Property
v1.GET("/spaces/:space_id/properties", s.ensureAnalyticsEvent("PropertyList", eventService), handler.ListPropertiesHandler(s.service))
v1.GET("/spaces/:space_id/properties/:property_id", s.ensureAnalyticsEvent("PropertyOpen", eventService), handler.GetPropertyHandler(s.service))
v1.POST("/spaces/:space_id/properties", writeRateLimitMW, s.ensureAnalyticsEvent("PropertyCreate", eventService), handler.CreatePropertyHandler(s.service))
v1.PATCH("/spaces/:space_id/properties/:property_id", writeRateLimitMW, s.ensureAnalyticsEvent("PropertyUpdate", eventService), handler.UpdatePropertyHandler(s.service))
v1.DELETE("/spaces/:space_id/properties/:property_id", writeRateLimitMW, s.ensureAnalyticsEvent("PropertyDelete", eventService), handler.DeletePropertyHandler(s.service))
v1.GET("/spaces/:space_id/properties",
ensureFilters(),
ensureAnalyticsEvent("PropertyList", eventService),
handler.ListPropertiesHandler(s.service),
)
v1.GET("/spaces/:space_id/properties/:property_id",
ensureAnalyticsEvent("PropertyOpen", eventService),
handler.GetPropertyHandler(s.service),
)
v1.POST("/spaces/:space_id/properties",
writeRateLimitMW,
ensureAnalyticsEvent("PropertyCreate", eventService),
handler.CreatePropertyHandler(s.service),
)
v1.PATCH("/spaces/:space_id/properties/:property_id",
writeRateLimitMW,
ensureAnalyticsEvent("PropertyUpdate", eventService),
handler.UpdatePropertyHandler(s.service),
)
v1.DELETE("/spaces/:space_id/properties/:property_id",
writeRateLimitMW,
ensureAnalyticsEvent("PropertyDelete", eventService),
handler.DeletePropertyHandler(s.service),
)
// Search
v1.POST("/search", s.ensureAnalyticsEvent("SearchGlobal", eventService), handler.GlobalSearchHandler(s.service))
v1.POST("/spaces/:space_id/search", s.ensureAnalyticsEvent("SearchSpace", eventService), handler.SearchHandler(s.service))
v1.POST("/search",
ensureAnalyticsEvent("SearchGlobal", eventService),
handler.GlobalSearchHandler(s.service),
)
v1.POST("/spaces/:space_id/search",
ensureAnalyticsEvent("SearchSpace", eventService),
handler.SearchHandler(s.service),
)
// Space
v1.GET("/spaces", s.ensureAnalyticsEvent("SpaceList", eventService), handler.ListSpacesHandler(s.service))
v1.GET("/spaces/:space_id", s.ensureAnalyticsEvent("SpaceOpen", eventService), handler.GetSpaceHandler(s.service))
v1.POST("/spaces", writeRateLimitMW, s.ensureAnalyticsEvent("SpaceCreate", eventService), handler.CreateSpaceHandler(s.service))
v1.PATCH("/spaces/:space_id", writeRateLimitMW, s.ensureAnalyticsEvent("SpaceUpdate", eventService), handler.UpdateSpaceHandler(s.service))
v1.GET("/spaces",
ensureFilters(),
ensureAnalyticsEvent("SpaceList", eventService),
handler.ListSpacesHandler(s.service),
)
v1.GET("/spaces/:space_id",
ensureAnalyticsEvent("SpaceOpen", eventService),
handler.GetSpaceHandler(s.service),
)
v1.POST("/spaces",
writeRateLimitMW,
ensureAnalyticsEvent("SpaceCreate", eventService),
handler.CreateSpaceHandler(s.service),
)
v1.PATCH("/spaces/:space_id",
writeRateLimitMW,
ensureAnalyticsEvent("SpaceUpdate", eventService),
handler.UpdateSpaceHandler(s.service),
)
// Tag
v1.GET("/spaces/:space_id/properties/:property_id/tags", s.ensureAnalyticsEvent("TagList", eventService), handler.ListTagsHandler(s.service))
v1.GET("/spaces/:space_id/properties/:property_id/tags/:tag_id", s.ensureAnalyticsEvent("TagOpen", eventService), handler.GetTagHandler(s.service))
v1.POST("/spaces/:space_id/properties/:property_id/tags", writeRateLimitMW, s.ensureAnalyticsEvent("TagCreate", eventService), handler.CreateTagHandler(s.service))
v1.PATCH("/spaces/:space_id/properties/:property_id/tags/:tag_id", writeRateLimitMW, s.ensureAnalyticsEvent("TagUpdate", eventService), handler.UpdateTagHandler(s.service))
v1.DELETE("/spaces/:space_id/properties/:property_id/tags/:tag_id", writeRateLimitMW, s.ensureAnalyticsEvent("TagDelete", eventService), handler.DeleteTagHandler(s.service))
v1.GET("/spaces/:space_id/properties/:property_id/tags",
ensureFilters(),
ensureAnalyticsEvent("TagList", eventService),
handler.ListTagsHandler(s.service),
)
v1.GET("/spaces/:space_id/properties/:property_id/tags/:tag_id",
ensureAnalyticsEvent("TagOpen", eventService),
handler.GetTagHandler(s.service),
)
v1.POST("/spaces/:space_id/properties/:property_id/tags",
writeRateLimitMW,
ensureAnalyticsEvent("TagCreate", eventService),
handler.CreateTagHandler(s.service),
)
v1.PATCH("/spaces/:space_id/properties/:property_id/tags/:tag_id",
writeRateLimitMW,
ensureAnalyticsEvent("TagUpdate", eventService),
handler.UpdateTagHandler(s.service),
)
v1.DELETE("/spaces/:space_id/properties/:property_id/tags/:tag_id",
writeRateLimitMW,
ensureAnalyticsEvent("TagDelete", eventService),
handler.DeleteTagHandler(s.service),
)
// Template
v1.GET("/spaces/:space_id/types/:type_id/templates", s.ensureAnalyticsEvent("TemplateList", eventService), handler.ListTemplatesHandler(s.service))
v1.GET("/spaces/:space_id/types/:type_id/templates/:template_id", s.ensureAnalyticsEvent("TemplateOpen", eventService), handler.GetTemplateHandler(s.service))
v1.GET("/spaces/:space_id/types/:type_id/templates",
ensureFilters(),
ensureAnalyticsEvent("TemplateList", eventService),
handler.ListTemplatesHandler(s.service),
)
v1.GET("/spaces/:space_id/types/:type_id/templates/:template_id",
ensureAnalyticsEvent("TemplateOpen", eventService),
handler.GetTemplateHandler(s.service),
)
// Type
v1.GET("/spaces/:space_id/types", s.ensureAnalyticsEvent("TypeList", eventService), handler.ListTypesHandler(s.service))
v1.GET("/spaces/:space_id/types/:type_id", s.ensureAnalyticsEvent("TypeOpen", eventService), handler.GetTypeHandler(s.service))
v1.POST("/spaces/:space_id/types", writeRateLimitMW, s.ensureAnalyticsEvent("TypeCreate", eventService), handler.CreateTypeHandler(s.service))
v1.PATCH("/spaces/:space_id/types/:type_id", writeRateLimitMW, s.ensureAnalyticsEvent("TypeUpdate", eventService), handler.UpdateTypeHandler(s.service))
v1.DELETE("/spaces/:space_id/types/:type_id", writeRateLimitMW, s.ensureAnalyticsEvent("TypeDelete", eventService), handler.DeleteTypeHandler(s.service))
v1.GET("/spaces/:space_id/types",
ensureFilters(),
ensureAnalyticsEvent("TypeList", eventService),
handler.ListTypesHandler(s.service),
)
v1.GET("/spaces/:space_id/types/:type_id",
ensureAnalyticsEvent("TypeOpen", eventService),
handler.GetTypeHandler(s.service),
)
v1.POST("/spaces/:space_id/types",
writeRateLimitMW,
ensureAnalyticsEvent("TypeCreate", eventService),
handler.CreateTypeHandler(s.service),
)
v1.PATCH("/spaces/:space_id/types/:type_id",
writeRateLimitMW,
ensureAnalyticsEvent("TypeUpdate", eventService),
handler.UpdateTypeHandler(s.service),
)
v1.DELETE("/spaces/:space_id/types/:type_id",
writeRateLimitMW,
ensureAnalyticsEvent("TypeDelete", eventService),
handler.DeleteTypeHandler(s.service),
)
}
return router

View file

@ -1,12 +0,0 @@
package service
import (
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
"github.com/gogo/protobuf/types"
)
type Filter struct {
RelationKey string
Condition model.BlockContentDataviewFilterCondition
Value *types.Value
}