diff options
Diffstat (limited to 'internal/database/sql')
| -rw-r--r-- | internal/database/sql/main.go | 141 | 
1 files changed, 141 insertions, 0 deletions
| diff --git a/internal/database/sql/main.go b/internal/database/sql/main.go new file mode 100644 index 0000000..80d5f5c --- /dev/null +++ b/internal/database/sql/main.go @@ -0,0 +1,141 @@ +package database + +import ( +	"apjournal/internal/database/migrations" +	"os" +	"time" + +	"github.com/jmoiron/sqlx" + +	// driver postgres for migrations +	"log/slog" + +	"github.com/golang-migrate/migrate" +	_ "github.com/golang-migrate/migrate/database/postgres" +	bindata "github.com/golang-migrate/migrate/source/go_bindata" +	_ "github.com/jackc/pgx/v5/stdlib" // register pgx driver +	"github.com/pkg/errors" +) + +var log = slog.New(slog.NewJSONHandler(os.Stdout, nil)) + +type DB struct { +	Conn *sqlx.DB +	URI  string +} + +func (d *DB) CloseAll() error { +	for _, conn := range []*sqlx.DB{d.Conn} { +		if err := closeConn(conn); err != nil { +			return err +		} +	} +	return nil +} + +func closeConn(conn *sqlx.DB) error { +	return conn.Close() +} + +func Init(DBURI string) (*DB, error) { +	var result DB +	var err error +	result.Conn, err = openDBConnection(DBURI, "pgx") +	if err != nil { +		return nil, err +	} +	result.URI = DBURI + +	if err := testConnection(result.Conn); err != nil { +		return nil, err +	} +	return &result, nil +} + +func InitWithMigrate(DBURI string, up bool) (*DB, error) { +	var ( +		result DB +		err    error +	) +	result.Conn, err = openDBConnection(DBURI, "pgx") +	if err != nil { +		return nil, err +	} +	result.URI = DBURI + +	if err = testConnection(result.Conn); err != nil { +		return nil, err +	} +	if err = result.Migrate(DBURI, up); err != nil { +		return nil, err +	} +	return &result, nil +} + +func openDBConnection(dbURI, driver string) (*sqlx.DB, error) { +	conn, err := sqlx.Open(driver, dbURI) +	if err != nil { +		return nil, err +	} +	return conn, nil +} + +func testConnection(conn *sqlx.DB) error { +	err := conn.Ping() +	if err != nil { +		return errors.Wrap(err, "can't ping database") +	} +	return nil +} + +func (db *DB) Migrate(url string, up bool) error { +	source := bindata.Resource(migrations.AssetNames(), migrations.Asset) +	driver, err := bindata.WithInstance(source) +	if err != nil { +		return errors.WithStack(errors.WithMessage(err, +			"unable to instantiate driver from bindata")) +	} +	migration, err := migrate.NewWithSourceInstance("go-bindata", +		driver, url) +	if err != nil { +		return errors.WithStack(errors.WithMessage(err, +			"unable to start migration")) +	} +	if up { +		if err = migration.Up(); err != nil && err.Error() != "no change" { +			return errors.WithStack(errors.WithMessage(err, +				"unable to migrate up")) +		} +	} else { +		if err = migration.Down(); err != nil && +			err.Error() != "no change" { +			return errors.WithStack(errors.WithMessage(err, +				"unable to migrate down")) +		} +	} +	return nil +} + +func (d *DB) PingRoutine(interval time.Duration) { +	ticker := time.NewTicker(interval) +	done := make(chan bool) + +	for { +		select { +		case <-done: +			return +		case t := <-ticker.C: +			if err := testConnection(d.Conn); err != nil { +				log.Error("failed to ping postrges db", "error", err, "ping_at", t) +				// reconnect +				if err := closeConn(d.Conn); err != nil { +					log.Error("failed to close db connection", "error", err, "ping_at", t) +				} +				d.Conn, err = openDBConnection(d.URI, "pgx") +				if err != nil { +					log.Error("failed to reconnect", "error", err, "ping_at", t) +				} +			} +		} +	} +} | 
