From 3557b32298a4d2ebbc4b9401aef667ab3a8e36f4 Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Tue, 2 Jun 2026 14:49:46 -0400 Subject: [PATCH] cmd/horsebot: serve data from mdb instead of json --- cmd/horsebot/main.go | 58 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/cmd/horsebot/main.go b/cmd/horsebot/main.go index 2cce94d..dcc5dc9 100644 --- a/cmd/horsebot/main.go +++ b/cmd/horsebot/main.go @@ -13,7 +13,7 @@ import ( "net/http" "os" "os/signal" - "path/filepath" + "strconv" "time" "github.com/disgoorg/disgo" @@ -24,16 +24,19 @@ import ( "github.com/disgoorg/disgo/httpserver" "github.com/disgoorg/disgo/rest" httpmiddle "github.com/go-chi/chi/v5/middleware" + "zombiezen.com/go/sqlite" + "zombiezen.com/go/sqlite/sqlitex" "git.sunturtle.xyz/zephyr/horse" + "git.sunturtle.xyz/zephyr/horse/mdb" ) func main() { var ( // public site - addr string - dataDir string - public string + addr string + mdbf string + public string // discord tokenFile string apiRoute string @@ -43,7 +46,7 @@ func main() { textfmt string ) flag.StringVar(&addr, "http", ":80", "`address` to bind HTTP server") - flag.StringVar(&dataDir, "data", "", "`dir`ectory containing exported json data") + flag.StringVar(&mdbf, "mdb", "", "`path` to master.mdb") flag.StringVar(&public, "public", "", "`dir`ectory containing the website to serve") flag.StringVar(&tokenFile, "token", "", "`file` containing the Discord bot token") flag.StringVar(&apiRoute, "route", "/interactions/callback", "`path` to serve Discord HTTP API calls") @@ -74,9 +77,17 @@ func main() { os.Exit(1) } - skills, err := loadSkills(filepath.Join(dataDir, "skill.json")) + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + + db, err := sqlitex.NewPool(mdbf, sqlitex.PoolOptions{Flags: sqlite.OpenReadOnly}) + if err != nil { + slog.Error("mdb", slog.Any("err", err)) + os.Exit(1) + } + + skills, err := mdb.Skills(ctx, db) slog.Info("loaded skills", slog.Int("count", len(skills))) - groups, err2 := loadSkillGroups(filepath.Join(dataDir, "skill-group.json")) + groups, err2 := mdb.SkillGroups(ctx, db) slog.Info("loaded skill groups", slog.Int("count", len(groups))) if err = errors.Join(err, err2); err != nil { slog.Error("loading data", slog.Any("err", err)) @@ -92,8 +103,6 @@ func main() { } token = bytes.TrimSuffix(token, []byte{'\n'}) - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) - r := handler.New() r.DefaultContext(func() context.Context { return ctx }) r.Use(middleware.Go) @@ -113,7 +122,16 @@ func main() { mux := http.NewServeMux() mux.Handle("GET /", httpmiddle.Compress(5)(http.FileServerFS(os.DirFS(public)))) - mux.Handle("GET /api/data/", httpmiddle.Compress(5)(http.StripPrefix("/api/data", http.FileServerFS(os.DirFS(dataDir))))) + mux.Handle("GET /api/data/affinity", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.AffinitySummary))) + mux.Handle("GET /api/data/character", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Characters))) + mux.Handle("GET /api/data/conversation", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Conversations))) + mux.Handle("GET /api/data/race", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Races))) + mux.Handle("GET /api/data/saddle", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Saddles))) + mux.Handle("GET /api/data/scenario", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Scenarios))) + mux.Handle("GET /api/data/skill", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Skills))) + mux.Handle("GET /api/data/skill-group", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.SkillGroups))) + mux.Handle("GET /api/data/spark", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Sparks))) + mux.Handle("GET /api/data/uma", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Umas))) if pubkey != "" { pk, err := hex.DecodeString(pubkey) if err != nil { @@ -209,3 +227,23 @@ func loadSkillGroups(file string) ([]horse.SkillGroup, error) { } return groups, nil } + +func dataroute[T any](ctx context.Context, db *sqlitex.Pool, f func(context.Context, *sqlitex.Pool) ([]T, error)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + data, err := f(ctx, db) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + b, err := json.Marshal(data) + if err != nil { + panic(err) + } + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Length", strconv.Itoa(len(b))) + if _, err := w.Write(b); err != nil { + slog.Error("writing response", slog.Any("err", err)) + return + } + } +}