diff --git a/cmd/numerus/main.go b/cmd/numerus/main.go new file mode 100644 index 0000000..010d2ae --- /dev/null +++ b/cmd/numerus/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "context" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/jackc/pgx/v5/pgxpool" + + "dev.tandem.ws/tandem/numerus/pkg/router" +) + +func main() { + dbpool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL")) + if err != nil { + log.Fatal(err) + } + defer dbpool.Close() + + srv := http.Server{ + Addr: "localhost:8080", + Handler: router.NewRouter(dbpool), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 2 * time.Minute, + } + + go func() { + log.Printf("INFO - listening on %s\n", srv.Addr) + if err := srv.ListenAndServe(); err != http.ErrServerClosed { + log.Fatalf("http server: %v", err) + } + }() + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + <-sigCh + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + log.Print("INFO - stopping server") + if err := srv.Shutdown(ctx); err != nil { + log.Fatal(err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..081f63d --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module dev.tandem.ws/tandem/numerus + +go 1.18 + +require github.com/jackc/pgx/v5 v5.2.0 + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/puddle/v2 v2.1.2 // indirect + go.uber.org/atomic v1.10.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 // indirect + golang.org/x/text v0.3.8 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4ddcb68 --- /dev/null +++ b/go.sum @@ -0,0 +1,27 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +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-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8= +github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk= +github.com/jackc/puddle/v2 v2.1.2 h1:0f7vaaXINONKTsxYDn4otOAiJanX/BMeAtY//BXqzlg= +github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= +golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/logger/middleware.go b/pkg/logger/middleware.go new file mode 100644 index 0000000..3e411e1 --- /dev/null +++ b/pkg/logger/middleware.go @@ -0,0 +1,60 @@ +package logger + +import ( + "log" + "net/http" + "time" +) + +type loggerResponseWriter struct { + http.ResponseWriter + statusCode int + responseSize int +} + +func (w *loggerResponseWriter) WriteHeader(statusCode int) { + w.statusCode = statusCode + w.ResponseWriter.WriteHeader(statusCode) +} + +func (w *loggerResponseWriter) Write(b []byte) (int, error) { + w.responseSize += len(b) + return w.ResponseWriter.Write(b) +} + +func (w *loggerResponseWriter) Flush() { + if f, ok := w.ResponseWriter.(http.Flusher); ok { + f.Flush() + } +} + +func Logger(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t := time.Now() + logger := loggerResponseWriter{w, 0, 0} + + handler.ServeHTTP(&logger, r) + + statusCode := logger.statusCode + if statusCode == 0 { + statusCode = http.StatusOK + } + referer := r.Referer() + if referer == "" { + referer = "-" + } + log.Printf("HTTP - %s - - [%s] \"%s %s %s\" %d %d \"%s\" \"%s\" %s\n", + r.RemoteAddr, + t.Format("02/Jan/2006:15:04:05 -0700"), + r.Method, + r.URL.Path, + r.Proto, + statusCode, + logger.responseSize, + referer, + r.UserAgent(), + time.Since(t), + ) + + }) +} diff --git a/pkg/router/router.go b/pkg/router/router.go new file mode 100644 index 0000000..cec86dd --- /dev/null +++ b/pkg/router/router.go @@ -0,0 +1,52 @@ +package router + +import ( + "context" + "html/template" + "log" + "net/http" + + "dev.tandem.ws/tandem/numerus/pkg/logger" + "github.com/jackc/pgx/v5/pgxpool" +) + +func NewRouter(db *pgxpool.Pool) http.Handler { + router := http.NewServeMux() + router.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + email := r.FormValue("email") + password := r.FormValue("password") + var role string + if _, err := db.Exec(context.Background(), "select set_config('search_path', 'numerus, public', false)"); err != nil { + log.Printf("ERROR - %s", err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + err := db.QueryRow(context.Background(), "select login($1, $2)", email, password).Scan(&role) + if err != nil { + log.Printf("ERROR - %s", err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) + w.Write([]byte(role)) + }) + router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles("web/template/index.html") + if err != nil { + log.Printf("ERROR - %s", err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + err = t.Execute(w, nil) + if err != nil { + log.Printf("ERROR - %s", err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + var handler http.Handler = router + handler = logger.Logger(handler) + return handler +} diff --git a/web/template/index.html b/web/template/index.html new file mode 100644 index 0000000..d6f337c --- /dev/null +++ b/web/template/index.html @@ -0,0 +1,21 @@ + + + + + + Numerus + + +

Numerus

+

Login

+
+ + + + + + + +
+ +