DEVELOPMENT ENVIRONMENT

~liljamo/tixe

cd02d545dc910ebcf43b3f88a3c40c4267879a21 — Jonni Liljamo 1 year, 3 months ago 98b2d44
feat: psql with (maybe) working migration system
6 files changed, 145 insertions(+), 0 deletions(-)

A db/db.go
A db/migrations.go
M go.mod
M go.sum
M tixe.go
A util/env.go
A db/db.go => db/db.go +43 -0
@@ 0,0 1,43 @@
package db

import (
	"context"
	"fmt"
	"log"
	"time"
	"tixe/util"

	"github.com/jackc/pgx/v5/pgxpool"
)

var PgPool *pgxpool.Pool

func NewPgPool() {
	var err error
	PgPool, err = pgxpool.New(context.Background(), pgConnectionString())
	if err != nil {
		log.Fatalf("[tixe/db] Unable to create psql connection pool: '%s'", err)
	}
	log.Print("[tixe/db] Created psql pool")

	for {
		err = PgPool.Ping(context.Background())
		if err != nil {
			log.Print("[tixe/db] Database not ready, retrying in 5")
			time.Sleep(5 * time.Second)
		} else {
			log.Print("[tixe/db] Database connection verified")
			return
		}
	}
}

func pgConnectionString() string {
	user := util.LoadVar("TIXE_PSQL_USER")
	pwd := util.LoadVar("TIXE_PSQL_PASSWORD")
	host := util.LoadVar("TIXE_PSQL_HOST")
	port := util.LoadVar("TIXE_PSQL_PORT")
	db := util.LoadVar("TIXE_PSQL_DB")

	return fmt.Sprintf("postgresql://%s:%s@%s:%s/%s?sslmode=disable", user, pwd, host, port, db)
}

A db/migrations.go => db/migrations.go +68 -0
@@ 0,0 1,68 @@
package db

import (
	"context"
	"fmt"
	"log"

	"github.com/jackc/pgx/v5/pgtype"
)

var migrationsTable string = "schema_migrations"

func RunMigrations() {
	log.Print("[tixe/db] Running migrations")

	var ranTimestamp pgtype.Timestamptz
	var schemaVersion int = 0

	schemaVersionQuery := fmt.Sprintf(
		`SELECT ran_timestamp, schema_version
			FROM %s
				ORDER BY schema_version
					DESC LIMIT 1;`,
		migrationsTable)
	err := PgPool.QueryRow(context.Background(), schemaVersionQuery).Scan(&ranTimestamp, &schemaVersion)
	if err != nil {
		log.Print("[tixe/db] No schema version found, running all migrations")
	} else {
		log.Printf("[tixe/db] Last migration ran on %s, with schema version %d", ranTimestamp.Time, schemaVersion)
	}

	migrations := migrations()
	if schemaVersion != len(migrations) {
		for i := 0; i < len(migrations); i++ {
			if i <= schemaVersion {
				log.Printf("[tixe/db] Running migration %d", i)
				_, err := PgPool.Exec(context.Background(), migrations[i])
				if err != nil {
					log.Fatalf("[tixe/db] Migration %d failed to run!", i)
				}
			}
		}

		// We are now up to date
		schemaVersion = len(migrations)
		// Create a new entry in the migrations table
		schemaMigrationInsertQuery := fmt.Sprintf(
			`INSERT INTO %s(ran_timestamp, schema_version) VALUES(CURRENT_TIMESTAMP, %d);`,
			migrationsTable, schemaVersion)
		_, err = PgPool.Exec(context.Background(), schemaMigrationInsertQuery)
		if err != nil {
			log.Fatal("[tixe/db] Migrations ran, but was not able to create migration entry")
		} else {
			log.Print("[tixe/db] Migrations ran successfully")
		}
	} else {
		log.Printf("[tixe/db] Already on schema version %d, no migrations to run", schemaVersion)
	}
}

func migrations() []string {
	return []string{
		fmt.Sprintf(`CREATE TABLE %s (
			ran_timestamp timestamp,
			schema_version integer
		)`,migrationsTable),
	}
}

M go.mod => go.mod +5 -0
@@ 13,6 13,10 @@ require (
	github.com/go-playground/universal-translator v0.18.1 // indirect
	github.com/go-playground/validator/v10 v10.14.1 // indirect
	github.com/goccy/go-json v0.10.2 // indirect
	github.com/jackc/pgpassfile v1.0.0 // indirect
	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
	github.com/jackc/pgx/v5 v5.4.0 // indirect
	github.com/jackc/puddle/v2 v2.2.0 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
	github.com/leodido/go-urn v1.2.4 // indirect


@@ 25,6 29,7 @@ require (
	golang.org/x/arch v0.3.0 // indirect
	golang.org/x/crypto v0.9.0 // indirect
	golang.org/x/net v0.10.0 // indirect
	golang.org/x/sync v0.1.0 // indirect
	golang.org/x/sys v0.8.0 // indirect
	golang.org/x/text v0.9.0 // indirect
	google.golang.org/protobuf v1.30.0 // indirect

M go.sum => go.sum +10 -0
@@ 23,6 23,14 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.4.0 h1:BSr+GCm4N6QcgIwv0DyTFHK9ugfEFF9DzSbbzxOiXU0=
github.com/jackc/pgx/v5 v5.4.0/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY=
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=


@@ 61,6 69,8 @@ golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=

M tixe.go => tixe.go +5 -0
@@ 4,6 4,7 @@ import (
	"log"
	"net/http"
	"tixe/api"
	"tixe/db"
	"tixe/template"

	"github.com/gin-gonic/gin"


@@ 43,6 44,10 @@ func setupRouter() *gin.Engine {
func main() {
	log.Print("[tixe] Starting up...")

	db.NewPgPool()
	defer db.PgPool.Close()
	db.RunMigrations()

	err := template.NewTemplateEngine()
	if err != nil {
		log.Fatalf("[tixe] Creating a new TemplateEngine failed, '%s'", err)

A util/env.go => util/env.go +14 -0
@@ 0,0 1,14 @@
package util

import (
	"log"
	"os"
)

func LoadVar(key string) string {
	value := os.Getenv(key)
	if key == "" {
		log.Fatalf("[tixe/util] Environment variable %s is empty!", key)
	}
	return value
}