feat: migrations
This commit is contained in:
37
server/internal/database/migrate.go
Normal file
37
server/internal/database/migrate.go
Normal file
@ -0,0 +1,37 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"log"
|
||||
"net/url"
|
||||
|
||||
"github.com/amacneil/dbmate/v2/pkg/dbmate"
|
||||
_ "github.com/spotdemo4/dbmate-sqlite-modernc/pkg/driver/sqlite" // Modernc sqlite
|
||||
)
|
||||
|
||||
func Migrate(url *url.URL, dbFS *embed.FS) error {
|
||||
if dbFS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
db := dbmate.New(url)
|
||||
db.Driver()
|
||||
db.FS = dbFS
|
||||
|
||||
log.Println("Migrations:")
|
||||
migrations, err := db.FindMigrations()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range migrations {
|
||||
log.Println(m.Version, m.FilePath)
|
||||
}
|
||||
|
||||
log.Println("\nApplying...")
|
||||
err = db.CreateAndMigrate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -2,14 +2,16 @@ package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"runtime"
|
||||
|
||||
_ "github.com/lib/pq" // Postgres
|
||||
"github.com/stephenafamo/bob"
|
||||
)
|
||||
|
||||
func NewPostgresConnection(user, pass, host, port, name string) (*bob.DB, error) {
|
||||
dsn := "host=" + host + " user=" + user + " password=" + pass + " dbname=" + name + " port=" + port + " sslmode=disable TimeZone=UTC"
|
||||
db, err := sql.Open("postgres", dsn)
|
||||
func NewPostgresConnection(url *url.URL) (*bob.DB, error) {
|
||||
db, err := sql.Open("postgres", postgresConnectionString(url))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -18,3 +20,55 @@ func NewPostgresConnection(user, pass, host, port, name string) (*bob.DB, error)
|
||||
|
||||
return &bobdb, nil
|
||||
}
|
||||
|
||||
func postgresConnectionString(u *url.URL) string {
|
||||
hostname := u.Hostname()
|
||||
port := u.Port()
|
||||
query := u.Query()
|
||||
|
||||
// support socket parameter for consistency with mysql
|
||||
if query.Get("socket") != "" {
|
||||
query.Set("host", query.Get("socket"))
|
||||
query.Del("socket")
|
||||
}
|
||||
|
||||
// default hostname
|
||||
if hostname == "" && query.Get("host") == "" {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
query.Set("host", "/var/run/postgresql")
|
||||
case "darwin", "freebsd", "dragonfly", "openbsd", "netbsd":
|
||||
query.Set("host", "/tmp")
|
||||
default:
|
||||
hostname = "localhost"
|
||||
}
|
||||
}
|
||||
|
||||
// host param overrides url hostname
|
||||
if query.Get("host") != "" {
|
||||
hostname = ""
|
||||
}
|
||||
|
||||
// always specify a port
|
||||
if query.Get("port") != "" {
|
||||
port = query.Get("port")
|
||||
query.Del("port")
|
||||
}
|
||||
if port == "" {
|
||||
switch u.Scheme {
|
||||
case "redshift":
|
||||
port = "5439"
|
||||
default:
|
||||
port = "5432"
|
||||
}
|
||||
}
|
||||
|
||||
// generate output URL
|
||||
out, _ := url.Parse(u.String())
|
||||
// force scheme back to postgres if there was another postgres-compatible scheme
|
||||
out.Scheme = "postgres"
|
||||
out.Host = fmt.Sprintf("%s:%s", hostname, port)
|
||||
out.RawQuery = query.Encode()
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
@ -2,30 +2,15 @@ package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
"github.com/stephenafamo/bob"
|
||||
_ "modernc.org/sqlite" // Sqlite
|
||||
)
|
||||
|
||||
func NewSQLiteConnection(name string) (*bob.DB, error) {
|
||||
// Find config diretory
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create database directory if not exists
|
||||
settingsPath := filepath.Join(configDir, "trevstack")
|
||||
err = os.MkdirAll(settingsPath, 0766)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Open database
|
||||
dbPath := filepath.Join(settingsPath, name)
|
||||
db, err := sql.Open("sqlite", dbPath)
|
||||
func NewSQLiteConnection(url *url.URL) (*bob.DB, error) {
|
||||
db, err := sql.Open("sqlite", sqliteConnectionString(url))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -35,3 +20,47 @@ func NewSQLiteConnection(name string) (*bob.DB, error) {
|
||||
|
||||
return &bobdb, nil
|
||||
}
|
||||
|
||||
// ConnectionString converts a URL into a valid connection string
|
||||
func sqliteConnectionString(u *url.URL) string {
|
||||
// duplicate URL and remove scheme
|
||||
newURL := *u
|
||||
newURL.Scheme = ""
|
||||
|
||||
if newURL.Opaque == "" && newURL.Path != "" {
|
||||
// When the DSN is in the form "scheme:/absolute/path" or
|
||||
// "scheme://absolute/path" or "scheme:///absolute/path", url.Parse
|
||||
// will consider the file path as :
|
||||
// - "absolute" as the hostname
|
||||
// - "path" (and the rest until "?") as the URL path.
|
||||
// Instead, when the DSN is in the form "scheme:", the (relative) file
|
||||
// path is stored in the "Opaque" field.
|
||||
// See: https://pkg.go.dev/net/url#URL
|
||||
//
|
||||
// While Opaque is not escaped, the URL Path is. So, if .Path contains
|
||||
// the file path, we need to un-escape it, and rebuild the full path.
|
||||
|
||||
newURL.Opaque = "//" + newURL.Host + mustUnescapePath(newURL.Path)
|
||||
newURL.Path = ""
|
||||
}
|
||||
|
||||
// trim duplicate leading slashes
|
||||
str := regexp.MustCompile("^//+").ReplaceAllString(newURL.String(), "/")
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// MustUnescapePath unescapes a URL path, and panics if it fails.
|
||||
// It is used during in cases where we are parsing a generated path.
|
||||
func mustUnescapePath(s string) string {
|
||||
if s == "" {
|
||||
panic("missing path")
|
||||
}
|
||||
|
||||
path, err := url.PathUnescape(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
Reference in New Issue
Block a user