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