109 lines
2.7 KiB
Go
109 lines
2.7 KiB
Go
package database
|
|
|
|
import (
|
|
domainUpdate "RenewCMS/internal/domain/update"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const (
|
|
DriverPostgres = "postgres"
|
|
|
|
postgresTimeZone = "UTC"
|
|
postgresConnectTimeoutSeconds = 5
|
|
postgresScheme = "postgres"
|
|
postgresUTCCallback = "renewcms:postgres_utc"
|
|
gormQueryCallback = "gorm:query"
|
|
)
|
|
|
|
var timeType = reflect.TypeOf(time.Time{})
|
|
|
|
type postgresDriver struct{}
|
|
|
|
type PostgresConfig struct {
|
|
Host string
|
|
Port string
|
|
User string
|
|
Password string
|
|
Name string
|
|
SSLMode string
|
|
}
|
|
|
|
func PostgresDSN(config PostgresConfig) string {
|
|
dsn := url.URL{
|
|
Scheme: postgresScheme,
|
|
User: url.UserPassword(config.User, config.Password),
|
|
Host: net.JoinHostPort(config.Host, config.Port),
|
|
Path: "/" + config.Name,
|
|
}
|
|
query := url.Values{}
|
|
query.Set("sslmode", config.SSLMode)
|
|
query.Set("timezone", postgresTimeZone)
|
|
query.Set("connect_timeout", fmt.Sprint(postgresConnectTimeoutSeconds))
|
|
dsn.RawQuery = query.Encode()
|
|
return dsn.String()
|
|
}
|
|
|
|
func (postgresDriver) Open(source string) (*gorm.DB, error) {
|
|
parsed, err := pgconn.ParseConfig(source)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the postgres connection settings are invalid")
|
|
}
|
|
db, err := gorm.Open(postgres.Open(source), &gorm.Config{})
|
|
if err != nil {
|
|
address := net.JoinHostPort(parsed.Host, fmt.Sprint(parsed.Port))
|
|
return nil, fmt.Errorf("cannot use the postgres server at %s: %s", address, redact(err.Error(), parsed.Password))
|
|
}
|
|
if err := db.Callback().Query().After(gormQueryCallback).Register(postgresUTCCallback, readTimesInUTC); err != nil {
|
|
return nil, err
|
|
}
|
|
return db, nil
|
|
}
|
|
|
|
func readTimesInUTC(db *gorm.DB) {
|
|
if db.Error != nil || db.Statement.Dest == nil {
|
|
return
|
|
}
|
|
convertTimesToUTC(reflect.ValueOf(db.Statement.Dest))
|
|
}
|
|
|
|
func convertTimesToUTC(value reflect.Value) {
|
|
switch value.Kind() {
|
|
case reflect.Pointer, reflect.Interface:
|
|
if !value.IsNil() {
|
|
convertTimesToUTC(value.Elem())
|
|
}
|
|
case reflect.Slice, reflect.Array:
|
|
for index := 0; index < value.Len(); index++ {
|
|
convertTimesToUTC(value.Index(index))
|
|
}
|
|
case reflect.Struct:
|
|
convertStructTimesToUTC(value)
|
|
}
|
|
}
|
|
|
|
func convertStructTimesToUTC(value reflect.Value) {
|
|
if value.Type() == timeType {
|
|
if value.CanSet() {
|
|
value.Set(reflect.ValueOf(value.Interface().(time.Time).UTC()))
|
|
}
|
|
return
|
|
}
|
|
for index := 0; index < value.NumField(); index++ {
|
|
if value.Type().Field(index).IsExported() {
|
|
convertTimesToUTC(value.Field(index))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (postgresDriver) NewBackup(db *gorm.DB, directory string, now func() time.Time) domainUpdate.DatabaseBackup {
|
|
return NewPostgresBackup(db, directory, now)
|
|
}
|