feat: initial http server and logger
This commit is contained in:
parent
66826ebfdd
commit
01c8a69b89
|
@ -1,7 +1,35 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fmartingr/notion2ical/internal/server"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("I come in peace.")
|
||||
ctx := context.Background()
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// TODO: set log level
|
||||
|
||||
defer func() {
|
||||
if err := logger.Sync(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
server := server.NewServer(
|
||||
logger,
|
||||
server.ParseServerConfiguration(ctx, logger),
|
||||
)
|
||||
|
||||
if err := server.Start(ctx); err != nil {
|
||||
logger.Panic("error starting server", zap.Error(err))
|
||||
}
|
||||
|
||||
server.WaitStop()
|
||||
}
|
||||
|
|
17
go.mod
17
go.mod
|
@ -1,3 +1,20 @@
|
|||
module github.com/fmartingr/notion2ical
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/gofiber/fiber/v2 v2.36.0
|
||||
github.com/sethvargo/go-envconfig v0.8.2
|
||||
go.uber.org/zap v1.22.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/klauspost/compress v1.15.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.38.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
|
||||
)
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gofiber/fiber/v2 v2.36.0 h1:1qLMe5rhXFLPa2SjK10Wz7WFgLwYi4TYg7XrjztJHqA=
|
||||
github.com/gofiber/fiber/v2 v2.36.0/go.mod h1:tgCr+lierLwLoVHHO/jn3Niannv34WRkQETU8wiL9fQ=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
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/sethvargo/go-envconfig v0.8.2 h1:DDUVuG21RMgeB/bn4leclUI/837y6cQCD4w8hb5797k=
|
||||
github.com/sethvargo/go-envconfig v0.8.2/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0=
|
||||
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.38.0 h1:yTjSSNjuDi2PPvXY2836bIwLmiTS2T4T9p1coQshpco=
|
||||
github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
|
||||
go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
@ -0,0 +1,8 @@
|
|||
package models
|
||||
|
||||
import "context"
|
||||
|
||||
type Server interface {
|
||||
Start(ctx context.Context) error
|
||||
Stop(ctx context.Context) error
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sethvargo/go-envconfig"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// readDotEnv reads the configuration from variables in a .env file (only for contributing)
|
||||
func readDotEnv(logger *zap.Logger) map[string]string {
|
||||
file, err := os.Open(".env")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
result := make(map[string]string)
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
keyval := strings.SplitN(line, "=", 2)
|
||||
result[keyval[0]] = keyval[1]
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
logger.Fatal("error reading dotenv", zap.Error(err))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Hostname string `env:"HOSTNAME,required"`
|
||||
Http struct {
|
||||
Enabled bool `env:"HTTP_ENABLED,default=True"`
|
||||
Port int `env:"HTTP_PORT,default=8080"`
|
||||
}
|
||||
LogLevel string `env:"LOG_LEVEL,default=info"`
|
||||
}
|
||||
|
||||
func ParseServerConfiguration(ctx context.Context, logger *zap.Logger) *ServerConfig {
|
||||
var cfg ServerConfig
|
||||
|
||||
lookuper := envconfig.MultiLookuper(
|
||||
envconfig.MapLookuper(map[string]string{"HOSTNAME": os.Getenv("HOSTNAME")}),
|
||||
envconfig.MapLookuper(readDotEnv(logger)),
|
||||
envconfig.PrefixLookuper("NOTION2ICAL_", envconfig.OsLookuper()),
|
||||
envconfig.OsLookuper(),
|
||||
)
|
||||
if err := envconfig.ProcessWith(ctx, &cfg, lookuper); err != nil {
|
||||
logger.Fatal("Error parsing configuration: %s", zap.Error(err))
|
||||
}
|
||||
|
||||
return &cfg
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type httpServer struct {
|
||||
http *fiber.App
|
||||
addr string
|
||||
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func (s *httpServer) Start(_ context.Context) error {
|
||||
s.http.
|
||||
Static("/", "./public").
|
||||
Get("/calendar", s.calendarHandler).
|
||||
Get("/liveness", s.livenessHandler).
|
||||
Use(s.notFound)
|
||||
|
||||
s.logger.Info("starting http server", zap.String("addr", s.addr))
|
||||
return s.http.Listen(s.addr)
|
||||
}
|
||||
|
||||
func (s *httpServer) Stop(ctx context.Context) error {
|
||||
s.logger.Info("stoppping http server")
|
||||
return s.http.Shutdown()
|
||||
}
|
||||
|
||||
func (s *httpServer) notFound(c *fiber.Ctx) error {
|
||||
return c.SendStatus(404)
|
||||
}
|
||||
|
||||
func (s *httpServer) livenessHandler(c *fiber.Ctx) error {
|
||||
return c.SendStatus(200)
|
||||
}
|
||||
|
||||
func (s *httpServer) calendarHandler(c *fiber.Ctx) error {
|
||||
return c.SendStatus(501)
|
||||
}
|
||||
|
||||
func NewHttpServer(logger *zap.Logger, port int) *httpServer {
|
||||
return &httpServer{
|
||||
logger: logger,
|
||||
addr: fmt.Sprintf(":%d", port),
|
||||
http: fiber.New(fiber.Config{
|
||||
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
||||
logger.Error(
|
||||
"handler error",
|
||||
zap.String("method", c.Method()),
|
||||
zap.String("path", c.Path()),
|
||||
zap.Error(err),
|
||||
)
|
||||
return c.SendStatus(500)
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
internalModels "github.com/fmartingr/notion2ical/internal/models"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Http internalModels.Server
|
||||
config *ServerConfig
|
||||
logger *zap.Logger
|
||||
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
s.cancel = cancel
|
||||
|
||||
if s.config.Http.Enabled {
|
||||
go func() {
|
||||
if err := s.Http.Start(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
s.logger.Fatal("error starting server", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) WaitStop() {
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
sig := <-signals
|
||||
s.logger.Info("signal received, shutting down", zap.String("signal", sig.String()))
|
||||
|
||||
s.Stop()
|
||||
}
|
||||
|
||||
func (s *Server) Stop() {
|
||||
s.cancel()
|
||||
|
||||
shuwdownContext, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if s.config.Http.Enabled {
|
||||
if err := s.Http.Stop(shuwdownContext); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
s.logger.Fatal("error shutting down http server", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewServer(logger *zap.Logger, conf *ServerConfig) *Server {
|
||||
server := &Server{
|
||||
logger: logger,
|
||||
config: conf,
|
||||
}
|
||||
if conf.Http.Enabled {
|
||||
server.Http = NewHttpServer(logger, conf.Http.Port)
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=320, initial-scale=1.0">
|
||||
<title>Notion to iCal</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Notion to ical
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue