mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-07-26 12:17:38 +00:00
52d3dca1fa
Tests / Unit Tests (push) Failing after 11s
Tests / Integration Tests (push) Failing after 15s
Build , Vet Test, and Lint / Build (push) Successful in 1m45s
Build , Vet Test, and Lint / Run Vet Tests (1.24.x) (push) Successful in 2m0s
Build , Vet Test, and Lint / Run Vet Tests (1.23.x) (push) Successful in 2m0s
Build , Vet Test, and Lint / Lint Code (push) Successful in 2m4s
Adds a BeforeOp HookType across resolvespec, restheadspec, websocketspec, mqttspec, and funcspec that fires before every SQL operation (read, create, update, delete, scan/query) via a new ExecuteBeforeOp helper. Also closes a row-level-security gap: resolvespec registered a BeforeScan hook for ApplyRowSecurity but never fired it, and websocketspec/mqttspec had no BeforeScan hook point at all, so row security was never applied to their queries. BeforeScan now fires right before the actual scan in all three, with the (possibly hook-modified) query used for execution.
235 lines
7.0 KiB
Go
235 lines
7.0 KiB
Go
package websocketspec
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/bitechdev/ResolveSpec/pkg/common"
|
|
)
|
|
|
|
// HookType represents the type of lifecycle hook
|
|
type HookType string
|
|
|
|
const (
|
|
// BeforeHandle fires after model resolution, before operation dispatch.
|
|
// Use this for auth checks that need model rules and user context simultaneously.
|
|
BeforeHandle HookType = "before_handle"
|
|
|
|
// BeforeRead is called before a read operation
|
|
BeforeRead HookType = "before_read"
|
|
// AfterRead is called after a read operation
|
|
AfterRead HookType = "after_read"
|
|
|
|
// BeforeCreate is called before a create operation
|
|
BeforeCreate HookType = "before_create"
|
|
// AfterCreate is called after a create operation
|
|
AfterCreate HookType = "after_create"
|
|
|
|
// BeforeUpdate is called before an update operation
|
|
BeforeUpdate HookType = "before_update"
|
|
// AfterUpdate is called after an update operation
|
|
AfterUpdate HookType = "after_update"
|
|
|
|
// BeforeDelete is called before a delete operation
|
|
BeforeDelete HookType = "before_delete"
|
|
// AfterDelete is called after a delete operation
|
|
AfterDelete HookType = "after_delete"
|
|
|
|
// BeforeScan is called right before a read query is executed against the database,
|
|
// after all filters/sort/pagination have been applied. Use this for row-level
|
|
// security that needs to modify the query (stored in HookContext.Metadata["query"]).
|
|
BeforeScan HookType = "before_scan"
|
|
|
|
// BeforeSubscribe is called before creating a subscription
|
|
BeforeSubscribe HookType = "before_subscribe"
|
|
// AfterSubscribe is called after creating a subscription
|
|
AfterSubscribe HookType = "after_subscribe"
|
|
|
|
// BeforeUnsubscribe is called before removing a subscription
|
|
BeforeUnsubscribe HookType = "before_unsubscribe"
|
|
// AfterUnsubscribe is called after removing a subscription
|
|
AfterUnsubscribe HookType = "after_unsubscribe"
|
|
|
|
// BeforeConnect is called when a new connection is established
|
|
BeforeConnect HookType = "before_connect"
|
|
// AfterConnect is called after a connection is established
|
|
AfterConnect HookType = "after_connect"
|
|
|
|
// BeforeDisconnect is called before a connection is closed
|
|
BeforeDisconnect HookType = "before_disconnect"
|
|
// AfterDisconnect is called after a connection is closed
|
|
AfterDisconnect HookType = "after_disconnect"
|
|
|
|
// BeforeOp fires immediately before every SQL operation (read, create, update, delete).
|
|
// Unlike BeforeHandle, which fires once before operation dispatch, BeforeOp fires at each
|
|
// individual SQL-operation hook point, so it runs once per statement executed.
|
|
BeforeOp HookType = "before_op"
|
|
)
|
|
|
|
// HookContext contains context information for hook execution
|
|
type HookContext struct {
|
|
// Context is the request context
|
|
Context context.Context
|
|
|
|
// Handler provides access to the handler, database, and registry
|
|
Handler *Handler
|
|
|
|
// Connection is the WebSocket connection
|
|
Connection *Connection
|
|
|
|
// Message is the original message
|
|
Message *Message
|
|
|
|
// Schema is the database schema
|
|
Schema string
|
|
|
|
// Entity is the table/model name
|
|
Entity string
|
|
|
|
// TableName is the actual database table name
|
|
TableName string
|
|
|
|
// Model is the registered model instance
|
|
Model interface{}
|
|
|
|
// ModelPtr is a pointer to the model for queries
|
|
ModelPtr interface{}
|
|
|
|
// Options contains the parsed request options
|
|
Options *common.RequestOptions
|
|
|
|
// Operation being dispatched (e.g. "read", "create", "update", "delete")
|
|
Operation string
|
|
|
|
// ID is the record ID for single-record operations
|
|
ID string
|
|
|
|
// Data is the request data (for create/update operations)
|
|
Data interface{}
|
|
|
|
// Result is the operation result (for after hooks)
|
|
Result interface{}
|
|
|
|
// Subscription is the subscription being created/removed
|
|
Subscription *Subscription
|
|
|
|
// Error is any error that occurred (for after hooks)
|
|
Error error
|
|
|
|
// Allow hooks to abort the operation
|
|
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
|
|
Tx common.Database
|
|
|
|
// Metadata is additional context data
|
|
Metadata map[string]interface{}
|
|
}
|
|
|
|
// HookFunc is a function that processes a hook
|
|
type HookFunc func(*HookContext) error
|
|
|
|
// HookRegistry manages lifecycle hooks
|
|
type HookRegistry struct {
|
|
hooks map[HookType][]HookFunc
|
|
}
|
|
|
|
// NewHookRegistry creates a new hook registry
|
|
func NewHookRegistry() *HookRegistry {
|
|
return &HookRegistry{
|
|
hooks: make(map[HookType][]HookFunc),
|
|
}
|
|
}
|
|
|
|
// Register registers a hook function for a specific hook type
|
|
func (hr *HookRegistry) Register(hookType HookType, fn HookFunc) {
|
|
hr.hooks[hookType] = append(hr.hooks[hookType], fn)
|
|
}
|
|
|
|
// RegisterBefore registers a hook that runs before an operation
|
|
// Convenience method for BeforeRead, BeforeCreate, BeforeUpdate, BeforeDelete
|
|
func (hr *HookRegistry) RegisterBefore(operation OperationType, fn HookFunc) {
|
|
switch operation {
|
|
case OperationRead:
|
|
hr.Register(BeforeRead, fn)
|
|
case OperationCreate:
|
|
hr.Register(BeforeCreate, fn)
|
|
case OperationUpdate:
|
|
hr.Register(BeforeUpdate, fn)
|
|
case OperationDelete:
|
|
hr.Register(BeforeDelete, fn)
|
|
case OperationSubscribe:
|
|
hr.Register(BeforeSubscribe, fn)
|
|
case OperationUnsubscribe:
|
|
hr.Register(BeforeUnsubscribe, fn)
|
|
}
|
|
}
|
|
|
|
// RegisterAfter registers a hook that runs after an operation
|
|
// Convenience method for AfterRead, AfterCreate, AfterUpdate, AfterDelete
|
|
func (hr *HookRegistry) RegisterAfter(operation OperationType, fn HookFunc) {
|
|
switch operation {
|
|
case OperationRead:
|
|
hr.Register(AfterRead, fn)
|
|
case OperationCreate:
|
|
hr.Register(AfterCreate, fn)
|
|
case OperationUpdate:
|
|
hr.Register(AfterUpdate, fn)
|
|
case OperationDelete:
|
|
hr.Register(AfterDelete, fn)
|
|
case OperationSubscribe:
|
|
hr.Register(AfterSubscribe, fn)
|
|
case OperationUnsubscribe:
|
|
hr.Register(AfterUnsubscribe, fn)
|
|
}
|
|
}
|
|
|
|
// Execute runs all hooks for a specific type
|
|
func (hr *HookRegistry) Execute(hookType HookType, ctx *HookContext) error {
|
|
hooks, exists := hr.hooks[hookType]
|
|
if !exists {
|
|
return nil
|
|
}
|
|
|
|
for _, hook := range hooks {
|
|
if err := hook(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if hook requested abort
|
|
if ctx.Abort {
|
|
return fmt.Errorf("operation aborted by hook: %s", ctx.AbortMessage)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExecuteBeforeOp executes the BeforeOp hook followed by the given SQL-operation hook
|
|
// (BeforeRead, BeforeCreate, BeforeUpdate, or BeforeDelete). BeforeOp always runs first
|
|
// so it can observe/veto every SQL operation regardless of type.
|
|
func (hr *HookRegistry) ExecuteBeforeOp(hookType HookType, ctx *HookContext) error {
|
|
if err := hr.Execute(BeforeOp, ctx); err != nil {
|
|
return err
|
|
}
|
|
return hr.Execute(hookType, ctx)
|
|
}
|
|
|
|
// HasHooks checks if any hooks are registered for a hook type
|
|
func (hr *HookRegistry) HasHooks(hookType HookType) bool {
|
|
hooks, exists := hr.hooks[hookType]
|
|
return exists && len(hooks) > 0
|
|
}
|
|
|
|
// Clear removes all hooks of a specific type
|
|
func (hr *HookRegistry) Clear(hookType HookType) {
|
|
delete(hr.hooks, hookType)
|
|
}
|
|
|
|
// ClearAll removes all registered hooks
|
|
func (hr *HookRegistry) ClearAll() {
|
|
hr.hooks = make(map[HookType][]HookFunc)
|
|
}
|