2025-05-25 20:39:43 +02:00

159 lines
4.2 KiB
Go

package main
import (
"embed"
"flag"
"fmt"
"io/fs"
"log/slog"
"net/http"
"os"
"time"
"git.clustercockpit.org/moebiusband/go-http-skeleton/internal/handlers"
"git.clustercockpit.org/moebiusband/go-http-skeleton/internal/middleware"
"git.clustercockpit.org/moebiusband/go-http-skeleton/internal/repository"
"github.com/joho/godotenv"
_ "modernc.org/sqlite"
)
//go:embed web/static/*
var static embed.FS
func init() {
_, jsonLogger := os.LookupEnv("JSON_LOGGER")
_, debug := os.LookupEnv("DEBUG")
var programLevel slog.Level
if debug {
programLevel = slog.LevelDebug
}
programLevel = slog.LevelDebug
if jsonLogger {
jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: programLevel,
})
slog.SetDefault(slog.New(jsonHandler))
} else {
textHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: programLevel,
})
slog.SetDefault(slog.New(textHandler))
}
slog.Info("Logger initialized", slog.Bool("debug", debug))
}
func main() {
var flagMigrateDB, flagRevertDB, flagForceDB bool
flag.BoolVar(&flagMigrateDB, "migrate-db", false, "Migrate database to supported version and exit")
flag.BoolVar(&flagRevertDB, "revert-db", false, "Migrate database to previous version and exit")
flag.BoolVar(&flagForceDB, "force-db", false, "Force database version, clear dirty flag and exit")
err := godotenv.Load()
if err != nil {
slog.Error("Could not parse existing .env file at location './.env'. Application startup failed, exited.\nError: %s\n", "Error", err.Error())
}
dbURL := os.Getenv("DB")
if dbURL == "" {
dbURL = "file:app.db"
}
if flagMigrateDB {
err := repository.MigrateDB(dbURL)
if err != nil {
slog.Error("MigrateDB Failed: Could not migrate database at location.", "version", repository.Version, "error", err)
os.Exit(1)
}
slog.Info("MigrateDB Success: Migrated database at location.\n", "version", repository.Version)
}
if flagRevertDB {
err := repository.RevertDB(dbURL)
if err != nil {
slog.Error("RevertDB Failed: Could not revert database at location", "version", (repository.Version - 1), "error", err)
os.Exit(1)
}
slog.Info("RevertDB Success: Reverted database", "version", (repository.Version - 1))
}
if flagForceDB {
err := repository.ForceDB(dbURL)
if err != nil {
slog.Error("ForceDB Failed: Could not force database version", "version", repository.Version, "error", err)
os.Exit(1)
}
slog.Error("ForceDB Success: Forced database version", "version", repository.Version)
}
repository.Connect(dbURL)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
addr := ":" + port
mux := http.NewServeMux()
// Use an embedded filesystem rooted at "web/static"
fs, err := fs.Sub(static, "web/static")
if err != nil {
slog.Error("Failed to create sub filesystem", "error", err)
return
}
// Serve files from the embedded /web/static directory at /static
fileServer := http.FileServer(http.FS(fs))
mux.Handle("GET /static/", http.StripPrefix("/static/", fileServer))
mux.HandleFunc("GET /favicon.ico", func(w http.ResponseWriter, r *http.Request) {
data, err := static.ReadFile("web/static/favicon.ico")
if err != nil {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Write(data)
})
mux.HandleFunc("GET /robots.txt", func(w http.ResponseWriter, r *http.Request) {
data, err := static.ReadFile("web/static/robots.txt")
if err != nil {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Write(data)
})
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(`OK`))
})
mux.HandleFunc("GET /", handlers.RootHandler())
chain := &middleware.Chain{}
chain.Use(middleware.RecoverMiddleware)
wrappedMux := chain.Then(mux)
server := &http.Server{
Addr: fmt.Sprintf(":%s", port),
Handler: wrappedMux,
// Recommended timeouts from
// https://blog.cloudflare.com/exposing-go-on-the-internet/
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
slog.Info("Server listening", "addr", addr)
if err := server.ListenAndServe(); err != nil {
slog.Error("Server failed to start", "error", err)
}
}