package db import ( "context" "database/sql" "github.com/pkg/errors" "github.com/uptrace/bun" ) type deleter[T any] struct { tx bun.Tx q *bun.DeleteQuery resourceID any // Store ID before deletion for audit audit *AuditMeta auditInfo *AuditInfo } type systemType interface { isSystem() bool } func DeleteItem[T any](tx bun.Tx) *deleter[T] { return &deleter[T]{ tx: tx, q: tx.NewDelete(). Model((*T)(nil)), } } func (d *deleter[T]) Where(query string, args ...any) *deleter[T] { d.q = d.q.Where(query, args...) // Try to capture resource ID from WHERE clause if it's a simple "id = ?" pattern if query == "id = ?" && len(args) > 0 { d.resourceID = args[0] } return d } // WithAudit enables audit logging for this delete operation // If the provided *AuditInfo is nil, will use reflection to automatically work out the details func (d *deleter[T]) WithAudit(meta *AuditMeta, info *AuditInfo) *deleter[T] { d.audit = meta d.auditInfo = info return d } func (d *deleter[T]) Delete(ctx context.Context) error { _, err := d.q.Exec(ctx) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil } return errors.Wrap(err, "bun.DeleteQuery.Exec") } // Handle audit logging if enabled if d.audit != nil { if d.auditInfo == nil { tableName := extractTableName[T]() resourceType := extractResourceType(tableName) action := buildAction(resourceType, "delete") d.auditInfo = &AuditInfo{ Action: action, ResourceType: resourceType, ResourceID: d.resourceID, Details: nil, // Delete doesn't need details } } err = LogSuccess(ctx, d.tx, d.audit, d.auditInfo) if err != nil { return errors.Wrap(err, "LogSuccess") } } return nil } func DeleteByID[T any](tx bun.Tx, id int) *deleter[T] { return DeleteItem[T](tx).Where("id = ?", id) } func DeleteWithProtection[T systemType](ctx context.Context, tx bun.Tx, id int, audit *AuditMeta) error { deleter := DeleteByID[T](tx, id) item, err := GetByID[T](tx, id).Get(ctx) if err != nil { return errors.Wrap(err, "GetByID") } if item == nil { return errors.New("record not found") } if (*item).isSystem() { return errors.New("record is system protected") } if audit != nil { deleter = deleter.WithAudit(audit, nil) } return deleter.Delete(ctx) }