mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-08 05:47:07 +09:00
GO-4459: Refactor object service, add tests again
This commit is contained in:
parent
2bb2285551
commit
3520002bee
9 changed files with 1052 additions and 301 deletions
|
@ -578,7 +578,13 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "The created object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Object"
|
||||
"$ref": "#/definitions/object.CreateObjectResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/util.ValidationError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
|
@ -691,7 +697,13 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "The updated object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Object"
|
||||
"$ref": "#/definitions/object.UpdateObjectResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/util.ValidationError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
|
@ -768,6 +780,14 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"object.CreateObjectResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object": {
|
||||
"$ref": "#/definitions/object.Object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Detail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -922,6 +942,14 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"object.UpdateObjectResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object": {
|
||||
"$ref": "#/definitions/object.Object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pagination.PaginatedResponse-space_Member": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -572,7 +572,13 @@
|
|||
"200": {
|
||||
"description": "The created object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Object"
|
||||
"$ref": "#/definitions/object.CreateObjectResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/util.ValidationError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
|
@ -685,7 +691,13 @@
|
|||
"200": {
|
||||
"description": "The updated object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Object"
|
||||
"$ref": "#/definitions/object.UpdateObjectResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/util.ValidationError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
|
@ -762,6 +774,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"object.CreateObjectResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object": {
|
||||
"$ref": "#/definitions/object.Object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Detail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -916,6 +936,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"object.UpdateObjectResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object": {
|
||||
"$ref": "#/definitions/object.Object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pagination.PaginatedResponse-space_Member": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -34,6 +34,11 @@ definitions:
|
|||
vertical_align:
|
||||
type: string
|
||||
type: object
|
||||
object.CreateObjectResponse:
|
||||
properties:
|
||||
object:
|
||||
$ref: '#/definitions/object.Object'
|
||||
type: object
|
||||
object.Detail:
|
||||
properties:
|
||||
details:
|
||||
|
@ -140,6 +145,11 @@ definitions:
|
|||
text:
|
||||
type: string
|
||||
type: object
|
||||
object.UpdateObjectResponse:
|
||||
properties:
|
||||
object:
|
||||
$ref: '#/definitions/object.Object'
|
||||
type: object
|
||||
pagination.PaginatedResponse-space_Member:
|
||||
properties:
|
||||
data:
|
||||
|
@ -687,7 +697,11 @@ paths:
|
|||
"200":
|
||||
description: The created object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Object'
|
||||
$ref: '#/definitions/object.CreateObjectResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/util.ValidationError'
|
||||
"403":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
|
@ -762,7 +776,11 @@ paths:
|
|||
"200":
|
||||
description: The updated object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Object'
|
||||
$ref: '#/definitions/object.UpdateObjectResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/util.ValidationError'
|
||||
"403":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
|
|
|
@ -4,24 +4,11 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anyproto/anytype-heart/cmd/api/pagination"
|
||||
"github.com/anyproto/anytype-heart/cmd/api/util"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
type CreateObjectRequest struct {
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
TemplateId string `json:"template_id"`
|
||||
ObjectTypeUniqueKey string `json:"object_type_unique_key"`
|
||||
WithChat bool `json:"with_chat"`
|
||||
}
|
||||
|
||||
// GetObjectsHandler retrieves objects in a specific space
|
||||
//
|
||||
// @Summary Retrieve objects in a specific space
|
||||
|
@ -42,77 +29,19 @@ func GetObjectsHandler(s *ObjectService) gin.HandlerFunc {
|
|||
offset := c.GetInt("offset")
|
||||
limit := c.GetInt("limit")
|
||||
|
||||
resp := s.mw.ObjectSearch(c.Request.Context(), &pb.RpcObjectSearchRequest{
|
||||
SpaceId: spaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyLayout.String(),
|
||||
Condition: model.BlockContentDataviewFilter_In,
|
||||
Value: pbtypes.IntList([]int{
|
||||
int(model.ObjectType_basic),
|
||||
int(model.ObjectType_profile),
|
||||
int(model.ObjectType_todo),
|
||||
int(model.ObjectType_note),
|
||||
int(model.ObjectType_bookmark),
|
||||
int(model.ObjectType_set),
|
||||
int(model.ObjectType_collection),
|
||||
int(model.ObjectType_participant),
|
||||
}...),
|
||||
},
|
||||
},
|
||||
Sorts: []*model.BlockContentDataviewSort{{
|
||||
RelationKey: bundle.RelationKeyLastModifiedDate.String(),
|
||||
Type: model.BlockContentDataviewSort_Desc,
|
||||
Format: model.RelationFormat_longtext,
|
||||
IncludeTime: true,
|
||||
EmptyPlacement: model.BlockContentDataviewSort_NotSpecified,
|
||||
}},
|
||||
Keys: []string{"id", "name", "type", "layout", "iconEmoji", "iconImage"},
|
||||
})
|
||||
objects, total, hasMore, err := s.ListObjects(c.Request.Context(), spaceId, offset, limit)
|
||||
code := util.MapErrorCode(err,
|
||||
util.ErrToCode(ErrorFailedRetrieveObjects, http.StatusInternalServerError),
|
||||
util.ErrToCode(ErrNoObjectsFound, http.StatusNotFound),
|
||||
)
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to retrieve list of objects."})
|
||||
if code != http.StatusOK {
|
||||
apiErr := util.CodeToAPIError(code, err.Error())
|
||||
c.JSON(code, apiErr)
|
||||
return
|
||||
}
|
||||
|
||||
if len(resp.Records) == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"message": "No objects found."})
|
||||
return
|
||||
}
|
||||
|
||||
paginatedObjects, hasMore := pagination.Paginate(resp.Records, offset, limit)
|
||||
objects := make([]Object, 0, len(paginatedObjects))
|
||||
|
||||
for _, record := range paginatedObjects {
|
||||
icon := util.GetIconFromEmojiOrImage(s.AccountInfo, record.Fields["iconEmoji"].GetStringValue(), record.Fields["iconImage"].GetStringValue())
|
||||
objectTypeName, err := util.ResolveTypeToName(s.mw, spaceId, record.Fields["type"].GetStringValue())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to resolve object type name."})
|
||||
return
|
||||
}
|
||||
|
||||
objectShowResp := s.mw.ObjectShow(c.Request.Context(), &pb.RpcObjectShowRequest{
|
||||
SpaceId: spaceId,
|
||||
ObjectId: record.Fields["id"].GetStringValue(),
|
||||
})
|
||||
|
||||
object := Object{
|
||||
// TODO fix type inconsistency
|
||||
Type: model.ObjectTypeLayout_name[int32(record.Fields["layout"].GetNumberValue())],
|
||||
Id: record.Fields["id"].GetStringValue(),
|
||||
Name: record.Fields["name"].GetStringValue(),
|
||||
Icon: icon,
|
||||
ObjectType: objectTypeName,
|
||||
SpaceId: spaceId,
|
||||
RootId: objectShowResp.ObjectView.RootId,
|
||||
Blocks: s.GetBlocks(objectShowResp),
|
||||
Details: s.GetDetails(objectShowResp),
|
||||
}
|
||||
|
||||
objects = append(objects, object)
|
||||
}
|
||||
|
||||
pagination.RespondWithPagination(c, http.StatusOK, objects, len(resp.Records), offset, limit, hasMore)
|
||||
pagination.RespondWithPagination(c, http.StatusOK, objects, total, offset, limit, hasMore)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,38 +63,19 @@ func GetObjectHandler(s *ObjectService) gin.HandlerFunc {
|
|||
spaceId := c.Param("space_id")
|
||||
objectId := c.Param("object_id")
|
||||
|
||||
resp := s.mw.ObjectShow(c.Request.Context(), &pb.RpcObjectShowRequest{
|
||||
SpaceId: spaceId,
|
||||
ObjectId: objectId,
|
||||
})
|
||||
object, err := s.GetObject(c.Request.Context(), spaceId, objectId)
|
||||
code := util.MapErrorCode(err,
|
||||
util.ErrToCode(ErrObjectNotFound, http.StatusNotFound),
|
||||
util.ErrToCode(ErrFailedRetrieveObject, http.StatusInternalServerError),
|
||||
)
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectShowResponseError_NULL {
|
||||
if resp.Error.Code == pb.RpcObjectShowResponseError_NOT_FOUND {
|
||||
c.JSON(http.StatusNotFound, gin.H{"message": "Object not found", "space_id": spaceId, "object_id": objectId})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to retrieve object."})
|
||||
if code != http.StatusOK {
|
||||
apiErr := util.CodeToAPIError(code, err.Error())
|
||||
c.JSON(code, apiErr)
|
||||
return
|
||||
}
|
||||
|
||||
objectTypeName, err := util.ResolveTypeToName(s.mw, spaceId, resp.ObjectView.Details[0].Details.Fields["type"].GetStringValue())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to resolve object type name."})
|
||||
return
|
||||
}
|
||||
|
||||
object := Object{
|
||||
Type: "object",
|
||||
Id: objectId,
|
||||
Name: resp.ObjectView.Details[0].Details.Fields["name"].GetStringValue(),
|
||||
Icon: resp.ObjectView.Details[0].Details.Fields["iconEmoji"].GetStringValue(),
|
||||
ObjectType: objectTypeName,
|
||||
RootId: resp.ObjectView.RootId,
|
||||
Blocks: s.GetBlocks(resp),
|
||||
Details: s.GetDetails(resp),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"object": object})
|
||||
c.JSON(http.StatusOK, GetObjectResponse{Object: object})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,7 +87,8 @@ func GetObjectHandler(s *ObjectService) gin.HandlerFunc {
|
|||
// @Produce json
|
||||
// @Param space_id path string true "The ID of the space"
|
||||
// @Param object body map[string]string true "Object details (e.g., name)"
|
||||
// @Success 200 {object} Object "The created object"
|
||||
// @Success 200 {object} CreateObjectResponse "The created object"
|
||||
// @Failure 400 {object} util.ValidationError "Bad request"
|
||||
// @Failure 403 {object} util.UnauthorizedError "Unauthorized"
|
||||
// @Failure 502 {object} util.ServerError "Internal server error"
|
||||
// @Router /spaces/{space_id}/objects [post]
|
||||
|
@ -187,42 +98,23 @@ func CreateObjectHandler(s *ObjectService) gin.HandlerFunc {
|
|||
|
||||
request := CreateObjectRequest{}
|
||||
if err := c.BindJSON(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"message": "Invalid JSON"})
|
||||
c.JSON(http.StatusBadRequest, util.CodeToAPIError(http.StatusBadRequest, ErrBadInput.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
resp := s.mw.ObjectCreate(c.Request.Context(), &pb.RpcObjectCreateRequest{
|
||||
Details: &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
"name": pbtypes.String(request.Name),
|
||||
"iconEmoji": pbtypes.String(request.Icon),
|
||||
},
|
||||
},
|
||||
TemplateId: request.TemplateId,
|
||||
SpaceId: spaceId,
|
||||
ObjectTypeUniqueKey: request.ObjectTypeUniqueKey,
|
||||
WithChat: request.WithChat,
|
||||
})
|
||||
object, err := s.CreateObject(c.Request.Context(), spaceId, request)
|
||||
code := util.MapErrorCode(err,
|
||||
util.ErrToCode(ErrFailedCreateObject, http.StatusInternalServerError),
|
||||
util.ErrToCode(ErrFailedRetrieveObject, http.StatusInternalServerError),
|
||||
)
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectCreateResponseError_NULL {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to create a new object."})
|
||||
if code != http.StatusOK {
|
||||
apiErr := util.CodeToAPIError(code, err.Error())
|
||||
c.JSON(code, apiErr)
|
||||
return
|
||||
}
|
||||
|
||||
object := Object{
|
||||
Type: "object",
|
||||
Id: resp.ObjectId,
|
||||
Name: resp.Details.Fields["name"].GetStringValue(),
|
||||
Icon: resp.Details.Fields["iconEmoji"].GetStringValue(),
|
||||
ObjectType: request.ObjectTypeUniqueKey,
|
||||
SpaceId: resp.Details.Fields["spaceId"].GetStringValue(),
|
||||
// TODO populate other fields
|
||||
// RootId: resp.RootId,
|
||||
// Blocks: []Block{},
|
||||
// Details: []Detail{},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"object": object})
|
||||
c.JSON(http.StatusOK, CreateObjectResponse{Object: object})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,7 +127,8 @@ func CreateObjectHandler(s *ObjectService) gin.HandlerFunc {
|
|||
// @Param space_id path string true "The ID of the space"
|
||||
// @Param object_id path string true "The ID of the object"
|
||||
// @Param object body Object true "The updated object details"
|
||||
// @Success 200 {object} Object "The updated object"
|
||||
// @Success 200 {object} UpdateObjectResponse "The updated object"
|
||||
// @Failure 400 {object} util.ValidationError "Bad request"
|
||||
// @Failure 403 {object} util.UnauthorizedError "Unauthorized"
|
||||
// @Failure 404 {object} util.NotFoundError "Resource not found"
|
||||
// @Failure 502 {object} util.ServerError "Internal server error"
|
||||
|
@ -244,12 +137,31 @@ func UpdateObjectHandler(s *ObjectService) gin.HandlerFunc {
|
|||
return func(c *gin.Context) {
|
||||
spaceId := c.Param("space_id")
|
||||
objectId := c.Param("object_id")
|
||||
// TODO: Implement logic to update an existing object
|
||||
c.JSON(http.StatusNotImplemented, gin.H{"message": "Not implemented yet", "space_id": spaceId, "object_id": objectId})
|
||||
|
||||
request := UpdateObjectRequest{}
|
||||
if err := c.BindJSON(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.CodeToAPIError(http.StatusBadRequest, ErrBadInput.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
object, err := s.UpdateObject(c.Request.Context(), spaceId, objectId, request)
|
||||
code := util.MapErrorCode(err,
|
||||
util.ErrToCode(ErrNotImplemented, http.StatusNotImplemented),
|
||||
util.ErrToCode(ErrFailedUpdateObject, http.StatusInternalServerError),
|
||||
util.ErrToCode(ErrFailedRetrieveObject, http.StatusInternalServerError),
|
||||
)
|
||||
|
||||
if code != http.StatusOK {
|
||||
apiErr := util.CodeToAPIError(code, err.Error())
|
||||
c.JSON(code, apiErr)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusNotImplemented, UpdateObjectResponse{Object: object})
|
||||
}
|
||||
}
|
||||
|
||||
// GetObjectTypesHandler retrieves object types in a specific space
|
||||
// GetTypesHandler retrieves object types in a specific space
|
||||
//
|
||||
// @Summary Retrieve object types in a specific space
|
||||
// @Tags types_and_templates
|
||||
|
@ -263,63 +175,29 @@ func UpdateObjectHandler(s *ObjectService) gin.HandlerFunc {
|
|||
// @Failure 404 {object} util.NotFoundError "Resource not found"
|
||||
// @Failure 502 {object} util.ServerError "Internal server error"
|
||||
// @Router /spaces/{space_id}/objectTypes [get]
|
||||
func GetObjectTypesHandler(s *ObjectService) gin.HandlerFunc {
|
||||
func GetTypesHandler(s *ObjectService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
spaceId := c.Param("space_id")
|
||||
offset := c.GetInt("offset")
|
||||
limit := c.GetInt("limit")
|
||||
|
||||
resp := s.mw.ObjectSearch(c.Request.Context(), &pb.RpcObjectSearchRequest{
|
||||
SpaceId: spaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyLayout.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.Int64(int64(model.ObjectType_objectType)),
|
||||
},
|
||||
{
|
||||
RelationKey: bundle.RelationKeyIsHidden.String(),
|
||||
Condition: model.BlockContentDataviewFilter_NotEqual,
|
||||
Value: pbtypes.Bool(true),
|
||||
},
|
||||
},
|
||||
Sorts: []*model.BlockContentDataviewSort{
|
||||
{
|
||||
RelationKey: "name",
|
||||
Type: model.BlockContentDataviewSort_Asc,
|
||||
},
|
||||
},
|
||||
Keys: []string{"id", "uniqueKey", "name", "iconEmoji"},
|
||||
})
|
||||
types, total, hasMore, err := s.ListTypes(c.Request.Context(), spaceId, offset, limit)
|
||||
code := util.MapErrorCode(err,
|
||||
util.ErrToCode(ErrFailedRetrieveTypes, http.StatusInternalServerError),
|
||||
util.ErrToCode(ErrNoTypesFound, http.StatusNotFound),
|
||||
)
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to retrieve object types."})
|
||||
if code != http.StatusOK {
|
||||
apiErr := util.CodeToAPIError(code, err.Error())
|
||||
c.JSON(code, apiErr)
|
||||
return
|
||||
}
|
||||
|
||||
if len(resp.Records) == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"message": "No object types found."})
|
||||
return
|
||||
}
|
||||
|
||||
paginatedTypes, hasMore := pagination.Paginate(resp.Records, offset, limit)
|
||||
objectTypes := make([]ObjectType, 0, len(paginatedTypes))
|
||||
|
||||
for _, record := range paginatedTypes {
|
||||
objectTypes = append(objectTypes, ObjectType{
|
||||
Type: "object_type",
|
||||
Id: record.Fields["id"].GetStringValue(),
|
||||
UniqueKey: record.Fields["uniqueKey"].GetStringValue(),
|
||||
Name: record.Fields["name"].GetStringValue(),
|
||||
Icon: record.Fields["iconEmoji"].GetStringValue(),
|
||||
})
|
||||
}
|
||||
|
||||
pagination.RespondWithPagination(c, http.StatusOK, objectTypes, len(resp.Records), offset, limit, hasMore)
|
||||
pagination.RespondWithPagination(c, http.StatusOK, types, total, offset, limit, hasMore)
|
||||
}
|
||||
}
|
||||
|
||||
// GetObjectTypeTemplatesHandler retrieves a list of templates for a specific object type in a space
|
||||
// GetTemplatesHandler retrieves a list of templates for a specific object type in a space
|
||||
//
|
||||
// @Summary Retrieve a list of templates for a specific object type in a space
|
||||
// @Tags types_and_templates
|
||||
|
@ -334,91 +212,28 @@ func GetObjectTypesHandler(s *ObjectService) gin.HandlerFunc {
|
|||
// @Failure 404 {object} util.NotFoundError "Resource not found"
|
||||
// @Failure 502 {object} util.ServerError "Internal server error"
|
||||
// @Router /spaces/{space_id}/objectTypes/{typeId}/templates [get]
|
||||
func GetObjectTypeTemplatesHandler(s *ObjectService) gin.HandlerFunc {
|
||||
func GetTemplatesHandler(s *ObjectService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
spaceId := c.Param("space_id")
|
||||
typeId := c.Param("typeId")
|
||||
offset := c.GetInt("offset")
|
||||
limit := c.GetInt("limit")
|
||||
|
||||
// First, determine the type ID of "ot-template" in the space
|
||||
templateTypeIdResp := s.mw.ObjectSearch(c.Request.Context(), &pb.RpcObjectSearchRequest{
|
||||
SpaceId: spaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyUniqueKey.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.String("ot-template"),
|
||||
},
|
||||
},
|
||||
Keys: []string{"id"},
|
||||
})
|
||||
templates, total, hasMore, err := s.ListTemplates(c.Request.Context(), spaceId, typeId, offset, limit)
|
||||
code := util.MapErrorCode(err,
|
||||
util.ErrToCode(ErrFailedRetrieveTemplateType, http.StatusInternalServerError),
|
||||
util.ErrToCode(ErrTemplateTypeNotFound, http.StatusNotFound),
|
||||
util.ErrToCode(ErrFailedRetrieveTemplates, http.StatusInternalServerError),
|
||||
util.ErrToCode(ErrFailedRetrieveTemplate, http.StatusInternalServerError),
|
||||
util.ErrToCode(ErrNoTemplatesFound, http.StatusNotFound),
|
||||
)
|
||||
|
||||
if templateTypeIdResp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to retrieve template type."})
|
||||
if code != http.StatusOK {
|
||||
apiErr := util.CodeToAPIError(code, err.Error())
|
||||
c.JSON(code, apiErr)
|
||||
return
|
||||
}
|
||||
|
||||
if len(templateTypeIdResp.Records) == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"message": "Template type not found."})
|
||||
return
|
||||
}
|
||||
|
||||
templateTypeId := templateTypeIdResp.Records[0].Fields["id"].GetStringValue()
|
||||
|
||||
// Then, search all objects of the template type and filter by the target object type
|
||||
templateObjectsResp := s.mw.ObjectSearch(c.Request.Context(), &pb.RpcObjectSearchRequest{
|
||||
SpaceId: spaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyType.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.String(templateTypeId),
|
||||
},
|
||||
},
|
||||
Keys: []string{"id", "targetObjectType", "name", "iconEmoji"},
|
||||
})
|
||||
|
||||
if templateObjectsResp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to retrieve template objects."})
|
||||
return
|
||||
}
|
||||
|
||||
if len(templateObjectsResp.Records) == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"message": "No templates found."})
|
||||
return
|
||||
}
|
||||
|
||||
templateIds := make([]string, 0)
|
||||
for _, record := range templateObjectsResp.Records {
|
||||
if record.Fields["targetObjectType"].GetStringValue() == typeId {
|
||||
templateIds = append(templateIds, record.Fields["id"].GetStringValue())
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, open each template and populate the response
|
||||
paginatedTemplates, hasMore := pagination.Paginate(templateIds, offset, limit)
|
||||
templates := make([]ObjectTemplate, 0, len(paginatedTemplates))
|
||||
|
||||
for _, templateId := range paginatedTemplates {
|
||||
templateResp := s.mw.ObjectShow(c.Request.Context(), &pb.RpcObjectShowRequest{
|
||||
SpaceId: spaceId,
|
||||
ObjectId: templateId,
|
||||
})
|
||||
|
||||
if templateResp.Error.Code != pb.RpcObjectShowResponseError_NULL {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to retrieve template."})
|
||||
return
|
||||
}
|
||||
|
||||
templates = append(templates, ObjectTemplate{
|
||||
Type: "object_template",
|
||||
Id: templateId,
|
||||
Name: templateResp.ObjectView.Details[0].Details.Fields["name"].GetStringValue(),
|
||||
Icon: templateResp.ObjectView.Details[0].Details.Fields["iconEmoji"].GetStringValue(),
|
||||
})
|
||||
}
|
||||
|
||||
pagination.RespondWithPagination(c, http.StatusOK, templates, len(templateIds), offset, limit, hasMore)
|
||||
pagination.RespondWithPagination(c, http.StatusOK, templates, total, offset, limit, hasMore)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,29 @@
|
|||
package object
|
||||
|
||||
type GetObjectResponse struct {
|
||||
Object Object `json:"object"`
|
||||
}
|
||||
|
||||
type CreateObjectRequest struct {
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
TemplateId string `json:"template_id"`
|
||||
ObjectTypeUniqueKey string `json:"object_type_unique_key"`
|
||||
WithChat bool `json:"with_chat"`
|
||||
}
|
||||
|
||||
type CreateObjectResponse struct {
|
||||
Object Object `json:"object"`
|
||||
}
|
||||
|
||||
type UpdateObjectRequest struct {
|
||||
Object Object `json:"object"`
|
||||
}
|
||||
|
||||
type UpdateObjectResponse struct {
|
||||
Object Object `json:"object"`
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
Type string `json:"type" example:"object"`
|
||||
Id string `json:"id" example:"bafyreie6n5l5nkbjal37su54cha4coy7qzuhrnajluzv5qd5jvtsrxkequ"`
|
||||
|
|
|
@ -1,27 +1,45 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anyproto/anytype-heart/cmd/api/pagination"
|
||||
"github.com/anyproto/anytype-heart/cmd/api/util"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pb/service"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFailedGenerateChallenge = errors.New("failed to generate a new challenge")
|
||||
ErrInvalidInput = errors.New("invalid input")
|
||||
ErrorFailedAuthenticate = errors.New("failed to authenticate user")
|
||||
ErrObjectNotFound = errors.New("object not found")
|
||||
ErrFailedRetrieveObject = errors.New("failed to retrieve object")
|
||||
ErrorFailedRetrieveObjects = errors.New("failed to retrieve list of objects")
|
||||
ErrNoObjectsFound = errors.New("no objects found")
|
||||
ErrFailedCreateObject = errors.New("failed to create object")
|
||||
ErrBadInput = errors.New("bad input")
|
||||
ErrNotImplemented = errors.New("not implemented")
|
||||
ErrFailedUpdateObject = errors.New("failed to update object")
|
||||
ErrFailedRetrieveTypes = errors.New("failed to retrieve types")
|
||||
ErrNoTypesFound = errors.New("no types found")
|
||||
ErrFailedRetrieveTemplateType = errors.New("failed to retrieve template type")
|
||||
ErrTemplateTypeNotFound = errors.New("template type not found")
|
||||
ErrFailedRetrieveTemplate = errors.New("failed to retrieve template")
|
||||
ErrFailedRetrieveTemplates = errors.New("failed to retrieve templates")
|
||||
ErrNoTemplatesFound = errors.New("no templates found")
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
ListObjects() ([]Object, error)
|
||||
GetObject(id string) (Object, error)
|
||||
CreateObject(obj Object) (Object, error)
|
||||
UpdateObject(obj Object) (Object, error)
|
||||
ListTypes() ([]ObjectType, error)
|
||||
ListTemplates() ([]ObjectTemplate, error)
|
||||
ListObjects(ctx context.Context, spaceId string, offset int, limit int) ([]Object, int, bool, error)
|
||||
GetObject(ctx context.Context, spaceId string, objectId string) (Object, error)
|
||||
CreateObject(ctx context.Context, spaceId string, obj Object) (Object, error)
|
||||
UpdateObject(ctx context.Context, spaceId string, obj Object) (Object, error)
|
||||
ListTypes(ctx context.Context, spaceId string, offset int, limit int) ([]ObjectType, int, bool, error)
|
||||
ListTemplates(ctx context.Context, spaceId string, typeId string, offset int, limit int) ([]ObjectTemplate, int, bool, error)
|
||||
}
|
||||
|
||||
type ObjectService struct {
|
||||
|
@ -29,41 +47,290 @@ type ObjectService struct {
|
|||
AccountInfo *model.AccountInfo
|
||||
}
|
||||
|
||||
// NewService creates a new object service
|
||||
func NewService(mw service.ClientCommandsServer) *ObjectService {
|
||||
return &ObjectService{mw: mw}
|
||||
}
|
||||
|
||||
func (s *ObjectService) ListObjects() ([]Object, error) {
|
||||
// TODO
|
||||
return nil, nil
|
||||
// ListObjects retrieves a list of objects in a specific space
|
||||
func (s *ObjectService) ListObjects(ctx context.Context, spaceId string, offset int, limit int) (objects []Object, total int, hasMore bool, err error) {
|
||||
resp := s.mw.ObjectSearch(ctx, &pb.RpcObjectSearchRequest{
|
||||
SpaceId: spaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyLayout.String(),
|
||||
Condition: model.BlockContentDataviewFilter_In,
|
||||
Value: pbtypes.IntList([]int{
|
||||
int(model.ObjectType_basic),
|
||||
int(model.ObjectType_profile),
|
||||
int(model.ObjectType_todo),
|
||||
int(model.ObjectType_note),
|
||||
int(model.ObjectType_bookmark),
|
||||
int(model.ObjectType_set),
|
||||
int(model.ObjectType_collection),
|
||||
int(model.ObjectType_participant),
|
||||
}...),
|
||||
},
|
||||
},
|
||||
Sorts: []*model.BlockContentDataviewSort{{
|
||||
RelationKey: bundle.RelationKeyLastModifiedDate.String(),
|
||||
Type: model.BlockContentDataviewSort_Desc,
|
||||
Format: model.RelationFormat_longtext,
|
||||
IncludeTime: true,
|
||||
EmptyPlacement: model.BlockContentDataviewSort_NotSpecified,
|
||||
}},
|
||||
FullText: "",
|
||||
Offset: 0,
|
||||
Limit: 0,
|
||||
ObjectTypeFilter: []string{},
|
||||
Keys: []string{"id", "name", "type", "layout", "iconEmoji", "iconImage"},
|
||||
})
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
return nil, 0, false, ErrorFailedRetrieveObjects
|
||||
}
|
||||
|
||||
if len(resp.Records) == 0 {
|
||||
return nil, 0, false, ErrNoObjectsFound
|
||||
}
|
||||
|
||||
total = len(resp.Records)
|
||||
paginatedObjects, hasMore := pagination.Paginate(resp.Records, offset, limit)
|
||||
objects = make([]Object, 0, len(paginatedObjects))
|
||||
|
||||
for _, record := range paginatedObjects {
|
||||
object, err := s.GetObject(ctx, spaceId, record.Fields["id"].GetStringValue())
|
||||
if err != nil {
|
||||
return nil, 0, false, err
|
||||
}
|
||||
|
||||
// TODO: layout is not correctly returned in ObjectShow; therefore we need to resolve it here
|
||||
object.Type = model.ObjectTypeLayout_name[int32(record.Fields["layout"].GetNumberValue())]
|
||||
|
||||
objects = append(objects, object)
|
||||
}
|
||||
return objects, total, hasMore, nil
|
||||
}
|
||||
|
||||
func (s *ObjectService) GetObject(id string) (Object, error) {
|
||||
// TODO
|
||||
return Object{}, nil
|
||||
// GetObject retrieves a single object by its ID in a specific space
|
||||
func (s *ObjectService) GetObject(ctx context.Context, spaceId string, objectId string) (Object, error) {
|
||||
resp := s.mw.ObjectShow(ctx, &pb.RpcObjectShowRequest{
|
||||
SpaceId: spaceId,
|
||||
ObjectId: objectId,
|
||||
})
|
||||
|
||||
if resp.Error.Code == pb.RpcObjectShowResponseError_NOT_FOUND {
|
||||
return Object{}, ErrObjectNotFound
|
||||
}
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectShowResponseError_NULL {
|
||||
return Object{}, ErrFailedRetrieveObject
|
||||
}
|
||||
|
||||
icon := util.GetIconFromEmojiOrImage(s.AccountInfo, resp.ObjectView.Details[0].Details.Fields["iconEmoji"].GetStringValue(), resp.ObjectView.Details[0].Details.Fields["iconImage"].GetStringValue())
|
||||
objectTypeName, err := util.ResolveTypeToName(s.mw, spaceId, resp.ObjectView.Details[0].Details.Fields["type"].GetStringValue())
|
||||
if err != nil {
|
||||
return Object{}, err
|
||||
}
|
||||
|
||||
object := Object{
|
||||
Type: "object",
|
||||
Id: resp.ObjectView.Details[0].Details.Fields["id"].GetStringValue(),
|
||||
Name: resp.ObjectView.Details[0].Details.Fields["name"].GetStringValue(),
|
||||
Icon: icon,
|
||||
ObjectType: objectTypeName,
|
||||
SpaceId: resp.ObjectView.Details[0].Details.Fields["spaceId"].GetStringValue(),
|
||||
RootId: resp.ObjectView.RootId,
|
||||
Blocks: s.GetBlocks(resp),
|
||||
Details: s.GetDetails(resp),
|
||||
}
|
||||
|
||||
return object, nil
|
||||
}
|
||||
|
||||
func (s *ObjectService) CreateObject(obj Object) (Object, error) {
|
||||
// TODO
|
||||
return Object{}, nil
|
||||
// CreateObject creates a new object in a specific space
|
||||
func (s *ObjectService) CreateObject(ctx context.Context, spaceId string, request CreateObjectRequest) (Object, error) {
|
||||
resp := s.mw.ObjectCreate(ctx, &pb.RpcObjectCreateRequest{
|
||||
Details: &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
"name": pbtypes.String(request.Name),
|
||||
"iconEmoji": pbtypes.String(request.Icon),
|
||||
},
|
||||
},
|
||||
TemplateId: request.TemplateId,
|
||||
SpaceId: spaceId,
|
||||
ObjectTypeUniqueKey: request.ObjectTypeUniqueKey,
|
||||
WithChat: request.WithChat,
|
||||
})
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectCreateResponseError_NULL {
|
||||
return Object{}, ErrFailedCreateObject
|
||||
}
|
||||
|
||||
objShowResp := s.mw.ObjectShow(ctx, &pb.RpcObjectShowRequest{
|
||||
SpaceId: spaceId,
|
||||
ObjectId: resp.ObjectId,
|
||||
})
|
||||
|
||||
if objShowResp.Error.Code != pb.RpcObjectShowResponseError_NULL {
|
||||
return Object{}, ErrFailedRetrieveObject
|
||||
}
|
||||
|
||||
icon2 := util.GetIconFromEmojiOrImage(s.AccountInfo, objShowResp.ObjectView.Details[0].Details.Fields["iconEmoji"].GetStringValue(), objShowResp.ObjectView.Details[0].Details.Fields["iconImage"].GetStringValue())
|
||||
objectTypeName, err := util.ResolveTypeToName(s.mw, spaceId, objShowResp.ObjectView.Details[0].Details.Fields["type"].GetStringValue())
|
||||
if err != nil {
|
||||
return Object{}, err
|
||||
}
|
||||
|
||||
object := Object{
|
||||
Type: "object",
|
||||
Id: resp.ObjectId,
|
||||
Name: objShowResp.ObjectView.Details[0].Details.Fields["name"].GetStringValue(),
|
||||
Icon: icon2,
|
||||
ObjectType: objectTypeName,
|
||||
SpaceId: objShowResp.ObjectView.Details[0].Details.Fields["spaceId"].GetStringValue(),
|
||||
RootId: objShowResp.ObjectView.RootId,
|
||||
Blocks: s.GetBlocks(objShowResp),
|
||||
Details: s.GetDetails(objShowResp),
|
||||
}
|
||||
|
||||
return object, nil
|
||||
}
|
||||
|
||||
func (s *ObjectService) UpdateObject(obj Object) (Object, error) {
|
||||
// TODO
|
||||
return Object{}, nil
|
||||
// UpdateObject updates an existing object in a specific space
|
||||
func (s *ObjectService) UpdateObject(ctx context.Context, spaceId string, objectId string, request UpdateObjectRequest) (Object, error) {
|
||||
// TODO: Implement logic to update an existing object
|
||||
return Object{}, ErrNotImplemented
|
||||
}
|
||||
|
||||
func (s *ObjectService) ListTypes() ([]ObjectType, error) {
|
||||
// TODO
|
||||
return nil, nil
|
||||
// ListTypes returns the list of types in a specific space
|
||||
func (s *ObjectService) ListTypes(ctx context.Context, spaceId string, offset int, limit int) (types []ObjectType, total int, hasMore bool, err error) {
|
||||
resp := s.mw.ObjectSearch(ctx, &pb.RpcObjectSearchRequest{
|
||||
SpaceId: spaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyLayout.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.Int64(int64(model.ObjectType_objectType)),
|
||||
},
|
||||
{
|
||||
RelationKey: bundle.RelationKeyIsHidden.String(),
|
||||
Condition: model.BlockContentDataviewFilter_NotEqual,
|
||||
Value: pbtypes.Bool(true),
|
||||
},
|
||||
},
|
||||
Sorts: []*model.BlockContentDataviewSort{
|
||||
{
|
||||
RelationKey: "name",
|
||||
Type: model.BlockContentDataviewSort_Asc,
|
||||
},
|
||||
},
|
||||
Keys: []string{"id", "uniqueKey", "name", "iconEmoji"},
|
||||
})
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
return nil, 0, false, ErrFailedRetrieveTypes
|
||||
}
|
||||
|
||||
if len(resp.Records) == 0 {
|
||||
return nil, 0, false, ErrNoTypesFound
|
||||
}
|
||||
|
||||
total = len(resp.Records)
|
||||
paginatedTypes, hasMore := pagination.Paginate(resp.Records, offset, limit)
|
||||
objectTypes := make([]ObjectType, 0, len(paginatedTypes))
|
||||
|
||||
for _, record := range paginatedTypes {
|
||||
objectTypes = append(objectTypes, ObjectType{
|
||||
Type: "object_type",
|
||||
Id: record.Fields["id"].GetStringValue(),
|
||||
UniqueKey: record.Fields["uniqueKey"].GetStringValue(),
|
||||
Name: record.Fields["name"].GetStringValue(),
|
||||
Icon: record.Fields["iconEmoji"].GetStringValue(),
|
||||
})
|
||||
}
|
||||
return objectTypes, total, hasMore, nil
|
||||
}
|
||||
|
||||
func (s *ObjectService) ListTemplates() ([]ObjectTemplate, error) {
|
||||
// TODO
|
||||
return nil, nil
|
||||
// ListTemplates returns the list of templates in a specific space
|
||||
func (s *ObjectService) ListTemplates(ctx context.Context, spaceId string, typeId string, offset int, limit int) (templates []ObjectTemplate, total int, hasMore bool, err error) {
|
||||
// First, determine the type ID of "ot-template" in the space
|
||||
templateTypeIdResp := s.mw.ObjectSearch(ctx, &pb.RpcObjectSearchRequest{
|
||||
SpaceId: spaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyUniqueKey.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.String("ot-template"),
|
||||
},
|
||||
},
|
||||
Keys: []string{"id"},
|
||||
})
|
||||
|
||||
if templateTypeIdResp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
return nil, 0, false, ErrFailedRetrieveTemplateType
|
||||
}
|
||||
|
||||
if len(templateTypeIdResp.Records) == 0 {
|
||||
return nil, 0, false, ErrTemplateTypeNotFound
|
||||
}
|
||||
|
||||
// Then, search all objects of the template type and filter by the target object type
|
||||
templateTypeId := templateTypeIdResp.Records[0].Fields["id"].GetStringValue()
|
||||
templateObjectsResp := s.mw.ObjectSearch(ctx, &pb.RpcObjectSearchRequest{
|
||||
SpaceId: spaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyType.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.String(templateTypeId),
|
||||
},
|
||||
},
|
||||
Keys: []string{"id", "targetObjectType", "name", "iconEmoji"},
|
||||
})
|
||||
|
||||
if templateObjectsResp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
return nil, 0, false, ErrFailedRetrieveTemplates
|
||||
}
|
||||
|
||||
if len(templateObjectsResp.Records) == 0 {
|
||||
return nil, 0, false, ErrNoTemplatesFound
|
||||
}
|
||||
|
||||
templateIds := make([]string, 0)
|
||||
for _, record := range templateObjectsResp.Records {
|
||||
if record.Fields["targetObjectType"].GetStringValue() == typeId {
|
||||
templateIds = append(templateIds, record.Fields["id"].GetStringValue())
|
||||
}
|
||||
}
|
||||
|
||||
total = len(templateIds)
|
||||
paginatedTemplates, hasMore := pagination.Paginate(templateIds, offset, limit)
|
||||
templates = make([]ObjectTemplate, 0, len(paginatedTemplates))
|
||||
|
||||
// Finally, open each template and populate the response
|
||||
for _, templateId := range paginatedTemplates {
|
||||
templateResp := s.mw.ObjectShow(ctx, &pb.RpcObjectShowRequest{
|
||||
SpaceId: spaceId,
|
||||
ObjectId: templateId,
|
||||
})
|
||||
|
||||
if templateResp.Error.Code != pb.RpcObjectShowResponseError_NULL {
|
||||
return nil, 0, false, ErrFailedRetrieveTemplate
|
||||
}
|
||||
|
||||
templates = append(templates, ObjectTemplate{
|
||||
Type: "object_template",
|
||||
Id: templateId,
|
||||
Name: templateResp.ObjectView.Details[0].Details.Fields["name"].GetStringValue(),
|
||||
Icon: templateResp.ObjectView.Details[0].Details.Fields["iconEmoji"].GetStringValue(),
|
||||
})
|
||||
}
|
||||
|
||||
return templates, total, hasMore, nil
|
||||
}
|
||||
|
||||
// GetDetails returns the details of the object
|
||||
// GetDetails returns the list of details from the ObjectShowResponse
|
||||
func (s *ObjectService) GetDetails(resp *pb.RpcObjectShowResponse) []Detail {
|
||||
return []Detail{
|
||||
{
|
||||
|
@ -87,7 +354,7 @@ func (s *ObjectService) GetDetails(resp *pb.RpcObjectShowResponse) []Detail {
|
|||
}
|
||||
}
|
||||
|
||||
// getTags returns the list of tags from the object details
|
||||
// getTags returns the list of tags from the ObjectShowResponse
|
||||
func (s *ObjectService) getTags(resp *pb.RpcObjectShowResponse) []Tag {
|
||||
tags := []Tag{}
|
||||
|
||||
|
@ -112,7 +379,7 @@ func (s *ObjectService) getTags(resp *pb.RpcObjectShowResponse) []Tag {
|
|||
return tags
|
||||
}
|
||||
|
||||
// GetBlocks returns the blocks of the object
|
||||
// GetBlocks returns the list of blocks from the ObjectShowResponse
|
||||
func (s *ObjectService) GetBlocks(resp *pb.RpcObjectShowResponse) []Block {
|
||||
blocks := []Block{}
|
||||
|
||||
|
@ -158,6 +425,7 @@ func (s *ObjectService) GetBlocks(resp *pb.RpcObjectShowResponse) []Block {
|
|||
return blocks
|
||||
}
|
||||
|
||||
// mapAlign maps the protobuf BlockAlign to a string
|
||||
func mapAlign(align model.BlockAlign) string {
|
||||
switch align {
|
||||
case model.Block_AlignLeft:
|
||||
|
@ -173,6 +441,7 @@ func mapAlign(align model.BlockAlign) string {
|
|||
}
|
||||
}
|
||||
|
||||
// mapVerticalAlign maps the protobuf BlockVerticalAlign to a string
|
||||
func mapVerticalAlign(align model.BlockVerticalAlign) string {
|
||||
switch align {
|
||||
case model.Block_VerticalAlignTop:
|
||||
|
|
568
cmd/api/object/service_test.go
Normal file
568
cmd/api/object/service_test.go
Normal file
|
@ -0,0 +1,568 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pb/service/mock_service"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
const (
|
||||
offset = 0
|
||||
limit = 100
|
||||
mockedSpaceId = "mocked-space-id"
|
||||
mockedObjectId = "mocked-object-id"
|
||||
mockedNewObjectId = "mocked-new-object-id"
|
||||
mockedTechSpaceId = "mocked-tech-space-id"
|
||||
gatewayUrl = "http://localhost:31006"
|
||||
)
|
||||
|
||||
type fixture struct {
|
||||
*ObjectService
|
||||
mwMock *mock_service.MockClientCommandsServer
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
mw := mock_service.NewMockClientCommandsServer(t)
|
||||
objectService := NewService(mw)
|
||||
objectService.AccountInfo = &model.AccountInfo{
|
||||
TechSpaceId: mockedTechSpaceId,
|
||||
GatewayUrl: gatewayUrl,
|
||||
}
|
||||
|
||||
return &fixture{
|
||||
ObjectService: objectService,
|
||||
mwMock: mw,
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectService_ListObjects(t *testing.T) {
|
||||
t.Run("successfully get objects for a space", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{
|
||||
SpaceId: mockedSpaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyLayout.String(),
|
||||
Condition: model.BlockContentDataviewFilter_In,
|
||||
Value: pbtypes.IntList([]int{
|
||||
int(model.ObjectType_basic),
|
||||
int(model.ObjectType_profile),
|
||||
int(model.ObjectType_todo),
|
||||
int(model.ObjectType_note),
|
||||
int(model.ObjectType_bookmark),
|
||||
int(model.ObjectType_set),
|
||||
int(model.ObjectType_collection),
|
||||
int(model.ObjectType_participant),
|
||||
}...),
|
||||
},
|
||||
},
|
||||
Sorts: []*model.BlockContentDataviewSort{{
|
||||
RelationKey: bundle.RelationKeyLastModifiedDate.String(),
|
||||
Type: model.BlockContentDataviewSort_Desc,
|
||||
Format: model.RelationFormat_longtext,
|
||||
IncludeTime: true,
|
||||
EmptyPlacement: model.BlockContentDataviewSort_NotSpecified,
|
||||
}},
|
||||
FullText: "",
|
||||
Offset: 0,
|
||||
Limit: 0,
|
||||
ObjectTypeFilter: []string{},
|
||||
Keys: []string{"id", "name", "type", "layout", "iconEmoji", "iconImage"},
|
||||
}).Return(&pb.RpcObjectSearchResponse{
|
||||
Records: []*types.Struct{
|
||||
{
|
||||
Fields: map[string]*types.Value{
|
||||
"id": pbtypes.String(mockedObjectId),
|
||||
"name": pbtypes.String("My Object"),
|
||||
"type": pbtypes.String("ot-page"),
|
||||
"layout": pbtypes.Float64(float64(model.ObjectType_basic)),
|
||||
"iconEmoji": pbtypes.String("📄"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// Mock object show for object details
|
||||
fx.mwMock.On("ObjectShow", mock.Anything, &pb.RpcObjectShowRequest{
|
||||
SpaceId: mockedSpaceId,
|
||||
ObjectId: mockedObjectId,
|
||||
}).Return(&pb.RpcObjectShowResponse{
|
||||
ObjectView: &model.ObjectView{
|
||||
RootId: mockedObjectId,
|
||||
Details: []*model.ObjectViewDetailsSet{
|
||||
{
|
||||
Details: &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
"id": pbtypes.String(mockedObjectId),
|
||||
"name": pbtypes.String("My Object"),
|
||||
"type": pbtypes.String("ot-page"),
|
||||
"iconEmoji": pbtypes.String("📄"),
|
||||
"lastModifiedDate": pbtypes.Float64(999999),
|
||||
"createdDate": pbtypes.Float64(888888),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// Mock type resolution
|
||||
fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{
|
||||
SpaceId: mockedSpaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: "uniqueKey",
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.String("ot-page"),
|
||||
},
|
||||
},
|
||||
Keys: []string{"name"},
|
||||
}).Return(&pb.RpcObjectSearchResponse{
|
||||
Records: []*types.Struct{
|
||||
{
|
||||
Fields: map[string]*types.Value{
|
||||
"name": pbtypes.String("Page"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// when
|
||||
objects, total, hasMore, err := fx.ListObjects(ctx, mockedSpaceId, offset, limit)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Len(t, objects, 1)
|
||||
require.Equal(t, mockedObjectId, objects[0].Id)
|
||||
require.Equal(t, "My Object", objects[0].Name)
|
||||
require.Equal(t, "Page", objects[0].ObjectType)
|
||||
require.Equal(t, "📄", objects[0].Icon)
|
||||
require.Equal(t, 3, len(objects[0].Details))
|
||||
|
||||
for _, detail := range objects[0].Details {
|
||||
if detail.Id == "createdDate" {
|
||||
require.Equal(t, float64(888888), detail.Details["createdDate"])
|
||||
} else if detail.Id == "lastModifiedDate" {
|
||||
require.Equal(t, float64(999999), detail.Details["lastModifiedDate"])
|
||||
} else if detail.Id == "tags" {
|
||||
require.Empty(t, detail.Details["tags"])
|
||||
} else {
|
||||
t.Errorf("unexpected detail id: %s", detail.Id)
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, 1, total)
|
||||
require.False(t, hasMore)
|
||||
})
|
||||
|
||||
t.Run("no objects found", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).
|
||||
Return(&pb.RpcObjectSearchResponse{
|
||||
Records: []*types.Struct{},
|
||||
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// when
|
||||
objects, total, hasMore, err := fx.ListObjects(ctx, "empty-space", offset, limit)
|
||||
|
||||
// then
|
||||
require.ErrorIs(t, err, ErrNoObjectsFound)
|
||||
require.Len(t, objects, 0)
|
||||
require.Equal(t, 0, total)
|
||||
require.False(t, hasMore)
|
||||
})
|
||||
}
|
||||
|
||||
func TestObjectService_GetObject(t *testing.T) {
|
||||
t.Run("object found", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
fx.mwMock.On("ObjectShow", mock.Anything, &pb.RpcObjectShowRequest{
|
||||
SpaceId: mockedSpaceId,
|
||||
ObjectId: mockedObjectId,
|
||||
}).
|
||||
Return(&pb.RpcObjectShowResponse{
|
||||
Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL},
|
||||
ObjectView: &model.ObjectView{
|
||||
RootId: mockedObjectId,
|
||||
Details: []*model.ObjectViewDetailsSet{
|
||||
{
|
||||
Details: &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
"id": pbtypes.String(mockedObjectId),
|
||||
"name": pbtypes.String("Found Object"),
|
||||
"type": pbtypes.String("ot-page"),
|
||||
"iconEmoji": pbtypes.String("🔍"),
|
||||
"lastModifiedDate": pbtypes.Float64(999999),
|
||||
"createdDate": pbtypes.Float64(888888),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil).Once()
|
||||
|
||||
// Mock type resolution
|
||||
fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{
|
||||
SpaceId: mockedSpaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: "uniqueKey",
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.String("ot-page"),
|
||||
},
|
||||
},
|
||||
Keys: []string{"name"},
|
||||
}).Return(&pb.RpcObjectSearchResponse{
|
||||
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
|
||||
Records: []*types.Struct{
|
||||
{
|
||||
Fields: map[string]*types.Value{
|
||||
"name": pbtypes.String("Page"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil).Once()
|
||||
|
||||
// when
|
||||
object, err := fx.GetObject(ctx, mockedSpaceId, mockedObjectId)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, mockedObjectId, object.Id)
|
||||
require.Equal(t, "Found Object", object.Name)
|
||||
require.Equal(t, "Page", object.ObjectType)
|
||||
require.Equal(t, "🔍", object.Icon)
|
||||
require.Equal(t, 3, len(object.Details))
|
||||
|
||||
for _, detail := range object.Details {
|
||||
if detail.Id == "createdDate" {
|
||||
require.Equal(t, float64(888888), detail.Details["createdDate"])
|
||||
} else if detail.Id == "lastModifiedDate" {
|
||||
require.Equal(t, float64(999999), detail.Details["lastModifiedDate"])
|
||||
} else if detail.Id == "tags" {
|
||||
require.Empty(t, detail.Details["tags"])
|
||||
} else {
|
||||
t.Errorf("unexpected detail id: %s", detail.Id)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("object not found", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
fx.mwMock.On("ObjectShow", mock.Anything, mock.Anything).
|
||||
Return(&pb.RpcObjectShowResponse{
|
||||
Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NOT_FOUND},
|
||||
}, nil).Once()
|
||||
|
||||
// when
|
||||
object, err := fx.GetObject(ctx, mockedSpaceId, "missing-obj")
|
||||
|
||||
// then
|
||||
require.ErrorIs(t, err, ErrObjectNotFound)
|
||||
require.Empty(t, object)
|
||||
})
|
||||
}
|
||||
|
||||
func TestObjectService_CreateObject(t *testing.T) {
|
||||
t.Run("successful object creation", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
fx.mwMock.On("ObjectCreate", mock.Anything, &pb.RpcObjectCreateRequest{
|
||||
Details: &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
"name": pbtypes.String("New Object"),
|
||||
"iconEmoji": pbtypes.String("🆕"),
|
||||
},
|
||||
},
|
||||
TemplateId: "",
|
||||
SpaceId: mockedSpaceId,
|
||||
ObjectTypeUniqueKey: "",
|
||||
WithChat: false,
|
||||
}).Return(&pb.RpcObjectCreateResponse{
|
||||
ObjectId: mockedNewObjectId,
|
||||
Details: &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
"id": pbtypes.String(mockedNewObjectId),
|
||||
"name": pbtypes.String("New Object"),
|
||||
"iconEmoji": pbtypes.String("🆕"),
|
||||
"spaceId": pbtypes.String(mockedSpaceId),
|
||||
},
|
||||
},
|
||||
Error: &pb.RpcObjectCreateResponseError{Code: pb.RpcObjectCreateResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// Mock object show for object details
|
||||
fx.mwMock.On("ObjectShow", mock.Anything, &pb.RpcObjectShowRequest{
|
||||
SpaceId: mockedSpaceId,
|
||||
ObjectId: mockedNewObjectId,
|
||||
}).Return(&pb.RpcObjectShowResponse{
|
||||
ObjectView: &model.ObjectView{
|
||||
RootId: mockedNewObjectId,
|
||||
Details: []*model.ObjectViewDetailsSet{
|
||||
{
|
||||
Details: &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
"id": pbtypes.String(mockedNewObjectId),
|
||||
"name": pbtypes.String("New Object"),
|
||||
"type": pbtypes.String("ot-page"),
|
||||
"iconEmoji": pbtypes.String("🆕"),
|
||||
"spaceId": pbtypes.String(mockedSpaceId),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// Mock type resolution
|
||||
fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{
|
||||
SpaceId: mockedSpaceId,
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: "uniqueKey",
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.String("ot-page"),
|
||||
},
|
||||
},
|
||||
Keys: []string{"name"},
|
||||
}).Return(&pb.RpcObjectSearchResponse{
|
||||
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
|
||||
Records: []*types.Struct{
|
||||
{
|
||||
Fields: map[string]*types.Value{
|
||||
"name": pbtypes.String("Page"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}).Once()
|
||||
|
||||
// when
|
||||
object, err := fx.CreateObject(ctx, mockedSpaceId, CreateObjectRequest{
|
||||
Name: "New Object",
|
||||
Icon: "🆕",
|
||||
// TODO: use actual values
|
||||
TemplateId: "",
|
||||
ObjectTypeUniqueKey: "",
|
||||
WithChat: false,
|
||||
})
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, mockedNewObjectId, object.Id)
|
||||
require.Equal(t, "New Object", object.Name)
|
||||
require.Equal(t, "Page", object.ObjectType)
|
||||
require.Equal(t, "🆕", object.Icon)
|
||||
require.Equal(t, mockedSpaceId, object.SpaceId)
|
||||
})
|
||||
|
||||
t.Run("creation error", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
fx.mwMock.On("ObjectCreate", mock.Anything, mock.Anything).
|
||||
Return(&pb.RpcObjectCreateResponse{
|
||||
Error: &pb.RpcObjectCreateResponseError{Code: pb.RpcObjectCreateResponseError_UNKNOWN_ERROR},
|
||||
}).Once()
|
||||
|
||||
// when
|
||||
object, err := fx.CreateObject(ctx, mockedSpaceId, CreateObjectRequest{
|
||||
Name: "Fail Object",
|
||||
Icon: "",
|
||||
})
|
||||
|
||||
// then
|
||||
require.ErrorIs(t, err, ErrFailedCreateObject)
|
||||
require.Empty(t, object)
|
||||
})
|
||||
}
|
||||
|
||||
func TestObjectService_UpdateObject(t *testing.T) {
|
||||
t.Run("not implemented", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
// when
|
||||
object, err := fx.UpdateObject(ctx, mockedSpaceId, mockedObjectId, UpdateObjectRequest{
|
||||
Object: Object{
|
||||
Name: "Updated Object",
|
||||
},
|
||||
})
|
||||
|
||||
// then
|
||||
require.ErrorIs(t, err, ErrNotImplemented)
|
||||
require.Empty(t, object)
|
||||
})
|
||||
|
||||
// TODO: further tests
|
||||
}
|
||||
|
||||
func TestObjectService_ListTypes(t *testing.T) {
|
||||
t.Run("types found", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).
|
||||
Return(&pb.RpcObjectSearchResponse{
|
||||
Records: []*types.Struct{
|
||||
{
|
||||
Fields: map[string]*types.Value{
|
||||
"id": pbtypes.String("type-1"),
|
||||
"name": pbtypes.String("Type One"),
|
||||
"uniqueKey": pbtypes.String("type-one-key"),
|
||||
"iconEmoji": pbtypes.String("🗂️"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// when
|
||||
types, total, hasMore, err := fx.ListTypes(ctx, mockedSpaceId, offset, limit)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Len(t, types, 1)
|
||||
require.Equal(t, "type-1", types[0].Id)
|
||||
require.Equal(t, "Type One", types[0].Name)
|
||||
require.Equal(t, "type-one-key", types[0].UniqueKey)
|
||||
require.Equal(t, "🗂️", types[0].Icon)
|
||||
require.Equal(t, 1, total)
|
||||
require.False(t, hasMore)
|
||||
})
|
||||
|
||||
t.Run("no types found", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).
|
||||
Return(&pb.RpcObjectSearchResponse{
|
||||
Records: []*types.Struct{},
|
||||
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// when
|
||||
types, total, hasMore, err := fx.ListTypes(ctx, "empty-space", offset, limit)
|
||||
|
||||
// then
|
||||
require.ErrorIs(t, err, ErrNoTypesFound)
|
||||
require.Len(t, types, 0)
|
||||
require.Equal(t, 0, total)
|
||||
require.False(t, hasMore)
|
||||
})
|
||||
}
|
||||
|
||||
func TestObjectService_ListTemplates(t *testing.T) {
|
||||
t.Run("templates found", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
// Mock template type search
|
||||
fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).Return(&pb.RpcObjectSearchResponse{
|
||||
Records: []*types.Struct{
|
||||
{
|
||||
Fields: map[string]*types.Value{
|
||||
"id": pbtypes.String("template-type-id"),
|
||||
"uniqueKey": pbtypes.String("ot-template"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// Mock actual template objects search
|
||||
fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).Return(&pb.RpcObjectSearchResponse{
|
||||
Records: []*types.Struct{
|
||||
{
|
||||
Fields: map[string]*types.Value{
|
||||
"id": pbtypes.String("template-1"),
|
||||
"targetObjectType": pbtypes.String("target-type-id"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// Mock object show for template details
|
||||
fx.mwMock.On("ObjectShow", mock.Anything, mock.Anything).Return(&pb.RpcObjectShowResponse{
|
||||
Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL},
|
||||
ObjectView: &model.ObjectView{
|
||||
Details: []*model.ObjectViewDetailsSet{
|
||||
{
|
||||
Details: &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
"name": pbtypes.String("Template Name"),
|
||||
"iconEmoji": pbtypes.String("📝"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil).Once()
|
||||
|
||||
// when
|
||||
templates, total, hasMore, err := fx.ListTemplates(ctx, mockedSpaceId, "target-type-id", offset, limit)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Len(t, templates, 1)
|
||||
require.Equal(t, "template-1", templates[0].Id)
|
||||
require.Equal(t, "Template Name", templates[0].Name)
|
||||
require.Equal(t, "📝", templates[0].Icon)
|
||||
require.Equal(t, 1, total)
|
||||
require.False(t, hasMore)
|
||||
})
|
||||
|
||||
t.Run("no template type found", func(t *testing.T) {
|
||||
// given
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).
|
||||
Return(&pb.RpcObjectSearchResponse{
|
||||
Records: []*types.Struct{},
|
||||
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
|
||||
}).Once()
|
||||
|
||||
// when
|
||||
templates, total, hasMore, err := fx.ListTemplates(ctx, mockedSpaceId, "missing-type-id", offset, limit)
|
||||
|
||||
// then
|
||||
require.ErrorIs(t, err, ErrTemplateTypeNotFound)
|
||||
require.Len(t, templates, 0)
|
||||
require.Equal(t, 0, total)
|
||||
require.False(t, hasMore)
|
||||
})
|
||||
}
|
|
@ -46,8 +46,8 @@ func (s *Server) NewRouter() *gin.Engine {
|
|||
readOnly.GET("/spaces/:space_id/members", paginator, space.GetMembersHandler(s.spaceService))
|
||||
readOnly.GET("/spaces/:space_id/objects", paginator, object.GetObjectsHandler(s.objectService))
|
||||
readOnly.GET("/spaces/:space_id/objects/:object_id", object.GetObjectHandler(s.objectService))
|
||||
readOnly.GET("/spaces/:space_id/objectTypes", paginator, object.GetObjectTypesHandler(s.objectService))
|
||||
readOnly.GET("/spaces/:space_id/objectTypes/:typeId/templates", paginator, object.GetObjectTypeTemplatesHandler(s.objectService))
|
||||
readOnly.GET("/spaces/:space_id/objectTypes", paginator, object.GetTypesHandler(s.objectService))
|
||||
readOnly.GET("/spaces/:space_id/objectTypes/:typeId/templates", paginator, object.GetTemplatesHandler(s.objectService))
|
||||
readOnly.GET("/search", paginator, search.SearchHandler(s.searchService))
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ func ResolveTypeToName(mw service.ClientCommandsServer, spaceId string, typeId s
|
|||
Value: pbtypes.String(typeId),
|
||||
},
|
||||
},
|
||||
Keys: []string{"name"},
|
||||
})
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue