From 7b8cdcfd7ba2b574587c4b7b1f792075d5cdf3aa Mon Sep 17 00:00:00 2001 From: noe Date: Wed, 26 Mar 2025 23:10:30 -0700 Subject: [PATCH 1/2] fix integration, test more stuff --- authmiddleware/authmiddleware.go | 20 +++++----- authmiddleware/session.go | 25 ++++++------ authmiddleware/util_test.go | 3 +- default.nix | 2 +- interactions/interactions.go | 2 +- interactions/interactions_test.go | 35 +++++++++++++++- main.go | 1 + roleypoly/fiber.go | 66 +++++++++++++++++++------------ roleypoly/fiber_test.go | 40 +++++++++++++++++++ static/humans.txt | 1 + types/keys.go | 3 ++ 11 files changed, 147 insertions(+), 51 deletions(-) create mode 100644 roleypoly/fiber_test.go create mode 100644 static/humans.txt create mode 100644 types/keys.go diff --git a/authmiddleware/authmiddleware.go b/authmiddleware/authmiddleware.go index 281bf02..41405cf 100644 --- a/authmiddleware/authmiddleware.go +++ b/authmiddleware/authmiddleware.go @@ -21,6 +21,16 @@ type AuthMiddleware struct { superuserIDs []string } +func New(discordClient discord.IDiscordClient, supportIDs []string, superuserIDs []string) func(fiber.Ctx) error { + am := AuthMiddleware{ + Client: discordClient, + supportIDs: supportIDs, + superuserIDs: superuserIDs, + } + + return am.Handle +} + func (am *AuthMiddleware) Handle(c fiber.Ctx) error { sc := session.FromContext(c) sess := am.Init(sc) @@ -52,16 +62,6 @@ func (am *AuthMiddleware) Commit(sc *session.Middleware, sess *Session) { sc.Set(SessionKey, sess.AsJSON()) } -func SessionFrom(c fiber.Ctx) (s *Session) { - sess := session.FromContext(c) - sessionJSON := sess.Get(SessionKey).([]byte) - s, err := SessionFromJSON(sessionJSON) - if err != nil { - log.Panicln("session failed to parse", err) - } - return s -} - func (am *AuthMiddleware) isSupport(userID string) bool { return slices.Contains(am.supportIDs, userID) } diff --git a/authmiddleware/session.go b/authmiddleware/session.go index c2e9f3d..34325bd 100644 --- a/authmiddleware/session.go +++ b/authmiddleware/session.go @@ -4,10 +4,15 @@ import ( "log" "time" - "git.sapphic.engineer/roleypoly/v4/discord" "git.sapphic.engineer/roleypoly/v4/types" "github.com/goccy/go-json" "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/session" +) + +var ( + SessionKey uint8 + DefaultSession *Session = &Session{Permissions: PermAnonymous} ) type Session struct { @@ -32,16 +37,12 @@ func SessionFromJSON(input []byte) (*Session, error) { return &s, err } -var SessionKey uint8 - -func New(discordClient discord.IDiscordClient, supportIDs []string, superuserIDs []string) func(fiber.Ctx) error { - am := AuthMiddleware{ - Client: discordClient, - supportIDs: supportIDs, - superuserIDs: superuserIDs, +func SessionFrom(c fiber.Ctx) (s *Session) { + sess := session.FromContext(c) + sessionJSON := sess.Get(SessionKey).([]byte) + s, err := SessionFromJSON(sessionJSON) + if err != nil { + log.Panicln("session failed to parse", err) } - - return am.Handle + return s } - -var DefaultSession *Session = &Session{Permissions: PermAnonymous} diff --git a/authmiddleware/util_test.go b/authmiddleware/util_test.go index 681407b..b77099b 100644 --- a/authmiddleware/util_test.go +++ b/authmiddleware/util_test.go @@ -9,6 +9,7 @@ import ( "git.sapphic.engineer/roleypoly/v4/authmiddleware" "git.sapphic.engineer/roleypoly/v4/discord" "git.sapphic.engineer/roleypoly/v4/discord/clientmock" + "git.sapphic.engineer/roleypoly/v4/roleypoly" "git.sapphic.engineer/roleypoly/v4/types" "github.com/goccy/go-json" "github.com/gofiber/fiber/v3" @@ -102,7 +103,7 @@ func ok(c fiber.Ctx) error { } func getApp(dc discord.IDiscordClient) *fiber.App { - app := fiber.New(fiber.Config{}) + app := roleypoly.CreateFiberApp() sessionMiddleware, sessionStore := session.NewWithStore() sessionStore.RegisterType(authmiddleware.Session{}) diff --git a/default.nix b/default.nix index 36ec488..1ff83fa 100644 --- a/default.nix +++ b/default.nix @@ -1,6 +1,6 @@ { pkgs ? import {}, - vendorHash ? "sha256-TvGGu74AWLrWRQvfAesVGJFTp4omPGVmIgyzB3QUKnc=", + vendorHash ? "sha256-ilEuRNE61UN22Jm6Yyv80S6VdRa1mB6J/Pde1x/DgEk=", }: rec { default = roleypoly; diff --git a/interactions/interactions.go b/interactions/interactions.go index 0a80b2b..3a5bcd1 100644 --- a/interactions/interactions.go +++ b/interactions/interactions.go @@ -27,7 +27,6 @@ type Interactions struct { } func NewInteractions(publicKey string, publicBaseURL string, guildsService *discord.GuildService) *Interactions { - return &Interactions{ PublicKey: ed25519.PublicKey(publicKey), PublicBaseURL: publicBaseURL, @@ -41,6 +40,7 @@ func (i *Interactions) Routes(r fiber.Router) { r.Post("/", i.PostHandler) i.Router.Add("hello-world", i.CmdHelloWorld) + i.Router.Add("roleypoly", i.CmdRoleypoly) } func (i *Interactions) PostHandler(c fiber.Ctx) error { diff --git a/interactions/interactions_test.go b/interactions/interactions_test.go index 1d2f1bf..f16347b 100644 --- a/interactions/interactions_test.go +++ b/interactions/interactions_test.go @@ -7,11 +7,13 @@ import ( "net/http" "testing" + "git.sapphic.engineer/roleypoly/v4/authmiddleware" "git.sapphic.engineer/roleypoly/v4/discord" "git.sapphic.engineer/roleypoly/v4/interactions" "git.sapphic.engineer/roleypoly/v4/roleypoly" "github.com/goccy/go-json" "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/session" "github.com/stretchr/testify/assert" ) @@ -28,7 +30,37 @@ func TestNewInteractions(t *testing.T) { func TestPostHandler(t *testing.T) { i, _, key := makeInteractions(t) - app := fiber.New() + app := roleypoly.CreateFiberApp() + i.Routes(app) + + body, err := json.Marshal(map[string]any{ + "type": 1, + }) + if err != nil { + t.Error(err) + return + } + + sig, ts := sign(t, key, body) + + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(body)) + req.Header.Add("X-Signature-Ed25519", sig) + req.Header.Add("X-Signature-Timestamp", ts) + + res, err := app.Test(req, fiber.TestConfig{}) + + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) +} + +func TestPostHandlerIntegration(t *testing.T) { + i, dsm, key := makeInteractions(t) + app := roleypoly.CreateFiberApp() + sessionMiddleware, sessionStore := session.NewWithStore() + sessionStore.RegisterType(authmiddleware.Session{}) + app.Use(sessionMiddleware) + app.Use(authmiddleware.New(dsm, []string{}, []string{})) + i.Routes(app) body, err := json.Marshal(map[string]any{ @@ -54,6 +86,7 @@ func TestPostHandler(t *testing.T) { func TestPostHandlerSigFail(t *testing.T) { i, _, _ := makeInteractions(t) app := roleypoly.CreateFiberApp() + i.Routes(app) body, err := json.Marshal(map[string]any{ diff --git a/main.go b/main.go index a467e20..b75ce5f 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ func main() { utils.DiscordBotToken, ) + roleypoly.SetupStatic(app) roleypoly.SetupControllers( app, dc, diff --git a/roleypoly/fiber.go b/roleypoly/fiber.go index 33a5706..d546d41 100644 --- a/roleypoly/fiber.go +++ b/roleypoly/fiber.go @@ -19,6 +19,7 @@ import ( staticfs "git.sapphic.engineer/roleypoly/v4/static" "git.sapphic.engineer/roleypoly/v4/templates" "git.sapphic.engineer/roleypoly/v4/testing" + "git.sapphic.engineer/roleypoly/v4/types" ) func CreateFiberApp() *fiber.App { @@ -31,24 +32,6 @@ func CreateFiberApp() *fiber.App { ViewsLayout: "layouts/main", }) - sessionMiddleware, sessionStore := session.NewWithStore() - sessionStore.RegisterType(authmiddleware.Session{}) - - app.Use(sessionMiddleware) - app.Use(csrf.New(csrf.Config{ - Session: sessionStore, - })) - - app.Get("/static*", static.New("", static.Config{ - FS: staticfs.FS, - Compress: true, - CacheDuration: 24 * 8 * time.Hour, - })) - app.Get("/favicon.ico", oneStatic) - app.Get("/manifest.json", oneStatic) - app.Get("/robots.txt", oneStatic) - app.Get("/humans.txt", oneStatic) - return app } @@ -63,16 +46,49 @@ func oneStatic(c fiber.Ctx) error { return c.SendStream(f) } -func SetupControllers(app *fiber.App, dc *discord.DiscordClient, publicKey string, publicBaseURL string) { - gs := discord.NewGuildService(dc) +func SetupStatic(app *fiber.App) { + app.Get("/static*", static.New("", static.Config{ + FS: staticfs.FS, + Compress: true, + CacheDuration: 24 * 8 * time.Hour, + })) + app.Get("/favicon.ico", oneStatic) + app.Get("/manifest.json", oneStatic) + app.Get("/robots.txt", oneStatic) + app.Get("/humans.txt", oneStatic) +} - (&testing.TestingController{ - Guilds: gs, - }).Routes(app.Group("/testing")) +type ControllerConfig struct { + DiscordClient discord.IDiscordClient + PublicKey string + PublicBaseURL string + SupportIDs []string + SuperuserIDs []string +} - interactions.NewInteractions(publicKey, publicBaseURL, gs).Routes(app.Group("/interactions")) +func SetupControllers(app *fiber.App, cfg ControllerConfig) { + app.Use(func(c fiber.Ctx) error { + c.Locals(types.ConfigKey, cfg) + return c.Next() + }) + + sessionMiddleware, sessionStore := session.NewWithStore() + sessionStore.RegisterType(authmiddleware.Session{}) + + app.Use(sessionMiddleware) + app.Use(csrf.New(csrf.Config{ + Session: sessionStore, + })) + app.Use(authmiddleware.New(cfg.DiscordClient, cfg.SupportIDs, cfg.SuperuserIDs)) + + gs := discord.NewGuildService(cfg.DiscordClient) + + (&testing.TestingController{Guilds: gs}). + Routes(app.Group("/testing")) + + interactions.NewInteractions(cfg.PublicKey, cfg.PublicBaseURL, gs). + Routes(app.Group("/interactions")) - app.Use(authmiddleware.New(dc, []string{}, []string{})) } // func getStorageDirectory() string { diff --git a/roleypoly/fiber_test.go b/roleypoly/fiber_test.go new file mode 100644 index 0000000..ea85bcf --- /dev/null +++ b/roleypoly/fiber_test.go @@ -0,0 +1,40 @@ +package roleypoly_test + +import ( + "bytes" + "net/http" + "testing" + + "git.sapphic.engineer/roleypoly/v4/roleypoly" + "github.com/stretchr/testify/assert" +) + +func TestStaticRoot(t *testing.T) { + app := roleypoly.CreateFiberApp() + roleypoly.SetupStatic(app) + + req, err := http.NewRequest("GET", "/humans.txt", nil) + assert.Nil(t, err) + + resp, err := app.Test(req) + assert.Nil(t, err) + + buf := bytes.Buffer{} + buf.ReadFrom(resp.Body) + assert.Equal(t, "you're on thin ice, soup head.", buf.String()) +} + +func TestStaticPath(t *testing.T) { + app := roleypoly.CreateFiberApp() + roleypoly.SetupStatic(app) + + req, err := http.NewRequest("GET", "/static/humans.txt", nil) + assert.Nil(t, err) + + resp, err := app.Test(req) + assert.Nil(t, err) + + buf := bytes.Buffer{} + buf.ReadFrom(resp.Body) + assert.Equal(t, "you're on thin ice, soup head.", buf.String()) +} diff --git a/static/humans.txt b/static/humans.txt new file mode 100644 index 0000000..4c416a0 --- /dev/null +++ b/static/humans.txt @@ -0,0 +1 @@ +you're on thin ice, soup head. \ No newline at end of file diff --git a/types/keys.go b/types/keys.go new file mode 100644 index 0000000..7c38735 --- /dev/null +++ b/types/keys.go @@ -0,0 +1,3 @@ +package types + +var ConfigKey int From 143caaac2bcd39a725cad085b973e89c00f69bf7 Mon Sep 17 00:00:00 2001 From: noe Date: Thu, 27 Mar 2025 10:19:25 -0700 Subject: [PATCH 2/2] devex --- .air.toml | 52 +++++++++++++++++++++++++++++++ .gitignore | 3 +- README.md | 40 ++++++++++++++++++++++-- interactions/interactions_test.go | 2 ++ justfile | 9 ++++-- main.go | 10 ++++-- shell.nix | 2 +- templates/tests/landing.html | 0 utils/env.go | 7 ++++- 9 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 .air.toml create mode 100644 templates/tests/landing.html diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..2f2e011 --- /dev/null +++ b/.air.toml @@ -0,0 +1,52 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] +args_bin = [] +bin = "./tmp/main" +cmd = "go build -o ./tmp/main ." +delay = 1000 +exclude_dir = ["assets", "tmp", "vendor", "testdata", "scripts"] +exclude_file = [] +exclude_regex = ["_test.go"] +exclude_unchanged = false +follow_symlink = false +full_bin = "" +include_dir = [] +include_ext = ["go", "tpl", "tmpl", "html", "css", "js", "svg", "png"] +include_file = [] +kill_delay = "0s" +log = "build-errors.log" +poll = false +poll_interval = 0 +post_cmd = [] +pre_cmd = [] +rerun = false +rerun_delay = 500 +send_interrupt = false +stop_on_error = false + +[color] +app = "" +build = "yellow" +main = "magenta" +runner = "green" +watcher = "cyan" + +[log] +main_only = false +silent = false +time = false + +[misc] +clean_on_exit = false + +[proxy] +app_port = 8169 +enabled = true +proxy_port = 8170 + +[screen] +clear_on_rebuild = false +keep_scroll = true diff --git a/.gitignore b/.gitignore index e44b9f1..3d51441 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .direnv .env .storage -result \ No newline at end of file +result +tmp \ No newline at end of file diff --git a/README.md b/README.md index e393cc5..be2dabe 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,45 @@ yeah 4 of em.. ## Developing -use `nix shell` or `nix-shell` or maybe just run `go run .` or somethin its ok +highly recommended to use nix. its actually optional, but there's caveats. -please run `just precommit` before committing thx, it fixes weird build issues +**Quickstart** + +```sh +## - Step one, get an environment! +nix develop +# or +nix-shell +# or +direnv allow + +## - Step two, setup .env +cp .env.example .env +# fill out .env (the file has instructions) + +## - Step three, run the thing!! +just +# or .. maybe this.. but it may be unpredictable +go run . + +## - Step four, clean up the code +just precommit +``` + +> _the caveat: using `go` alone is doable. certain things won't happen, like dependency tagging and env var loading, so.. consider `nix`.._ + +### just commands + +- **watch** - (default) runs air livereloader + - open https://localhost:8170 for live reloading, note: the console will output 8169. that works too, but doesn't reload browser on changes +- **run** - runs the app with go run +- **nix-run** - runs the app built via nix (slower) +- **run-container** - builds the app as a docker image, then runs it. (slowest) +- **fmt** - go fmt and prettier +- **tidy** - fix gomods +- **test** - runs normal tests +- **update-vendor-hash** - updates `default.nix` vendorHash field, it won't build without it. +- **precommit** - runs fmt, tidy, update-vendor-hash, and test, in that order. ## Deploying diff --git a/interactions/interactions_test.go b/interactions/interactions_test.go index f16347b..1abfcb6 100644 --- a/interactions/interactions_test.go +++ b/interactions/interactions_test.go @@ -56,6 +56,8 @@ func TestPostHandler(t *testing.T) { func TestPostHandlerIntegration(t *testing.T) { i, dsm, key := makeInteractions(t) app := roleypoly.CreateFiberApp() + + // Tests that everything works including auth middleware sessionMiddleware, sessionStore := session.NewWithStore() sessionStore.RegisterType(authmiddleware.Session{}) app.Use(sessionMiddleware) diff --git a/justfile b/justfile index 1bea0d0..6e7217e 100644 --- a/justfile +++ b/justfile @@ -1,3 +1,6 @@ +watch: + air + run: go run . @@ -15,11 +18,13 @@ fmt: go fmt ./... tidy: - go clean -modcache go mod tidy update-vendor-hash: scripts/update-vendor-hash.sh test: - go test ./... \ No newline at end of file + go test ./... + +clean-repo: + rm -rf tmp result \ No newline at end of file diff --git a/main.go b/main.go index b75ce5f..a1ac032 100644 --- a/main.go +++ b/main.go @@ -20,9 +20,13 @@ func main() { roleypoly.SetupStatic(app) roleypoly.SetupControllers( app, - dc, - utils.DiscordPublicKey, - utils.PublicBaseURL, + roleypoly.ControllerConfig{ + DiscordClient: dc, + PublicKey: utils.DiscordPublicKey, + PublicBaseURL: utils.PublicBaseURL, + SupportIDs: utils.SupportIDs, + SuperuserIDs: utils.SupueruserIDs, + }, ) log.Fatal(app.Listen(utils.ListenAddr)) diff --git a/shell.nix b/shell.nix index d55a473..944bef8 100644 --- a/shell.nix +++ b/shell.nix @@ -3,6 +3,6 @@ go just nil - just + air ]; } diff --git a/templates/tests/landing.html b/templates/tests/landing.html new file mode 100644 index 0000000..e69de29 diff --git a/utils/env.go b/utils/env.go index 7a3294c..2007562 100644 --- a/utils/env.go +++ b/utils/env.go @@ -1,6 +1,9 @@ package utils -import "os" +import ( + "os" + "strings" +) var ( DiscordBotToken = os.Getenv("DISCORD_BOT_TOKEN") @@ -9,4 +12,6 @@ var ( DiscordPublicKey = os.Getenv("DISCORD_PUBLIC_KEY") ListenAddr = Or(os.Getenv("LISTEN_ADDR"), ":8169") PublicBaseURL = os.Getenv("PUBLIC_BASE_URL") + SupportIDs = strings.Split(os.Getenv("SUPPORT_IDS"), ",") + SupueruserIDs = strings.Split(os.Getenv("SUPERUSER_IDS"), ",") )