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))
}
}
|