Compare commits
2 commits
c50bfc1a7f
...
143caaac2b
Author | SHA1 | Date | |
---|---|---|---|
143caaac2b | |||
7b8cdcfd7b |
18 changed files with 262 additions and 61 deletions
52
.air.toml
Normal file
52
.air.toml
Normal file
|
@ -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
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
.direnv
|
||||
.env
|
||||
.storage
|
||||
result
|
||||
result
|
||||
tmp
|
40
README.md
40
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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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{})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
pkgs ? import <nixpkgs> {},
|
||||
vendorHash ? "sha256-TvGGu74AWLrWRQvfAesVGJFTp4omPGVmIgyzB3QUKnc=",
|
||||
vendorHash ? "sha256-ilEuRNE61UN22Jm6Yyv80S6VdRa1mB6J/Pde1x/DgEk=",
|
||||
}:
|
||||
rec {
|
||||
default = roleypoly;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,39 @@ 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()
|
||||
|
||||
// Tests that everything works including auth middleware
|
||||
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 +88,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{
|
||||
|
|
9
justfile
9
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 ./...
|
||||
go test ./...
|
||||
|
||||
clean-repo:
|
||||
rm -rf tmp result
|
11
main.go
11
main.go
|
@ -17,11 +17,16 @@ func main() {
|
|||
utils.DiscordBotToken,
|
||||
)
|
||||
|
||||
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))
|
||||
|
|
|
@ -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 {
|
||||
|
|
40
roleypoly/fiber_test.go
Normal file
40
roleypoly/fiber_test.go
Normal file
|
@ -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())
|
||||
}
|
|
@ -3,6 +3,6 @@
|
|||
go
|
||||
just
|
||||
nil
|
||||
just
|
||||
air
|
||||
];
|
||||
}
|
||||
|
|
1
static/humans.txt
Normal file
1
static/humans.txt
Normal file
|
@ -0,0 +1 @@
|
|||
you're on thin ice, soup head.
|
0
templates/tests/landing.html
Normal file
0
templates/tests/landing.html
Normal file
3
types/keys.go
Normal file
3
types/keys.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package types
|
||||
|
||||
var ConfigKey int
|
|
@ -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"), ",")
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue