fixed some migration issues and added generics for update and insert
This commit is contained in:
115
internal/db/update.go
Normal file
115
internal/db/update.go
Normal file
@@ -0,0 +1,115 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user