116 lines
3.1 KiB
Go
116 lines
3.1 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
type updater[T any] struct {
|
|
tx bun.Tx
|
|
q *bun.UpdateQuery
|
|
model *T
|
|
columns []string
|
|
auditCallback AuditCallback
|
|
auditRequest *http.Request
|
|
}
|
|
|
|
// Update creates an updater for a model
|
|
// You must specify which columns to update via .Column() or use .WherePK()
|
|
func Update[T any](tx bun.Tx, model *T) *updater[T] {
|
|
if model == nil {
|
|
panic("model cannot be nil")
|
|
}
|
|
return &updater[T]{
|
|
tx: tx,
|
|
q: tx.NewUpdate().Model(model),
|
|
model: model,
|
|
}
|
|
}
|
|
|
|
// UpdateByID creates an updater with an ID where clause
|
|
// You must still specify which columns to update via .Column()
|
|
func UpdateByID[T any](tx bun.Tx, id int, model *T) *updater[T] {
|
|
if id <= 0 {
|
|
panic("id must be positive")
|
|
}
|
|
return Update(tx, model).Where("id = ?", id)
|
|
}
|
|
|
|
// Column specifies which columns to update
|
|
// Example: .Column("start_date", "end_date")
|
|
func (u *updater[T]) Column(columns ...string) *updater[T] {
|
|
u.columns = append(u.columns, columns...)
|
|
u.q = u.q.Column(columns...)
|
|
return u
|
|
}
|
|
|
|
// Where adds a WHERE clause
|
|
// Example: .Where("id = ?", 123)
|
|
func (u *updater[T]) Where(query string, args ...any) *updater[T] {
|
|
u.q = u.q.Where(query, args...)
|
|
return u
|
|
}
|
|
|
|
// WherePK adds a WHERE clause on the primary key
|
|
// The model must have its primary key field populated
|
|
func (u *updater[T]) WherePK() *updater[T] {
|
|
u.q = u.q.WherePK()
|
|
return u
|
|
}
|
|
|
|
// Set adds a raw SET clause for complex updates
|
|
// Example: .Set("updated_at = NOW()")
|
|
func (u *updater[T]) Set(query string, args ...any) *updater[T] {
|
|
u.q = u.q.Set(query, args...)
|
|
return u
|
|
}
|
|
|
|
// WithAudit enables audit logging for this update operation
|
|
// The callback will be invoked after successful update with auto-generated audit info
|
|
// If the callback returns an error, the transaction will be rolled back
|
|
func (u *updater[T]) WithAudit(r *http.Request, callback AuditCallback) *updater[T] {
|
|
u.auditRequest = r
|
|
u.auditCallback = callback
|
|
return u
|
|
}
|
|
|
|
// Exec executes the update and optionally logs to audit
|
|
// Returns an error if update fails or if audit callback fails (triggering rollback)
|
|
func (u *updater[T]) Exec(ctx context.Context) error {
|
|
// Build audit details BEFORE update (captures changed fields)
|
|
var details map[string]any
|
|
if u.auditCallback != nil && len(u.columns) > 0 {
|
|
details = extractChangedFields(u.model, u.columns)
|
|
}
|
|
|
|
// Execute update
|
|
_, err := u.q.Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "bun.UpdateQuery.Exec")
|
|
}
|
|
|
|
// Handle audit logging if enabled
|
|
if u.auditCallback != nil && u.auditRequest != nil {
|
|
tableName := extractTableName[T]()
|
|
resourceType := extractResourceType(tableName)
|
|
action := buildAction(resourceType, "update")
|
|
|
|
info := &AuditInfo{
|
|
Action: action,
|
|
ResourceType: resourceType,
|
|
ResourceID: extractPrimaryKey(u.model),
|
|
Details: details, // Changed fields only
|
|
}
|
|
|
|
// Call audit callback - if it fails, return error to trigger rollback
|
|
if err := u.auditCallback(ctx, u.tx, info, u.auditRequest); err != nil {
|
|
return errors.Wrap(err, "audit.callback")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|