//go:build !dev package db import ( "context" "fmt" "os" "time" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" ) var Conn *PoolProxy func Connect() error { // Connect to Postgres on local network (127.x.x.x) dsn := fmt.Sprintf( "postgres://%s:%s@%s:5432/%s?require_auth=scram-sha-256", getEnv("POSTGRES_USER"), getEnv("POSTGRES_PASSWORD"), getEnv("POSTGRES_HOST"), getEnv("POSTGRES_DB"), ) poolCfg, err := pgxpool.ParseConfig(dsn) if err != nil { return err } poolCfg.ConnConfig.RuntimeParams["timezone"] = "UTC" // Scan TIMESTAMPTZ into time.Time in UTC, never the host's local timezone. // Without this, pgx scans into time.Local, so the JSON offset in API // responses silently depends on the deployment host's TZ (e.g. +01:00 on a // London host, Z on a UTC Docker host) — the instant is the same but the // documented "backend emits UTC" invariant would be violated. // The codec is registered as a NEW *Type rather than mutating the Type // returned by TypeForOID: every connection's type map shares the same // *Type pointers with the package default map, so mutating .Codec on the // shared Type would be a data race when concurrent connections establish. poolCfg.AfterConnect = func(_ context.Context, conn *pgx.Conn) error { tzType, ok := conn.TypeMap().TypeForOID(pgtype.TimestamptzOID) if !ok { return nil } conn.TypeMap().RegisterType(&pgtype.Type{ Codec: &pgtype.TimestamptzCodec{ScanLocation: time.UTC}, Name: tzType.Name, OID: tzType.OID, }) return nil } pool, err := pgxpool.NewWithConfig(context.Background(), poolCfg) if err != nil { return err } Conn = NewPoolProxy(pool) err = testDB() if err != nil { return err } return nil } func testDB() error { ctx := context.Background() conn, err := Conn.Acquire(ctx) if err != nil { return err } defer conn.Release() row := conn.QueryRow(ctx, "SELECT 1") var result int err = row.Scan(&result) if err != nil { return err } return nil } func getEnv(key string) string { if val := os.Getenv(key); val != "" { return val } // Return empty string instead of fatal error - allows tests to run without prod env vars return "" }