123 lines
3.2 KiB
Go
123 lines
3.2 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
type updater[T any] struct {
|
|
tx bun.Tx
|
|
q *bun.UpdateQuery
|
|
model *T
|
|
columns []string
|
|
audit *AuditMeta
|
|
auditInfo *AuditInfo
|
|
}
|
|
|
|
// 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
|
|
// If the provided *AuditInfo is nil, will use reflection to automatically work out the details
|
|
func (u *updater[T]) WithAudit(meta *AuditMeta, info *AuditInfo) *updater[T] {
|
|
u.audit = meta
|
|
u.auditInfo = info
|
|
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.audit != nil && len(u.columns) > 0 {
|
|
details = extractChangedFields(u.model, u.columns)
|
|
}
|
|
|
|
// Execute update
|
|
result, err := u.q.Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "bun.UpdateQuery.Exec")
|
|
}
|
|
rows, err := result.RowsAffected()
|
|
if err != nil {
|
|
return errors.Wrap(err, "result.RowsAffected")
|
|
}
|
|
if rows == 0 {
|
|
resource := extractResourceType(extractTableName[T]())
|
|
return BadRequestNotFound(resource, "id", extractPrimaryKey(u.model))
|
|
}
|
|
|
|
// Handle audit logging if enabled
|
|
if u.audit != nil {
|
|
if u.auditInfo == nil {
|
|
tableName := extractTableName[T]()
|
|
resourceType := extractResourceType(tableName)
|
|
action := buildAction(resourceType, "update")
|
|
|
|
u.auditInfo = &AuditInfo{
|
|
Action: action,
|
|
ResourceType: resourceType,
|
|
ResourceID: extractPrimaryKey(u.model),
|
|
Details: details, // Changed fields only
|
|
}
|
|
}
|
|
err = LogSuccess(ctx, u.tx, u.audit, u.auditInfo)
|
|
if err != nil {
|
|
return errors.Wrap(err, "LogSuccess")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|