Updated to BunRouter
This commit is contained in:
parent
ab639dec20
commit
8d3f803474
17 changed files with 387 additions and 160 deletions
|
@ -2,7 +2,7 @@
|
||||||
Go + Templates + HTMX
|
Go + Templates + HTMX
|
||||||
|
|
||||||
## Stack:
|
## Stack:
|
||||||
- Backend: Golang + Echo
|
- Backend: Golang + BunRouter
|
||||||
- Rendering: Golang templates
|
- Rendering: Golang templates
|
||||||
- Style: TailwindCSS + DaisyUI
|
- Style: TailwindCSS + DaisyUI
|
||||||
- Content format: Markdown
|
- Content format: Markdown
|
||||||
|
@ -19,5 +19,5 @@ Go + Templates + HTMX
|
||||||
5. Run ```air``` to start the dev server
|
5. Run ```air``` to start the dev server
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
Without Coverage: `go test atri.dad/lib`
|
Without Coverage: `go test goth.stack/lib`
|
||||||
With Coverage: `go test atri.dad/lib -cover`
|
With Coverage: `go test goth.stack/lib -cover`
|
|
@ -3,9 +3,11 @@ package api
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/uptrace/bunrouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Ping(c echo.Context) error {
|
func Ping(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
return c.String(http.StatusOK, "Pong!")
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("Pong!"))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
24
api/sse.go
24
api/sse.go
|
@ -3,26 +3,28 @@ package api
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/uptrace/bunrouter"
|
||||||
"goth.stack/lib"
|
"goth.stack/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SSEDemo(c echo.Context) error {
|
func SSE(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
channel := c.QueryParam("channel")
|
queryParams := req.URL.Query()
|
||||||
|
channel := queryParams.Get("channel")
|
||||||
if channel == "" {
|
if channel == "" {
|
||||||
channel = "default"
|
channel = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the request context, which is cancelled when the client disconnects
|
// Use the request context, which is cancelled when the client disconnects
|
||||||
ctx := c.Request().Context()
|
ctx := req.Context()
|
||||||
|
|
||||||
pubsub, _ := lib.Subscribe(lib.RedisClient, channel)
|
pubsub, _ := lib.Subscribe(lib.RedisClient, channel)
|
||||||
|
|
||||||
c.Response().Header().Set(echo.HeaderContentType, "text/event-stream")
|
w.Header().Set("Content-Type", "text/event-stream")
|
||||||
c.Response().Header().Set(echo.HeaderConnection, "keep-alive")
|
w.Header().Set("Connection", "keep-alive")
|
||||||
c.Response().Header().Set(echo.HeaderCacheControl, "no-cache")
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
|
||||||
// Create a ticker that fires every 15 seconds
|
// Create a ticker that fires every 15 seconds
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
@ -35,10 +37,10 @@ func SSEDemo(c echo.Context) error {
|
||||||
return nil
|
return nil
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
// Every 30 seconds, send a comment to keep the connection alive
|
// Every 30 seconds, send a comment to keep the connection alive
|
||||||
if _, err := c.Response().Write([]byte(": keep-alive\n\n")); err != nil {
|
if _, err := w.Write([]byte(": keep-alive\n\n")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.Response().Flush()
|
w.(http.Flusher).Flush()
|
||||||
default:
|
default:
|
||||||
// Handle incoming messages as before
|
// Handle incoming messages as before
|
||||||
msg, err := pubsub.ReceiveMessage(ctx)
|
msg, err := pubsub.ReceiveMessage(ctx)
|
||||||
|
@ -48,11 +50,11 @@ func SSEDemo(c echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
data := fmt.Sprintf("data: %s\n\n", msg.Payload)
|
data := fmt.Sprintf("data: %s\n\n", msg.Payload)
|
||||||
if _, err := c.Response().Write([]byte(data)); err != nil {
|
if _, err := w.Write([]byte(data)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Response().Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,32 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/uptrace/bunrouter"
|
||||||
"goth.stack/lib"
|
"goth.stack/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SSEDemoSend(c echo.Context) error {
|
func SSEDemoSend(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
channel := c.QueryParam("channel")
|
// Get query parameters
|
||||||
|
queryParams := req.URL.Query()
|
||||||
|
|
||||||
|
// Get channel from query parameters
|
||||||
|
channel := queryParams.Get("channel")
|
||||||
if channel == "" {
|
if channel == "" {
|
||||||
channel = "default"
|
channel = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get message from query parameters, form value, or request body
|
// Get message from query parameters, form value, or request body
|
||||||
message := c.QueryParam("message")
|
message := queryParams.Get("message")
|
||||||
if message == "" {
|
if message == "" {
|
||||||
message = c.FormValue("message")
|
message = req.PostFormValue("message")
|
||||||
if message == "" {
|
if message == "" {
|
||||||
var body map[string]string
|
var body map[string]string
|
||||||
if err := c.Bind(&body); err != nil {
|
err := json.NewDecoder(req.Body).Decode(&body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
message = body["message"]
|
message = body["message"]
|
||||||
|
@ -27,11 +34,18 @@ func SSEDemoSend(c echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if message == "" {
|
if message == "" {
|
||||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "message parameter is required"})
|
errMsg := map[string]string{"error": "message parameter is required"}
|
||||||
|
errMsgBytes, _ := json.Marshal(errMsg)
|
||||||
|
http.Error(w, string(errMsgBytes), http.StatusBadRequest)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send message
|
// Send message
|
||||||
lib.SendSSE("default", message)
|
lib.SendSSE("default", message)
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, map[string]string{"status": "message sent"})
|
statusMsg := map[string]string{"status": "message sent"}
|
||||||
|
statusMsgBytes, _ := json.Marshal(statusMsg)
|
||||||
|
w.Write(statusMsgBytes)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
16
go.mod
16
go.mod
|
@ -4,7 +4,6 @@ go 1.21.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma/v2 v2.12.0
|
github.com/alecthomas/chroma/v2 v2.12.0
|
||||||
github.com/labstack/echo/v4 v4.11.4
|
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,29 +13,30 @@ require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||||
|
github.com/fatih/color v1.16.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.22.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/assert/v2 v2.4.1
|
github.com/alecthomas/assert/v2 v2.4.1
|
||||||
github.com/go-redis/redismock/v9 v9.2.0
|
github.com/go-redis/redismock/v9 v9.2.0
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
github.com/google/uuid v1.6.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/labstack/gommon v0.4.2 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.4.0
|
github.com/redis/go-redis/v9 v9.4.0
|
||||||
github.com/resendlabs/resend-go v1.7.0
|
github.com/resendlabs/resend-go v1.7.0
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/unrolled/secure v1.14.0
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/uptrace/bunrouter v1.0.21
|
||||||
|
github.com/uptrace/bunrouter/extra/reqlog v1.0.21
|
||||||
github.com/yuin/goldmark v1.6.0
|
github.com/yuin/goldmark v1.6.0
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
golang.org/x/crypto v0.18.0 // indirect
|
|
||||||
golang.org/x/net v0.20.0 // indirect
|
golang.org/x/net v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
|
||||||
golang.org/x/time v0.5.0 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
38
go.sum
38
go.sum
|
@ -21,22 +21,22 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
|
||||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/go-redis/redismock/v9 v9.2.0 h1:ZrMYQeKPECZPjOj5u9eyOjg8Nnb0BS9lkVIZ6IpsKLw=
|
github.com/go-redis/redismock/v9 v9.2.0 h1:ZrMYQeKPECZPjOj5u9eyOjg8Nnb0BS9lkVIZ6IpsKLw=
|
||||||
github.com/go-redis/redismock/v9 v9.2.0/go.mod h1:18KHfGDK4Y6c2R0H38EUGWAdc7ZQS9gfYxc94k7rWT0=
|
github.com/go-redis/redismock/v9 v9.2.0/go.mod h1:18KHfGDK4Y6c2R0H38EUGWAdc7ZQS9gfYxc94k7rWT0=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
|
|
||||||
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
|
|
||||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
|
||||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
@ -58,17 +58,25 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
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.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/unrolled/secure v1.14.0 h1:u9vJTU/pR4Bny0ntLUMxdfLtmIRGvQf2sEFuA0TG9AE=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/unrolled/secure v1.14.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
github.com/uptrace/bunrouter v1.0.21 h1:HXarvX+N834sXyHpl+I/TuE11m19kLW/qG5u3YpHUag=
|
||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
github.com/uptrace/bunrouter v1.0.21/go.mod h1:TwT7Bc0ztF2Z2q/ZzMuSVkcb/Ig/d3MQeP2cxn3e1hI=
|
||||||
|
github.com/uptrace/bunrouter/extra/reqlog v1.0.21 h1:k9ebZATe9NOBUrPcMYJAQ2OMb62ERRN7qfwNwoa6Kxs=
|
||||||
|
github.com/uptrace/bunrouter/extra/reqlog v1.0.21/go.mod h1:j+pXrYzYe3OxTzFb4f/f2JL+CVoGVi1QJJiU0YKSUlw=
|
||||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
|
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
|
||||||
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||||
|
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
|
||||||
|
go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
|
||||||
|
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||||
|
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||||
|
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
|
||||||
|
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
@ -77,8 +85,6 @@ golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
|
41
lib/templates.go
Normal file
41
lib/templates.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RenderTemplate(w http.ResponseWriter, layout string, partials []string, props interface{}) error {
|
||||||
|
// Get the name of the current file
|
||||||
|
_, filename, _, _ := runtime.Caller(1)
|
||||||
|
page := filepath.Base(filename)
|
||||||
|
page = page[:len(page)-len(filepath.Ext(page))] // remove the file extension
|
||||||
|
|
||||||
|
// Build the list of templates
|
||||||
|
templates := []string{
|
||||||
|
"./pages/templates/layouts/" + layout + ".html",
|
||||||
|
"./pages/templates/" + page + ".html",
|
||||||
|
}
|
||||||
|
for _, partial := range partials {
|
||||||
|
templates = append(templates, "./pages/templates/partials/"+partial+".html")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the templates
|
||||||
|
ts, err := template.ParseFiles(templates...)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the layout template
|
||||||
|
err = ts.ExecuteTemplate(w, layout, props)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
56
main.go
56
main.go
|
@ -2,14 +2,17 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"goth.stack/api"
|
"goth.stack/api"
|
||||||
|
"goth.stack/middleware"
|
||||||
"goth.stack/pages"
|
"goth.stack/pages"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/uptrace/bunrouter"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/uptrace/bunrouter/extra/reqlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Template Type
|
// Template Type
|
||||||
|
@ -18,7 +21,7 @@ type Template struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template Render function
|
// Template Render function
|
||||||
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
func (t *Template) Render(w io.Writer, name string, data interface{}) error {
|
||||||
return t.templates.ExecuteTemplate(w, name, data)
|
return t.templates.ExecuteTemplate(w, name, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,38 +29,31 @@ func main() {
|
||||||
godotenv.Load(".env")
|
godotenv.Load(".env")
|
||||||
|
|
||||||
// Initialize router
|
// Initialize router
|
||||||
e := echo.New()
|
router := bunrouter.New(
|
||||||
|
bunrouter.Use(reqlog.NewMiddleware(), middleware.RequestID, middleware.SecureHeaders),
|
||||||
|
)
|
||||||
|
|
||||||
// Middlewares
|
// Static server
|
||||||
e.Pre(middleware.RemoveTrailingSlash())
|
fs := http.FileServer(http.Dir("public"))
|
||||||
e.Use(middleware.Logger())
|
|
||||||
e.Use(middleware.RequestID())
|
|
||||||
e.Use(middleware.Secure())
|
|
||||||
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
|
|
||||||
Level: 5,
|
|
||||||
}))
|
|
||||||
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(69)))
|
|
||||||
|
|
||||||
// Template Parsing
|
router.GET("/public/*filepath", func(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
t := &Template{
|
http.StripPrefix("/public", fs).ServeHTTP(w, req.Request)
|
||||||
templates: template.Must(template.ParseGlob("pages/**/*.html")),
|
return nil
|
||||||
}
|
})
|
||||||
|
|
||||||
e.Renderer = t
|
|
||||||
|
|
||||||
// // Static server
|
|
||||||
e.Static("/public", "public")
|
|
||||||
|
|
||||||
// Page routes
|
// Page routes
|
||||||
e.GET("/", pages.Home)
|
pageGroup := router.NewGroup("", bunrouter.Use(middleware.NewRateLimiter(50).RateLimit))
|
||||||
e.GET("/blog", pages.Blog)
|
pageGroup.GET("/", pages.Home)
|
||||||
e.GET("/post/:post", pages.Post)
|
pageGroup.GET("/blog", pages.Blog)
|
||||||
e.GET("/ssedemo", pages.SSEDemo)
|
pageGroup.GET("/post/:post", pages.Post)
|
||||||
|
pageGroup.GET("/sse", pages.SSEDemo)
|
||||||
|
|
||||||
// API Routes:
|
// API Routes:
|
||||||
e.GET("/api/ping", api.Ping)
|
apiGroup := router.NewGroup("/api")
|
||||||
e.GET("/api/ssedemo", api.SSEDemo)
|
apiGroup.GET("/ping", api.Ping)
|
||||||
e.POST("/api/sendsse", api.SSEDemoSend)
|
|
||||||
|
|
||||||
e.Logger.Fatal(e.Start(":3000"))
|
apiGroup.GET("/sse", api.SSE)
|
||||||
|
apiGroup.POST("/sendsse", api.SSEDemoSend)
|
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe(":3000", router))
|
||||||
}
|
}
|
||||||
|
|
60
middleware/ratelimit.go
Normal file
60
middleware/ratelimit.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/uptrace/bunrouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rateLimiter struct {
|
||||||
|
visitors map[string]*visitor
|
||||||
|
mu sync.Mutex
|
||||||
|
rps int
|
||||||
|
}
|
||||||
|
|
||||||
|
type visitor struct {
|
||||||
|
firstSeen time.Time
|
||||||
|
requests int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRateLimiter(rps int) *rateLimiter {
|
||||||
|
return &rateLimiter{
|
||||||
|
visitors: make(map[string]*visitor),
|
||||||
|
rps: rps,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *rateLimiter) RateLimit(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
|
rl.mu.Lock()
|
||||||
|
defer rl.mu.Unlock()
|
||||||
|
|
||||||
|
ip, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
// handle error, e.g., return an HTTP 500 error
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, exists := rl.visitors[ip]
|
||||||
|
if !exists || time.Since(v.firstSeen) > 1*time.Minute {
|
||||||
|
v = &visitor{
|
||||||
|
firstSeen: time.Now(),
|
||||||
|
}
|
||||||
|
rl.visitors[ip] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
v.requests++
|
||||||
|
|
||||||
|
// Limit each IP to rps requests per minute
|
||||||
|
if v.requests > rl.rps {
|
||||||
|
http.Error(w, "Too many requests", http.StatusTooManyRequests)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(w, req)
|
||||||
|
}
|
||||||
|
}
|
41
middleware/requestid.go
Normal file
41
middleware/requestid.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/uptrace/bunrouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
func (c contextKey) String() string {
|
||||||
|
return string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
HeaderXRequestID = "X-Request-ID"
|
||||||
|
requestIDKey = contextKey("requestID")
|
||||||
|
)
|
||||||
|
|
||||||
|
func RequestID(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
|
reqID := req.Header.Get(HeaderXRequestID)
|
||||||
|
if reqID == "" {
|
||||||
|
reqID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(req.Context(), requestIDKey, reqID)
|
||||||
|
|
||||||
|
w.Header().Set(HeaderXRequestID, reqID)
|
||||||
|
return next(w, req.WithContext(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRequestID(ctx context.Context) string {
|
||||||
|
if reqID, ok := ctx.Value(requestIDKey).(string); ok {
|
||||||
|
return reqID
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
21
middleware/secure.go
Normal file
21
middleware/secure.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/unrolled/secure"
|
||||||
|
"github.com/uptrace/bunrouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SecureHeaders(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
|
||||||
|
secureMiddleware := secure.New(secure.Options{
|
||||||
|
FrameDeny: true,
|
||||||
|
ContentTypeNosniff: true,
|
||||||
|
BrowserXssFilter: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
|
secureMiddleware.HandlerFuncWithNext(w, req.Request, nil)
|
||||||
|
return next(w, req)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package pages
|
package pages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -9,7 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/uptrace/bunrouter"
|
||||||
"goth.stack/lib"
|
"goth.stack/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,18 +16,20 @@ type BlogProps struct {
|
||||||
Posts []lib.CardLink
|
Posts []lib.CardLink
|
||||||
}
|
}
|
||||||
|
|
||||||
func Blog(c echo.Context) error {
|
func Blog(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
var posts []lib.CardLink
|
var posts []lib.CardLink
|
||||||
|
|
||||||
files, err := os.ReadDir("./content/")
|
files, err := os.ReadDir("./content/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "There was an finding posts!")
|
http.Error(w, "There was an issue finding posts!", http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
frontMatter, err := lib.ExtractFrontMatter(file, "./content/")
|
frontMatter, err := lib.ExtractFrontMatter(file, "./content/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "There was an issue rendering the posts!")
|
http.Error(w, "There was an issue rendering the posts!", http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
frontMatter.Href = "post/" + strings.TrimSuffix(file.Name(), ".md")
|
frontMatter.Href = "post/" + strings.TrimSuffix(file.Name(), ".md")
|
||||||
|
@ -57,19 +58,9 @@ func Blog(c echo.Context) error {
|
||||||
Posts: posts,
|
Posts: posts,
|
||||||
}
|
}
|
||||||
|
|
||||||
templates := []string{
|
// Specify the partials used by this page
|
||||||
"./pages/templates/layouts/base.html",
|
partials := []string{"header", "navitems", "cardlinks"}
|
||||||
"./pages/templates/partials/header.html",
|
|
||||||
"./pages/templates/partials/navitems.html",
|
|
||||||
"./pages/templates/partials/cardlinks.html",
|
|
||||||
"./pages/templates/blog.html",
|
|
||||||
}
|
|
||||||
|
|
||||||
ts, err := template.ParseFiles(templates...)
|
// Render the template
|
||||||
if err != nil {
|
return lib.RenderTemplate(w, "base", partials, props)
|
||||||
log.Print(err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ts.ExecuteTemplate(c.Response().Writer, "base", props)
|
|
||||||
}
|
}
|
||||||
|
|
106
pages/home.go
106
pages/home.go
File diff suppressed because one or more lines are too long
|
@ -7,7 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
|
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/uptrace/bunrouter"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
@ -21,24 +21,27 @@ type PostProps struct {
|
||||||
Tags []string
|
Tags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Post(c echo.Context) error {
|
func Post(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
postName := c.ParamValues()[0]
|
postName := req.Param("post")
|
||||||
|
|
||||||
filePath := "content/" + postName + ".md"
|
filePath := "content/" + postName + ".md"
|
||||||
|
|
||||||
md, err := os.ReadFile(filePath)
|
md, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusNotFound, "This post does not exist!")
|
http.Error(w, "This post does not exist!", http.StatusNotFound)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
frontmatterBytes, content, err := lib.SplitFrontmatter(md)
|
frontmatterBytes, content, err := lib.SplitFrontmatter(md)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "There was an issue rendering this post!")
|
http.Error(w, "There was an issue rendering this post!", http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var frontmatter lib.FrontMatter
|
var frontmatter lib.FrontMatter
|
||||||
if err := yaml.Unmarshal(frontmatterBytes, &frontmatter); err != nil {
|
if err := yaml.Unmarshal(frontmatterBytes, &frontmatter); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "There was an issue rendering this post!")
|
http.Error(w, "There was an issue rendering this post!", http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
@ -54,7 +57,8 @@ func Post(c echo.Context) error {
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := markdown.Convert(content, &buf); err != nil {
|
if err := markdown.Convert(content, &buf); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "There was an issue rendering this post!")
|
http.Error(w, "There was an issue rendering this post!", http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
props := PostProps{
|
props := PostProps{
|
||||||
|
@ -64,17 +68,9 @@ func Post(c echo.Context) error {
|
||||||
Tags: frontmatter.Tags,
|
Tags: frontmatter.Tags,
|
||||||
}
|
}
|
||||||
|
|
||||||
templates := []string{
|
// Specify the partials used by this page
|
||||||
"./pages/templates/layouts/post.html",
|
partials := []string{"header", "navitems"}
|
||||||
"./pages/templates/partials/header.html",
|
|
||||||
"./pages/templates/partials/navitems.html",
|
|
||||||
"./pages/templates/post.html",
|
|
||||||
}
|
|
||||||
|
|
||||||
ts, err := template.ParseFiles(templates...)
|
// Render the template
|
||||||
if err != nil {
|
return lib.RenderTemplate(w, "post", partials, props)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "There was an issue rendering this post!")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ts.ExecuteTemplate(c.Response().Writer, "post", props)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,16 @@
|
||||||
package pages
|
package pages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"net/http"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/uptrace/bunrouter"
|
||||||
|
"goth.stack/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SSEDemo(c echo.Context) error {
|
func SSEDemo(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
templates := []string{
|
// Specify the partials used by this page
|
||||||
"./pages/templates/layouts/base.html",
|
partials := []string{"header", "navitems"}
|
||||||
"./pages/templates/partials/header.html",
|
|
||||||
"./pages/templates/partials/navitems.html",
|
|
||||||
"./pages/templates/ssedemo.html",
|
|
||||||
}
|
|
||||||
|
|
||||||
ts, err := template.ParseFiles(templates...)
|
// Render the template
|
||||||
if err != nil {
|
return lib.RenderTemplate(w, "base", partials, nil)
|
||||||
log.Print(err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ts.ExecuteTemplate(c.Response().Writer, "base", nil)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="no-underline" href="/ssedemo">
|
<a class="no-underline" href="/sse">
|
||||||
SSE Demo
|
SSE Demo
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -13,7 +13,7 @@ GOTH // SSE <div class="badge badge-accent">DEMO</div>
|
||||||
<h1 class="text-4xl">Server Sent Events</h1>
|
<h1 class="text-4xl">Server Sent Events</h1>
|
||||||
<h2 class="text-xl">This page demonstrates the use of the <a href="https://htmx.org/extensions/sse/">HTMX SSE Extention</a> to receive Server Sent Events on the "default" channel.</h2>
|
<h2 class="text-xl">This page demonstrates the use of the <a href="https://htmx.org/extensions/sse/">HTMX SSE Extention</a> to receive Server Sent Events on the "default" channel.</h2>
|
||||||
<p class="text-lg">Any events received on the "default" channel will appear below:</p>
|
<p class="text-lg">Any events received on the "default" channel will appear below:</p>
|
||||||
<div hx-ext="sse" sse-connect="/api/ssedemo" sse-swap="message">
|
<div hx-ext="sse" sse-connect="/api/sse" sse-swap="message">
|
||||||
Waiting for SSE Message...
|
Waiting for SSE Message...
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue