package hn import ( "os" "time" "github.com/jinzhu/gorm" // sqlite3 dialect registered with gorm via import side effects. _ "github.com/jinzhu/gorm/dialects/sqlite" "github.com/labstack/echo" "github.com/labstack/echo/middleware" gl "github.com/labstack/gommon/log" "github.com/mattn/go-isatty" ep2 "github.com/mayowa/echo-pongo2" "github.com/ziflex/lecho/v2" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) // App holds the running server's dependencies: the echo instance, the // logger, the database handle, the startup time, and the background fetcher. type App struct { version string buildarch string e *echo.Echo log *zerolog.Logger db *gorm.DB startup time.Time fetcher *Fetcher } // RunServer boots the application, starts the background fetcher, and blocks // serving HTTP until the server exits, returning a process exit code. func RunServer(version string, buildarch string) int { a := new(App) a.version = version a.buildarch = buildarch a.startup = time.Now() a.init() defer func() { _ = a.db.Close() }() a.fetcher = NewFetcher(a.db) a.fetcher.AddLogger(a.log) go a.fetcher.run() return a.runForever() } func (a *App) init() { // setup logging l := log.With().Caller().Logger() log.Logger = l tty := isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd()) if tty { out := zerolog.NewConsoleWriter( func(w *zerolog.ConsoleWriter) { // Customize time format w.TimeFormat = time.RFC3339 }, ) log.Logger = log.Output(out) } // always log in UTC zerolog.TimestampFunc = func() time.Time { return time.Now().UTC() } zerolog.SetGlobalLevel(zerolog.InfoLevel) if os.Getenv("DEBUG") != "" { zerolog.SetGlobalLevel(zerolog.DebugLevel) } a.log = &log.Logger a.identify() // database path defaults to the container data volume, overridable // with the DATABASE_PATH environment variable dbPath := "/data/storage.sqlite" if os.Getenv("DATABASE_PATH") != "" { dbPath = os.Getenv("DATABASE_PATH") } db, err := gorm.Open("sqlite3", dbPath) if err != nil { panic("failed to open database: " + err.Error()) } a.db = db } func (a *App) identify() { log.Info(). Str("version", a.version). Str("buildarch", a.buildarch). Msg("starting") } func (a *App) runForever() int { // Echo instance a.e = echo.New() lev := gl.INFO if os.Getenv("DEBUG") != "" { lev = gl.DEBUG } logger := lecho.New( os.Stdout, lecho.WithLevel(lev), lecho.WithTimestamp(), lecho.WithCaller(), ) a.e.Logger = logger a.e.Use(middleware.RequestID()) // Middleware a.e.Use(middleware.Logger()) a.e.Use(middleware.Recover()) r, err := ep2.NewRenderer("view") if err != nil { a.e.Logger.Fatal(err) } a.e.Renderer = r rhs := NewRequestHandlerSet(a.version, a.db) // Routes a.e.Static("/static", "static") a.e.GET("/", rhs.indexHandler) a.e.GET("/about", rhs.aboutHandler) // Start server (blocks; Fatal exits the process on error) a.e.Logger.Fatal(a.e.Start(":8080")) return 0 }