From d8c8ce4b004a1979cc6acdda47ce12d2f5c3e01e Mon Sep 17 00:00:00 2001 From: Jannis Metrikat <120120832+jmetrikat@users.noreply.github.com> Date: Thu, 9 Jan 2025 00:17:58 +0100 Subject: [PATCH] GO-4459: Add endpoint to delete object --- cmd/api/demo/api_demo.go | 1 + cmd/api/docs/docs.go | 84 ++++++++++++++++++++++++++++++--------- cmd/api/docs/swagger.json | 84 ++++++++++++++++++++++++++++++--------- cmd/api/docs/swagger.yaml | 57 ++++++++++++++++++++------ cmd/api/object/handler.go | 47 +++++++++++++++++++--- cmd/api/object/model.go | 11 +---- cmd/api/object/service.go | 25 +++++++++++- cmd/api/server/router.go | 1 + 8 files changed, 242 insertions(+), 68 deletions(-) diff --git a/cmd/api/demo/api_demo.go b/cmd/api/demo/api_demo.go index 58ba523c1..5901cd37c 100644 --- a/cmd/api/demo/api_demo.go +++ b/cmd/api/demo/api_demo.go @@ -66,6 +66,7 @@ func main() { // objects // {"GET", "/spaces/{space_id}/objects?limit={limit}&offset={offset}", map[string]interface{}{"space_id": testSpaceId, "limit": 100, "offset": 0}, nil}, // {"GET", "/spaces/{space_id}/objects/{object_id}", map[string]interface{}{"space_id": testSpaceId, "object_id": testObjectId}, nil}, + // {"DELETE", "/spaces/{space_id}/objects/{object_id}", map[string]interface{}{"space_id": "asd", "object_id": "asd"}, nil}, // {"POST", "/spaces/{space_id}/objects", map[string]interface{}{"space_id": testSpaceId}, map[string]interface{}{"name": "New Object from demo", "icon": "💥", "template_id": "", "object_type_unique_key": "ot-page", "with_chat": false}}, // {"PUT", "/spaces/{space_id}/objects/{object_id}", map[string]interface{}{"space_id": testSpaceId, "object_id": testObjectId}, map[string]interface{}{"name": "Updated Object"}}, // {"GET", "/spaces/{space_id}/object_types?limit={limit}&offset={offset}", map[string]interface{}{"space_id": testSpaceId, "limit": 100, "offset": 0}, nil}, diff --git a/cmd/api/docs/docs.go b/cmd/api/docs/docs.go index e83846886..bd37c0172 100644 --- a/cmd/api/docs/docs.go +++ b/cmd/api/docs/docs.go @@ -588,7 +588,7 @@ const docTemplate = `{ "200": { "description": "The created object", "schema": { - "$ref": "#/definitions/object.CreateObjectResponse" + "$ref": "#/definitions/object.ObjectResponse" } }, "400": { @@ -644,7 +644,7 @@ const docTemplate = `{ "200": { "description": "The requested object", "schema": { - "$ref": "#/definitions/object.Object" + "$ref": "#/definitions/object.ObjectResponse" } }, "403": { @@ -707,7 +707,7 @@ const docTemplate = `{ "200": { "description": "The updated object", "schema": { - "$ref": "#/definitions/object.UpdateObjectResponse" + "$ref": "#/definitions/object.ObjectResponse" } }, "400": { @@ -735,6 +735,60 @@ const docTemplate = `{ } } } + }, + "delete": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "objects" + ], + "summary": "Delete a specific object in a space", + "parameters": [ + { + "type": "string", + "description": "The ID of the space", + "name": "space_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "The ID of the object", + "name": "object_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The deleted object", + "schema": { + "$ref": "#/definitions/object.ObjectResponse" + } + }, + "403": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.UnauthorizedError" + } + }, + "404": { + "description": "Resource not found", + "schema": { + "$ref": "#/definitions/util.NotFoundError" + } + }, + "502": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/util.ServerError" + } + } + } } }, "/spaces/{space_id}/objects/{object_id}/export/{format}": { @@ -867,14 +921,6 @@ const docTemplate = `{ } } }, - "object.CreateObjectResponse": { - "type": "object", - "properties": { - "object": { - "$ref": "#/definitions/object.Object" - } - } - }, "object.Detail": { "type": "object", "properties": { @@ -967,6 +1013,14 @@ const docTemplate = `{ } } }, + "object.ObjectResponse": { + "type": "object", + "properties": { + "object": { + "$ref": "#/definitions/object.Object" + } + } + }, "object.ObjectTemplate": { "type": "object", "properties": { @@ -1033,14 +1087,6 @@ const docTemplate = `{ } } }, - "object.UpdateObjectResponse": { - "type": "object", - "properties": { - "object": { - "$ref": "#/definitions/object.Object" - } - } - }, "pagination.PaginatedResponse-space_Member": { "type": "object", "properties": { diff --git a/cmd/api/docs/swagger.json b/cmd/api/docs/swagger.json index d9d5fd390..7c882dc72 100644 --- a/cmd/api/docs/swagger.json +++ b/cmd/api/docs/swagger.json @@ -582,7 +582,7 @@ "200": { "description": "The created object", "schema": { - "$ref": "#/definitions/object.CreateObjectResponse" + "$ref": "#/definitions/object.ObjectResponse" } }, "400": { @@ -638,7 +638,7 @@ "200": { "description": "The requested object", "schema": { - "$ref": "#/definitions/object.Object" + "$ref": "#/definitions/object.ObjectResponse" } }, "403": { @@ -701,7 +701,7 @@ "200": { "description": "The updated object", "schema": { - "$ref": "#/definitions/object.UpdateObjectResponse" + "$ref": "#/definitions/object.ObjectResponse" } }, "400": { @@ -729,6 +729,60 @@ } } } + }, + "delete": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "objects" + ], + "summary": "Delete a specific object in a space", + "parameters": [ + { + "type": "string", + "description": "The ID of the space", + "name": "space_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "The ID of the object", + "name": "object_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The deleted object", + "schema": { + "$ref": "#/definitions/object.ObjectResponse" + } + }, + "403": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.UnauthorizedError" + } + }, + "404": { + "description": "Resource not found", + "schema": { + "$ref": "#/definitions/util.NotFoundError" + } + }, + "502": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/util.ServerError" + } + } + } } }, "/spaces/{space_id}/objects/{object_id}/export/{format}": { @@ -861,14 +915,6 @@ } } }, - "object.CreateObjectResponse": { - "type": "object", - "properties": { - "object": { - "$ref": "#/definitions/object.Object" - } - } - }, "object.Detail": { "type": "object", "properties": { @@ -961,6 +1007,14 @@ } } }, + "object.ObjectResponse": { + "type": "object", + "properties": { + "object": { + "$ref": "#/definitions/object.Object" + } + } + }, "object.ObjectTemplate": { "type": "object", "properties": { @@ -1027,14 +1081,6 @@ } } }, - "object.UpdateObjectResponse": { - "type": "object", - "properties": { - "object": { - "$ref": "#/definitions/object.Object" - } - } - }, "pagination.PaginatedResponse-space_Member": { "type": "object", "properties": { diff --git a/cmd/api/docs/swagger.yaml b/cmd/api/docs/swagger.yaml index 7bb0dae7a..6c2bc2bdc 100644 --- a/cmd/api/docs/swagger.yaml +++ b/cmd/api/docs/swagger.yaml @@ -39,11 +39,6 @@ definitions: vertical_align: type: string type: object - object.CreateObjectResponse: - properties: - object: - $ref: '#/definitions/object.Object' - type: object object.Detail: properties: details: @@ -107,6 +102,11 @@ definitions: example: object type: string type: object + object.ObjectResponse: + properties: + object: + $ref: '#/definitions/object.Object' + type: object object.ObjectTemplate: properties: icon: @@ -153,11 +153,6 @@ definitions: text: type: string type: object - object.UpdateObjectResponse: - properties: - object: - $ref: '#/definitions/object.Object' - type: object pagination.PaginatedResponse-space_Member: properties: data: @@ -712,7 +707,7 @@ paths: "200": description: The created object schema: - $ref: '#/definitions/object.CreateObjectResponse' + $ref: '#/definitions/object.ObjectResponse' "400": description: Bad request schema: @@ -729,6 +724,42 @@ paths: tags: - objects /spaces/{space_id}/objects/{object_id}: + delete: + consumes: + - application/json + parameters: + - description: The ID of the space + in: path + name: space_id + required: true + type: string + - description: The ID of the object + in: path + name: object_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: The deleted object + schema: + $ref: '#/definitions/object.ObjectResponse' + "403": + description: Unauthorized + schema: + $ref: '#/definitions/util.UnauthorizedError' + "404": + description: Resource not found + schema: + $ref: '#/definitions/util.NotFoundError' + "502": + description: Internal server error + schema: + $ref: '#/definitions/util.ServerError' + summary: Delete a specific object in a space + tags: + - objects get: consumes: - application/json @@ -749,7 +780,7 @@ paths: "200": description: The requested object schema: - $ref: '#/definitions/object.Object' + $ref: '#/definitions/object.ObjectResponse' "403": description: Unauthorized schema: @@ -791,7 +822,7 @@ paths: "200": description: The updated object schema: - $ref: '#/definitions/object.UpdateObjectResponse' + $ref: '#/definitions/object.ObjectResponse' "400": description: Bad request schema: diff --git a/cmd/api/object/handler.go b/cmd/api/object/handler.go index 12b9a253d..c6f60d89f 100644 --- a/cmd/api/object/handler.go +++ b/cmd/api/object/handler.go @@ -55,7 +55,7 @@ func GetObjectsHandler(s *ObjectService) gin.HandlerFunc { // @Produce json // @Param space_id path string true "The ID of the space" // @Param object_id path string true "The ID of the object" -// @Success 200 {object} Object "The requested object" +// @Success 200 {object} ObjectResponse "The requested object" // @Failure 403 {object} util.UnauthorizedError "Unauthorized" // @Failure 404 {object} util.NotFoundError "Resource not found" // @Failure 502 {object} util.ServerError "Internal server error" @@ -77,7 +77,42 @@ func GetObjectHandler(s *ObjectService) gin.HandlerFunc { return } - c.JSON(http.StatusOK, GetObjectResponse{Object: object}) + c.JSON(http.StatusOK, ObjectResponse{Object: object}) + } +} + +// DeleteObjectHandler deletes a specific object in a space +// +// @Summary Delete a specific object in a space +// @Tags objects +// @Accept json +// @Produce json +// @Param space_id path string true "The ID of the space" +// @Param object_id path string true "The ID of the object" +// @Success 200 {object} ObjectResponse "The deleted object" +// @Failure 403 {object} util.UnauthorizedError "Unauthorized" +// @Failure 404 {object} util.NotFoundError "Resource not found" +// @Failure 502 {object} util.ServerError "Internal server error" +// @Router /spaces/{space_id}/objects/{object_id} [delete] +func DeleteObjectHandler(s *ObjectService) gin.HandlerFunc { + return func(c *gin.Context) { + spaceId := c.Param("space_id") + objectId := c.Param("object_id") + + object, err := s.DeleteObject(c.Request.Context(), spaceId, objectId) + code := util.MapErrorCode(err, + util.ErrToCode(ErrObjectNotFound, http.StatusNotFound), + util.ErrToCode(ErrFailedRetrieveObject, http.StatusInternalServerError), + util.ErrToCode(ErrFailedDeleteObject, http.StatusInternalServerError), + ) + + if code != http.StatusOK { + apiErr := util.CodeToAPIError(code, err.Error()) + c.JSON(code, apiErr) + return + } + + c.JSON(http.StatusOK, ObjectResponse{Object: object}) } } @@ -89,7 +124,7 @@ 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} CreateObjectResponse "The created object" +// @Success 200 {object} ObjectResponse "The created object" // @Failure 400 {object} util.ValidationError "Bad request" // @Failure 403 {object} util.UnauthorizedError "Unauthorized" // @Failure 502 {object} util.ServerError "Internal server error" @@ -121,7 +156,7 @@ func CreateObjectHandler(s *ObjectService) gin.HandlerFunc { return } - c.JSON(http.StatusOK, CreateObjectResponse{Object: object}) + c.JSON(http.StatusOK, ObjectResponse{Object: object}) } } @@ -134,7 +169,7 @@ 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} UpdateObjectResponse "The updated object" +// @Success 200 {object} ObjectResponse "The updated object" // @Failure 400 {object} util.ValidationError "Bad request" // @Failure 403 {object} util.UnauthorizedError "Unauthorized" // @Failure 404 {object} util.NotFoundError "Resource not found" @@ -165,7 +200,7 @@ func UpdateObjectHandler(s *ObjectService) gin.HandlerFunc { return } - c.JSON(http.StatusNotImplemented, UpdateObjectResponse{Object: object}) + c.JSON(http.StatusNotImplemented, ObjectResponse{Object: object}) } } diff --git a/cmd/api/object/model.go b/cmd/api/object/model.go index 5cb45f5fd..faa4cb350 100644 --- a/cmd/api/object/model.go +++ b/cmd/api/object/model.go @@ -1,9 +1,5 @@ package object -type GetObjectResponse struct { - Object Object `json:"object"` -} - type CreateObjectRequest struct { Name string `json:"name"` Icon string `json:"icon"` @@ -15,15 +11,12 @@ type CreateObjectRequest struct { WithChat bool `json:"with_chat"` } -type CreateObjectResponse struct { - Object Object `json:"object"` -} - +// TODO: Add fields to the request type UpdateObjectRequest struct { Object Object `json:"object"` } -type UpdateObjectResponse struct { +type ObjectResponse struct { Object Object `json:"object"` } diff --git a/cmd/api/object/service.go b/cmd/api/object/service.go index 9e8872330..e482ca803 100644 --- a/cmd/api/object/service.go +++ b/cmd/api/object/service.go @@ -20,6 +20,7 @@ var ( ErrFailedRetrieveObject = errors.New("failed to retrieve object") ErrorFailedRetrieveObjects = errors.New("failed to retrieve list of objects") ErrNoObjectsFound = errors.New("no objects found") + ErrFailedDeleteObject = errors.New("failed to delete object") ErrFailedCreateObject = errors.New("failed to create object") ErrInputMissingSource = errors.New("source is missing for bookmark") ErrFailedSetRelationFeatured = errors.New("failed to set relation featured") @@ -39,8 +40,9 @@ var ( type Service interface { 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) + DeleteObject(ctx context.Context, spaceId string, objectId string) error + CreateObject(ctx context.Context, spaceId string, request CreateObjectRequest) (Object, error) + UpdateObject(ctx context.Context, spaceId string, objectId string, request UpdateObjectRequest) (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) } @@ -148,6 +150,25 @@ func (s *ObjectService) GetObject(ctx context.Context, spaceId string, objectId return object, nil } +// DeleteObject deletes an existing object in a specific space. +func (s *ObjectService) DeleteObject(ctx context.Context, spaceId string, objectId string) (Object, error) { + object, err := s.GetObject(ctx, spaceId, objectId) + if err != nil { + return Object{}, err + } + + resp := s.mw.ObjectListSetIsArchived(ctx, &pb.RpcObjectListSetIsArchivedRequest{ + ObjectIds: []string{objectId}, + IsArchived: true, + }) + + if resp.Error.Code != pb.RpcObjectListSetIsArchivedResponseError_NULL { + return Object{}, ErrFailedDeleteObject + } + + 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) { if request.ObjectTypeUniqueKey == "ot-bookmark" && request.Source == "" { diff --git a/cmd/api/server/router.go b/cmd/api/server/router.go index ba0d309f3..9104b84cb 100644 --- a/cmd/api/server/router.go +++ b/cmd/api/server/router.go @@ -46,6 +46,7 @@ func (s *Server) NewRouter() *gin.Engine { // Object v1.GET("/spaces/:space_id/objects", paginator, 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.POST("/spaces/:space_id/objects", object.CreateObjectHandler(s.objectService))