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) }