go-web-server/main.go

193 lines
4.7 KiB
Go

package main
import (
"fmt"
"log/slog"
"math"
"net/http"
"os"
"gitea.antoine-langlois.net/${REPO_OWNER}/${REPO_NAME}/controllers"
"github.com/joho/godotenv"
"github.com/urfave/cli/v2"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var app = &cli.App{
Name: "${REPO_NAME}",
Usage: "${REPO_DESCRIPTION}",
Version: "0.1.0",
Before: func(ctx *cli.Context) error {
dotenvFile := ".env"
if v := os.Getenv("${REPO_NAME_UPPER}_DOTENV"); v != "" {
dotenvFile = v
}
if err := godotenv.Overload(dotenvFile); err != nil && !os.IsNotExist(err) {
return err
}
return nil
},
Commands: []*cli.Command{
{
Name: "serve",
Usage: "Start the web server",
Flags: []cli.Flag{
&cli.UintFlag{
Name: "port",
Usage: "Port to listen on",
Category: "server",
Aliases: []string{"P"},
Value: 8080,
EnvVars: []string{"${REPO_NAME_UPPER}_HTTP_PORT"},
Action: func(ctx *cli.Context, p uint) error {
if p > math.MaxUint16 {
return cli.Exit("Port number must be between 0 and 65535", 1)
}
return nil
},
},
&cli.StringFlag{
Name: "host",
Usage: "Host to listen on",
Category: "server",
Aliases: []string{"H"},
Value: "localhost",
EnvVars: []string{"${REPO_NAME_UPPER}_HTTP_HOST"},
},
&cli.StringFlag{
Name: "log-level",
Usage: "Log verbosity level (debug, info, warn, error)",
Category: "logging",
EnvVars: []string{"${REPO_NAME_UPPER}_LOG_LEVEL"},
Value: "info",
Action: func(ctx *cli.Context, l string) error {
switch l {
case "debug", "info", "warn", "error":
return nil
default:
return cli.Exit("Log level must be one of debug, info, warn or error", 1)
}
},
},
&cli.PathFlag{
Name: "log-file",
Usage: "Log file",
Category: "logging",
EnvVars: []string{"${REPO_NAME_UPPER}_LOG_FILE"},
},
&cli.StringFlag{
Name: "db-host",
Usage: "Database host",
Category: "database",
EnvVars: []string{"${REPO_NAME_UPPER}_DB_HOST"},
Value: "localhost",
},
&cli.UintFlag{
Name: "db-port",
Usage: "Database port",
Category: "database",
EnvVars: []string{"${REPO_NAME_UPPER}_DB_PORT"},
Value: 5432,
Action: func(ctx *cli.Context, p uint) error {
if p > math.MaxUint16 {
return cli.Exit("Port number must be between 0 and 65535", 1)
}
return nil
},
},
&cli.StringFlag{
Name: "db-user",
Usage: "Database user",
Category: "database",
EnvVars: []string{"${REPO_NAME_UPPER}_DB_USER"},
Value: "${REPO_NAME_LOWER}",
},
&cli.StringFlag{
Name: "db-password",
Usage: "Database password",
Category: "database",
EnvVars: []string{"${REPO_NAME_UPPER}_DB_PASSWORD"},
Value: "${REPO_NAME_LOWER}",
},
},
Action: func(ctx *cli.Context) error {
var logLevel slog.Level
switch ctx.String("log-level") {
case "debug":
logLevel = slog.LevelDebug
case "info":
logLevel = slog.LevelInfo
case "warn":
logLevel = slog.LevelWarn
case "error":
logLevel = slog.LevelError
default:
panic("invalid log level")
}
logWriter := os.Stdout
if logFile := ctx.Path("log-file"); logFile != "" {
var err error
logWriter, err = os.Open(ctx.Path("log-file"))
if err != nil {
return cli.Exit(err, 1)
}
}
defer logWriter.Close()
logger := slog.NewTextHandler(logWriter, &slog.HandlerOptions{
Level: logLevel,
})
_, err := openDB(
ctx.String("db-host"),
uint16(ctx.Uint("db-port")),
ctx.String("db-user"),
ctx.String("db-password"),
"${REPO_NAME_LOWER}",
)
if err != nil {
return cli.Exit(err, 1)
}
c, err := controllers.Register(logger, fmt.Sprintf("http://%s:%d", ctx.String("host"), ctx.Uint("port")))
if err != nil {
return cli.Exit(err, 1)
}
fmt.Printf("Server started at http://%s:%d\n", ctx.String("host"), ctx.Uint("port"))
addr := fmt.Sprintf("%s:%d", ctx.String("host"), ctx.Uint("port"))
return http.ListenAndServe(addr, c)
},
},
},
}
func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
}
}
func openDB(
host string,
port uint16,
user, password, dbname string,
) (*gorm.DB, error) {
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Europe/Paris", host, user, password, dbname, port)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
return nil, err
}
return db, nil
}