mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-09 09:35:00 +09:00
GO-4459: Add object export endpoint
This commit is contained in:
parent
0358d34af7
commit
f869fbf7b7
10 changed files with 365 additions and 27 deletions
|
@ -736,6 +736,75 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/spaces/{space_id}/objects/{object_id}/export/{format}": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"exports"
|
||||
],
|
||||
"summary": "Export an object",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Space ID",
|
||||
"name": "space_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Object ID",
|
||||
"name": "object_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Export format",
|
||||
"name": "format",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Object exported successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/export.ObjectExportResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/util.ValidationError"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
|
@ -761,6 +830,14 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"export.ObjectExportResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Block": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -730,6 +730,75 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/spaces/{space_id}/objects/{object_id}/export/{format}": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"exports"
|
||||
],
|
||||
"summary": "Export an object",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Space ID",
|
||||
"name": "space_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Object ID",
|
||||
"name": "object_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Export format",
|
||||
"name": "format",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Object exported successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/export.ObjectExportResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/util.ValidationError"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
|
@ -755,6 +824,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"export.ObjectExportResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Block": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -15,6 +15,11 @@ definitions:
|
|||
example: ""
|
||||
type: string
|
||||
type: object
|
||||
export.ObjectExportResponse:
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
type: object
|
||||
object.Block:
|
||||
properties:
|
||||
align:
|
||||
|
@ -803,6 +808,52 @@ paths:
|
|||
summary: Update an existing object in a specific space
|
||||
tags:
|
||||
- space_objects
|
||||
/spaces/{space_id}/objects/{object_id}/export/{format}:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: Space ID
|
||||
in: path
|
||||
name: space_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Object ID
|
||||
in: path
|
||||
name: object_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Export format
|
||||
in: query
|
||||
name: format
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Object exported successfully
|
||||
schema:
|
||||
$ref: '#/definitions/export.ObjectExportResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/util.ValidationError'
|
||||
"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: Export an object
|
||||
tags:
|
||||
- exports
|
||||
securityDefinitions:
|
||||
BasicAuth:
|
||||
type: basic
|
||||
|
|
59
cmd/api/export/handler.go
Normal file
59
cmd/api/export/handler.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package export
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/anyproto/anytype-heart/cmd/api/util"
|
||||
)
|
||||
|
||||
// GetObjectExportHandler exports an object to the specified format
|
||||
//
|
||||
// @Summary Export an object
|
||||
// @Tags exports
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param space_id path string true "Space ID"
|
||||
// @Param object_id path string true "Object ID"
|
||||
// @Param format query string true "Export format"
|
||||
// @Success 200 {object} ObjectExportResponse "Object exported successfully"
|
||||
// @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"
|
||||
// @Router /spaces/{space_id}/objects/{object_id}/export/{format} [post]
|
||||
func GetObjectExportHandler(s *ExportService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
spaceId := c.Param("space_id")
|
||||
objectId := c.Param("object_id")
|
||||
format := c.Query("format")
|
||||
|
||||
objectAsRequest := ObjectExportRequest{}
|
||||
if err := c.ShouldBindJSON(&objectAsRequest); err != nil {
|
||||
apiErr := util.CodeToAPIError(http.StatusBadRequest, ErrBadInput.Error())
|
||||
c.JSON(http.StatusBadRequest, apiErr)
|
||||
return
|
||||
}
|
||||
|
||||
outputPath, err := s.GetObjectExport(c.Request.Context(), spaceId, objectId, format, objectAsRequest.Path)
|
||||
code := util.MapErrorCode(err, util.ErrToCode(ErrFailedExportObjectAsMarkdown, http.StatusInternalServerError))
|
||||
|
||||
if code != http.StatusOK {
|
||||
apiErr := util.CodeToAPIError(code, err.Error())
|
||||
c.JSON(code, apiErr)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ObjectExportResponse{Path: outputPath})
|
||||
}
|
||||
}
|
||||
|
||||
func GetSpaceExportHandler(s *ExportService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// spaceId := c.Param("space_id")
|
||||
// format := c.Query("format")
|
||||
|
||||
c.JSON(http.StatusNotImplemented, "Not implemented")
|
||||
}
|
||||
}
|
9
cmd/api/export/model.go
Normal file
9
cmd/api/export/model.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package export
|
||||
|
||||
type ObjectExportRequest struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type ObjectExportResponse struct {
|
||||
Path string `json:"path"`
|
||||
}
|
61
cmd/api/export/service.go
Normal file
61
cmd/api/export/service.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package export
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pb/service"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFailedExportObjectAsMarkdown = errors.New("failed to export object as markdown")
|
||||
ErrBadInput = errors.New("bad input")
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
GetObjectExport(ctx context.Context, spaceId string, objectId string, format string, path string) (string, error)
|
||||
}
|
||||
|
||||
type ExportService struct {
|
||||
mw service.ClientCommandsServer
|
||||
AccountInfo *model.AccountInfo
|
||||
}
|
||||
|
||||
func NewService(mw service.ClientCommandsServer) *ExportService {
|
||||
return &ExportService{mw: mw}
|
||||
}
|
||||
|
||||
// GetObjectExport retrieves an object from a space and exports it as a specific format.
|
||||
func (s *ExportService) GetObjectExport(ctx context.Context, spaceId string, objectId string, format string, path string) (string, error) {
|
||||
resp := s.mw.ObjectListExport(ctx, &pb.RpcObjectListExportRequest{
|
||||
SpaceId: spaceId,
|
||||
Path: path,
|
||||
ObjectIds: []string{objectId},
|
||||
Format: s.mapStringToFormat(format),
|
||||
Zip: false,
|
||||
IncludeNested: false,
|
||||
IncludeFiles: true,
|
||||
IsJson: false,
|
||||
IncludeArchived: false,
|
||||
})
|
||||
|
||||
if resp.Error.Code != pb.RpcObjectListExportResponseError_NULL {
|
||||
return "", ErrFailedExportObjectAsMarkdown
|
||||
}
|
||||
|
||||
return resp.Path, nil
|
||||
}
|
||||
|
||||
// mapStringToFormat maps a format string to an ExportFormat enum.
|
||||
func (s *ExportService) mapStringToFormat(format string) model.ExportFormat {
|
||||
switch format {
|
||||
case "markdown":
|
||||
return model.Export_Markdown
|
||||
case "protobuf":
|
||||
return model.Export_Protobuf
|
||||
default:
|
||||
return model.Export_Markdown
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ func (s *Server) initAccountInfo() gin.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
s.exportService.AccountInfo = accInfo
|
||||
s.objectService.AccountInfo = accInfo
|
||||
s.spaceService.AccountInfo = accInfo
|
||||
s.searchService.AccountInfo = accInfo
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/webstradev/gin-pagination/v2/pkg/pagination"
|
||||
|
||||
"github.com/anyproto/anytype-heart/cmd/api/auth"
|
||||
"github.com/anyproto/anytype-heart/cmd/api/export"
|
||||
"github.com/anyproto/anytype-heart/cmd/api/object"
|
||||
"github.com/anyproto/anytype-heart/cmd/api/search"
|
||||
"github.com/anyproto/anytype-heart/cmd/api/space"
|
||||
|
@ -15,7 +16,6 @@ import (
|
|||
// NewRouter builds and returns a *gin.Engine with all routes configured.
|
||||
func (s *Server) NewRouter() *gin.Engine {
|
||||
router := gin.Default()
|
||||
router.Use(s.initAccountInfo())
|
||||
|
||||
// Pagination middleware setup
|
||||
paginator := pagination.New(
|
||||
|
@ -30,35 +30,34 @@ func (s *Server) NewRouter() *gin.Engine {
|
|||
// Swagger route
|
||||
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
|
||||
// Auth
|
||||
authRouter := router.Group("/v1/auth")
|
||||
// API routes
|
||||
v1 := router.Group("/v1")
|
||||
v1.Use(s.initAccountInfo())
|
||||
v1.Use(s.ensureAuthenticated())
|
||||
{
|
||||
authRouter.POST("/displayCode", auth.DisplayCodeHandler(s.authService))
|
||||
authRouter.GET("/token", auth.TokenHandler(s.authService))
|
||||
}
|
||||
// Auth
|
||||
v1.POST("/auth/display_code", auth.DisplayCodeHandler(s.authService))
|
||||
v1.GET("/auth/token", auth.TokenHandler(s.authService))
|
||||
|
||||
// Read-only group
|
||||
readOnly := router.Group("/v1")
|
||||
// readOnly.Use(a.AuthMiddleware())
|
||||
// readOnly.Use(a.PermissionMiddleware("read-only"))
|
||||
{
|
||||
readOnly.GET("/spaces", paginator, space.GetSpacesHandler(s.spaceService))
|
||||
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.GetTypesHandler(s.objectService))
|
||||
readOnly.GET("/spaces/:space_id/objectTypes/:typeId/templates", paginator, object.GetTemplatesHandler(s.objectService))
|
||||
readOnly.GET("/search", paginator, search.SearchHandler(s.searchService))
|
||||
}
|
||||
// Export
|
||||
v1.POST("/spaces/:space_id/objects/:object_id/export/:format", export.GetObjectExportHandler(s.exportService))
|
||||
v1.GET("/spaces/:space_id/objects/export/:format", export.GetSpaceExportHandler(s.exportService))
|
||||
|
||||
// Read-write group
|
||||
readWrite := router.Group("/v1")
|
||||
// readWrite.Use(a.AuthMiddleware())
|
||||
// readWrite.Use(a.PermissionMiddleware("read-write"))
|
||||
{
|
||||
readWrite.POST("/spaces", space.CreateSpaceHandler(s.spaceService))
|
||||
readWrite.POST("/spaces/:space_id/objects", object.CreateObjectHandler(s.objectService))
|
||||
readWrite.PUT("/spaces/:space_id/objects/:object_id", object.UpdateObjectHandler(s.objectService))
|
||||
// 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.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))
|
||||
v1.PUT("/spaces/:space_id/objects/:object_id", object.UpdateObjectHandler(s.objectService))
|
||||
|
||||
// Search
|
||||
v1.GET("/search", paginator, search.SearchHandler(s.searchService))
|
||||
|
||||
// Space
|
||||
v1.GET("/spaces", paginator, space.GetSpacesHandler(s.spaceService))
|
||||
v1.GET("/spaces/:space_id/members", paginator, space.GetMembersHandler(s.spaceService))
|
||||
v1.POST("/spaces", space.CreateSpaceHandler(s.spaceService))
|
||||
}
|
||||
|
||||
return router
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/anyproto/anytype-heart/cmd/api/auth"
|
||||
"github.com/anyproto/anytype-heart/cmd/api/export"
|
||||
"github.com/anyproto/anytype-heart/cmd/api/object"
|
||||
"github.com/anyproto/anytype-heart/cmd/api/search"
|
||||
"github.com/anyproto/anytype-heart/cmd/api/space"
|
||||
|
@ -28,6 +29,7 @@ type Server struct {
|
|||
|
||||
mwInternal core.MiddlewareInternal
|
||||
authService *auth.AuthService
|
||||
exportService *export.ExportService
|
||||
objectService *object.ObjectService
|
||||
spaceService *space.SpaceService
|
||||
searchService *search.SearchService
|
||||
|
@ -38,6 +40,7 @@ func NewServer(mw service.ClientCommandsServer, mwInternal core.MiddlewareIntern
|
|||
s := &Server{
|
||||
mwInternal: mwInternal,
|
||||
authService: auth.NewService(mw),
|
||||
exportService: export.NewService(mw),
|
||||
objectService: object.NewService(mw),
|
||||
spaceService: space.NewService(mw),
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ var (
|
|||
ErrFailedOpenWorkspace = errors.New("failed to open workspace")
|
||||
ErrFailedGenerateRandomIcon = errors.New("failed to generate random icon")
|
||||
ErrFailedCreateSpace = errors.New("failed to create space")
|
||||
ErrBadInput = errors.New("bad input")
|
||||
ErrNoMembersFound = errors.New("no members found")
|
||||
ErrFailedListMembers = errors.New("failed to retrieve list of members")
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue