refactored for maintainability

This commit is contained in:
2026-02-08 17:19:45 +11:00
parent 860cae3977
commit c16a82f2ad
40 changed files with 1211 additions and 920 deletions

19
internal/db/isunique.go Normal file
View File

@@ -0,0 +1,19 @@
package db
import (
"context"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
func IsUnique(ctx context.Context, tx bun.Tx, model any, field, value string) (bool, error) {
count, err := tx.NewSelect().
Model(model).
Where("? = ?", bun.Ident(field), value).
Count(ctx)
if err != nil {
return false, errors.Wrap(err, "tx.NewSelect")
}
return count == 0, nil
}

View File

@@ -87,25 +87,3 @@ func GetSeason(ctx context.Context, tx bun.Tx, shortname string) (*Season, error
}
return season, nil
}
func IsSeasonNameUnique(ctx context.Context, tx bun.Tx, name string) (bool, error) {
count, err := tx.NewSelect().
Model((*Season)(nil)).
Where("name = ?", name).
Count(ctx)
if err != nil {
return false, errors.Wrap(err, "tx.NewSelect")
}
return count == 0, nil
}
func IsSeasonShortNameUnique(ctx context.Context, tx bun.Tx, shortname string) (bool, error) {
count, err := tx.NewSelect().
Model((*Season)(nil)).
Where("short_name = ?", shortname).
Count(ctx)
if err != nil {
return false, errors.Wrap(err, "tx.NewSelect")
}
return count == 0, nil
}

118
internal/db/txhelpers.go Normal file
View File

@@ -0,0 +1,118 @@
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 WithReadTx(
s *hws.Server,
w http.ResponseWriter,
r *http.Request,
conn *bun.DB,
fn TxFunc,
) bool {
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()
ok, err := withTx(ctx, conn, 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 WithTxFailSilently(
ctx context.Context,
conn *bun.DB,
fn TxFuncSilent,
) error {
fnc := func(ctx context.Context, tx bun.Tx) (bool, error) {
err := fn(ctx, tx)
return err != nil, err
}
_, err := withTx(ctx, conn, 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 WithWriteTx(
s *hws.Server,
w http.ResponseWriter,
r *http.Request,
conn *bun.DB,
fn TxFunc,
) bool {
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()
ok, err := withTx(ctx, conn, 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 WithNotifyTx(
s *hws.Server,
w http.ResponseWriter,
r *http.Request,
conn *bun.DB,
fn TxFunc,
) bool {
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()
ok, err := withTx(ctx, conn, 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 withTx(
ctx context.Context,
conn *bun.DB,
fn TxFunc,
write bool,
) (bool, error) {
tx, err := conn.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
}

View File

@@ -112,19 +112,6 @@ func GetUserByDiscordID(ctx context.Context, tx bun.Tx, discordID string) (*User
return user, nil
}
// IsUsernameUnique checks if the given username is unique (not already taken)
// Returns true if the username is available, false if it's taken
func IsUsernameUnique(ctx context.Context, tx bun.Tx, username string) (bool, error) {
count, err := tx.NewSelect().
Model((*User)(nil)).
Where("username = ?", username).
Count(ctx)
if err != nil {
return false, errors.Wrap(err, "tx.NewSelect")
}
return count == 0, nil
}
// GetRoles loads all the roles for this user
func (u *User) GetRoles(ctx context.Context, tx bun.Tx) ([]*Role, error) {
if u == nil {