mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-06-15 01:53:44 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2799fa224 | |||
| 1419542650 | |||
| c120b49529 |
@@ -174,7 +174,9 @@ func (h *HTTPResponseWriter) Write(data []byte) (int, error) {
|
|||||||
|
|
||||||
func (h *HTTPResponseWriter) WriteJSON(data interface{}) error {
|
func (h *HTTPResponseWriter) WriteJSON(data interface{}) error {
|
||||||
h.SetHeader("Content-Type", "application/json")
|
h.SetHeader("Content-Type", "application/json")
|
||||||
return json.NewEncoder(h.resp).Encode(data)
|
enc := json.NewEncoder(h.resp)
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
return enc.Encode(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnderlyingResponseWriter returns the underlying http.ResponseWriter
|
// UnderlyingResponseWriter returns the underlying http.ResponseWriter
|
||||||
|
|||||||
@@ -178,7 +178,9 @@ func (s *StandardResponseWriter) Write(data []byte) (int, error) {
|
|||||||
|
|
||||||
func (s *StandardResponseWriter) WriteJSON(data interface{}) error {
|
func (s *StandardResponseWriter) WriteJSON(data interface{}) error {
|
||||||
s.SetHeader("Content-Type", "application/json")
|
s.SetHeader("Content-Type", "application/json")
|
||||||
return json.NewEncoder(s.w).Encode(data)
|
enc := json.NewEncoder(s.w)
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
return enc.Encode(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StandardResponseWriter) UnderlyingResponseWriter() http.ResponseWriter {
|
func (s *StandardResponseWriter) UnderlyingResponseWriter() http.ResponseWriter {
|
||||||
|
|||||||
@@ -614,6 +614,15 @@ func extractTableAndColumn(cond string) (table string, column string) {
|
|||||||
// Remove any quotes
|
// Remove any quotes
|
||||||
columnRef = strings.Trim(columnRef, "`\"'")
|
columnRef = strings.Trim(columnRef, "`\"'")
|
||||||
|
|
||||||
|
// If the left side is a parenthesized subquery (starts with '(' and contains SQL keywords),
|
||||||
|
// don't attempt prefix extraction from inside it.
|
||||||
|
if len(columnRef) > 0 && columnRef[0] == '(' {
|
||||||
|
lowerRef := strings.ToLower(columnRef)
|
||||||
|
if strings.Contains(lowerRef, "select ") || strings.Contains(lowerRef, " from ") || strings.Contains(lowerRef, " where ") {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there's a function call (contains opening parenthesis)
|
// Check if there's a function call (contains opening parenthesis)
|
||||||
openParenIdx := strings.Index(columnRef, "(")
|
openParenIdx := strings.Index(columnRef, "(")
|
||||||
|
|
||||||
|
|||||||
@@ -781,6 +781,12 @@ func (h *Handler) create(hookCtx *HookContext) (interface{}, error) {
|
|||||||
return nil, fmt.Errorf("failed to create record: %w", err)
|
return nil, fmt.Errorf("failed to create record: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-fetch the created record to capture DB-generated defaults/triggers.
|
||||||
|
if pkVal := reflection.GetPrimaryKeyValue(hookCtx.ModelPtr); pkVal != nil {
|
||||||
|
hookCtx.ID = fmt.Sprintf("%v", pkVal)
|
||||||
|
return h.readByID(hookCtx)
|
||||||
|
}
|
||||||
|
|
||||||
return hookCtx.ModelPtr, nil
|
return hookCtx.ModelPtr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -428,15 +428,37 @@ func (h *Handler) executeCreate(ctx context.Context, schema, entity string, data
|
|||||||
// Use potentially modified data
|
// Use potentially modified data
|
||||||
data = hookCtx.Data
|
data = hookCtx.Data
|
||||||
|
|
||||||
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
|
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
query := h.db.NewInsert().Table(tableName)
|
query := h.db.NewInsert().Table(tableName)
|
||||||
for key, value := range v {
|
for key, value := range v {
|
||||||
query = query.Value(key, value)
|
query = query.Value(key, value)
|
||||||
}
|
}
|
||||||
|
if pkName != "" {
|
||||||
|
var insertedID interface{}
|
||||||
|
if err := query.Returning(pkName).Scan(ctx, &insertedID); err != nil {
|
||||||
|
return nil, fmt.Errorf("create error: %w", err)
|
||||||
|
}
|
||||||
|
// Re-fetch after insert to capture DB-generated defaults/triggers.
|
||||||
|
modelType := reflect.TypeOf(model)
|
||||||
|
if modelType.Kind() == reflect.Ptr {
|
||||||
|
modelType = modelType.Elem()
|
||||||
|
}
|
||||||
|
fetchedRecord := reflect.New(modelType).Interface()
|
||||||
|
if err := h.db.NewSelect().Model(fetchedRecord).
|
||||||
|
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), insertedID).
|
||||||
|
ScanModel(ctx); err == nil {
|
||||||
|
v = mergeWithInput(fetchedRecord, v)
|
||||||
|
} else {
|
||||||
|
logger.Warn("Failed to re-fetch created record with %s=%v: %v", pkName, insertedID, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if _, err := query.Exec(ctx); err != nil {
|
if _, err := query.Exec(ctx); err != nil {
|
||||||
return nil, fmt.Errorf("create error: %w", err)
|
return nil, fmt.Errorf("create error: %w", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
hookCtx.Result = v
|
hookCtx.Result = v
|
||||||
if err := h.hooks.Execute(AfterCreate, hookCtx); err != nil {
|
if err := h.hooks.Execute(AfterCreate, hookCtx); err != nil {
|
||||||
return nil, fmt.Errorf("AfterCreate hook failed: %w", err)
|
return nil, fmt.Errorf("AfterCreate hook failed: %w", err)
|
||||||
@@ -444,7 +466,12 @@ func (h *Handler) executeCreate(ctx context.Context, schema, entity string, data
|
|||||||
return v, nil
|
return v, nil
|
||||||
|
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
results := make([]interface{}, 0, len(v))
|
modelType := reflect.TypeOf(model)
|
||||||
|
if modelType.Kind() == reflect.Ptr {
|
||||||
|
modelType = modelType.Elem()
|
||||||
|
}
|
||||||
|
originals := make([]map[string]interface{}, 0, len(v))
|
||||||
|
insertedIDs := make([]interface{}, 0, len(v))
|
||||||
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
||||||
for _, item := range v {
|
for _, item := range v {
|
||||||
itemMap, ok := item.(map[string]interface{})
|
itemMap, ok := item.(map[string]interface{})
|
||||||
@@ -455,16 +482,43 @@ func (h *Handler) executeCreate(ctx context.Context, schema, entity string, data
|
|||||||
for key, value := range itemMap {
|
for key, value := range itemMap {
|
||||||
q = q.Value(key, value)
|
q = q.Value(key, value)
|
||||||
}
|
}
|
||||||
|
if pkName == "" {
|
||||||
if _, err := q.Exec(ctx); err != nil {
|
if _, err := q.Exec(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
results = append(results, item)
|
originals = append(originals, itemMap)
|
||||||
|
insertedIDs = append(insertedIDs, nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var returnedID interface{}
|
||||||
|
if err := q.Returning(pkName).Scan(ctx, &returnedID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
originals = append(originals, itemMap)
|
||||||
|
insertedIDs = append(insertedIDs, returnedID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("batch create error: %w", err)
|
return nil, fmt.Errorf("batch create error: %w", err)
|
||||||
}
|
}
|
||||||
|
// Re-fetch each record after transaction commits; fall back to input on failure.
|
||||||
|
results := make([]interface{}, 0, len(insertedIDs))
|
||||||
|
for i, pkVal := range insertedIDs {
|
||||||
|
if pkVal == nil {
|
||||||
|
results = append(results, originals[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fetchedRecord := reflect.New(modelType).Interface()
|
||||||
|
if err := h.db.NewSelect().Model(fetchedRecord).
|
||||||
|
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), pkVal).
|
||||||
|
ScanModel(ctx); err == nil {
|
||||||
|
results = append(results, mergeWithInput(fetchedRecord, originals[i]))
|
||||||
|
} else {
|
||||||
|
logger.Warn("Failed to re-fetch created record with %s=%v: %v", pkName, pkVal, err)
|
||||||
|
results = append(results, originals[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
hookCtx.Result = results
|
hookCtx.Result = results
|
||||||
if err := h.hooks.Execute(AfterCreate, hookCtx); err != nil {
|
if err := h.hooks.Execute(AfterCreate, hookCtx); err != nil {
|
||||||
return nil, fmt.Errorf("AfterCreate hook failed: %w", err)
|
return nil, fmt.Errorf("AfterCreate hook failed: %w", err)
|
||||||
@@ -584,6 +638,25 @@ func (h *Handler) executeUpdate(ctx context.Context, schema, entity, id string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-fetch the record after transaction commits to capture DB-generated changes.
|
||||||
|
modelType := reflect.TypeOf(model)
|
||||||
|
if modelType.Kind() == reflect.Ptr {
|
||||||
|
modelType = modelType.Elem()
|
||||||
|
}
|
||||||
|
fetchedRecord := reflect.New(modelType).Interface()
|
||||||
|
if err := h.db.NewSelect().Model(fetchedRecord).
|
||||||
|
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), id).
|
||||||
|
ScanModel(ctx); err == nil {
|
||||||
|
jsonData, marshalErr := json.Marshal(fetchedRecord)
|
||||||
|
if marshalErr == nil {
|
||||||
|
var fetchedMap map[string]interface{}
|
||||||
|
if json.Unmarshal(jsonData, &fetchedMap) == nil {
|
||||||
|
updateResult = fetchedMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return updateResult, nil
|
return updateResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,6 +822,28 @@ func (h *Handler) buildFilterCondition(filter common.FilterOption) (condition st
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeWithInput merges a database record with the original request data.
|
||||||
|
// DB values take precedence (capturing triggers/defaults), while extra
|
||||||
|
// input keys that have no DB column are preserved in the response.
|
||||||
|
func mergeWithInput(dbRecord interface{}, input map[string]interface{}) map[string]interface{} {
|
||||||
|
result := make(map[string]interface{}, len(input))
|
||||||
|
for k, v := range input {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
jsonData, err := json.Marshal(dbRecord)
|
||||||
|
if err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
var dbMap map[string]interface{}
|
||||||
|
if err := json.Unmarshal(jsonData, &dbMap); err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
for k, v := range dbMap {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) applyPreloads(model interface{}, query common.SelectQuery, preloads []common.PreloadOption) (common.SelectQuery, error) {
|
func (h *Handler) applyPreloads(model interface{}, query common.SelectQuery, preloads []common.PreloadOption) (common.SelectQuery, error) {
|
||||||
for i := range preloads {
|
for i := range preloads {
|
||||||
preload := &preloads[i]
|
preload := &preloads[i]
|
||||||
|
|||||||
+113
-6
@@ -602,10 +602,14 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Standard processing without nested relations
|
// Standard processing without nested relations
|
||||||
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
query := h.db.NewInsert().Table(tableName)
|
query := h.db.NewInsert().Table(tableName)
|
||||||
for key, value := range v {
|
for key, value := range v {
|
||||||
query = query.Value(key, common.ConvertSliceForBun(value))
|
query = query.Value(key, common.ConvertSliceForBun(value))
|
||||||
}
|
}
|
||||||
|
var responseData interface{} = v
|
||||||
|
if pkName == "" {
|
||||||
|
// No PK on model — insert and return input as-is.
|
||||||
result, err := query.Exec(ctx)
|
result, err := query.Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error creating record: %v", err)
|
logger.Error("Error creating record: %v", err)
|
||||||
@@ -613,12 +617,29 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info("Successfully created record, rows affected: %d", result.RowsAffected())
|
logger.Info("Successfully created record, rows affected: %d", result.RowsAffected())
|
||||||
|
} else {
|
||||||
|
var insertedID interface{}
|
||||||
|
if err := query.Returning(pkName).Scan(ctx, &insertedID); err != nil {
|
||||||
|
logger.Error("Error creating record: %v", err)
|
||||||
|
h.sendError(w, http.StatusInternalServerError, "create_error", "Error creating record", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Info("Successfully created record with %s: %v", pkName, insertedID)
|
||||||
|
fetchedRecord := reflect.New(reflection.GetPointerElement(reflect.TypeOf(model))).Interface()
|
||||||
|
if fetchErr := h.db.NewSelect().Model(fetchedRecord).
|
||||||
|
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), insertedID).
|
||||||
|
ScanModel(ctx); fetchErr == nil {
|
||||||
|
responseData = mergeWithInput(fetchedRecord, v)
|
||||||
|
} else {
|
||||||
|
logger.Warn("Failed to re-fetch created record with %s=%v: %v", pkName, insertedID, fetchErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Invalidate cache for this table
|
// Invalidate cache for this table
|
||||||
cacheTags := buildCacheTags(schema, tableName)
|
cacheTags := buildCacheTags(schema, tableName)
|
||||||
if err := invalidateCacheForTags(ctx, cacheTags); err != nil {
|
if err := invalidateCacheForTags(ctx, cacheTags); err != nil {
|
||||||
logger.Warn("Failed to invalidate cache for table %s: %v", tableName, err)
|
logger.Warn("Failed to invalidate cache for table %s: %v", tableName, err)
|
||||||
}
|
}
|
||||||
h.sendResponse(w, v, nil)
|
h.sendResponse(w, responseData, nil)
|
||||||
|
|
||||||
case []map[string]interface{}:
|
case []map[string]interface{}:
|
||||||
// Check if any item needs nested processing
|
// Check if any item needs nested processing
|
||||||
@@ -666,15 +687,30 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Standard batch insert without nested relations
|
// Standard batch insert without nested relations
|
||||||
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
|
modelElemType := reflection.GetPointerElement(reflect.TypeOf(model))
|
||||||
|
originals := make([]map[string]interface{}, 0, len(v))
|
||||||
|
insertedIDs := make([]interface{}, 0, len(v))
|
||||||
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
||||||
for _, item := range v {
|
for _, item := range v {
|
||||||
txQuery := tx.NewInsert().Table(tableName)
|
txQuery := tx.NewInsert().Table(tableName)
|
||||||
for key, value := range item {
|
for key, value := range item {
|
||||||
txQuery = txQuery.Value(key, common.ConvertSliceForBun(value))
|
txQuery = txQuery.Value(key, common.ConvertSliceForBun(value))
|
||||||
}
|
}
|
||||||
|
if pkName == "" {
|
||||||
if _, err := txQuery.Exec(ctx); err != nil {
|
if _, err := txQuery.Exec(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
originals = append(originals, item)
|
||||||
|
insertedIDs = append(insertedIDs, nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var returnedID interface{}
|
||||||
|
if err := txQuery.Returning(pkName).Scan(ctx, &returnedID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
originals = append(originals, item)
|
||||||
|
insertedIDs = append(insertedIDs, returnedID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -689,7 +725,24 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
if err := invalidateCacheForTags(ctx, cacheTags); err != nil {
|
if err := invalidateCacheForTags(ctx, cacheTags); err != nil {
|
||||||
logger.Warn("Failed to invalidate cache for table %s: %v", tableName, err)
|
logger.Warn("Failed to invalidate cache for table %s: %v", tableName, err)
|
||||||
}
|
}
|
||||||
h.sendResponse(w, v, nil)
|
// Re-fetch each record after transaction commits; fall back to input on failure.
|
||||||
|
responseItems := make([]interface{}, 0, len(insertedIDs))
|
||||||
|
for i, pkVal := range insertedIDs {
|
||||||
|
if pkVal == nil {
|
||||||
|
responseItems = append(responseItems, originals[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fetchedRecord := reflect.New(modelElemType).Interface()
|
||||||
|
if fetchErr := h.db.NewSelect().Model(fetchedRecord).
|
||||||
|
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), pkVal).
|
||||||
|
ScanModel(ctx); fetchErr == nil {
|
||||||
|
responseItems = append(responseItems, mergeWithInput(fetchedRecord, originals[i]))
|
||||||
|
} else {
|
||||||
|
logger.Warn("Failed to re-fetch created record with %s=%v: %v", pkName, pkVal, fetchErr)
|
||||||
|
responseItems = append(responseItems, originals[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.sendResponse(w, responseItems, nil)
|
||||||
|
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
// Handle []interface{} type from JSON unmarshaling
|
// Handle []interface{} type from JSON unmarshaling
|
||||||
@@ -742,19 +795,34 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Standard batch insert without nested relations
|
// Standard batch insert without nested relations
|
||||||
list := make([]interface{}, 0)
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
|
modelElemType := reflection.GetPointerElement(reflect.TypeOf(model))
|
||||||
|
originals := make([]map[string]interface{}, 0, len(v))
|
||||||
|
insertedIDs := make([]interface{}, 0, len(v))
|
||||||
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
||||||
for _, item := range v {
|
for _, item := range v {
|
||||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
itemMap, ok := item.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
txQuery := tx.NewInsert().Table(tableName)
|
txQuery := tx.NewInsert().Table(tableName)
|
||||||
for key, value := range itemMap {
|
for key, value := range itemMap {
|
||||||
txQuery = txQuery.Value(key, common.ConvertSliceForBun(value))
|
txQuery = txQuery.Value(key, common.ConvertSliceForBun(value))
|
||||||
}
|
}
|
||||||
|
if pkName == "" {
|
||||||
if _, err := txQuery.Exec(ctx); err != nil {
|
if _, err := txQuery.Exec(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
list = append(list, item)
|
originals = append(originals, itemMap)
|
||||||
|
insertedIDs = append(insertedIDs, nil)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
var returnedID interface{}
|
||||||
|
if err := txQuery.Returning(pkName).Scan(ctx, &returnedID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
originals = append(originals, itemMap)
|
||||||
|
insertedIDs = append(insertedIDs, returnedID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -769,7 +837,24 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
if err := invalidateCacheForTags(ctx, cacheTags); err != nil {
|
if err := invalidateCacheForTags(ctx, cacheTags); err != nil {
|
||||||
logger.Warn("Failed to invalidate cache for table %s: %v", tableName, err)
|
logger.Warn("Failed to invalidate cache for table %s: %v", tableName, err)
|
||||||
}
|
}
|
||||||
h.sendResponse(w, list, nil)
|
// Re-fetch each record after transaction commits; fall back to input on failure.
|
||||||
|
responseItems := make([]interface{}, 0, len(insertedIDs))
|
||||||
|
for i, pkVal := range insertedIDs {
|
||||||
|
if pkVal == nil {
|
||||||
|
responseItems = append(responseItems, originals[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fetchedRecord := reflect.New(modelElemType).Interface()
|
||||||
|
if fetchErr := h.db.NewSelect().Model(fetchedRecord).
|
||||||
|
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), pkVal).
|
||||||
|
ScanModel(ctx); fetchErr == nil {
|
||||||
|
responseItems = append(responseItems, mergeWithInput(fetchedRecord, originals[i]))
|
||||||
|
} else {
|
||||||
|
logger.Warn("Failed to re-fetch created record with %s=%v: %v", pkName, pkVal, fetchErr)
|
||||||
|
responseItems = append(responseItems, originals[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.sendResponse(w, responseItems, nil)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.Error("Invalid data type for create operation: %T", data)
|
logger.Error("Invalid data type for create operation: %T", data)
|
||||||
@@ -2122,3 +2207,25 @@ func (h *Handler) HandleOpenAPI(w common.ResponseWriter, r common.Request) {
|
|||||||
func (h *Handler) SetOpenAPIGenerator(generator func() (string, error)) {
|
func (h *Handler) SetOpenAPIGenerator(generator func() (string, error)) {
|
||||||
h.openAPIGenerator = generator
|
h.openAPIGenerator = generator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeWithInput merges a database record with the original request data.
|
||||||
|
// DB values take precedence (capturing triggers/defaults), while extra
|
||||||
|
// input keys that have no DB column are preserved in the response.
|
||||||
|
func mergeWithInput(dbRecord interface{}, input map[string]interface{}) map[string]interface{} {
|
||||||
|
result := make(map[string]interface{}, len(input))
|
||||||
|
for k, v := range input {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
jsonData, err := json.Marshal(dbRecord)
|
||||||
|
if err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
var dbMap map[string]interface{}
|
||||||
|
if err := json.Unmarshal(jsonData, &dbMap); err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
for k, v := range dbMap {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -671,6 +671,12 @@ func (h *Handler) create(hookCtx *HookContext) (interface{}, error) {
|
|||||||
return nil, fmt.Errorf("failed to create record: %w", err)
|
return nil, fmt.Errorf("failed to create record: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-fetch the created record to capture DB-generated defaults/triggers.
|
||||||
|
if pkVal := reflection.GetPrimaryKeyValue(hookCtx.ModelPtr); pkVal != nil {
|
||||||
|
hookCtx.ID = fmt.Sprintf("%v", pkVal)
|
||||||
|
return h.readByID(hookCtx)
|
||||||
|
}
|
||||||
|
|
||||||
return hookCtx.ModelPtr, nil
|
return hookCtx.ModelPtr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user