mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-06-07 22:33:46 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29449c93d5 | |||
| 3b6e5c75be | |||
| 549ccb8468 | |||
| 1af9c76337 | |||
| 938a2ef3d9 |
@@ -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
|
||||||
|
|||||||
@@ -713,6 +713,139 @@ func TestInjectForeignKeys(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Models for asymmetric join column tests (mirrors the bun has-many join:parentCol=childCol pattern).
|
||||||
|
// ActionOption has-many ActionOptionLinks via join:rid_actionoption=rid_actionoption_child.
|
||||||
|
// The child column ("rid_actionoption_child") differs from the parent column ("rid_actionoption").
|
||||||
|
type ActionOption struct {
|
||||||
|
RidActionoption int64 `json:"rid_actionoption" bun:"rid_actionoption,pk"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Links []*ActionOptionLink `json:"aol_rid_actionoption_child,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ActionOption) TableName() string { return "action_options" }
|
||||||
|
func (a ActionOption) GetIDName() string { return "RidActionoption" }
|
||||||
|
|
||||||
|
type ActionOptionLink struct {
|
||||||
|
RidActionoptionlink int64 `json:"rid_actionoptionlink" bun:"rid_actionoptionlink,pk"`
|
||||||
|
RidActionoptionChild int64 `json:"rid_actionoption_child" bun:"rid_actionoption_child"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
// Note: no field named "rid_actionoption" — that is the parent's column.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ActionOptionLink) TableName() string { return "action_option_links" }
|
||||||
|
func (a ActionOptionLink) GetIDName() string { return "RidActionoptionlink" }
|
||||||
|
|
||||||
|
// TestProcessNestedCUD_AsymmetricJoinColumns verifies that for a has-many relation with
|
||||||
|
// join:parentCol=childCol, the child rows are stamped with the child-side column (References),
|
||||||
|
// not the parent-side column (ForeignKey).
|
||||||
|
func TestProcessNestedCUD_AsymmetricJoinColumns(t *testing.T) {
|
||||||
|
db := newMockDatabase()
|
||||||
|
registry := &mockModelRegistry{}
|
||||||
|
relProvider := newMockRelationshipProvider()
|
||||||
|
|
||||||
|
// Mirrors: bun:"rel:has-many,join:rid_actionoption=rid_actionoption_child"
|
||||||
|
relProvider.RegisterRelation("ActionOption", "aol_rid_actionoption_child", &RelationshipInfo{
|
||||||
|
FieldName: "Links",
|
||||||
|
JSONName: "aol_rid_actionoption_child",
|
||||||
|
RelationType: "hasMany",
|
||||||
|
ForeignKey: "rid_actionoption", // parent-side column (left of join:)
|
||||||
|
References: "rid_actionoption_child", // child-side column (right of join:)
|
||||||
|
RelatedModel: ActionOptionLink{},
|
||||||
|
})
|
||||||
|
|
||||||
|
processor := NewNestedCUDProcessor(db, registry, relProvider)
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"label": "option-a",
|
||||||
|
"aol_rid_actionoption_child": []interface{}{
|
||||||
|
map[string]interface{}{"label": "link-1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := processor.ProcessNestedCUD(
|
||||||
|
context.Background(),
|
||||||
|
"insert",
|
||||||
|
data,
|
||||||
|
ActionOption{},
|
||||||
|
nil,
|
||||||
|
"action_options",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ProcessNestedCUD failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(db.insertCalls) < 2 {
|
||||||
|
t.Fatalf("Expected at least 2 insert calls (parent + child), got %d", len(db.insertCalls))
|
||||||
|
}
|
||||||
|
|
||||||
|
childInsert := db.insertCalls[1]
|
||||||
|
|
||||||
|
// The fix: child must receive "rid_actionoption_child", NOT "rid_actionoption".
|
||||||
|
if childInsert["rid_actionoption_child"] == nil {
|
||||||
|
t.Error("Expected child to have rid_actionoption_child set (child-side FK column)")
|
||||||
|
}
|
||||||
|
if childInsert["rid_actionoption"] != nil {
|
||||||
|
t.Errorf("Child must not receive parent-side column rid_actionoption, got %v", childInsert["rid_actionoption"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestProcessNestedCUD_BelongsToUnchanged verifies that the fix does not regress belongsTo
|
||||||
|
// relations, where ForeignKey is already the local (child) column.
|
||||||
|
func TestProcessNestedCUD_BelongsToUnchanged(t *testing.T) {
|
||||||
|
db := newMockDatabase()
|
||||||
|
registry := &mockModelRegistry{}
|
||||||
|
relProvider := newMockRelationshipProvider()
|
||||||
|
|
||||||
|
// For belongsTo, ForeignKey is the column on the child; References is on the parent.
|
||||||
|
// The old and new code must behave identically here.
|
||||||
|
relProvider.RegisterRelation("Employee", "department", &RelationshipInfo{
|
||||||
|
FieldName: "Department",
|
||||||
|
JSONName: "department",
|
||||||
|
RelationType: "belongsTo",
|
||||||
|
ForeignKey: "DepartmentID", // child's own column
|
||||||
|
References: "ID", // parent's PK
|
||||||
|
RelatedModel: Department{},
|
||||||
|
})
|
||||||
|
relProvider.RegisterRelation("Department", "employees", &RelationshipInfo{
|
||||||
|
FieldName: "Employees",
|
||||||
|
JSONName: "employees",
|
||||||
|
RelationType: "has_many",
|
||||||
|
ForeignKey: "DepartmentID",
|
||||||
|
RelatedModel: Employee{},
|
||||||
|
})
|
||||||
|
|
||||||
|
processor := NewNestedCUDProcessor(db, registry, relProvider)
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"name": "Engineering",
|
||||||
|
"employees": []interface{}{
|
||||||
|
map[string]interface{}{"name": "Alice"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := processor.ProcessNestedCUD(
|
||||||
|
context.Background(),
|
||||||
|
"insert",
|
||||||
|
data,
|
||||||
|
Department{},
|
||||||
|
nil,
|
||||||
|
"departments",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ProcessNestedCUD failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(db.insertCalls) < 2 {
|
||||||
|
t.Fatalf("Expected at least 2 inserts, got %d", len(db.insertCalls))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Employees relation uses has_many (old-style) so it goes through the parentIDs injection path,
|
||||||
|
// not the foreignKeyFieldName path. Just confirm no panic and employee is inserted.
|
||||||
|
if db.insertCalls[0]["name"] != "Engineering" {
|
||||||
|
t.Errorf("Expected department name 'Engineering', got %v", db.insertCalls[0]["name"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetPrimaryKeyName(t *testing.T) {
|
func TestGetPrimaryKeyName(t *testing.T) {
|
||||||
dept := Department{}
|
dept := Department{}
|
||||||
pkName := reflection.GetPrimaryKeyName(dept)
|
pkName := reflection.GetPrimaryKeyName(dept)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+19
-16
@@ -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