Home

mint @c32dcc68dbf0cacc9e749a5916729b80e2717d1e - refs - log -
-
https://git.jolheiser.com/mint.git
Budget
mint / main.go
- raw -
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package main

import (
	"database/sql"
	_ "embed"
	"errors"
	"flag"
	"fmt"
	"html/template"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"strings"

	"github.com/golang-migrate/migrate/v4"
	"github.com/peterbourgon/ff/v3"
	"go.jolheiser.com/ffjsonnet"
	"go.jolheiser.com/mint/database"
	"go.jolheiser.com/mint/database/sqlc/migrations"
)

var (
	//go:embed index.html
	_indexTmpl string
	indexTmpl  = template.Must(template.New("").Parse(_indexTmpl))
)

type Args struct {
	Port     int
	Database string
	JSON     bool
	LogLevel slog.Level
}

func maine() error {
	args := Args{
		LogLevel: slog.LevelInfo,
	}
	fs := flag.NewFlagSet("mint", flag.ExitOnError)
	fs.IntVar(&args.Port, "port", 8080, "Port to run on")
	fs.IntVar(&args.Port, "p", args.Port, "--port")
	fs.StringVar(&args.Database, "database", "mint.sqlite3", "Path to database file")
	fs.StringVar(&args.Database, "d", args.Database, "--database")
	fs.BoolVar(&args.JSON, "json", false, "JSON logging")
	fs.BoolVar(&args.JSON, "j", args.JSON, "--json")
	logFn := func(s string) error {
		switch strings.ToLower(s) {
		case "debug":
			args.LogLevel = slog.LevelDebug
		case "info":
			args.LogLevel = slog.LevelInfo
		case "warn", "warning":
			args.LogLevel = slog.LevelWarn
		case "error":
			args.LogLevel = slog.LevelError
		default:
			return fmt.Errorf("unknown log level %q: valid settings are debug, info, warn, or error", s)
		}
		return nil
	}
	fs.Func("log-level", "Logging level [debug, info, warn, error]", logFn)
	fs.BoolFunc("v", "--log-level debug", func(_ string) error {
		return logFn("debug")
	})
	fs.String("config", "mint.jsonnet", "Config file")

	if err := ff.Parse(fs, os.Args[1:],
		ff.WithConfigFileFlag("config"),
		ff.WithAllowMissingConfigFile(true),
		ff.WithConfigFileParser(ffjsonnet.Parser),
		ff.WithEnvVarPrefix("MINT"),
	); err != nil {
		return fmt.Errorf("could not parse CLI arguments: %w", err)
	}

	logOpts := &slog.HandlerOptions{Level: args.LogLevel}
	logger := slog.New(slog.NewTextHandler(os.Stderr, logOpts))
	if args.JSON {
		logger = slog.New(slog.NewJSONHandler(os.Stderr, logOpts))
	}
	slog.SetDefault(logger)

	dsn := fmt.Sprintf("file:%s", args.Database)
	sdb, err := sql.Open("sqlite", dsn)
	if err != nil {
		return fmt.Errorf("could not open database: %w", err)
	}

	migrator, err := migrations.New(sdb)
	if err != nil {
		return fmt.Errorf("could not init migrations: %w", err)
	}

	if err := migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
		return fmt.Errorf("could not migrate database: %w", err)
	}

	db := database.New(sdb)
	mux := mux(db)

	go func() {
		addr := fmt.Sprintf(":%d", args.Port)
		slog.Debug(fmt.Sprintf("Listening at http://localhost%s", addr))
		if err := http.ListenAndServe(addr, mux); err != nil {
			panic(err)
		}
	}()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Kill, os.Interrupt)
	<-ch

	return nil
}

func main() {
	if err := maine(); err != nil {
		slog.Error("error during runtime", slog.Any("err", err))
	}
}