mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-01-07 20:34:25 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb54ec5e27 | ||
|
|
7d6a9025f5 |
@@ -388,12 +388,17 @@ func (b *BunUpdateQuery) Set(column string, value interface{}) common.UpdateQuer
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *BunUpdateQuery) SetMap(values map[string]interface{}) common.UpdateQuery {
|
func (b *BunUpdateQuery) SetMap(values map[string]interface{}) common.UpdateQuery {
|
||||||
|
pkName := reflection.GetPrimaryKeyName(b.model)
|
||||||
for column, value := range values {
|
for column, value := range values {
|
||||||
// Validate column is writable if model is set
|
// Validate column is writable if model is set
|
||||||
if b.model != nil && !reflection.IsColumnWritable(b.model, column) {
|
if b.model != nil && !reflection.IsColumnWritable(b.model, column) {
|
||||||
// Skip scan-only columns
|
// Skip scan-only columns
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if pkName != "" && column == pkName {
|
||||||
|
// Skip primary key updates
|
||||||
|
continue
|
||||||
|
}
|
||||||
b.query = b.query.Set(column+" = ?", value)
|
b.query = b.query.Set(column+" = ?", value)
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
|
|||||||
@@ -369,13 +369,20 @@ func (g *GormUpdateQuery) Set(column string, value interface{}) common.UpdateQue
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GormUpdateQuery) SetMap(values map[string]interface{}) common.UpdateQuery {
|
func (g *GormUpdateQuery) SetMap(values map[string]interface{}) common.UpdateQuery {
|
||||||
|
|
||||||
// Filter out read-only columns if model is set
|
// Filter out read-only columns if model is set
|
||||||
if g.model != nil {
|
if g.model != nil {
|
||||||
|
pkName := reflection.GetPrimaryKeyName(g.model)
|
||||||
filteredValues := make(map[string]interface{})
|
filteredValues := make(map[string]interface{})
|
||||||
for column, value := range values {
|
for column, value := range values {
|
||||||
|
if pkName != "" && column == pkName {
|
||||||
|
// Skip primary key updates
|
||||||
|
continue
|
||||||
|
}
|
||||||
if reflection.IsColumnWritable(g.model, column) {
|
if reflection.IsColumnWritable(g.model, column) {
|
||||||
filteredValues[column] = value
|
filteredValues[column] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
g.updates = filteredValues
|
g.updates = filteredValues
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ func (p *NestedCUDProcessor) ProcessNestedCUD(
|
|||||||
// Inject parent IDs for foreign key resolution
|
// Inject parent IDs for foreign key resolution
|
||||||
p.injectForeignKeys(regularData, modelType, parentIDs)
|
p.injectForeignKeys(regularData, modelType, parentIDs)
|
||||||
|
|
||||||
|
// Get the primary key name for this model
|
||||||
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
|
|
||||||
// Process based on operation
|
// Process based on operation
|
||||||
switch strings.ToLower(operation) {
|
switch strings.ToLower(operation) {
|
||||||
case "insert", "create":
|
case "insert", "create":
|
||||||
@@ -128,30 +131,30 @@ func (p *NestedCUDProcessor) ProcessNestedCUD(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "update":
|
case "update":
|
||||||
rows, err := p.processUpdate(ctx, regularData, tableName, data["id"])
|
rows, err := p.processUpdate(ctx, regularData, tableName, data[pkName])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("update failed: %w", err)
|
return nil, fmt.Errorf("update failed: %w", err)
|
||||||
}
|
}
|
||||||
result.ID = data["id"]
|
result.ID = data[pkName]
|
||||||
result.AffectedRows = rows
|
result.AffectedRows = rows
|
||||||
result.Data = regularData
|
result.Data = regularData
|
||||||
|
|
||||||
// Process child relations for update
|
// Process child relations for update
|
||||||
if err := p.processChildRelations(ctx, "update", data["id"], relationFields, result.RelationData, modelType); err != nil {
|
if err := p.processChildRelations(ctx, "update", data[pkName], relationFields, result.RelationData, modelType); err != nil {
|
||||||
return nil, fmt.Errorf("failed to process child relations: %w", err)
|
return nil, fmt.Errorf("failed to process child relations: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "delete":
|
case "delete":
|
||||||
// Process child relations first (for referential integrity)
|
// Process child relations first (for referential integrity)
|
||||||
if err := p.processChildRelations(ctx, "delete", data["id"], relationFields, result.RelationData, modelType); err != nil {
|
if err := p.processChildRelations(ctx, "delete", data[pkName], relationFields, result.RelationData, modelType); err != nil {
|
||||||
return nil, fmt.Errorf("failed to process child relations before delete: %w", err)
|
return nil, fmt.Errorf("failed to process child relations before delete: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := p.processDelete(ctx, tableName, data["id"])
|
rows, err := p.processDelete(ctx, tableName, data[pkName])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("delete failed: %w", err)
|
return nil, fmt.Errorf("delete failed: %w", err)
|
||||||
}
|
}
|
||||||
result.ID = data["id"]
|
result.ID = data[pkName]
|
||||||
result.AffectedRows = rows
|
result.AffectedRows = rows
|
||||||
result.Data = regularData
|
result.Data = regularData
|
||||||
|
|
||||||
|
|||||||
@@ -610,6 +610,9 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
dataSlice := h.normalizeToSlice(data)
|
dataSlice := h.normalizeToSlice(data)
|
||||||
logger.Debug("Processing %d item(s) for creation", len(dataSlice))
|
logger.Debug("Processing %d item(s) for creation", len(dataSlice))
|
||||||
|
|
||||||
|
// Store original data maps for merging later
|
||||||
|
originalDataMaps := make([]map[string]interface{}, 0, len(dataSlice))
|
||||||
|
|
||||||
// Process all items in a transaction
|
// Process all items in a transaction
|
||||||
results := make([]interface{}, 0, len(dataSlice))
|
results := make([]interface{}, 0, len(dataSlice))
|
||||||
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
||||||
@@ -630,6 +633,13 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store a copy of the original data map for merging later
|
||||||
|
originalMap := make(map[string]interface{})
|
||||||
|
for k, v := range itemMap {
|
||||||
|
originalMap[k] = v
|
||||||
|
}
|
||||||
|
originalDataMaps = append(originalDataMaps, originalMap)
|
||||||
|
|
||||||
// Extract nested relations if present (but don't process them yet)
|
// Extract nested relations if present (but don't process them yet)
|
||||||
var nestedRelations map[string]interface{}
|
var nestedRelations map[string]interface{}
|
||||||
if h.shouldUseNestedProcessor(itemMap, model) {
|
if h.shouldUseNestedProcessor(itemMap, model) {
|
||||||
@@ -704,14 +714,26 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge created records with original request data
|
||||||
|
// This preserves extra keys from the request
|
||||||
|
mergedResults := make([]interface{}, 0, len(results))
|
||||||
|
for i, result := range results {
|
||||||
|
if i < len(originalDataMaps) {
|
||||||
|
merged := h.mergeRecordWithRequest(result, originalDataMaps[i])
|
||||||
|
mergedResults = append(mergedResults, merged)
|
||||||
|
} else {
|
||||||
|
mergedResults = append(mergedResults, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Execute AfterCreate hooks
|
// Execute AfterCreate hooks
|
||||||
var responseData interface{}
|
var responseData interface{}
|
||||||
if len(results) == 1 {
|
if len(mergedResults) == 1 {
|
||||||
responseData = results[0]
|
responseData = mergedResults[0]
|
||||||
hookCtx.Result = results[0]
|
hookCtx.Result = mergedResults[0]
|
||||||
} else {
|
} else {
|
||||||
responseData = results
|
responseData = mergedResults
|
||||||
hookCtx.Result = map[string]interface{}{"created": len(results), "data": results}
|
hookCtx.Result = map[string]interface{}{"created": len(mergedResults), "data": mergedResults}
|
||||||
}
|
}
|
||||||
hookCtx.Error = nil
|
hookCtx.Error = nil
|
||||||
|
|
||||||
@@ -721,7 +743,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Successfully created %d record(s)", len(results))
|
logger.Info("Successfully created %d record(s)", len(mergedResults))
|
||||||
h.sendResponseWithOptions(w, responseData, nil, &options)
|
h.sendResponseWithOptions(w, responseData, nil, &options)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,6 +812,12 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the primary key name for the model
|
||||||
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
|
|
||||||
|
// Variable to store the updated record
|
||||||
|
var updatedRecord interface{}
|
||||||
|
|
||||||
// Process nested relations if present
|
// Process nested relations if present
|
||||||
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
||||||
// Create temporary nested processor with transaction
|
// Create temporary nested processor with transaction
|
||||||
@@ -808,11 +836,10 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure ID is in the data map for the update
|
// Ensure ID is in the data map for the update
|
||||||
dataMap["id"] = targetID
|
dataMap[pkName] = targetID
|
||||||
|
|
||||||
// Create update query
|
// Create update query
|
||||||
query := tx.NewUpdate().Table(tableName).SetMap(dataMap)
|
query := tx.NewUpdate().Table(tableName).SetMap(dataMap)
|
||||||
pkName := reflection.GetPrimaryKeyName(model)
|
|
||||||
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), targetID)
|
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), targetID)
|
||||||
|
|
||||||
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
||||||
@@ -840,10 +867,18 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store result for hooks
|
// Fetch the updated record to return the new values
|
||||||
hookCtx.Result = map[string]interface{}{
|
modelValue := reflect.New(reflect.TypeOf(model)).Interface()
|
||||||
"updated": result.RowsAffected(),
|
selectQuery := tx.NewSelect().Model(modelValue).Table(tableName).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), targetID)
|
||||||
|
if err := selectQuery.ScanModel(ctx); err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch updated record: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedRecord = modelValue
|
||||||
|
|
||||||
|
// Store result for hooks
|
||||||
|
hookCtx.Result = updatedRecord
|
||||||
|
_ = result // Keep result variable for potential future use
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -853,7 +888,12 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge the updated record with the original request data
|
||||||
|
// This preserves extra keys from the request and updates values from the database
|
||||||
|
mergedData := h.mergeRecordWithRequest(updatedRecord, dataMap)
|
||||||
|
|
||||||
// Execute AfterUpdate hooks
|
// Execute AfterUpdate hooks
|
||||||
|
hookCtx.Result = mergedData
|
||||||
hookCtx.Error = nil
|
hookCtx.Error = nil
|
||||||
if err := h.hooks.Execute(AfterUpdate, hookCtx); err != nil {
|
if err := h.hooks.Execute(AfterUpdate, hookCtx); err != nil {
|
||||||
logger.Error("AfterUpdate hook failed: %v", err)
|
logger.Error("AfterUpdate hook failed: %v", err)
|
||||||
@@ -862,7 +902,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Successfully updated record with ID: %v", targetID)
|
logger.Info("Successfully updated record with ID: %v", targetID)
|
||||||
h.sendResponseWithOptions(w, hookCtx.Result, nil, &options)
|
h.sendResponseWithOptions(w, mergedData, nil, &options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id string, data interface{}) {
|
func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id string, data interface{}) {
|
||||||
@@ -936,6 +976,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
// Array of IDs or objects with ID field
|
// Array of IDs or objects with ID field
|
||||||
logger.Info("Batch delete with %d items ([]interface{})", len(v))
|
logger.Info("Batch delete with %d items ([]interface{})", len(v))
|
||||||
deletedCount := 0
|
deletedCount := 0
|
||||||
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
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 {
|
||||||
var itemID interface{}
|
var itemID interface{}
|
||||||
@@ -945,7 +986,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
case string:
|
case string:
|
||||||
itemID = v
|
itemID = v
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
itemID = v["id"]
|
itemID = v[pkName]
|
||||||
default:
|
default:
|
||||||
itemID = item
|
itemID = item
|
||||||
}
|
}
|
||||||
@@ -1002,9 +1043,10 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
// Array of objects with id field
|
// Array of objects with id field
|
||||||
logger.Info("Batch delete with %d items ([]map[string]interface{})", len(v))
|
logger.Info("Batch delete with %d items ([]map[string]interface{})", len(v))
|
||||||
deletedCount := 0
|
deletedCount := 0
|
||||||
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
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 itemID, ok := item["id"]; ok && itemID != nil {
|
if itemID, ok := item[pkName]; ok && itemID != nil {
|
||||||
itemIDStr := fmt.Sprintf("%v", itemID)
|
itemIDStr := fmt.Sprintf("%v", itemID)
|
||||||
|
|
||||||
// Execute hooks for each item
|
// Execute hooks for each item
|
||||||
@@ -1052,7 +1094,8 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
|
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
// Single object with id field
|
// Single object with id field
|
||||||
if itemID, ok := v["id"]; ok && itemID != nil {
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
|
if itemID, ok := v[pkName]; ok && itemID != nil {
|
||||||
id = fmt.Sprintf("%v", itemID)
|
id = fmt.Sprintf("%v", itemID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1122,6 +1165,39 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
h.sendResponse(w, responseData, nil)
|
h.sendResponse(w, responseData, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeRecordWithRequest merges a database record with the original request data
|
||||||
|
// This preserves extra keys from the request that aren't in the database model
|
||||||
|
// and updates values from the database (e.g., from SQL triggers or defaults)
|
||||||
|
func (h *Handler) mergeRecordWithRequest(dbRecord interface{}, requestData map[string]interface{}) map[string]interface{} {
|
||||||
|
// Convert the database record to a map
|
||||||
|
dbMap := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Marshal and unmarshal to convert struct to map
|
||||||
|
jsonData, err := json.Marshal(dbRecord)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to marshal database record for merging: %v", err)
|
||||||
|
return requestData
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(jsonData, &dbMap); err != nil {
|
||||||
|
logger.Warn("Failed to unmarshal database record for merging: %v", err)
|
||||||
|
return requestData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with the request data (preserves extra keys)
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
for k, v := range requestData {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update with values from database (overwrites with DB values, including trigger changes)
|
||||||
|
for k, v := range dbMap {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// normalizeToSlice converts data to a slice. Single items become a 1-item slice.
|
// normalizeToSlice converts data to a slice. Single items become a 1-item slice.
|
||||||
func (h *Handler) normalizeToSlice(data interface{}) []interface{} {
|
func (h *Handler) normalizeToSlice(data interface{}) []interface{} {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
@@ -1658,22 +1734,22 @@ func (h *Handler) cleanJSON(data interface{}) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) sendError(w common.ResponseWriter, statusCode int, code, message string, err error) {
|
func (h *Handler) sendError(w common.ResponseWriter, statusCode int, code, message string, err error) {
|
||||||
var details string
|
var errorMsg string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
details = err.Error()
|
errorMsg = err.Error()
|
||||||
|
} else if message != "" {
|
||||||
|
errorMsg = message
|
||||||
|
} else {
|
||||||
|
errorMsg = code
|
||||||
}
|
}
|
||||||
|
|
||||||
response := common.Response{
|
response := map[string]interface{}{
|
||||||
Success: false,
|
"_error": errorMsg,
|
||||||
Error: &common.APIError{
|
"_retval": 1,
|
||||||
Code: code,
|
|
||||||
Message: message,
|
|
||||||
Details: details,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
w.WriteHeader(statusCode)
|
w.WriteHeader(statusCode)
|
||||||
if err := w.WriteJSON(response); err != nil {
|
if jsonErr := w.WriteJSON(response); jsonErr != nil {
|
||||||
logger.Error("Failed to write JSON error response: %v", err)
|
logger.Error("Failed to write JSON error response: %v", jsonErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user