Hooks & Events
Use lifecycle hooks and events to extend EZ-Console functionality.
Overviewβ
EZ-Consoleβs authorization stack exposes user change and user role change hooks on server.Service (backed by pkg/service). You need a live Service value to register themβthe stock NewCommandServer binary builds that service internally, so wiring hooks from an external app may require a bootstrap that matches your version (for example tests, a fork, or future public extension points). The APIs below are accurate; where you call them depends on how you host the server.
User Change Hooksβ
Registering User Change Hooksβ
Called when user records change (implementation details vary by action). Example shape once you hold svc server.Service:
import (
"context"
"github.com/sven-victor/ez-console/pkg/model"
)
svc.RegisterUserChangeHook(func(ctx context.Context, user *model.User, action string) error {
switch action {
case "create":
sendWelcomeEmail(user)
case "update":
syncUserToExternalSystem(user)
case "delete":
cleanupUserData(user)
}
return nil
})
User Role Change Hooksβ
svc.RegisterUserRoleChangeHook(func(ctx context.Context, userID string, roleIDs []string) error {
updatePermissionsCache(userID, roleIDs)
notifyRoleChange(userID, roleIDs)
return nil
})
Model Hooks (GORM)β
BeforeCreate Hookβ
Execute code before creating a record:
type Product struct {
ID string `gorm:"primaryKey"`
Name string
Price float64
}
func (p *Product) BeforeCreate(tx *gorm.DB) error {
// Generate ID if not set
if p.ID == "" {
p.ID = uuid.New().String()
}
// Validate price
if p.Price < 0 {
return fmt.Errorf("price cannot be negative")
}
// Set timestamps
p.CreatedAt = time.Now()
p.UpdatedAt = time.Now()
return nil
}
AfterCreate Hookβ
Execute code after creating a record:
func (p *Product) AfterCreate(tx *gorm.DB) error {
// Publish event
events.Publish("product.created", p)
// Send notification
sendProductCreatedNotification(p)
// Update cache
cache.Set("product:"+p.ID, p, 5*time.Minute)
return nil
}
BeforeUpdate Hookβ
Execute code before updating a record:
func (p *Product) BeforeUpdate(tx *gorm.DB) error {
// Track changes
if tx.Statement.Changed("Price") {
logPriceChange(p.ID, p.Price)
}
// Update timestamp
p.UpdatedAt = time.Now()
return nil
}
AfterUpdate Hookβ
Execute code after updating a record:
func (p *Product) AfterUpdate(tx *gorm.DB) error {
// Invalidate cache
cache.Delete("product:" + p.ID)
// Publish event
events.Publish("product.updated", p)
return nil
}
BeforeDelete Hookβ
Execute code before deleting a record:
func (p *Product) BeforeDelete(tx *gorm.DB) error {
// Check if can be deleted
if p.HasOrders() {
return fmt.Errorf("cannot delete product with existing orders")
}
return nil
}
AfterDelete Hookβ
Execute code after deleting a record:
func (p *Product) AfterDelete(tx *gorm.DB) error {
// Cleanup related data
cleanupProductImages(p.ID)
cleanupProductReviews(p.ID)
// Publish event
events.Publish("product.deleted", p.ID)
return nil
}
Event Systemβ
Publishing Eventsβ
Publish custom events:
import "github.com/sven-victor/ez-console/pkg/events"
func (s *ProductService) CreateProduct(ctx context.Context, product *Product) error {
// Create product
if err := s.db.Create(product).Error; err != nil {
return err
}
// Publish event
events.Publish("product.created", product)
return nil
}
Subscribing to Eventsβ
Subscribe to events:
func init() {
events.Subscribe("product.created", func(data interface{}) {
product := data.(*Product)
// Send notification
sendProductCreatedNotification(product)
// Update search index
indexProduct(product)
})
events.Subscribe("product.updated", func(data interface{}) {
product := data.(*Product)
// Update cache
cache.Set("product:"+product.ID, product, 5*time.Minute)
})
}
Audit Logging Hooksβ
Using Audit Serviceβ
The framework provides an audit service for automatic logging:
func (c *ProductController) CreateProduct(ctx *gin.Context) {
var req CreateProductRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
util.RespondWithError(ctx, util.NewErrorMessage("E4001", "Invalid request", err))
return
}
var product *model.Product
err := c.svc.StartAudit(ctx, "", func(auditLog *model.AuditLog) error {
product, err = c.productService.Create(ctx.Request.Context(), req)
auditLog.ResourceType = "product"
auditLog.Action = "create"
auditLog.ResourceID = product.ID
return err
})
if err != nil {
util.RespondWithError(ctx, util.NewErrorMessage("E5001", "Failed to create", err))
return
}
util.RespondWithSuccess(ctx, http.StatusCreated, product)
}
Best Practicesβ
1. Keep Hooks Simpleβ
// β
Good: Simple hook
func (p *Product) BeforeCreate(tx *gorm.DB) error {
if p.ID == "" {
p.ID = uuid.New().String()
}
return nil
}
// β Bad: Complex business logic in hook
func (p *Product) BeforeCreate(tx *gorm.DB) error {
// Too much logic here
// Should be in service layer
}
2. Handle Errors Properlyβ
// β
Good: Return errors
func (p *Product) BeforeCreate(tx *gorm.DB) error {
if p.Price < 0 {
return fmt.Errorf("invalid price")
}
return nil
}
// β Bad: Ignore errors
func (p *Product) BeforeCreate(tx *gorm.DB) error {
// Error handling missing
return nil
}
3. Use Contextβ
// β
Good: Use context
func hook(ctx context.Context, user *model.User, action string) error {
// Use context for cancellation, timeouts, etc.
return nil
}
Related Topicsβ
- Extending Built-in Modules - Extending framework
- Extending Built-in Modules β Customize built-in UI and APIs
Need help? Ask in GitHub Discussions.