Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fea: add Lua support for scripting #5

Merged
merged 16 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .air.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fmt:

t: test
test: ./tests/
go test -v ./tests/
go test -v ./...

encrypt: .env
gpg -c .env
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ 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
)

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
Expand All @@ -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
Expand All @@ -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
)
7 changes: 5 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
5 changes: 0 additions & 5 deletions internal/entities/routes.go

This file was deleted.

47 changes: 47 additions & 0 deletions internal/lua/lua.go
Original file line number Diff line number Diff line change
@@ -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()
27 changes: 27 additions & 0 deletions internal/lua/lua_test.go
Original file line number Diff line number Diff line change
@@ -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")
})
}
30 changes: 30 additions & 0 deletions internal/server/routes/api/lua.go
Original file line number Diff line number Diff line change
@@ -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")
}
52 changes: 37 additions & 15 deletions internal/server/routes/api/routes.go
Original file line number Diff line number Diff line change
@@ -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)
}
17 changes: 16 additions & 1 deletion internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package server
import (
"fmt"
"log"
"os"
"os/signal"
"time"

"github.com/gofiber/fiber/v2"
Expand Down Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
## In-Progress // upcoming

- [ ] handle errors for form validation
- [ ] make menu lazyloaded by htmx

## Backlog

Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions views/partials/code.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<code id="code-output">
{{range .Lines}}
<p>{{.}}</p>
{{end}}
</code>
19 changes: 19 additions & 0 deletions views/partials/editor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div class="grid cols-1 py-12">
<form
class="grid cols-1 gap-4"
hx-post="/api/lua"
method="POST"
hx-swap="outerHTML"
hx-target="#code-output"
>
<label for="editor" class="w-full">Enter your Lua code:</label>

<textarea id="editor" name="snippet" rows="10" cols="80" class="w-full">
print("Hello, 🌙!")</textarea
>

<button type="submit" class="btn btn-primary w-full">▶️ Run</button>
</form>

<code id="code-output"> No output yet. </code>
</div>
2 changes: 1 addition & 1 deletion views/partials/footer.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<footer class="py-10">
<p class="text-center text-sm">Made with ☕️ + 🍋 + ❤️</p>
<p class="text-center text-sm">Made with ☕️ + ❤️</p>
</footer>
2 changes: 1 addition & 1 deletion views/partials/now.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<p class="hover:text-lg text-center">
<p class="text-center">
<code>{{ .Time }}</code>
</p>
4 changes: 3 additions & 1 deletion views/playground.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<h1 class="text-2xl">{{.Title}}</h1>

<button
hx-trigger="click, every 1s"
hx-trigger="click, every 1m"
hx-get="/api/now"
hx-swap="innerHTML"
hx-target="#time"
Expand All @@ -20,3 +20,5 @@ <h1 class="text-2xl">{{.Title}}</h1>
Contents of this box will be updated in real time with every SSE message
received from the chatroom.
</div>

{{ template "partials/editor" . }}