diff --git a/config/bookmark/bookmark.yaml b/config/bookmark/bookmark.yaml index a9b5e16..736bd72 100644 --- a/config/bookmark/bookmark.yaml +++ b/config/bookmark/bookmark.yaml @@ -48,6 +48,10 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/ServerInternalError' @@ -69,6 +73,12 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '500': + $ref: '#/components/responses/ServerInternalError' put: summary: 更新书签 @@ -100,6 +110,10 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/ServerInternalError' @@ -117,6 +131,10 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/ServerInternalError' @@ -139,6 +157,12 @@ components: application/json: schema: $ref: '#/components/schemas/Error' + Forbidden: + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' schemas: BookmarkRequest: type: object diff --git a/config/user_np/user_np.yaml b/config/user_np/user_np.yaml index 20a3dc3..9822d28 100644 --- a/config/user_np/user_np.yaml +++ b/config/user_np/user_np.yaml @@ -71,8 +71,47 @@ paths: description: 请求参数错误 '401': description: 认证失败 + + /auth/info: + get: + summary: 获取用户信息 + description: 获取用户信息 json object + operationId: getUserInfo + tags: [auth] + responses: + '200': + description: 用户信息 + content: + application/json: + schema: + type: object + '500': + $ref: '#/components/responses/ServerInternalError' + put: + summary: 保存用户信息 + description: 保存用户信息 json object + operationId: saveUserInfo + tags: [auth] + requestBody: + required: true + content: + application/json: + schema: + type: object + responses: + '200': + description: 保存成功 + '500': + $ref: '#/components/responses/ServerInternalError' components: + responses: + ServerInternalError: + description: 服务器内部错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' schemas: LoginRequest: type: object @@ -121,6 +160,22 @@ components: user_name: type: string + Error: + type: object + description: 错误信息 + properties: + errtype: + type: string + example: "ParameterError" + description: 错误类型 + message: + example: "传递的第一个参数错误" + type: string + description: 错误信息 + required: + - errtype + - message + securitySchemes: ApiKeyAuth: type: apiKey diff --git a/config/vfs/vfs.yaml b/config/vfs/vfs.yaml index 11ba8d8..9c50555 100644 --- a/config/vfs/vfs.yaml +++ b/config/vfs/vfs.yaml @@ -104,11 +104,9 @@ paths: type: array items: $ref: '#/components/schemas/VFSDirectoryEntry' - application/octet-stream: + text/plain: schema: - description: 文件或服务的二进制内容 type: string - format: binary '400': $ref: '#/components/responses/ParameterError' '401': @@ -128,20 +126,16 @@ paths: requestBody: required: false content: - application/octet-stream: - schema: - description: 文件或服务的二进制内容 - type: string - format: byte + text/plain: + schema: + type: string responses: '201': description: 创建成功 content: - application/octet-stream: + text/plain: schema: - description: 文件或服务的二进制内容 type: string - format: byte application/json: schema: $ref: '#/components/schemas/VFSNodeResponse' @@ -177,6 +171,9 @@ paths: type: string description: 目的文件夹路径 / 文件名路径 example: "/home/" + text/plain: + schema: + type: string responses: '200': description: 操作成功 diff --git a/internal/bookmarks/bookmark.go b/internal/bookmarks/bookmark.go index da7d575..96c97fb 100644 --- a/internal/bookmarks/bookmark.go +++ b/internal/bookmarks/bookmark.go @@ -17,14 +17,10 @@ type BookMarksImpl struct { db *gorm.DB } -var adminToken *string +var adminToken string = "random_token" func validateApiKey(apiKey string) bool { - if adminToken != nil && apiKey == *adminToken { - return true - } - - return false + return apiKey == adminToken } func AuthMiddleware() api.MiddlewareFunc { @@ -35,7 +31,7 @@ func AuthMiddleware() api.MiddlewareFunc { apiKey := c.GetHeader("X-BookMark-Token") // 验证 API Key(您需要实现这个逻辑) - if apiKey == "" || !validateApiKey(apiKey) { + if !validateApiKey(apiKey) { c.JSON(http.StatusUnauthorized, api.Error{ Errtype: "Unauthorized", Message: "Invalid or missing API key", @@ -232,6 +228,7 @@ func bookmarkModel2Res(bookmark models.Bookmark) api.BookmarkResponse { Detail: bookmark.Detail, Description: bookmark.Description, CreatedAt: bookmark.CreatedAt, + UpdatedAt: bookmark.UpdatedAt, } } diff --git a/internal/bookmarks/user_np.go b/internal/bookmarks/user_np.go index 86b51c1..67cf9b8 100644 --- a/internal/bookmarks/user_np.go +++ b/internal/bookmarks/user_np.go @@ -205,5 +205,15 @@ func (u *UserNPImpl) PutAuthPassword(c *gin.Context) { c.JSON(http.StatusOK, nil) } +// GetUserInfo implements server.ServerInterface. +func (u *UserNPImpl) GetUserInfo(c *gin.Context) { + panic("unimplemented") +} + +// SaveUserInfo implements server.ServerInterface. +func (u *UserNPImpl) SaveUserInfo(c *gin.Context) { + panic("unimplemented") +} + // Make sure we conform to ServerInterface var _ api.ServerInterface = (*UserNPImpl)(nil) diff --git a/internal/vfs/models/vfs.go b/internal/vfs/models/vfs.go index 8a845e0..b823965 100644 --- a/internal/vfs/models/vfs.go +++ b/internal/vfs/models/vfs.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "path" + "strings" "time" _ "github.com/mattn/go-sqlite3" @@ -438,6 +439,13 @@ func (v *Vfs) MoveToPath(node *VfsNode, destPath string) error { return nil } +func (v *Vfs) CheckNameValid(name string) error { + if name == "" || strings.Contains(name, "/") { + return fmt.Errorf("invalid node name") + } + return nil +} + func (v *Vfs) CreateVFSNode(p *VfsNode) error { _, err := v.DB.Exec(` INSERT INTO vfs_nodes (name, parent_id, type, created_at, updated_at) diff --git a/internal/vfs/vfs_impl.go b/internal/vfs/vfs_impl.go index a30b352..d37de06 100644 --- a/internal/vfs/vfs_impl.go +++ b/internal/vfs/vfs_impl.go @@ -1,10 +1,8 @@ package vfs import ( - "bytes" "context" "fmt" - "io" "log" "net/http" @@ -12,6 +10,8 @@ import ( "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models" ) +// TODO 重命名名称冲突,以及合法名称检测,以及JSONBody and TextBody的检测 + // CreateVFSNode implements server.StrictServerInterface. func (v *VfsImpl) CreateVFSNode(ctx context.Context, request api.CreateVFSNodeRequestObject) (api.CreateVFSNodeResponseObject, error) { // 解析路径组件 @@ -27,18 +27,7 @@ func (v *VfsImpl) CreateVFSNode(ctx context.Context, request api.CreateVFSNodeRe // 读取请求体数据 // FIXME: 使用stream可能更好 - var content []byte - if request.Body != nil { - content, err = io.ReadAll(request.Body) - if err != nil { - return api.CreateVFSNode500JSONResponse{ - ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ - Errtype: api.ErrorErrtypeInternalServerError, - Message: "Failed to read request body: " + err.Error(), - }, - }, nil - } - } + var content []byte = []byte(*request.Body) // 创建节点 (可能需要传递content数据到vfs层) node, err := v.vfs.CreateNodeByComponents(parentPath, nodeName, nodeType) @@ -56,18 +45,21 @@ func (v *VfsImpl) CreateVFSNode(ctx context.Context, request api.CreateVFSNodeRe // 这里可能需要将content传递给服务处理 if result, err := v.StrictProxy2Service(http.MethodPost, content, node); err != nil { // 回滚操作 - err := v.vfs.DeleteVFSNode(node) - if err != nil { + delete_err := v.vfs.DeleteVFSNode(node) + log.Printf("service node: %s, err %s", node.Name, err.Error()) + if delete_err != nil { // FIXME: 需要解决这种原子性 return nil, fmt.Errorf("consistency error: %w", err) } - return nil, err + return api.CreateVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeServiceProxyError, + Message: err.Error(), + }, + }, nil } else { // 返回二进制数据响应 - return api.CreateVFSNode201ApplicationoctetStreamResponse{ - Body: bytes.NewReader(result), - ContentLength: int64(len(result)), - }, nil + return api.CreateVFSNode201TextResponse(result), nil } } @@ -217,10 +209,7 @@ func (v *VfsImpl) GetVFSNode(ctx context.Context, request api.GetVFSNodeRequestO }, nil } - return api.GetVFSNode200ApplicationoctetStreamResponse{ - Body: bytes.NewReader(result), - ContentLength: int64(len(result)), - }, nil + return api.GetVFSNode200TextResponse(result), nil default: return api.GetVFSNode400JSONResponse{ @@ -249,7 +238,7 @@ func (v *VfsImpl) UpdateVFSNode(ctx context.Context, request api.UpdateVFSNodeRe switch request.Params.Op { case api.Rename: // 检查请求体 - if request.Body == nil { + if request.JSONBody == nil { return api.UpdateVFSNode400JSONResponse{ ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ Errtype: api.ErrorErrtypeParameterError, @@ -258,12 +247,12 @@ func (v *VfsImpl) UpdateVFSNode(ctx context.Context, request api.UpdateVFSNodeRe }, nil } - newName := string(*request.Body) - if newName == "" { + newName := string(*request.JSONBody) + if err := v.vfs.CheckNameValid(newName); err != nil { return api.UpdateVFSNode400JSONResponse{ ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ Errtype: api.ErrorErrtypeParameterError, - Message: "New name cannot be empty", + Message: err.Error(), }, }, nil } @@ -297,11 +286,9 @@ func (v *VfsImpl) UpdateVFSNode(ctx context.Context, request api.UpdateVFSNodeRe }, nil } - // FIXME: 性能问题 - var content []byte - if request.Body != nil { - content = []byte(*request.Body) - } + // 读取请求体数据 + // FIXME: 使用stream可能更好 + var content []byte = []byte(*request.TextBody) // 对于服务节点,通过代理发送变更 if node.Type == models.VfsNodeTypeService { @@ -327,7 +314,7 @@ func (v *VfsImpl) UpdateVFSNode(ctx context.Context, request api.UpdateVFSNodeRe case api.Move: // FIXME: 需要添加权限控制 - if request.Body == nil { + if request.JSONBody == nil { return api.UpdateVFSNode400JSONResponse{ ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ Errtype: api.ErrorErrtypeParameterError, @@ -336,7 +323,7 @@ func (v *VfsImpl) UpdateVFSNode(ctx context.Context, request api.UpdateVFSNodeRe }, nil } - targetPath := string(*request.Body) + targetPath := string(*request.JSONBody) if targetPath == "" { return api.UpdateVFSNode400JSONResponse{ ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ diff --git a/internal/vfs/vfsdriver/vfs_bookmark.go b/internal/vfs/vfsdriver/vfs_bookmark.go index 87aa47b..659c45c 100644 --- a/internal/vfs/vfsdriver/vfs_bookmark.go +++ b/internal/vfs/vfsdriver/vfs_bookmark.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "fmt" + "net/http" api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks_client" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs" @@ -14,6 +15,7 @@ import ( type VfsBookMarkService struct { client *api.ClientWithResponses + token string } func NewVfsBookMarkService(serverURL string) (*vfs.ProxyEntry, error) { @@ -25,7 +27,10 @@ func NewVfsBookMarkService(serverURL string) (*vfs.ProxyEntry, error) { ret := vfs.ProxyEntry{ Name: "bookmark", MatchExt: "bk", - Proxy: &VfsBookMarkService{client: client}, + Proxy: &VfsBookMarkService{ + client: client, + token: "random_token", + }, } return &ret, nil } @@ -41,7 +46,10 @@ func (v *VfsBookMarkService) Create(servicePath string, node *models.VfsNode, da } // 调用 bookmark 服务创建书签 - resp, err := v.client.CreateBookmarkWithResponse(ctx, int64(node.ID), req) + resp, err := v.client.CreateBookmarkWithResponse(ctx, int64(node.ID), req, func(ctx context.Context, req *http.Request) error { + req.Header.Set("X-BookMark-Token", v.token) + return nil + }) if err != nil { return nil, err } @@ -64,7 +72,7 @@ func (v *VfsBookMarkService) Create(servicePath string, node *models.VfsNode, da return nil, fmt.Errorf("server error: %s", resp.JSON500.Message) } - return nil, fmt.Errorf("unknown error") + return nil, fmt.Errorf("unknown error: %s %s", resp.HTTPResponse.Status, resp.Body) } // Delete implements ServiceProxy. @@ -72,7 +80,10 @@ func (v *VfsBookMarkService) Delete(servicePath string, node *models.VfsNode) er ctx := context.Background() // 调用 bookmark 服务删除书签 - resp, err := v.client.DeleteBookmarkWithResponse(ctx, int64(node.ID)) + resp, err := v.client.DeleteBookmarkWithResponse(ctx, int64(node.ID), func(ctx context.Context, req *http.Request) error { + req.Header.Set("X-BookMark-Token", v.token) + return nil + }) if err != nil { return err } @@ -99,7 +110,10 @@ func (v *VfsBookMarkService) Get(servicePath string, node *models.VfsNode) ([]by ctx := context.Background() // 调用 bookmark 服务获取书签 - resp, err := v.client.GetBookmarkWithResponse(ctx, int64(node.ID)) + resp, err := v.client.GetBookmarkWithResponse(ctx, int64(node.ID), func(ctx context.Context, req *http.Request) error { + req.Header.Set("X-BookMark-Token", v.token) + return nil + }) if err != nil { return nil, err } @@ -121,11 +135,6 @@ func (v *VfsBookMarkService) Get(servicePath string, node *models.VfsNode) ([]by return nil, fmt.Errorf("unknown error") } -// GetName implements ServiceProxy. -func (v *VfsBookMarkService) GetName() string { - return "bookmark" -} - // Update implements ServiceProxy. func (v *VfsBookMarkService) Update(servicePath string, node *models.VfsNode, data []byte) error { ctx := context.Background() @@ -137,7 +146,10 @@ func (v *VfsBookMarkService) Update(servicePath string, node *models.VfsNode, da } // 调用 bookmark 服务更新书签 - resp, err := v.client.UpdateBookmarkWithResponse(ctx, int64(node.ID), req) + resp, err := v.client.UpdateBookmarkWithResponse(ctx, int64(node.ID), req, func(ctx context.Context, req *http.Request) error { + req.Header.Set("X-BookMark-Token", v.token) + return nil + }) if err != nil { return err } @@ -163,4 +175,9 @@ func (v *VfsBookMarkService) Update(servicePath string, node *models.VfsNode, da return fmt.Errorf("unknown error") } +// GetName implements ServiceProxy. +func (v *VfsBookMarkService) GetName() string { + return "bookmark" +} + var _ vfs.ServiceProxy = (*VfsBookMarkService)(nil)