updated deps; updated healthchecks.io integration

This commit is contained in:
Aine
2024-04-07 14:42:12 +03:00
parent 271a4a0e31
commit 15d61f174e
122 changed files with 3432 additions and 4613 deletions

View File

@@ -31,3 +31,16 @@ func (j JSON) Value() (driver.Value, error) {
v, err := json.Marshal(j.Data)
return string(v), err
}
// JSONPtr is a convenience function for wrapping a pointer to a value in the JSON utility, but removing typed nils
// (i.e. preventing nils from turning into the string "null" in the database).
func JSONPtr[T any](val *T) JSON {
return JSON{Data: UntypedNil(val)}
}
func UntypedNil[T any](val *T) any {
if val == nil {
return nil
}
return val
}

View File

@@ -10,6 +10,7 @@ import (
"context"
"database/sql"
"errors"
"time"
"golang.org/x/exp/constraints"
)
@@ -60,6 +61,34 @@ func NumPtr[T constraints.Integer | constraints.Float](val T) *T {
return &val
}
// UnixPtr returns a pointer to the given time as unix seconds, or nil if the time is zero.
func UnixPtr(val time.Time) *int64 {
return ConvertedPtr(val, time.Time.Unix)
}
// UnixMilliPtr returns a pointer to the given time as unix milliseconds, or nil if the time is zero.
func UnixMilliPtr(val time.Time) *int64 {
return ConvertedPtr(val, time.Time.UnixMilli)
}
type Zeroable interface {
IsZero() bool
}
// ConvertedPtr returns a pointer to the converted version of the given value, or nil if the input is zero.
//
// This is primarily meant for time.Time, but it can be used with any type that has implements `IsZero() bool`.
//
// yourTime := time.Now()
// unixMSPtr := dbutil.TimePtr(yourTime, time.Time.UnixMilli)
func ConvertedPtr[Input Zeroable, Output any](val Input, converter func(Input) Output) *Output {
if val.IsZero() {
return nil
}
converted := converter(val)
return &converted
}
func (qh *QueryHelper[T]) GetDB() *Database {
return qh.db
}

View File

@@ -11,6 +11,7 @@ import (
"database/sql"
"errors"
"fmt"
"runtime"
"time"
"github.com/rs/zerolog"
@@ -45,27 +46,55 @@ func (db *Database) QueryRow(ctx context.Context, query string, args ...any) *sq
}
func (db *Database) BeginTx(ctx context.Context, opts *sql.TxOptions) (*LoggingTxn, error) {
if ctx == nil {
panic("BeginTx() called with nil ctx")
}
return db.LoggingDB.BeginTx(ctx, opts)
}
func (db *Database) DoTxn(ctx context.Context, opts *sql.TxOptions, fn func(ctx context.Context) error) error {
if ctx == nil {
panic("DoTxn() called with nil ctx")
}
if ctx.Value(ContextKeyDatabaseTransaction) != nil {
zerolog.Ctx(ctx).Trace().Msg("Already in a transaction, not creating a new one")
return fn(ctx)
}
log := zerolog.Ctx(ctx).With().Str("db_txn_id", random.String(12)).Logger()
slowLog := log
callerSkip := 1
if val := ctx.Value(ContextKeyDoTxnCallerSkip); val != nil {
callerSkip += val.(int)
}
if pc, file, line, ok := runtime.Caller(callerSkip); ok {
slowLog = log.With().Str(zerolog.CallerFieldName, zerolog.CallerMarshalFunc(pc, file, line)).Logger()
}
start := time.Now()
deadlockCh := make(chan struct{})
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
slowLog.Warn().
Dur("duration_seconds", time.Since(start)).
Msg("Transaction still running")
case <-deadlockCh:
return
}
}
}()
defer func() {
close(deadlockCh)
dur := time.Since(start)
if dur > time.Second {
val := ctx.Value(ContextKeyDoTxnCallerSkip)
callerSkip := 2
if val != nil {
callerSkip += val.(int)
}
log.Warn().
slowLog.Warn().
Float64("duration_seconds", dur.Seconds()).
Caller(callerSkip).
Msg("Transaction took long")
}
}()
@@ -100,7 +129,7 @@ func (db *Database) DoTxn(ctx context.Context, opts *sql.TxOptions, fn func(ctx
func (db *Database) Conn(ctx context.Context) Execable {
if ctx == nil {
return &db.LoggingDB
panic("Conn() called with nil ctx")
}
txn, ok := ctx.Value(ContextKeyDatabaseTransaction).(Transaction)
if ok {

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2024 Sumner Evans
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package jsonbytes
import (
"encoding/base64"
"encoding/json"
)
// UnpaddedBytes is a byte slice that is encoded and decoded using
// [base64.RawStdEncoding] instead of the default padded base64.
type UnpaddedBytes []byte
func (b UnpaddedBytes) MarshalJSON() ([]byte, error) {
return json.Marshal(base64.RawStdEncoding.EncodeToString(b))
}
func (b *UnpaddedBytes) UnmarshalJSON(data []byte) error {
var b64str string
err := json.Unmarshal(data, &b64str)
if err != nil {
return err
}
*b, err = base64.RawStdEncoding.DecodeString(b64str)
return err
}