mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-29 15:54:26 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab1ce869b6 | ||
|
|
ff72e04428 | ||
|
|
e35f8a4f14 |
@@ -238,7 +238,8 @@ func (h *Handler) SqlQueryList(sqlquery string, pNoCount, pBlankparms, pAllowFil
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dbobjlist = rows
|
// Normalize PostgreSQL types for proper JSON marshaling
|
||||||
|
dbobjlist = normalizePostgresTypesList(rows)
|
||||||
|
|
||||||
if pNoCount {
|
if pNoCount {
|
||||||
total = int64(len(dbobjlist))
|
total = int64(len(dbobjlist))
|
||||||
@@ -532,7 +533,7 @@ func (h *Handler) SqlQuery(sqlquery string, pBlankparms bool) HTTPFuncType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(rows) > 0 {
|
if len(rows) > 0 {
|
||||||
dbobj = rows[0]
|
dbobj = normalizePostgresTypes(rows[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute AfterSQLExec hook
|
// Execute AfterSQLExec hook
|
||||||
@@ -757,8 +758,8 @@ func (h *Handler) replaceMetaVariables(sqlquery string, r *http.Request, userCtx
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(sqlquery, "[rid_session]") {
|
if strings.Contains(sqlquery, "[rid_session]") {
|
||||||
sessionID := userCtx.SessionID
|
sessionID, _ := strconv.ParseInt(userCtx.SessionID, 10, 64)
|
||||||
sqlquery = strings.ReplaceAll(sqlquery, "[rid_session]", fmt.Sprintf("'%s'", sessionID))
|
sqlquery = strings.ReplaceAll(sqlquery, "[rid_session]", fmt.Sprintf("%d", sessionID))
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(sqlquery, "[method]") {
|
if strings.Contains(sqlquery, "[method]") {
|
||||||
@@ -874,7 +875,7 @@ func IsNumeric(s string) bool {
|
|||||||
|
|
||||||
// getReplacementForBlankParam determines the replacement value for an unused parameter
|
// getReplacementForBlankParam determines the replacement value for an unused parameter
|
||||||
// based on whether it appears within quotes in the SQL query.
|
// based on whether it appears within quotes in the SQL query.
|
||||||
// It checks for PostgreSQL quotes: single quotes ('') and dollar quotes ($...$)
|
// It checks for PostgreSQL quotes: single quotes (”) and dollar quotes ($...$)
|
||||||
func getReplacementForBlankParam(sqlquery, param string) string {
|
func getReplacementForBlankParam(sqlquery, param string) string {
|
||||||
// Find the parameter in the query
|
// Find the parameter in the query
|
||||||
idx := strings.Index(sqlquery, param)
|
idx := strings.Index(sqlquery, param)
|
||||||
@@ -946,3 +947,67 @@ func sendError(w http.ResponseWriter, status int, code, message string, err erro
|
|||||||
})
|
})
|
||||||
_, _ = w.Write(data)
|
_, _ = w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalizePostgresTypesList normalizes a list of result maps to handle PostgreSQL types correctly
|
||||||
|
func normalizePostgresTypesList(rows []map[string]interface{}) []map[string]interface{} {
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := make([]map[string]interface{}, len(rows))
|
||||||
|
for i, row := range rows {
|
||||||
|
normalized[i] = normalizePostgresTypes(row)
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizePostgresTypes normalizes a result map to handle PostgreSQL types correctly for JSON marshaling
|
||||||
|
// This is necessary because when scanning into map[string]interface{}, PostgreSQL types like jsonb, bytea, etc.
|
||||||
|
// are scanned as []byte which would be base64-encoded when marshaled to JSON.
|
||||||
|
func normalizePostgresTypes(row map[string]interface{}) map[string]interface{} {
|
||||||
|
if row == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := make(map[string]interface{}, len(row))
|
||||||
|
for key, value := range row {
|
||||||
|
normalized[key] = normalizePostgresValue(value)
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizePostgresValue normalizes a single value to the appropriate Go type for JSON marshaling
|
||||||
|
func normalizePostgresValue(value interface{}) interface{} {
|
||||||
|
if value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
// Check if it's valid JSON (jsonb type)
|
||||||
|
// Try to unmarshal as JSON first
|
||||||
|
var jsonObj interface{}
|
||||||
|
if err := json.Unmarshal(v, &jsonObj); err == nil {
|
||||||
|
// It's valid JSON, return as json.RawMessage so it's not double-encoded
|
||||||
|
return json.RawMessage(v)
|
||||||
|
}
|
||||||
|
// Not valid JSON, could be bytea - keep as []byte for base64 encoding
|
||||||
|
return v
|
||||||
|
|
||||||
|
case []interface{}:
|
||||||
|
// Recursively normalize array elements
|
||||||
|
normalized := make([]interface{}, len(v))
|
||||||
|
for i, elem := range v {
|
||||||
|
normalized[i] = normalizePostgresValue(elem)
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
case map[string]interface{}:
|
||||||
|
// Recursively normalize nested maps
|
||||||
|
return normalizePostgresTypes(v)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// For other types (int, float, string, bool, etc.), return as-is
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -784,7 +784,7 @@ func TestReplaceMetaVariables(t *testing.T) {
|
|||||||
userCtx := &security.UserContext{
|
userCtx := &security.UserContext{
|
||||||
UserID: 123,
|
UserID: 123,
|
||||||
UserName: "testuser",
|
UserName: "testuser",
|
||||||
SessionID: "session-abc",
|
SessionID: "456",
|
||||||
}
|
}
|
||||||
|
|
||||||
metainfo := map[string]interface{}{
|
metainfo := map[string]interface{}{
|
||||||
@@ -819,7 +819,7 @@ func TestReplaceMetaVariables(t *testing.T) {
|
|||||||
name: "Replace [rid_session]",
|
name: "Replace [rid_session]",
|
||||||
sqlQuery: "SELECT * FROM sessions WHERE session_id = [rid_session]",
|
sqlQuery: "SELECT * FROM sessions WHERE session_id = [rid_session]",
|
||||||
expectedCheck: func(result string) bool {
|
expectedCheck: func(result string) bool {
|
||||||
return strings.Contains(result, "'session-abc'")
|
return strings.Contains(result, "456")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,6 +151,8 @@ func (h *Handler) Handle(w common.ResponseWriter, r common.Request, params map[s
|
|||||||
h.handleUpdate(ctx, w, id, req.ID, req.Data, req.Options)
|
h.handleUpdate(ctx, w, id, req.ID, req.Data, req.Options)
|
||||||
case "delete":
|
case "delete":
|
||||||
h.handleDelete(ctx, w, id, req.Data)
|
h.handleDelete(ctx, w, id, req.Data)
|
||||||
|
case "meta":
|
||||||
|
h.handleMeta(ctx, w, schema, entity, model)
|
||||||
default:
|
default:
|
||||||
logger.Error("Invalid operation: %s", req.Operation)
|
logger.Error("Invalid operation: %s", req.Operation)
|
||||||
h.sendError(w, http.StatusBadRequest, "invalid_operation", "Invalid operation", nil)
|
h.sendError(w, http.StatusBadRequest, "invalid_operation", "Invalid operation", nil)
|
||||||
@@ -188,6 +190,21 @@ func (h *Handler) HandleGet(w common.ResponseWriter, r common.Request, params ma
|
|||||||
h.sendResponse(w, metadata, nil)
|
h.sendResponse(w, metadata, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleMeta processes meta operation requests
|
||||||
|
func (h *Handler) handleMeta(ctx context.Context, w common.ResponseWriter, schema, entity string, model interface{}) {
|
||||||
|
// Capture panics and return error response
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
h.handlePanic(w, "handleMeta", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger.Info("Getting metadata for %s.%s via meta operation", schema, entity)
|
||||||
|
|
||||||
|
metadata := h.generateMetadata(schema, entity, model)
|
||||||
|
h.sendResponse(w, metadata, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id string, options common.RequestOptions) {
|
func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id string, options common.RequestOptions) {
|
||||||
// Capture panics and return error response
|
// Capture panics and return error response
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
@@ -146,13 +146,25 @@ func (h *Handler) Handle(w common.ResponseWriter, r common.Request, params map[s
|
|||||||
h.handleRead(ctx, w, "", options)
|
h.handleRead(ctx, w, "", options)
|
||||||
}
|
}
|
||||||
case "POST":
|
case "POST":
|
||||||
// Create operation
|
// Read request body
|
||||||
body, err := r.Body()
|
body, err := r.Body()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to read request body: %v", err)
|
logger.Error("Failed to read request body: %v", err)
|
||||||
h.sendError(w, http.StatusBadRequest, "invalid_request", "Failed to read request body", err)
|
h.sendError(w, http.StatusBadRequest, "invalid_request", "Failed to read request body", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to detect if this is a meta operation request
|
||||||
|
var bodyMap map[string]interface{}
|
||||||
|
if err := json.Unmarshal(body, &bodyMap); err == nil {
|
||||||
|
if operation, ok := bodyMap["operation"].(string); ok && operation == "meta" {
|
||||||
|
logger.Info("Detected meta operation request for %s.%s", schema, entity)
|
||||||
|
h.handleMeta(ctx, w, schema, entity, model)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a meta operation, proceed with normal create/update
|
||||||
var data interface{}
|
var data interface{}
|
||||||
if err := json.Unmarshal(body, &data); err != nil {
|
if err := json.Unmarshal(body, &data); err != nil {
|
||||||
logger.Error("Failed to decode request body: %v", err)
|
logger.Error("Failed to decode request body: %v", err)
|
||||||
@@ -229,6 +241,21 @@ func (h *Handler) HandleGet(w common.ResponseWriter, r common.Request, params ma
|
|||||||
h.sendResponse(w, metadata, nil)
|
h.sendResponse(w, metadata, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleMeta processes meta operation requests
|
||||||
|
func (h *Handler) handleMeta(ctx context.Context, w common.ResponseWriter, schema, entity string, model interface{}) {
|
||||||
|
// Capture panics and return error response
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
h.handlePanic(w, "handleMeta", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger.Info("Getting metadata for %s.%s via meta operation", schema, entity)
|
||||||
|
|
||||||
|
metadata := h.generateMetadata(schema, entity, model)
|
||||||
|
h.sendResponse(w, metadata, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// parseOptionsFromHeaders is now implemented in headers.go
|
// parseOptionsFromHeaders is now implemented in headers.go
|
||||||
|
|
||||||
func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id string, options ExtendedRequestOptions) {
|
func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id string, options ExtendedRequestOptions) {
|
||||||
|
|||||||
Reference in New Issue
Block a user