mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-06-07 14:23:46 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b6e5c75be | |||
| 549ccb8468 | |||
| 1af9c76337 | |||
| 938a2ef3d9 | |||
| 69cc3e2839 |
@@ -1489,7 +1489,7 @@ func (b *BunInsertQuery) OnConflict(action string) common.InsertQuery {
|
|||||||
|
|
||||||
func (b *BunInsertQuery) Returning(columns ...string) common.InsertQuery {
|
func (b *BunInsertQuery) Returning(columns ...string) common.InsertQuery {
|
||||||
if len(columns) > 0 {
|
if len(columns) > 0 {
|
||||||
b.query = b.query.Returning(columns[0])
|
b.query = b.query.Returning(strings.Join(columns, ", "))
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
@@ -1606,7 +1606,7 @@ func (b *BunUpdateQuery) Where(query string, args ...interface{}) common.UpdateQ
|
|||||||
|
|
||||||
func (b *BunUpdateQuery) Returning(columns ...string) common.UpdateQuery {
|
func (b *BunUpdateQuery) Returning(columns ...string) common.UpdateQuery {
|
||||||
if len(columns) > 0 {
|
if len(columns) > 0 {
|
||||||
b.query = b.query.Returning(columns[0])
|
b.query = b.query.Returning(strings.Join(columns, ", "))
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -471,13 +471,17 @@ func (p *NestedCUDProcessor) processChildRelations(
|
|||||||
// Priority: Use foreign key field name if specified
|
// Priority: Use foreign key field name if specified
|
||||||
var foreignKeyFieldName string
|
var foreignKeyFieldName string
|
||||||
if relInfo.ForeignKey != "" {
|
if relInfo.ForeignKey != "" {
|
||||||
// Get the JSON name for the foreign key field in the child model
|
// For has-many/has-one: join:parentCol=childCol
|
||||||
foreignKeyFieldName = reflection.GetJSONNameForField(relatedModelType, relInfo.ForeignKey)
|
// ForeignKey = parent side, References = child side (where we actually set the value)
|
||||||
if foreignKeyFieldName == "" {
|
childField := relInfo.ForeignKey
|
||||||
// Fallback to lowercase field name
|
if (relInfo.RelationType == "hasMany" || relInfo.RelationType == "hasOne") && relInfo.References != "" {
|
||||||
foreignKeyFieldName = strings.ToLower(relInfo.ForeignKey)
|
childField = relInfo.References
|
||||||
}
|
}
|
||||||
logger.Debug("Using foreign key field for direct assignment: %s (from FK %s)", foreignKeyFieldName, relInfo.ForeignKey)
|
foreignKeyFieldName = reflection.GetJSONNameForField(relatedModelType, childField)
|
||||||
|
if foreignKeyFieldName == "" {
|
||||||
|
foreignKeyFieldName = strings.ToLower(childField)
|
||||||
|
}
|
||||||
|
logger.Debug("Using foreign key field for direct assignment: %s (from FK %s -> child %s)", foreignKeyFieldName, relInfo.ForeignKey, childField)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the primary key name for the child model to avoid overwriting it in recursive relationships
|
// Get the primary key name for the child model to avoid overwriting it in recursive relationships
|
||||||
|
|||||||
@@ -836,7 +836,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
err := h.db.RunInTransaction(ctx, func(tx common.Database) error {
|
||||||
// First, read the existing record from the database
|
// First, read the existing record from the database
|
||||||
existingRecord := reflect.New(reflection.GetPointerElement(reflect.TypeOf(model))).Interface()
|
existingRecord := reflect.New(reflection.GetPointerElement(reflect.TypeOf(model))).Interface()
|
||||||
selectQuery := tx.NewSelect().Model(existingRecord).Column("*")
|
selectQuery := tx.NewSelect().Model(existingRecord).Column(reflection.GetSQLModelColumns(model)...)
|
||||||
|
|
||||||
// Apply conditions to select
|
// Apply conditions to select
|
||||||
if urlID != "" {
|
if urlID != "" {
|
||||||
@@ -955,13 +955,34 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch the updated record after the transaction commits to capture any trigger changes
|
||||||
|
updatedRecord := reflect.New(reflection.GetPointerElement(reflect.TypeOf(model))).Interface()
|
||||||
|
fetchQuery := h.db.NewSelect().Model(updatedRecord).Column(reflection.GetSQLModelColumns(model)...)
|
||||||
|
if urlID != "" {
|
||||||
|
fetchQuery = fetchQuery.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), urlID)
|
||||||
|
} else if reqID != nil {
|
||||||
|
switch id := reqID.(type) {
|
||||||
|
case string:
|
||||||
|
fetchQuery = fetchQuery.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), id)
|
||||||
|
case []string:
|
||||||
|
if len(id) > 0 {
|
||||||
|
fetchQuery = fetchQuery.Where(fmt.Sprintf("%s IN (?)", common.QuoteIdent(pkName)), id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := fetchQuery.ScanModel(ctx); err != nil {
|
||||||
|
logger.Error("Failed to fetch updated record: %v", err)
|
||||||
|
h.sendError(w, http.StatusInternalServerError, "fetch_error", "Failed to fetch updated record", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
logger.Info("Successfully updated record(s)")
|
logger.Info("Successfully updated record(s)")
|
||||||
// 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, data, nil)
|
h.sendResponse(w, updatedRecord, nil)
|
||||||
|
|
||||||
case []map[string]interface{}:
|
case []map[string]interface{}:
|
||||||
// Batch update with array of objects
|
// Batch update with array of objects
|
||||||
@@ -1017,7 +1038,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
|
|
||||||
// First, read the existing record
|
// First, read the existing record
|
||||||
existingRecord := reflect.New(reflection.GetPointerElement(reflect.TypeOf(model))).Interface()
|
existingRecord := reflect.New(reflection.GetPointerElement(reflect.TypeOf(model))).Interface()
|
||||||
selectQuery := tx.NewSelect().Model(existingRecord).Column("*").Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), itemID)
|
selectQuery := tx.NewSelect().Model(existingRecord).Column(reflection.GetSQLModelColumns(model)...).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), itemID)
|
||||||
if err := selectQuery.ScanModel(ctx); err != nil {
|
if err := selectQuery.ScanModel(ctx); err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
continue // Skip if record not found
|
continue // Skip if record not found
|
||||||
@@ -1089,13 +1110,29 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
h.sendError(w, http.StatusInternalServerError, "update_error", "Error updating records", err)
|
h.sendError(w, http.StatusInternalServerError, "update_error", "Error updating records", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info("Successfully updated %d records", len(updates))
|
|
||||||
|
// Fetch updated records after the transaction commits to capture any trigger changes
|
||||||
|
fetchedUpdates := make([]interface{}, 0, len(updates))
|
||||||
|
for _, item := range updates {
|
||||||
|
if itemID, ok := item["id"]; ok && itemID != nil {
|
||||||
|
fetchedRecord := reflect.New(reflection.GetPointerElement(reflect.TypeOf(model))).Interface()
|
||||||
|
fetchQuery := h.db.NewSelect().Model(fetchedRecord).Column(reflection.GetSQLModelColumns(model)...).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), itemID)
|
||||||
|
if err := fetchQuery.ScanModel(ctx); err != nil {
|
||||||
|
logger.Error("Failed to fetch updated record with ID %v: %v", itemID, err)
|
||||||
|
h.sendError(w, http.StatusInternalServerError, "fetch_error", "Failed to fetch updated record", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fetchedUpdates = append(fetchedUpdates, fetchedRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Successfully updated %d records", len(fetchedUpdates))
|
||||||
// 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, updates, nil)
|
h.sendResponse(w, fetchedUpdates, nil)
|
||||||
|
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
// Batch update with []interface{}
|
// Batch update with []interface{}
|
||||||
@@ -1157,7 +1194,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
|
|
||||||
// First, read the existing record
|
// First, read the existing record
|
||||||
existingRecord := reflect.New(reflection.GetPointerElement(reflect.TypeOf(model))).Interface()
|
existingRecord := reflect.New(reflection.GetPointerElement(reflect.TypeOf(model))).Interface()
|
||||||
selectQuery := tx.NewSelect().Model(existingRecord).Column("*").Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), itemID)
|
selectQuery := tx.NewSelect().Model(existingRecord).Column(reflection.GetSQLModelColumns(model)...).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), itemID)
|
||||||
if err := selectQuery.ScanModel(ctx); err != nil {
|
if err := selectQuery.ScanModel(ctx); err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
continue // Skip if record not found
|
continue // Skip if record not found
|
||||||
@@ -1232,13 +1269,31 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
h.sendError(w, http.StatusInternalServerError, "update_error", "Error updating records", err)
|
h.sendError(w, http.StatusInternalServerError, "update_error", "Error updating records", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info("Successfully updated %d records", len(list))
|
|
||||||
|
// Fetch updated records after the transaction commits to capture any trigger changes
|
||||||
|
fetchedList := make([]interface{}, 0, len(list))
|
||||||
|
for _, item := range list {
|
||||||
|
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||||
|
if itemID, ok := itemMap["id"]; ok && itemID != nil {
|
||||||
|
fetchedRecord := reflect.New(reflection.GetPointerElement(reflect.TypeOf(model))).Interface()
|
||||||
|
fetchQuery := h.db.NewSelect().Model(fetchedRecord).Column(reflection.GetSQLModelColumns(model)...).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), itemID)
|
||||||
|
if err := fetchQuery.ScanModel(ctx); err != nil {
|
||||||
|
logger.Error("Failed to fetch updated record with ID %v: %v", itemID, err)
|
||||||
|
h.sendError(w, http.StatusInternalServerError, "fetch_error", "Failed to fetch updated record", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fetchedList = append(fetchedList, fetchedRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Successfully updated %d records", len(fetchedList))
|
||||||
// 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, list, nil)
|
h.sendResponse(w, fetchedList, nil)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.Error("Invalid data type for update operation: %T", data)
|
logger.Error("Invalid data type for update operation: %T", data)
|
||||||
|
|||||||
+21
-18
@@ -1218,8 +1218,8 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
if provider, ok := modelValue.(common.TableNameProvider); !ok || provider.TableName() == "" {
|
if provider, ok := modelValue.(common.TableNameProvider); !ok || provider.TableName() == "" {
|
||||||
query = query.Table(tableName)
|
query = query.Table(tableName)
|
||||||
}
|
}
|
||||||
|
fields := reflection.GetSQLModelColumns(model)
|
||||||
query = query.Returning("*")
|
query = query.Returning(fields...)
|
||||||
|
|
||||||
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
||||||
itemHookCtx := &HookContext{
|
itemHookCtx := &HookContext{
|
||||||
@@ -1480,18 +1480,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the updated record to return the new values
|
_ = result
|
||||||
modelValue := reflect.New(reflect.TypeOf(model)).Interface()
|
|
||||||
selectQuery = tx.NewSelect().Model(modelValue).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
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1501,6 +1490,16 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch the updated record after the transaction commits to capture any trigger changes
|
||||||
|
fetchedRecord := reflect.New(reflect.TypeOf(model)).Interface()
|
||||||
|
selectQuery := h.db.NewSelect().Model(fetchedRecord).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), targetID)
|
||||||
|
if err := selectQuery.ScanModel(ctx); err != nil {
|
||||||
|
logger.Error("Failed to fetch updated record: %v", err)
|
||||||
|
h.sendError(w, http.StatusInternalServerError, "fetch_error", "Failed to fetch updated record", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updatedRecord = fetchedRecord
|
||||||
|
|
||||||
// Merge the updated record with the original request data
|
// Merge the updated record with the original request data
|
||||||
// This preserves extra keys from the request and updates values from the database
|
// This preserves extra keys from the request and updates values from the database
|
||||||
mergedData := h.mergeRecordWithRequest(updatedRecord, dataMap)
|
mergedData := h.mergeRecordWithRequest(updatedRecord, dataMap)
|
||||||
@@ -2012,11 +2011,15 @@ func (h *Handler) processChildRelationsForField(
|
|||||||
// Priority: Use foreign key field name if specified, otherwise use parent's PK name
|
// Priority: Use foreign key field name if specified, otherwise use parent's PK name
|
||||||
var foreignKeyFieldName string
|
var foreignKeyFieldName string
|
||||||
if relInfo.ForeignKey != "" {
|
if relInfo.ForeignKey != "" {
|
||||||
// Get the JSON name for the foreign key field in the child model
|
// For has-many/has-one: join:parentCol=childCol
|
||||||
foreignKeyFieldName = reflection.GetJSONNameForField(relatedModelType, relInfo.ForeignKey)
|
// ForeignKey = parent side, References = child side (where we actually set the value)
|
||||||
|
childField := relInfo.ForeignKey
|
||||||
|
if (relInfo.RelationType == "hasMany" || relInfo.RelationType == "hasOne") && relInfo.References != "" {
|
||||||
|
childField = relInfo.References
|
||||||
|
}
|
||||||
|
foreignKeyFieldName = reflection.GetJSONNameForField(relatedModelType, childField)
|
||||||
if foreignKeyFieldName == "" {
|
if foreignKeyFieldName == "" {
|
||||||
// Fallback to lowercase field name
|
foreignKeyFieldName = strings.ToLower(childField)
|
||||||
foreignKeyFieldName = strings.ToLower(relInfo.ForeignKey)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: use parent's primary key name
|
// Fallback: use parent's primary key name
|
||||||
|
|||||||
@@ -70,6 +70,25 @@ func (m *mountPoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Try to open the file
|
// Try to open the file
|
||||||
file, err := m.provider.Open(strings.TrimPrefix(filePath, "/"))
|
file, err := m.provider.Open(strings.TrimPrefix(filePath, "/"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// For extensionless paths, also try path/index.html
|
||||||
|
if path.Ext(filePath) == "" {
|
||||||
|
indexFallback := path.Join(filePath, "index.html")
|
||||||
|
file, err = m.provider.Open(strings.TrimPrefix(indexFallback, "/"))
|
||||||
|
if err == nil {
|
||||||
|
defer file.Close()
|
||||||
|
m.serveFile(w, r, indexFallback, file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
indexFallback = fmt.Sprintf("%s.html", filePath)
|
||||||
|
file, err = m.provider.Open(strings.TrimPrefix(indexFallback, "/"))
|
||||||
|
if err == nil {
|
||||||
|
defer file.Close()
|
||||||
|
m.serveFile(w, r, indexFallback, file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// File doesn't exist - check if we should use fallback
|
// File doesn't exist - check if we should use fallback
|
||||||
if m.fallbackStrategy != nil && m.fallbackStrategy.ShouldFallback(filePath) {
|
if m.fallbackStrategy != nil && m.fallbackStrategy.ShouldFallback(filePath) {
|
||||||
fallbackPath := m.fallbackStrategy.GetFallbackPath(filePath)
|
fallbackPath := m.fallbackStrategy.GetFallbackPath(filePath)
|
||||||
@@ -80,16 +99,6 @@ func (m *mountPoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// For extensionless paths, also try path/index.html
|
|
||||||
if path.Ext(filePath) == "" {
|
|
||||||
indexFallback := path.Join(filePath, "index.html")
|
|
||||||
file, err = m.provider.Open(strings.TrimPrefix(indexFallback, "/"))
|
|
||||||
if err == nil {
|
|
||||||
defer file.Close()
|
|
||||||
m.serveFile(w, r, indexFallback, file)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No fallback or fallback failed - return 404
|
// No fallback or fallback failed - return 404
|
||||||
|
|||||||
Reference in New Issue
Block a user