diff --git a/assets/assets.go b/assets/assets.go index 8d82144..0877356 100644 --- a/assets/assets.go +++ b/assets/assets.go @@ -1,4 +1,5 @@ // +build !dev + //go:generate go run -mod vendor github.com/shurcooL/vfsgen/cmd/vfsgendev -source="git.coolaj86.com/coolaj86/goserv/assets".Assets package assets diff --git a/assets/configfs/config.go b/assets/configfs/config.go new file mode 100644 index 0000000..a9ba5b1 --- /dev/null +++ b/assets/configfs/config.go @@ -0,0 +1,5 @@ +// +build !dev + +//go:generate go run -mod vendor github.com/shurcooL/vfsgen/cmd/vfsgendev -source="git.coolaj86.com/coolaj86/goserv/assets/configfs".Assets + +package configfs diff --git a/assets/configfs/config_dev.go b/assets/configfs/config_dev.go new file mode 100644 index 0000000..f24445b --- /dev/null +++ b/assets/configfs/config_dev.go @@ -0,0 +1,8 @@ +// +build dev + +package configfs + +import "net/http" + +// Assets includes postgres/init.sql +var Assets http.FileSystem = http.Dir("./files") diff --git a/assets/configfs/files/postgres/init.sql b/assets/configfs/files/postgres/init.sql new file mode 100644 index 0000000..bb3dc4c --- /dev/null +++ b/assets/configfs/files/postgres/init.sql @@ -0,0 +1,17 @@ +CREATE extension IF NOT EXISTS pgcrypto; +SET TIMEZONE='UTC'; + +--DROP TABLE IF EXISTS authn; +CREATE TABLE IF NOT EXISTS authn ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + ppid TEXT NOT NULL, + email TEXT NOT NULL, + verified BOOL DEFAULT FALSE, + created_at TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'), + updated_at TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'), + deleted_at TIMESTAMP NOT NULL DEFAULT ('epoch' AT TIME ZONE 'UTC') +); + +--CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_slug ON authn (ppid); +CREATE INDEX IF NOT EXISTS idx_ppid ON authn (ppid); +CREATE INDEX IF NOT EXISTS idx_email ON authn (email); diff --git a/go.mod b/go.mod index 0e09511..0daecd7 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,11 @@ go 1.15 require ( github.com/go-chi/chi v4.1.2+incompatible + github.com/jmoiron/sqlx v1.2.0 github.com/joho/godotenv v1.3.0 + github.com/lib/pq v1.8.0 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 // indirect + google.golang.org/appengine v1.6.6 // indirect ) diff --git a/go.sum b/go.sum index 69f567e..a9db642 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,17 @@ github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= @@ -12,6 +22,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -22,9 +33,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 h1:hzJjkvxUIF3bSt+v8N5tBQNx/605vszZJ+3XsIamzZo= golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= diff --git a/internal/db/db.go b/internal/db/db.go new file mode 100644 index 0000000..503bd9e --- /dev/null +++ b/internal/db/db.go @@ -0,0 +1,47 @@ +package db + +import ( + "context" + "database/sql" + "io/ioutil" + "time" + + "git.coolaj86.com/coolaj86/goserv/assets/configfs" + "github.com/jmoiron/sqlx" + + // pq injects itself into sql as 'postgres' + _ "github.com/lib/pq" +) + +// DB is a concurrency-safe db connection instance +var DB *sqlx.DB + +// Init returns a, you guessed it, New Store +func Init(pgURL string) error { + // https://godoc.org/github.com/lib/pq + + f, err := configfs.Assets.Open("./postgres/init.sql") + if nil != err { + return err + } + + dbtype := "postgres" + sqlBytes, err := ioutil.ReadAll(f) + if nil != err { + return err + } + + ctx, done := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) + defer done() + db, err := sql.Open(dbtype, pgURL) + if err := db.PingContext(ctx); nil != err { + return err + } + if _, err := db.ExecContext(ctx, string(sqlBytes)); nil != err { + return err + } + + DB = sqlx.NewDb(db, dbtype) + + return nil +} diff --git a/main.go b/main.go index babfbe3..f0c8b9b 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,14 @@ import ( "compress/flate" "flag" "fmt" + "log" "net/http" "os" + "strings" "time" "git.coolaj86.com/coolaj86/goserv/assets" + "git.coolaj86.com/coolaj86/goserv/internal/db" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" @@ -45,6 +48,7 @@ type runOptions struct { var runFlags *flag.FlagSet var runOpts runOptions var initFlags *flag.FlagSet +var dbURL string func init() { runOpts = runOptions{} @@ -53,6 +57,12 @@ func init() { runFlags.BoolVar(&runOpts.trustProxy, "trust-proxy", false, "trust X-Forwarded-For header") runFlags.BoolVar(&runOpts.compress, "compress", true, "enable compression for text,html,js,css,etc") runFlags.StringVar(&runOpts.static, "serve-path", "", "path to serve, falls back to built-in web app") + runFlags.StringVar( + &dbURL, + "db-url", + "postgres://postgres:postgres@localhost:5432/postgres", + "database (postgres) connection url", + ) } func main() { @@ -92,9 +102,12 @@ func main() { } } +var startedAt = time.Now() var defaultMaxBytes int64 = 1 << 20 func serve() { + initDB(dbURL) + r := chi.NewRouter() // A good base middleware stack @@ -109,6 +122,25 @@ func serve() { r.Route("/api", func(r chi.Router) { r.Use(limitResponseSize) + r.Use(jsonAllTheThings) + + r.Route("/public", func(r chi.Router) { + r.Get("/status", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(fmt.Sprintf( + `{ "success": true, "uptime": %.0f }%s`, + time.Since(startedAt).Seconds(), + "\n", + ))) + }) + }) + + r.Route("/user", func(r chi.Router) { + r.Get("/inspect", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(fmt.Sprintf( + `{ "success": false, "error": "not implemented" }%s`, "\n", + ))) + }) + }) }) var staticHandler http.HandlerFunc @@ -149,9 +181,35 @@ func serve() { } } +func jsonAllTheThings(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // just setting a default, other handlers can change this + w.Header().Set("Content-Type", "application/json") + next.ServeHTTP(w, r) + }) +} + func limitResponseSize(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, defaultMaxBytes) next.ServeHTTP(w, r) }) } + +func initDB(connStr string) { + // TODO url.Parse + if strings.Contains(connStr, "@localhost/") || strings.Contains(connStr, "@localhost:") { + connStr += "?sslmode=disable" + } else { + connStr += "?sslmode=required" + } + + err := db.Init(connStr) + if nil != err { + log.Println("db connection error", err) + //log.Fatal("db connection error", err) + return + } + + return +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 91c7c0a..a6cae7f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2,10 +2,19 @@ ## explicit github.com/go-chi/chi github.com/go-chi/chi/middleware +# github.com/jmoiron/sqlx v1.2.0 +## explicit +github.com/jmoiron/sqlx +github.com/jmoiron/sqlx/reflectx # github.com/joho/godotenv v1.3.0 ## explicit github.com/joho/godotenv github.com/joho/godotenv/autoload +# github.com/lib/pq v1.8.0 +## explicit +github.com/lib/pq +github.com/lib/pq/oid +github.com/lib/pq/scram # github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 ## explicit github.com/shurcooL/httpfs/vfsutil @@ -15,3 +24,5 @@ github.com/shurcooL/vfsgen github.com/shurcooL/vfsgen/cmd/vfsgendev # golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 ## explicit +# google.golang.org/appengine v1.6.6 +## explicit