From 2f18dde29c530acbd018864c0b170c996e70b400 Mon Sep 17 00:00:00 2001 From: Hein Date: Fri, 12 Dec 2025 09:45:44 +0200 Subject: [PATCH] Added Tx common.Database to hooks --- pkg/resolvespec/hooks.go | 4 +++ pkg/restheadspec/handler.go | 9 +++++++ pkg/restheadspec/hooks.go | 4 +++ pkg/restheadspec/hooks_example.go | 44 +++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/pkg/resolvespec/hooks.go b/pkg/resolvespec/hooks.go index d269b5c..8121e7c 100644 --- a/pkg/resolvespec/hooks.go +++ b/pkg/resolvespec/hooks.go @@ -56,6 +56,10 @@ type HookContext struct { Abort bool // If set to true, the operation will be aborted AbortMessage string // Message to return if aborted AbortCode int // HTTP status code if aborted + + // Tx provides access to the database/transaction for executing additional SQL + // This allows hooks to run custom queries in addition to the main Query chain + Tx common.Database } // HookFunc is the signature for hook functions diff --git a/pkg/restheadspec/handler.go b/pkg/restheadspec/handler.go index 7e81953..5b55730 100644 --- a/pkg/restheadspec/handler.go +++ b/pkg/restheadspec/handler.go @@ -300,6 +300,7 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st Options: options, ID: id, Writer: w, + Tx: h.db, } if err := h.hooks.Execute(BeforeRead, hookCtx); err != nil { @@ -866,6 +867,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat Options: options, Data: data, Writer: w, + Tx: h.db, } if err := h.hooks.Execute(BeforeCreate, hookCtx); err != nil { @@ -955,6 +957,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat Data: modelValue, Writer: w, Query: query, + Tx: tx, } if err := h.hooks.Execute(BeforeScan, itemHookCtx); err != nil { return fmt.Errorf("BeforeScan hook failed for item %d: %w", i, err) @@ -1047,6 +1050,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id Schema: schema, Entity: entity, TableName: tableName, + Tx: h.db, Model: model, Options: options, ID: id, @@ -1122,6 +1126,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id // Execute BeforeScan hooks - pass query chain so hooks can modify it hookCtx.Query = query + hookCtx.Tx = tx if err := h.hooks.Execute(BeforeScan, hookCtx); err != nil { return fmt.Errorf("BeforeScan hook failed: %w", err) } @@ -1217,6 +1222,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id Model: model, ID: itemID, Writer: w, + Tx: tx, } if err := h.hooks.Execute(BeforeDelete, hookCtx); err != nil { @@ -1285,6 +1291,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id Model: model, ID: itemIDStr, Writer: w, + Tx: tx, } if err := h.hooks.Execute(BeforeDelete, hookCtx); err != nil { @@ -1337,6 +1344,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id Model: model, ID: itemIDStr, Writer: w, + Tx: tx, } if err := h.hooks.Execute(BeforeDelete, hookCtx); err != nil { @@ -1390,6 +1398,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id Model: model, ID: id, Writer: w, + Tx: h.db, } if err := h.hooks.Execute(BeforeDelete, hookCtx); err != nil { diff --git a/pkg/restheadspec/hooks.go b/pkg/restheadspec/hooks.go index 788fd2f..a8b4edd 100644 --- a/pkg/restheadspec/hooks.go +++ b/pkg/restheadspec/hooks.go @@ -55,6 +55,10 @@ type HookContext struct { // Response writer - allows hooks to modify response Writer common.ResponseWriter + + // Tx provides access to the database/transaction for executing additional SQL + // This allows hooks to run custom queries in addition to the main Query chain + Tx common.Database } // HookFunc is the signature for hook functions diff --git a/pkg/restheadspec/hooks_example.go b/pkg/restheadspec/hooks_example.go index a4f24f4..e313931 100644 --- a/pkg/restheadspec/hooks_example.go +++ b/pkg/restheadspec/hooks_example.go @@ -150,6 +150,50 @@ func ExampleRelatedDataHook(ctx *HookContext) error { return nil } +// ExampleTxHook demonstrates using the Tx field to execute additional SQL queries +// The Tx field provides access to the database/transaction for custom queries +func ExampleTxHook(ctx *HookContext) error { + // Example: Execute additional SQL operations alongside the main query + // This is useful for maintaining data consistency, updating related records, etc. + + if ctx.Entity == "orders" && ctx.Data != nil { + // Example: Update inventory when an order is created + // Extract product ID and quantity from the order data + // dataMap, ok := ctx.Data.(map[string]interface{}) + // if !ok { + // return fmt.Errorf("invalid data format") + // } + // productID := dataMap["product_id"] + // quantity := dataMap["quantity"] + + // Use ctx.Tx to execute additional SQL queries + // The Tx field contains the same database/transaction as the main operation + // If inside a transaction, your queries will be part of the same transaction + // query := ctx.Tx.NewUpdate(). + // Table("inventory"). + // Set("quantity = quantity - ?", quantity). + // Where("product_id = ?", productID) + // + // if _, err := query.Exec(ctx.Context); err != nil { + // logger.Error("Failed to update inventory: %v", err) + // return fmt.Errorf("failed to update inventory: %w", err) + // } + + // You can also execute raw SQL using ctx.Tx + // var result []map[string]interface{} + // err := ctx.Tx.Query(ctx.Context, &result, + // "INSERT INTO order_history (order_id, status) VALUES (?, ?)", + // orderID, "pending") + // if err != nil { + // return fmt.Errorf("failed to insert order history: %w", err) + // } + + logger.Debug("Executed additional SQL for order entity") + } + + return nil +} + // SetupExampleHooks demonstrates how to register hooks on a handler func SetupExampleHooks(handler *Handler) { hooks := handler.Hooks()