From d3ed830ec1d6aa887e9e42433068cd3719a1fe16 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 31 Dec 2025 11:54:36 +0800 Subject: [PATCH] Enhance OpenAPI and JSON Parsing Functionality - Added app handlers to the OpenAPI attachment process, improving routing for application-related endpoints. - Refactored the parseJSONField function to handle both JSON and boolean column types, enhancing value parsing flexibility and robustness. - Improved error handling and clarity in the JSON parsing logic, ensuring original values are returned when parsing fails. --- openapi/app/app.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++ openapi/openapi.go | 4 ++ seed/seed.go | 42 +++++++++++++-------- widgets/app/app.go | 7 +++- 4 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 openapi/app/app.go diff --git a/openapi/app/app.go b/openapi/app/app.go new file mode 100644 index 00000000..0609a34e --- /dev/null +++ b/openapi/app/app.go @@ -0,0 +1,91 @@ +package app + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/yaoapp/gou/process" + "github.com/yaoapp/yao/openapi/oauth/authorized" + "github.com/yaoapp/yao/openapi/oauth/types" + "github.com/yaoapp/yao/openapi/response" +) + +// Attach attaches the app handlers to the router +func Attach(group *gin.RouterGroup, oauth types.OAuth) { + // Menu endpoint - requires authentication + group.GET("/menu", oauth.Guard, getMenu) +} + +// MenuRequest represents the menu request parameters +type MenuRequest struct { + Locale string `form:"locale" json:"locale"` +} + +// getMenu handles GET /app/menu +// Returns the application menu based on user permissions and locale +func getMenu(c *gin.Context) { + var req MenuRequest + if err := c.ShouldBindQuery(&req); err != nil { + response.RespondWithError(c, http.StatusBadRequest, &response.ErrorResponse{ + Code: response.ErrInvalidRequest.Code, + ErrorDescription: err.Error(), + }) + return + } + + // Get authorized info from context (set by oauth.Guard) + authInfo := authorized.GetInfo(c) + if authInfo == nil { + response.RespondWithError(c, http.StatusUnauthorized, &response.ErrorResponse{ + Code: response.ErrInvalidToken.Code, + ErrorDescription: "Authorization required", + }) + return + } + + // Call yao.app.Menu process with locale parameter + handle, err := process.Of("yao.app.Menu", req.Locale) + if err != nil { + response.RespondWithError(c, http.StatusBadRequest, &response.ErrorResponse{ + Code: response.ErrInvalidRequest.Code, + ErrorDescription: err.Error(), + }) + return + } + + // Set process context + handle.WithSID(authInfo.SessionID) + + // Set authorized info for process + handle.WithAuthorized(map[string]interface{}{ + "subject": authInfo.Subject, + "client_id": authInfo.ClientID, + "user_id": authInfo.UserID, + "scope": authInfo.Scope, + "team_id": authInfo.TeamID, + "tenant_id": authInfo.TenantID, + "session_id": authInfo.SessionID, + "remember_me": authInfo.RememberMe, + "constraints": map[string]interface{}{ + "owner_only": authInfo.Constraints.OwnerOnly, + "creator_only": authInfo.Constraints.CreatorOnly, + "editor_only": authInfo.Constraints.EditorOnly, + "team_only": authInfo.Constraints.TeamOnly, + "extra": authInfo.Constraints.Extra, + }, + }) + + // Execute the process + err = handle.Execute() + if err != nil { + response.RespondWithError(c, http.StatusInternalServerError, &response.ErrorResponse{ + Code: response.ErrServerError.Code, + ErrorDescription: err.Error(), + }) + return + } + defer handle.Dispose() + + // Return the menu data + response.RespondWithSuccess(c, http.StatusOK, handle.Value()) +} diff --git a/openapi/openapi.go b/openapi/openapi.go index cab93dd6..a7d6ff0c 100644 --- a/openapi/openapi.go +++ b/openapi/openapi.go @@ -7,6 +7,7 @@ import ( "github.com/yaoapp/gou/application" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/openapi/agent" + "github.com/yaoapp/yao/openapi/app" "github.com/yaoapp/yao/openapi/captcha" "github.com/yaoapp/yao/openapi/chat" "github.com/yaoapp/yao/openapi/dsl" @@ -150,6 +151,9 @@ func (openapi *OpenAPI) Attach(router *gin.Engine) { // Trace handlers openapiTrace.Attach(group.Group("/trace"), openapi.OAuth) + // App handlers (menu, etc.) + app.Attach(group.Group("/app"), openapi.OAuth) + // Custom handlers (Defined by developer) } diff --git a/seed/seed.go b/seed/seed.go index 0c5fedcf..5a4b1408 100644 --- a/seed/seed.go +++ b/seed/seed.go @@ -572,17 +572,14 @@ func buildColumnTypeMap(mod *model.Model, header []string) []string { return columnTypes } -// parseJSONField attempts to parse a value as JSON if the column type is json -// Returns the parsed JSON object if successful, otherwise returns the original value +// parseJSONField attempts to parse a value based on column type +// For JSON columns: parses JSON string to object +// For boolean columns: converts "true"/"false"/"1"/"0" to bool +// Returns the parsed value if successful, otherwise returns the original value func parseJSONField(value interface{}, columnType string) interface{} { - // Check if column type is JSON - if columnType != "json" && columnType != "jsonb" { - return value - } - - // Try to parse string value as JSON + // Try to parse string value strValue, ok := value.(string) - if !ok || strValue == "" { + if !ok { return value } @@ -592,15 +589,30 @@ func parseJSONField(value interface{}, columnType string) interface{} { return value } - // Try to parse as JSON - var jsonValue interface{} - if err := json.Unmarshal([]byte(strValue), &jsonValue); err != nil { - // If parsing fails, return original value (might be empty or malformed) - // Don't log error as this is expected for non-JSON strings + // Handle boolean type + if columnType == "boolean" || columnType == "bool" { + switch strings.ToLower(strValue) { + case "true", "1", "yes": + return true + case "false", "0", "no": + return false + } return value } - return jsonValue + // Handle JSON type + if columnType == "json" || columnType == "jsonb" { + // Try to parse as JSON + var jsonValue interface{} + if err := json.Unmarshal([]byte(strValue), &jsonValue); err != nil { + // If parsing fails, return original value (might be empty or malformed) + // Don't log error as this is expected for non-JSON strings + return value + } + return jsonValue + } + + return value } // sortColumns sorts column names alphabetically for consistent ordering diff --git a/widgets/app/app.go b/widgets/app/app.go index 5f974c11..d868a29d 100644 --- a/widgets/app/app.go +++ b/widgets/app/app.go @@ -396,7 +396,12 @@ func processMenu(p *process.Process) interface{} { exception.New(err.Error(), 400).Throw() } - err = handle.WithGlobal(p.Global).WithSID(p.Sid).Execute() + handle.WithGlobal(p.Global).WithSID(p.Sid) + if p.Authorized != nil { + handle = handle.WithAuthorized(p.Authorized) + } + + err = handle.Execute() if err != nil { exception.New(err.Error(), 500).Throw() }