htmgo/framework/h/app.go

204 lines
4.8 KiB
Go
Raw Normal View History

2024-09-11 00:52:18 +00:00
package h
import (
"context"
"fmt"
"github.com/go-chi/chi/v5"
2024-09-21 16:52:56 +00:00
"github.com/maddalax/htmgo/framework/hx"
"github.com/maddalax/htmgo/framework/service"
"log/slog"
"net/http"
2024-09-28 17:51:23 +00:00
"os"
"os/exec"
"runtime"
2024-09-28 17:51:23 +00:00
"strings"
"time"
2024-09-11 00:52:18 +00:00
)
type RequestContext struct {
2024-10-01 03:08:52 +00:00
Request *http.Request
Response http.ResponseWriter
2024-09-21 16:52:56 +00:00
locator *service.Locator
isBoosted bool
currentBrowserUrl string
hxPromptResponse string
isHxRequest bool
hxTargetId string
hxTriggerName string
hxTriggerId string
kv map[string]interface{}
}
func (c *RequestContext) QueryParam(key string) string {
return c.Request.URL.Query().Get(key)
}
func (c *RequestContext) Set(key string, value interface{}) {
if c.kv == nil {
c.kv = make(map[string]interface{})
}
c.kv[key] = value
}
func (c *RequestContext) Get(key string) interface{} {
if c.kv == nil {
return nil
}
return c.kv[key]
}
func (c *RequestContext) ServiceLocator() *service.Locator {
return c.locator
}
type AppOpts struct {
LiveReload bool
ServiceLocator *service.Locator
Register func(app *App)
}
2024-09-11 00:52:18 +00:00
type App struct {
Opts AppOpts
Router *chi.Mux
2024-09-11 00:52:18 +00:00
}
func Start(opts AppOpts) {
router := chi.NewRouter()
instance := App{
Opts: opts,
Router: router,
}
instance.start()
2024-09-11 00:52:18 +00:00
}
const RequestContextKey = "htmgo.request.context"
2024-09-21 16:52:56 +00:00
func populateHxFields(cc *RequestContext) {
cc.isBoosted = cc.Request.Header.Get(hx.BoostedHeader) == "true"
cc.isBoosted = cc.Request.Header.Get(hx.BoostedHeader) == "true"
cc.currentBrowserUrl = cc.Request.Header.Get(hx.CurrentUrlHeader)
cc.hxPromptResponse = cc.Request.Header.Get(hx.PromptResponseHeader)
cc.isHxRequest = cc.Request.Header.Get(hx.RequestHeader) == "true"
cc.hxTargetId = cc.Request.Header.Get(hx.TargetIdHeader)
cc.hxTriggerName = cc.Request.Header.Get(hx.TriggerNameHeader)
cc.hxTriggerId = cc.Request.Header.Get(hx.TriggerIdHeader)
2024-09-21 16:52:56 +00:00
}
func (app *App) UseWithContext(h func(w http.ResponseWriter, r *http.Request, context map[string]any)) {
app.Router.Use(func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cc := r.Context().Value(RequestContextKey).(*RequestContext)
h(w, r, cc.kv)
handler.ServeHTTP(w, r)
})
})
}
2024-09-11 00:52:18 +00:00
2024-09-28 17:51:23 +00:00
func GetLogLevel() slog.Level {
// Get the log level from the environment variable
logLevel := os.Getenv("LOG_LEVEL")
switch strings.ToUpper(logLevel) {
case "DEBUG":
return slog.LevelDebug
case "INFO":
return slog.LevelInfo
case "WARN":
return slog.LevelWarn
case "ERROR":
return slog.LevelError
default:
// Default to INFO if no valid log level is set
return slog.LevelInfo
}
}
func (app *App) start() {
2024-09-20 18:25:14 +00:00
2024-09-28 17:51:23 +00:00
slog.SetLogLoggerLevel(GetLogLevel())
app.Router.Use(func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cc := &RequestContext{
2024-10-01 03:08:52 +00:00
locator: app.Opts.ServiceLocator,
Request: r,
Response: w,
kv: make(map[string]interface{}),
}
2024-09-21 16:52:56 +00:00
populateHxFields(cc)
ctx := context.WithValue(r.Context(), RequestContextKey, cc)
h.ServeHTTP(w, r.WithContext(ctx))
})
})
if app.Opts.Register != nil {
app.Opts.Register(app)
}
if app.Opts.LiveReload && IsDevelopment() {
app.AddLiveReloadHandler("/dev/livereload")
2024-09-11 00:52:18 +00:00
}
port := ":3000"
slog.Info(fmt.Sprintf("Server started on port %s", port))
err := http.ListenAndServe(port, app.Router)
2024-09-11 00:52:18 +00:00
if err != nil {
// If we are in watch mode, just try to kill any processes holding that port
// and try again
if IsDevelopment() && IsWatchMode() {
slog.Info("Port already in use, trying to kill the process and start again")
if runtime.GOOS == "windows" {
cmd := exec.Command("cmd", "/C", fmt.Sprintf(`for /F "tokens=5" %%i in ('netstat -aon ^| findstr :%s') do taskkill /F /PID %%i`, port))
cmd.Run()
} else {
cmd := exec.Command("bash", "-c", fmt.Sprintf("kill -9 $(lsof -ti%s)", port))
cmd.Run()
}
time.Sleep(time.Millisecond * 50)
err = http.ListenAndServe(":3000", app.Router)
if err != nil {
panic(err)
}
} else {
panic(err)
}
2024-09-24 16:08:35 +00:00
panic(err)
2024-09-11 00:52:18 +00:00
}
}
func writeHtml(w http.ResponseWriter, element Ren) error {
w.Header().Set("Content-Type", "text/html")
_, err := fmt.Fprint(w, Render(element))
return err
}
func HtmlView(w http.ResponseWriter, page *Page) error {
return writeHtml(w, page.Root)
2024-09-11 00:52:18 +00:00
}
func PartialViewWithHeaders(w http.ResponseWriter, headers *Headers, partial *Partial) error {
2024-09-11 18:09:55 +00:00
if partial.Headers != nil {
for s, a := range *partial.Headers {
w.Header().Set(s, a)
2024-09-11 18:09:55 +00:00
}
}
if headers != nil {
for s, a := range *headers {
w.Header().Set(s, a)
2024-09-11 18:09:55 +00:00
}
}
return writeHtml(w, partial.Root)
2024-09-11 18:09:55 +00:00
}
func PartialView(w http.ResponseWriter, partial *Partial) error {
2024-09-11 00:52:18 +00:00
if partial.Headers != nil {
for s, a := range *partial.Headers {
w.Header().Set(s, a)
2024-09-11 00:52:18 +00:00
}
}
return writeHtml(w, partial.Root)
2024-09-11 00:52:18 +00:00
}