diff --git a/.air.toml b/.air.toml
index 53197bd..a3dd87b 100644
--- a/.air.toml
+++ b/.air.toml
@@ -6,7 +6,7 @@ testdata_dir = "testdata"
[build]
args_bin = []
bin = "./tmp/server"
- cmd = "CGO_ENABLED=1 go build -o ./tmp/server ./cmd/api/main.go"
+ cmd = "pkill server; CGO_ENABLED=1 go build -o ./tmp/server ./cmd/main.go"
include_ext = ["go", "tpl", "tmpl", "html"]
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_regex = ["(\\.null-ls.*|_test)\\.go"]
diff --git a/Makefile b/Makefile
index 7859db3..bd75c46 100644
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@ fmt:
t: test
test: ./tests/
- go test -v ./tests/
+ go test -v ./...
encrypt: .env
gpg -c .env
diff --git a/go.mod b/go.mod
index 0267644..e7bf9c2 100644
--- a/go.mod
+++ b/go.mod
@@ -3,12 +3,14 @@ module github.com/marco-souza/marco.fly.dev
go 1.22
require (
+ github.com/Shopify/go-lua v0.0.0-20240312125312-5d657e363856
github.com/go-playground/validator/v10 v10.16.0
github.com/gofiber/fiber/v2 v2.52.1
github.com/gofiber/storage/sqlite3/v2 v2.1.0
github.com/gofiber/template/html/v2 v2.0.5
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2
github.com/joho/godotenv v1.5.1
+ github.com/stretchr/testify v1.9.0
github.com/valyala/fasthttp v1.51.0
gorm.io/driver/sqlite v1.5.4
gorm.io/gorm v1.25.5
@@ -16,6 +18,7 @@ require (
require (
github.com/andybalholm/brotli v1.0.6 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
@@ -31,6 +34,7 @@ require (
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-sqlite3 v1.14.18 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
@@ -39,4 +43,5 @@ require (
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 80ac990..622fff5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/Shopify/go-lua v0.0.0-20240312125312-5d657e363856 h1:N32EXb3LZ0FJwbdoMokLXysTHgWHccuYUPxmVqPowkw=
+github.com/Shopify/go-lua v0.0.0-20240312125312-5d657e363856/go.mod h1:M4CxjVc/1Nwka5atBv7G/sb7Ac2BDe3+FxbiT9iVNIQ=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -59,8 +61,8 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@@ -109,6 +111,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/internal/entities/routes.go b/internal/entities/routes.go
deleted file mode 100644
index 0869f7f..0000000
--- a/internal/entities/routes.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package entities
-
-import "github.com/gofiber/fiber/v2"
-
-type Register func(router fiber.Router)
diff --git a/internal/lua/lua.go b/internal/lua/lua.go
new file mode 100644
index 0000000..d54017a
--- /dev/null
+++ b/internal/lua/lua.go
@@ -0,0 +1,47 @@
+package lua
+
+import (
+ "io"
+ "log"
+ "os"
+
+ "github.com/Shopify/go-lua"
+)
+
+type luaRuntime struct {
+ l *lua.State
+}
+
+func new() *luaRuntime {
+ l := lua.NewState()
+ lua.OpenLibraries(l)
+ return &luaRuntime{l}
+}
+
+func (r *luaRuntime) Run(snippet string) (string, error) {
+ log.Println("Running Lua snippet", snippet)
+
+ outputReader, outputWriter, _ := os.Pipe()
+ rescueStdout := os.Stdout // save the actual stdout
+ os.Stdout = outputWriter // redirect stdout to pipe
+
+ err := lua.DoString(r.l, snippet)
+ if err != nil {
+ log.Println("Error running Lua snippet", err)
+ return "", err
+ }
+
+ outputWriter.Close() // close pipe writer
+ os.Stdout = rescueStdout // restore stdout
+
+ output, err := io.ReadAll(outputReader)
+ if err != nil {
+ log.Println("Error reading Lua output", err)
+ return "", err
+ }
+
+ log.Println("Lua output", string(output))
+ return string(output), nil
+}
+
+var Runtime = new()
diff --git a/internal/lua/lua_test.go b/internal/lua/lua_test.go
new file mode 100644
index 0000000..3833905
--- /dev/null
+++ b/internal/lua/lua_test.go
@@ -0,0 +1,27 @@
+package lua
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestLua(t *testing.T) {
+ t.Run("should run code if valid", func(t *testing.T) {
+ snippet := "print(10^3)"
+ output, err := Runtime.Run(snippet)
+ assert.Nil(t, err)
+ assert.Contains(t, output, "1000")
+ })
+
+ t.Run("should error with invalid code", func(t *testing.T) {
+ _, err := Runtime.Run("(- (+ 1 1) 2)")
+ assert.Error(t, err, "syntax error")
+
+ _, err = Runtime.Run("function js() { return 1+1 } js()")
+ assert.Error(t, err, "syntax error")
+
+ _, err = Runtime.Run("invalid code")
+ assert.Error(t, err, "syntax error")
+ })
+}
diff --git a/internal/server/routes/api/lua.go b/internal/server/routes/api/lua.go
new file mode 100644
index 0000000..d40129d
--- /dev/null
+++ b/internal/server/routes/api/lua.go
@@ -0,0 +1,30 @@
+package api
+
+import (
+ "log"
+ "strings"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/marco-souza/marco.fly.dev/internal/lua"
+)
+
+func luaHandler(c *fiber.Ctx) error {
+ snippet := c.FormValue("snippet")
+ if snippet == "" {
+ log.Println("No snippet provided.")
+ lines := []string{"No output yet."}
+ return c.Render("partials/code", fiber.Map{"Lines": lines}, "layouts/empty")
+ }
+
+ log.Println("Lua code:", snippet)
+ code, err := lua.Runtime.Run(snippet)
+ if err != nil {
+ log.Println("Lua error:", err)
+ lines := []string{err.Error()}
+ return c.Render("partials/code", fiber.Map{"Lines": lines}, "layouts/empty")
+ }
+
+ log.Println("Lua output:", code)
+ lines := strings.Split(code, "\n")
+ return c.Render("partials/code", fiber.Map{"Lines": lines}, "layouts/empty")
+}
diff --git a/internal/server/routes/api/routes.go b/internal/server/routes/api/routes.go
index f1445e2..159ca05 100644
--- a/internal/server/routes/api/routes.go
+++ b/internal/server/routes/api/routes.go
@@ -1,41 +1,63 @@
package api
import (
+ "log"
+
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/limiter"
"github.com/gofiber/storage/sqlite3/v2"
"github.com/marco-souza/marco.fly.dev/internal/config"
+ "github.com/marco-souza/marco.fly.dev/internal/github"
)
var conf = config.Load()
+// create auth check middleware
+func authMiddleware(c *fiber.Ctx) error {
+ token := github.AccessToken(c)
+ if token == "" {
+ log.Println("Unauthorized access, redirecting to login page.")
+ return c.Redirect(conf.Github.LoginPage)
+ }
+ return c.Next()
+}
+
func Apply(router fiber.Router) {
// https://docs.gofiber.io/api/middleware/limiter
- router.Use(limiter.New(limiter.Config{
- Max: conf.RateLimit,
- Storage: sqlite3.New(),
- }))
+ if conf.Env == "production" {
+ router.Use(limiter.New(limiter.Config{
+ Max: conf.RateLimit,
+ Storage: sqlite3.New(),
+ }))
+ }
- router.Group("/orders").
- Get("/", ordersHandler).
- Post("/", createOrderHandler).
- Delete("/:id", deleteOrderHandler)
+ // public routes
+ router.Group("/").
+ Get("/resume", resumeHandler).
+ Get("/menu", menuHandler)
+ // auth
router.Group("/auth/github").
Get("/", redirectGithubAuth).
Get("/callback", callbackGithubAuth).
Get("/refresh", logoutGithubAuth).
Get("/logout", logoutGithubAuth)
- // ref into one Group
- router.Group("/").
- Get("/now", nowHandler).
- Get("/sse", sseHandler).
- Get("/resume", resumeHandler).
- Get("/menu", menuHandler)
-
if conf.Env == "development" {
router.Get("/reload", sseReloadHandler)
}
+
+ // private routes
+ router.Use(authMiddleware)
+
+ router.Group("/").
+ Post("/lua", luaHandler).
+ Get("/now", nowHandler).
+ Get("/sse", sseHandler)
+
+ router.Group("/orders").
+ Get("/", ordersHandler).
+ Post("/", createOrderHandler).
+ Delete("/:id", deleteOrderHandler)
}
diff --git a/internal/server/server.go b/internal/server/server.go
index 15a9faf..f2b3ffc 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -3,6 +3,8 @@ package server
import (
"fmt"
"log"
+ "os"
+ "os/signal"
"time"
"github.com/gofiber/fiber/v2"
@@ -53,7 +55,20 @@ func (s *server) Start() {
models.Seed()
}
- log.Fatal(s.app.Listen(s.addr))
+ shutdown := make(chan os.Signal, 1)
+ signal.Notify(shutdown, os.Interrupt) // register channel to interrupt signals
+ teardown := func() {
+ <-shutdown
+ fmt.Println("shutting down server...")
+ s.app.Shutdown()
+ }
+
+ go teardown() // start listening for interrupt signals
+
+ // await for server to shutdown
+ if err := s.app.Listen(s.addr); err != nil {
+ log.Fatal(err)
+ }
}
func (s *server) setupRoutes() {
diff --git a/todo.md b/todo.md
index 2c5e117..9c98127 100644
--- a/todo.md
+++ b/todo.md
@@ -3,7 +3,6 @@
## In-Progress // upcoming
- [ ] handle errors for form validation
- - [ ] make menu lazyloaded by htmx
## Backlog
@@ -14,6 +13,7 @@
## Done
+ - [x] make menu lazyloaded by htmx
- [x] make resume page
- [x] make resume lazy loadede by htmx
- [x] add midleware for private routes
diff --git a/views/partials/code.html b/views/partials/code.html
new file mode 100644
index 0000000..f964452
--- /dev/null
+++ b/views/partials/code.html
@@ -0,0 +1,5 @@
+ {{.}}
+ {{range .Lines}}
+
diff --git a/views/partials/editor.html b/views/partials/editor.html
new file mode 100644
index 0000000..4e930b9
--- /dev/null
+++ b/views/partials/editor.html
@@ -0,0 +1,19 @@
+
No output yet.
++
{{ .Time }}