package db import ( "context" "net/http" "time" "git.haelnorr.com/h/golib/hws" "git.haelnorr.com/h/oslstats/internal/notify" "git.haelnorr.com/h/oslstats/internal/throw" "github.com/pkg/errors" "github.com/uptrace/bun" ) // TxFunc is a function that runs within a database transaction type ( TxFunc func(ctx context.Context, tx bun.Tx) (bool, error) TxFuncSilent func(ctx context.Context, tx bun.Tx) error ) var timeout = 15 * time.Second // WithReadTx executes a read-only transaction with automatic rollback // Returns true if successful, false if error was thrown to client func (db *DB) WithReadTx( s *hws.Server, w http.ResponseWriter, r *http.Request, fn TxFunc, ) bool { ctx, cancel := context.WithTimeout(r.Context(), timeout) defer cancel() ok, err := db.withTx(ctx, fn, false) if err != nil { throw.InternalServiceError(s, w, r, "Database error", err) } return ok } // WithTxFailSilently executes a transaction with automatic rollback // Returns true if successful, false if error occured. // Does not throw any errors to the client. func (db *DB) WithTxFailSilently( ctx context.Context, fn TxFuncSilent, ) error { fnc := func(ctx context.Context, tx bun.Tx) (bool, error) { err := fn(ctx, tx) return err == nil, err } _, err := db.withTx(ctx, fnc, true) return err } // WithWriteTx executes a write transaction with automatic rollback on error // Commits only if fn returns nil. Returns true if successful. func (db *DB) WithWriteTx( s *hws.Server, w http.ResponseWriter, r *http.Request, fn TxFunc, ) bool { ctx, cancel := context.WithTimeout(r.Context(), timeout) defer cancel() ok, err := db.withTx(ctx, fn, true) if err != nil { throw.InternalServiceError(s, w, r, "Database error", err) } return ok } // WithNotifyTx executes a transaction with notification-based error handling // Uses notifyInternalServiceError instead of throwInternalServiceError func (db *DB) WithNotifyTx( s *hws.Server, w http.ResponseWriter, r *http.Request, fn TxFunc, ) bool { ctx, cancel := context.WithTimeout(r.Context(), timeout) defer cancel() ok, err := db.withTx(ctx, fn, true) if err != nil { notify.InternalServiceError(s, w, r, "Database error", err) } return ok } // withTx executes a transaction with automatic rollback on error func (db *DB) withTx( ctx context.Context, fn TxFunc, write bool, ) (bool, error) { tx, err := db.BeginTx(ctx, nil) if err != nil { return false, errors.Wrap(err, "conn.BeginTx") } defer func() { _ = tx.Rollback() }() ok, err := fn(ctx, tx) if err != nil || !ok { return false, err } if write { err = tx.Commit() if err != nil { return false, errors.Wrap(err, "tx.Commit") } } else { _ = tx.Commit() } return true, nil }