Compare commits

..

5 Commits

Author SHA1 Message Date
01073aca78 latest
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2023-01-30 00:15:38 -08:00
49709ad3d2 latest
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-01-28 19:05:02 -08:00
3f49d528e7 builds and runs! not sure if it works, needs testing
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-11-28 06:00:44 +01:00
46b67f8a6e more progress
Some checks failed
continuous-integration/drone/push Build is failing
2022-11-28 05:33:52 +01:00
5fc22c36b0 getting closer
Some checks failed
continuous-integration/drone/push Build is failing
2022-11-28 05:09:23 +01:00
28 changed files with 467 additions and 288 deletions

View File

@ -5,7 +5,9 @@ import (
"git.eeqj.de/sneak/gohttpserver/internal/database"
"git.eeqj.de/sneak/gohttpserver/internal/globals"
"git.eeqj.de/sneak/gohttpserver/internal/handlers"
"git.eeqj.de/sneak/gohttpserver/internal/healthcheck"
"git.eeqj.de/sneak/gohttpserver/internal/logger"
"git.eeqj.de/sneak/gohttpserver/internal/middleware"
"git.eeqj.de/sneak/gohttpserver/internal/server"
"go.uber.org/fx"
)
@ -29,6 +31,8 @@ func main() {
handlers.New,
logger.New,
server.New,
middleware.New,
healthcheck.New,
),
fx.Invoke(func(*server.Server) {}),
).Run()

5
go.mod
View File

@ -12,6 +12,8 @@ require (
github.com/rs/zerolog v1.28.0
github.com/slok/go-http-metrics v0.10.0
github.com/spf13/viper v1.14.0
go.uber.org/fx v1.18.2
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e
)
require (
@ -37,11 +39,12 @@ require (
github.com/subosito/gotenv v1.4.1 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/dig v1.15.0 // indirect
go.uber.org/fx v1.18.2 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

5
go.sum
View File

@ -380,6 +380,7 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -584,6 +585,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y=
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -600,6 +603,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View File

@ -20,20 +20,24 @@ import (
)
type ConfigParams struct {
Globals globals.Globals
Logger logger.Logger
fx.In
Globals *globals.Globals
Logger *logger.Logger
}
type Config struct {
DBURL string
Debug bool
MaintenanceMode bool
MetricsPassword string
MetricsUsername string
Port int
SentryDSN string
params ConfigParams
log *zerolog.Logger
DBURL string
Debug bool
MaintenanceMode bool
DevelopmentMode bool
DevAdminUsername string
DevAdminPassword string
MetricsPassword string
MetricsUsername string
Port int
SentryDSN string
params *ConfigParams
log *zerolog.Logger
}
func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
@ -51,6 +55,9 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
viper.SetDefault("DEBUG", "false")
viper.SetDefault("MAINTENANCE_MODE", "false")
viper.SetDefault("DEVELOPMENT_MODE", "false")
viper.SetDefault("DEV_ADMIN_USERNAME", "")
viper.SetDefault("DEV_ADMIN_PASSWORD", "")
viper.SetDefault("PORT", "8080")
viper.SetDefault("DBURL", "")
viper.SetDefault("SENTRY_DSN", "")
@ -69,14 +76,23 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
}
s := &Config{
DBURL: viper.GetString("DBURL"),
Debug: viper.GetBool("debug"),
Port: viper.GetInt("PORT"),
SentryDSN: viper.GetString("SENTRY_DSN"),
MaintenanceMode: viper.GetBool("MAINTENANCE_MODE"),
MetricsUsername: viper.GetString("METRICS_USERNAME"),
MetricsPassword: viper.GetString("METRICS_PASSWORD"),
log: log,
DBURL: viper.GetString("DBURL"),
Debug: viper.GetBool("debug"),
Port: viper.GetInt("PORT"),
SentryDSN: viper.GetString("SENTRY_DSN"),
MaintenanceMode: viper.GetBool("MAINTENANCE_MODE"),
DevelopmentMode: viper.GetBool("DEVELOPMENT_MODE"),
DevAdminUsername: viper.GetString("DEV_ADMIN_USERNAME"),
DevAdminPassword: viper.GetString("DEV_ADMIN_PASSWORD"),
MetricsUsername: viper.GetString("METRICS_USERNAME"),
MetricsPassword: viper.GetString("METRICS_PASSWORD"),
log: log,
params: &params,
}
if s.Debug {
params.Logger.EnableDebugLogging()
s.log = params.Logger.Get()
}
return s, nil

View File

@ -20,29 +20,32 @@ import (
type DatabaseParams struct {
fx.In
Logger logger.Logger
Config config.Config
Logger *logger.Logger
Config *config.Config
}
type Database struct {
URL string
log *zerolog.Logger
params DatabaseParams
params *DatabaseParams
}
func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) {
s.log.Info().Msg("Database instantiated")
s := new(Database)
s.params = params
s.params = &params
s.log = params.Logger.Get()
s.log.Info().Msg("Database instantiated")
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
s.log.Info().Msg("Database OnStart Hook")
// FIXME connect to db
return nil
},
OnStop: func(ctx context.Context) error {
// FIXME disconnect from db
return nil
},
})
return s, nil

View File

@ -1,30 +0,0 @@
package handlers
import (
"net/http"
"time"
)
func (s *Handlers) handleHealthCheck() http.HandlerFunc {
type response struct {
Status string `json:"status"`
Now string `json:"now"`
UptimeSeconds int64 `json:"uptime_seconds"`
UptimeHuman string `json:"uptime_human"`
Version string `json:"version"`
Appname string `json:"appname"`
Maintenance bool `json:"maintenance_mode"`
}
return func(w http.ResponseWriter, req *http.Request) {
resp := &response{
Status: "ok",
Now: time.Now().UTC().Format(time.RFC3339Nano),
UptimeSeconds: int64(s.uptime().Seconds()),
UptimeHuman: s.uptime().String(),
Maintenance: s.maintenance(),
Appname: s.appname,
Version: s.version,
}
s.respondJSON(w, req, resp, 200)
}
}

View File

@ -1,12 +0,0 @@
package handlers
import (
"fmt"
"net/http"
)
func (s *Handlers) HandleLogin() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello login")
}
}

View File

@ -4,47 +4,43 @@ import (
"context"
"encoding/json"
"net/http"
"time"
"git.eeqj.de/sneak/gohttpserver/internal/database"
"git.eeqj.de/sneak/gohttpserver/internal/globals"
"git.eeqj.de/sneak/gohttpserver/internal/healthcheck"
"git.eeqj.de/sneak/gohttpserver/internal/logger"
"github.com/rs/zerolog"
"go.uber.org/fx"
"google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
type HandlersParams struct {
fx.In
Logger *zerolog.Logger
Globals globals.Globals
Database database.Database
Logger *logger.Logger
Globals *globals.Globals
Database *database.Database
Healthcheck *healthcheck.Healthcheck
}
type Handlers struct {
params HandlersParams
params *HandlersParams
log *zerolog.Logger
hc *healthcheck.Healthcheck
}
func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) {
s := new(Handlers)
s.params = params
s.params = &params
s.log = params.Logger.Get()
s.hc = params.Healthcheck
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
// FIXME compile some templates here or something
return nil
},
})
return s, nil
}
func (s *Handlers) HandleNow() http.HandlerFunc {
type response struct {
Now time.Time `json:"now"`
}
return func(w http.ResponseWriter, r *http.Request) {
s.respondJSON(w, r, &response{Now: time.Now()}, 200)
}
}
func (s *Handlers) respondJSON(w http.ResponseWriter, r *http.Request, data interface{}, status int) {
w.WriteHeader(status)
w.Header().Set("Content-Type", "application/json")

View File

@ -0,0 +1,12 @@
package handlers
import (
"net/http"
)
func (s *Handlers) HandleHealthCheck() http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
resp := s.hc.Healthcheck()
s.respondJSON(w, req, resp, 200)
}
}

View File

@ -1,19 +1,18 @@
package handlers
import (
"html/template"
"net/http"
"git.eeqj.de/sneak/gohttpserver/templates"
)
func (s *Handlers) HandleIndex() http.HandlerFunc {
indexTemplate := template.Must(template.New("index").Parse(templates.MustString("index.html")))
t := templates.GetParsed()
return func(w http.ResponseWriter, r *http.Request) {
err := indexTemplate.ExecuteTemplate(w, "index", nil)
err := t.ExecuteTemplate(w, "index.html", nil)
if err != nil {
s.log.Println(err.Error())
s.log.Error().Err(err).Msg("")
http.Error(w, http.StatusText(500), 500)
}
}

View File

@ -0,0 +1,19 @@
package handlers
import (
"net/http"
"git.eeqj.de/sneak/gohttpserver/templates"
)
func (s *Handlers) HandleLoginGET() http.HandlerFunc {
t := templates.GetParsed()
return func(w http.ResponseWriter, r *http.Request) {
err := t.ExecuteTemplate(w, "login.html", nil)
if err != nil {
s.log.Error().Err(err).Msg("")
http.Error(w, http.StatusText(500), 500)
}
}
}

View File

@ -0,0 +1,34 @@
package handlers
import (
"net/http"
"git.eeqj.de/sneak/gohttpserver/templates"
)
func (s *Handlers) HandleSignupGET() http.HandlerFunc {
t := templates.GetParsed()
return func(w http.ResponseWriter, r *http.Request) {
err := t.ExecuteTemplate(w, "signup.html", nil)
if err != nil {
s.log.Error().Err(err).Msg("")
http.Error(w, http.StatusText(500), 500)
}
}
}
func (s *Handlers) HandleSignupPOST() http.HandlerFunc {
t := templates.GetParsed()
return func(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
err := t.ExecuteTemplate(w, "signup.html", nil)
if err != nil {
s.log.Error().Err(err).Msg("")
http.Error(w, http.StatusText(500), 500)
}
}
}

View File

@ -0,0 +1,71 @@
package healthcheck
import (
"context"
"time"
"git.eeqj.de/sneak/gohttpserver/internal/config"
"git.eeqj.de/sneak/gohttpserver/internal/database"
"git.eeqj.de/sneak/gohttpserver/internal/globals"
"git.eeqj.de/sneak/gohttpserver/internal/logger"
"github.com/rs/zerolog"
"go.uber.org/fx"
)
type HealthcheckParams struct {
fx.In
Globals *globals.Globals
Config *config.Config
Logger *logger.Logger
Database *database.Database
}
type Healthcheck struct {
StartupTime time.Time
log *zerolog.Logger
params *HealthcheckParams
}
func New(lc fx.Lifecycle, params HealthcheckParams) (*Healthcheck, error) {
s := new(Healthcheck)
s.params = &params
s.log = params.Logger.Get()
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
s.StartupTime = time.Now()
return nil
},
OnStop: func(ctx context.Context) error {
// FIXME do server shutdown here
return nil
},
})
return s, nil
}
type HealthcheckResponse struct {
Status string `json:"status"`
Now string `json:"now"`
UptimeSeconds int64 `json:"uptime_seconds"`
UptimeHuman string `json:"uptime_human"`
Version string `json:"version"`
Appname string `json:"appname"`
Maintenance bool `json:"maintenance_mode"`
}
func (s *Healthcheck) uptime() time.Duration {
return time.Since(s.StartupTime)
}
func (s *Healthcheck) Healthcheck() *HealthcheckResponse {
resp := &HealthcheckResponse{
Status: "ok",
Now: time.Now().UTC().Format(time.RFC3339Nano),
UptimeSeconds: int64(s.uptime().Seconds()),
UptimeHuman: s.uptime().String(),
Appname: s.params.Globals.Appname,
Version: s.params.Globals.Version,
}
return resp
}

View File

@ -1,4 +1,4 @@
package server
package logger
import (
"io"
@ -8,13 +8,11 @@ import (
"git.eeqj.de/sneak/gohttpserver/internal/globals"
"github.com/rs/zerolog"
"go.uber.org/fx"
"honnef.co/go/tools/config"
)
type LoggerParams struct {
fx.In
Globals globals.Globals
Config config.Config
Globals *globals.Globals
}
type Logger struct {
@ -78,14 +76,14 @@ func New(lc fx.Lifecycle, params LoggerParams) (*Logger, error) {
l.log = &logger
// log.Logger = logger
if l.params.Config.Debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
l.log.Debug().Bool("debug", true).Send()
}
return l, nil
}
func (l *Logger) EnableDebugLogging() {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
l.log.Debug().Bool("debug", true).Send()
}
func (l *Logger) Get() *zerolog.Logger {
return l.log
}

View File

@ -1,19 +1,43 @@
package server
package middleware
import (
"net"
"net/http"
"time"
"git.eeqj.de/sneak/gohttpserver/internal/config"
"git.eeqj.de/sneak/gohttpserver/internal/globals"
"git.eeqj.de/sneak/gohttpserver/internal/logger"
basicauth "github.com/99designs/basicauth-go"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/cors"
"github.com/rs/zerolog"
metrics "github.com/slok/go-http-metrics/metrics/prometheus"
ghmm "github.com/slok/go-http-metrics/middleware"
"github.com/slok/go-http-metrics/middleware/std"
"github.com/spf13/viper"
"go.uber.org/fx"
)
type MiddlewareParams struct {
fx.In
Logger *logger.Logger
Globals *globals.Globals
Config *config.Config
}
type Middleware struct {
log *zerolog.Logger
params *MiddlewareParams
}
func New(lc fx.Lifecycle, params MiddlewareParams) (*Middleware, error) {
s := new(Middleware)
s.params = &params
s.log = params.Logger.Get()
return s, nil
}
// the following is from
// https://learning-cloud-native-go.github.io/docs/a6.adding_zerolog_logger/
@ -45,7 +69,7 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) {
// type Middleware func(http.Handler) http.Handler
// this returns a Middleware that is designed to do every request through the
// mux, note the signature:
func (s *Server) LoggingMiddleware() func(http.Handler) http.Handler {
func (s *Middleware) Logging() func(http.Handler) http.Handler {
// FIXME this should use https://github.com/google/go-cloud/blob/master/server/requestlog/requestlog.go
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -73,7 +97,7 @@ func (s *Server) LoggingMiddleware() func(http.Handler) http.Handler {
}
}
func (s *Server) CORSMiddleware() func(http.Handler) http.Handler {
func (s *Middleware) CORS() func(http.Handler) http.Handler {
return cors.Handler(cors.Options{
// CHANGEME! these are defaults, change them to suit your needs or
// read from environment/viper.
@ -88,7 +112,7 @@ func (s *Server) CORSMiddleware() func(http.Handler) http.Handler {
})
}
func (s *Server) AuthMiddleware() func(http.Handler) http.Handler {
func (s *Middleware) Auth() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// CHANGEME you'll want to change this to do stuff.
@ -98,7 +122,7 @@ func (s *Server) AuthMiddleware() func(http.Handler) http.Handler {
}
}
func (s *Server) MetricsMiddleware() func(http.Handler) http.Handler {
func (s *Middleware) Metrics() func(http.Handler) http.Handler {
mdlw := ghmm.New(ghmm.Config{
Recorder: metrics.NewRecorder(metrics.Config{}),
})
@ -107,7 +131,7 @@ func (s *Server) MetricsMiddleware() func(http.Handler) http.Handler {
}
}
func (s *Server) MetricsAuthMiddleware() func(http.Handler) http.Handler {
func (s *Middleware) MetricsAuth() func(http.Handler) http.Handler {
return basicauth.New(
"metrics",
map[string][]string{

View File

@ -7,7 +7,7 @@ import (
)
func (s *Server) serveUntilShutdown() {
listenAddr := fmt.Sprintf(":%d", s.port)
listenAddr := fmt.Sprintf(":%d", s.params.Config.Port)
s.httpServer = &http.Server{
Addr: listenAddr,
ReadTimeout: 10 * time.Second,
@ -18,7 +18,7 @@ func (s *Server) serveUntilShutdown() {
// add routes
// this does any necessary setup in each handler
s.routes()
s.SetupRoutes()
s.log.Info().Str("listenaddr", listenAddr).Msg("http begin listen")
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {

View File

@ -23,16 +23,16 @@ func (s *Server) SetupRoutes() {
s.router.Use(middleware.Recoverer)
s.router.Use(middleware.RequestID)
s.router.Use(s.LoggingMiddleware())
s.router.Use(s.mw.Logging())
// add metrics middleware only if we can serve them behind auth
if viper.GetString("METRICS_USERNAME") != "" {
s.router.Use(s.MetricsMiddleware())
s.router.Use(s.mw.Metrics())
}
// set up CORS headers. you'll probably want to configure that
// in middlewares.go.
s.router.Use(s.CORSMiddleware())
s.router.Use(s.mw.CORS())
// CHANGEME to suit your needs, or pull from config.
// timeout for request context; your handlers must finish within
@ -57,39 +57,48 @@ func (s *Server) SetupRoutes() {
// complete docs: https://github.com/go-chi/chi
////////////////////////////////////////////////////////////////////////
s.router.Get("/", s.handleIndex())
s.router.Get("/", s.h.HandleIndex())
s.router.Mount("/s", http.StripPrefix("/s", http.FileServer(http.FS(static.Static))))
s.router.Route("/api/v1", func(r chi.Router) {
r.Get("/now", s.handleNow())
r.Get("/now", s.h.HandleNow())
})
// if you want to use a general purpose middleware (http.Handler
// wrapper) on a specific HandleFunc route, you need to take the
// .ServeHTTP of the http.Handler to get its HandleFunc, viz:
authMiddleware := s.AuthMiddleware()
auth := s.mw.Auth()
s.router.Get(
"/login",
authMiddleware(s.handleLogin()).ServeHTTP,
auth(s.h.HandleLoginGET()).ServeHTTP,
)
s.router.Get(
"/signup",
auth(s.h.HandleSignupGET()).ServeHTTP,
)
s.router.Post(
"/signup",
auth(s.h.HandleSignupPOST()).ServeHTTP,
)
// route that panics for testing
// CHANGEME remove this
s.router.Get(
"/panic",
s.handlePanic(),
s.h.HandlePanic(),
)
s.router.Get(
"/.well-known/healthcheck.json",
s.handleHealthCheck(),
s.h.HandleHealthCheck(),
)
// set up authenticated /metrics route:
if viper.GetString("METRICS_USERNAME") != "" {
s.router.Group(func(r chi.Router) {
r.Use(s.MetricsAuthMiddleware())
r.Use(s.mw.MetricsAuth())
r.Get("/metrics", http.HandlerFunc(promhttp.Handler().ServeHTTP))
})
}

View File

@ -9,13 +9,13 @@ import (
"syscall"
"time"
"git.eeqj.de/sneak/gohttpserver/internal/config"
"git.eeqj.de/sneak/gohttpserver/internal/globals"
"github.com/docker/docker/daemon/logger"
"git.eeqj.de/sneak/gohttpserver/internal/handlers"
"git.eeqj.de/sneak/gohttpserver/internal/logger"
"git.eeqj.de/sneak/gohttpserver/internal/middleware"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"go.uber.org/fx"
"honnef.co/go/tools/config"
"github.com/getsentry/sentry-go"
"github.com/go-chi/chi"
@ -32,17 +32,15 @@ import (
type ServerParams struct {
fx.In
Logger logger.Logger
Globals globals.Globals
Config config.Config
Logger *logger.Logger
Globals *globals.Globals
Config *config.Config
Middleware *middleware.Middleware
Handlers *handlers.Handlers
}
type Server struct {
appname string
version string
buildarch string
startupTime time.Time
port int
exitCode int
sentryEnabled bool
log *zerolog.Logger
@ -51,21 +49,26 @@ type Server struct {
httpServer *http.Server
router *chi.Mux
params ServerParams
mw *middleware.Middleware
h *handlers.Handlers
}
func New(lc fx.Lifecycle, params ServerParams) (*Server, error) {
s := new(Server)
s.params = params
s.mw = params.Middleware
s.h = params.Handlers
s.log = params.Logger.Get()
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
s.startupTime = time.Now()
s.version = params.Globals.Version
s.Run()
go s.Run() // background FIXME
return nil
},
OnStop: func(ctx context.Context) error {
// FIXME do server shutdown here
return nil
},
})
return s, nil
@ -85,22 +88,22 @@ func (s *Server) Run() {
// logging before sentry, because sentry logs
s.enableSentry()
return s.serve()
s.serve() // FIXME deal with return value
}
func (s *Server) enableSentry() {
s.sentryEnabled = false
if s.Config.SentryDSN == "" {
if s.params.Config.SentryDSN == "" {
return
}
err := sentry.Init(sentry.ClientOptions{
Dsn: viper.GetString("SENTRY_DSN"),
Dsn: s.params.Config.SentryDSN,
Release: fmt.Sprintf("%s-%s", s.params.Globals.Appname, s.params.Globals.Version),
})
if err != nil {
log.Fatal().Err(err).Msg("sentry init failure")
s.log.Fatal().Err(err).Msg("sentry init failure")
return
}
s.log.Info().Msg("sentry error reporting activated")
@ -162,11 +165,7 @@ func (s *Server) cleanShutdown() {
}
}
func (s *Server) uptime() time.Duration {
return time.Since(s.startupTime)
}
func (s *Server) maintenance() bool {
func (s *Server) MaintenanceMode() bool {
return s.params.Config.MaintenanceMode
}

View File

@ -0,0 +1,3 @@
<script src="/s/js/jquery-3.5.1.slim.min.js"></script>
<script src="/s/js/bootstrap-4.5.3.bundle.min.js"></script>
<script src="/s/js/main.js"></script>

View File

@ -0,0 +1,4 @@
<meta charset="utf-8" />
<title>{{ .HTMLTitle }}</title>
<link rel="stylesheet" href="/s/css/bootstrap-4.5.3.min.css" />
<link rel="stylesheet" href="/s/css/style.css" />

View File

@ -1,84 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Changeme: Go HTTP Server Boilerplate</title>
<link rel="stylesheet" href="/s/css/bootstrap-4.5.3.min.css" />
<link rel="stylesheet" href="/s/css/style.css" />
{{ template "htmlheader.html" . }}
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="#">Quickstart</a>
<button
class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarsExampleDefault"
aria-controls="navbarsExampleDefault"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#"
>Home <span class="sr-only">(current)</span></a
>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a
class="nav-link disabled"
href="#"
tabindex="-1"
aria-disabled="true"
>Disabled</a
>
</li>
<li class="nav-item dropdown">
<a
class="nav-link dropdown-toggle"
href="#"
id="dropdown01"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>Dropdown</a
>
<div class="dropdown-menu" aria-labelledby="dropdown01">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input
class="form-control mr-sm-2"
type="text"
placeholder="Search"
aria-label="Search"
/>
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">
Search
</button>
</form>
</div>
</nav>
{{ template "navbar.html" .}}
<main role="main">
<div class="jumbotron">
<div class="container">
<h1 class="display-3">Hello, world!</h1>
<h2><a
href="https://git.eeqj.de/sneak/gohttpserver">gohttpserver</a></h2>
<p>
This is a boilerplate application for you to use as a base for your
own sites and services.
</p>
<p>
Find more info at <a
href="https://git.eeqj.de/sneak/gohttpserver">https://git.eeqj.de/sneak/gohttpserver</a>.
</p>
<p>
This software is provided by <a
href="https://sneak.berlin">@sneak</a>
and is released unconditionally into the public domain.
</p>
<p>
<a class="btn btn-primary btn-lg" href="#" role="button"
>Learn more &raquo;</a
@ -130,11 +75,7 @@
<!-- /container -->
</main>
<footer class="container">
<p>&copy; No rights reserved - This is in the public domain!</p>
</footer>
<script src="/s/js/jquery-3.5.1.slim.min.js"></script>
<script src="/s/js/bootstrap-4.5.3.bundle.min.js"></script>
<script src="/s/js/main.js"></script>
{{ template "pagefooter.html" . }}
</body>
{{ template "htmlfooter.html" . }}
</html>

View File

@ -1,10 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Changeme: Go HTTP Server Boilerplate</title>
<link rel="stylesheet" href="/s/css/bootstrap-4.5.3.min.css" />
{{ template "htmlheader.html" . }}
<style>
body {
padding-top: 3.5rem;
@ -28,74 +25,10 @@
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="#">Quickstart</a>
<button
class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarsExampleDefault"
aria-controls="navbarsExampleDefault"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#"
>Home <span class="sr-only">(current)</span></a
>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a
class="nav-link disabled"
href="#"
tabindex="-1"
aria-disabled="true"
>Disabled</a
>
</li>
<li class="nav-item dropdown">
<a
class="nav-link dropdown-toggle"
href="#"
id="dropdown01"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>Dropdown</a
>
<div class="dropdown-menu" aria-labelledby="dropdown01">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input
class="form-control mr-sm-2"
type="text"
placeholder="Search"
aria-label="Search"
/>
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">
Search
</button>
</form>
</div>
</nav>
{{ template "navbar.html" .}}
<main role="main">
<div class="login-form">
<div class="login-form">
<form action="/login" method="post">
<h2 class="text-center">Log in</h2>
<div class="form-group">
@ -118,11 +51,6 @@
</main>
<footer class="container">
<p>&copy; No rights reserved - This is in the public domain!</p>
</footer>
<script src="/s/js/jquery-3.5.1.slim.min.js"></script>
<script src="/s/js/bootstrap-4.5.3.bundle.min.js"></script>
<script src="/s/js/main.js"></script>
{{ template "pagefooter.html" . }}
</body>
</html>

64
templates/navbar.html Normal file
View File

@ -0,0 +1,64 @@
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="/">{{.SiteName}}</a>
<button
class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarsExampleDefault"
aria-controls="navbarsExampleDefault"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon">&nbsp;</span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#"
>Home <span class="sr-only">(current)</span></a
>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" aria-disabled="true"
>Disabled</a
>
</li>
<li class="nav-item dropdown">
<a
class="nav-link dropdown-toggle"
href="#"
id="dropdown01"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>Dropdown</a
>
<div class="dropdown-menu" aria-labelledby="dropdown01">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#"
>Something else here</a
>
</div>
</li>
</ul>
<form action="POST" class="form-inline my-2 my-lg-0">
<input
class="form-control mr-sm-2"
type="text"
placeholder="Search"
aria-label="Search"
/>
<button
class="btn btn-outline-success my-2 my-sm-0"
type="submit"
>
Search
</button>
</form>
</div>
</nav>

View File

@ -0,0 +1,3 @@
<footer class="container">
<p>&copy; No rights reserved - This is in the public domain!</p>
</footer>

76
templates/signup.html Normal file
View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
{{ template "htmlheader.html" .HTMLHeader }}
<style>
body {
padding-top: 3.5rem;
}
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style>
</head>
<body>
{{ template "navbar.html" .Navbar}}
<main role="main">
<div class="signup-form">
<form action="/signup" method="post">
<h2 class="text-center">Create New Account</h2>
<div class="form-group">
<span>Email:</span>
<input type="text" class="form-control" name="email"
placeholder="user@domain.com" required="required">
</div>
<div class="form-group">
<span>Desired Username:</span>
<input type="text" class="form-control" name="desiredUsername" placeholder="Username" required="required">
</div>
<div class="form-group">
<p>Please use a unique password that you don't use anywhere
else. Minimum 12 characters.</p>
<span>New Password:</span>
<input type="password" class="form-control"
name="desiredPassword1" placeholder="Password" required="required">
</div>
<div class="form-group">
<span>New Password (again):</span>
<input type="password" class="form-control"
name="desiredPassword2" placeholder="Password
(again)" required="required">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block">Create
New Account</button>
</div>
<!-- <div class="clearfix">
<label class="float-left form-check-label"><input type="checkbox"> Remember me</label>
<a href="#" class="float-right">Forgot Password?</a>
</div>
-->
</form>
<p class="text-center"><a href="#">Create an Account</a></p>
</div>
</main>
{{ template "pagefooter.html" .PageFooter }}
</body>
</html>

View File

@ -2,12 +2,21 @@ package templates
import (
"embed"
"strings"
"text/template"
)
//go:embed *.html
var Templates embed.FS
var TemplatesRaw embed.FS
var TemplatesParsed *template.Template
func GetParsed() *template.Template {
if TemplatesParsed == nil {
TemplatesParsed = template.Must(template.ParseFS(TemplatesRaw, "*"))
}
return TemplatesParsed
}
/*
func MustString(filename string) string {
bytes, error := Templates.ReadFile(filename)
if error != nil {
@ -17,3 +26,4 @@ func MustString(filename string) string {
out.Write(bytes)
return out.String()
}
*/