superkit/kit/kit.go

184 lines
4.2 KiB
Go
Raw Permalink Normal View History

2024-06-04 10:16:51 +02:00
package kit
import (
"context"
"encoding/json"
2024-06-11 17:08:06 +02:00
"fmt"
"log"
2024-06-04 10:16:51 +02:00
"log/slog"
"net/http"
2024-06-06 10:04:51 +02:00
"os"
2024-06-04 10:16:51 +02:00
"github.com/a-h/templ"
2024-06-11 17:08:06 +02:00
"github.com/gorilla/sessions"
"github.com/joho/godotenv"
2024-06-04 10:16:51 +02:00
)
2024-06-11 17:08:06 +02:00
var store *sessions.CookieStore
2024-06-04 10:16:51 +02:00
2024-06-11 17:08:06 +02:00
type HandlerFunc func(kit *Kit) error
2024-06-04 10:16:51 +02:00
type ErrorHandlerFunc func(kit *Kit, err error)
type AuthKey struct{}
type Auth interface {
Check() bool
}
var (
2024-06-06 10:04:51 +02:00
errorHandler = func(kit *Kit, err error) {
2024-06-04 10:16:51 +02:00
kit.Text(http.StatusInternalServerError, err.Error())
}
)
2024-06-06 10:04:51 +02:00
type DefaultAuth struct{}
2024-06-04 10:16:51 +02:00
2024-06-06 10:04:51 +02:00
func (DefaultAuth) Check() bool { return false }
2024-06-04 10:16:51 +02:00
type Kit struct {
Response http.ResponseWriter
Request *http.Request
}
func UseErrorHandler(h ErrorHandlerFunc) { errorHandler = h }
func (kit *Kit) Auth() Auth {
value, ok := kit.Request.Context().Value(AuthKey{}).(Auth)
if !ok {
slog.Warn("kit authentication not set")
2024-06-06 10:04:51 +02:00
return DefaultAuth{}
2024-06-04 10:16:51 +02:00
}
return value
}
2024-06-11 17:08:06 +02:00
// GetSession return a session by its name. GetSession always
// returns a session even if it does not exist.
func (kit *Kit) GetSession(name string) *sessions.Session {
sess, _ := store.Get(kit.Request, name)
return sess
}
2024-06-08 11:20:22 +02:00
// Redirect with HTMX support.
2024-06-06 10:04:51 +02:00
func (kit *Kit) Redirect(status int, url string) error {
if len(kit.Request.Header.Get("HX-Request")) > 0 {
kit.Response.Header().Set("HX-Redirect", url)
kit.Response.WriteHeader(http.StatusSeeOther)
return nil
}
http.Redirect(kit.Response, kit.Request, url, status)
return nil
2024-06-04 10:16:51 +02:00
}
2024-06-22 07:18:39 +02:00
func (kit *Kit) FormValue(name string) string {
return kit.Request.PostFormValue(name)
}
2024-06-04 10:16:51 +02:00
func (kit *Kit) JSON(status int, v any) error {
kit.Response.WriteHeader(status)
kit.Response.Header().Set("Content-Type", "application/json")
return json.NewEncoder(kit.Response).Encode(v)
}
func (kit *Kit) Text(status int, msg string) error {
kit.Response.WriteHeader(status)
kit.Response.Header().Set("Content-Type", "text/plain")
_, err := kit.Response.Write([]byte(msg))
return err
}
func (kit *Kit) Bytes(status int, b []byte) error {
kit.Response.WriteHeader(status)
kit.Response.Header().Set("Content-Type", "text/plain")
_, err := kit.Response.Write(b)
return err
}
func (kit *Kit) Render(c templ.Component) error {
return c.Render(kit.Request.Context(), kit.Response)
}
2024-06-16 11:18:34 +02:00
func (kit *Kit) Getenv(name string, def string) string {
return Getenv(name, def)
}
2024-06-04 10:16:51 +02:00
func Handler(h HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
kit := &Kit{
Response: w,
Request: r,
}
if err := h(kit); err != nil {
if errorHandler != nil {
errorHandler(kit, err)
return
}
kit.Text(http.StatusInternalServerError, err.Error())
}
}
}
type AuthenticationConfig struct {
2024-06-11 17:08:06 +02:00
AuthFunc func(*Kit) (Auth, error)
2024-06-04 10:16:51 +02:00
RedirectURL string
}
func WithAuthentication(config AuthenticationConfig, strict bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
kit := &Kit{
Response: w,
Request: r,
}
2024-06-11 17:08:06 +02:00
auth, err := config.AuthFunc(kit)
2024-06-04 10:16:51 +02:00
if err != nil {
errorHandler(kit, err)
return
}
if strict && !auth.Check() && r.URL.Path != config.RedirectURL {
kit.Redirect(http.StatusSeeOther, config.RedirectURL)
return
}
ctx := context.WithValue(r.Context(), AuthKey{}, auth)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
2024-06-06 10:04:51 +02:00
2024-06-14 16:20:24 +02:00
func Getenv(name string, def string) string {
env := os.Getenv(name)
if len(env) == 0 {
return def
}
return env
}
2024-06-06 10:04:51 +02:00
func IsDevelopment() bool {
2024-06-15 17:34:19 +02:00
return os.Getenv("SUPERKIT_ENV") == "development"
2024-06-06 10:04:51 +02:00
}
func IsProduction() bool {
2024-06-15 17:34:19 +02:00
return os.Getenv("SUPERKIT_ENV") == "production"
2024-06-06 10:04:51 +02:00
}
func Env() string {
2024-06-15 17:34:19 +02:00
return os.Getenv("SUPERKIT_ENV")
2024-06-06 10:04:51 +02:00
}
2024-06-11 17:08:06 +02:00
// initialize the store here so the environment variables are
// already initialized. Calling NewCookieStore() from outside of
// a function scope won't work.
func Setup() {
if err := godotenv.Load(); err != nil {
log.Fatal(err)
}
2024-06-15 17:34:19 +02:00
appSecret := os.Getenv("SUPERKIT_SECRET")
2024-06-11 17:08:06 +02:00
if len(appSecret) < 32 {
// For security reasons we are calling os.Exit(1) here so Go's panic recover won't
2024-06-15 17:34:19 +02:00
// recover the application without a valid SUPERKIT_SECRET set.
fmt.Println("invalid SUPERKIT_SECRET variable. Are you sure you have set the SUPERKIT_SECRET in your .env file?")
2024-06-11 17:08:06 +02:00
os.Exit(1)
}
store = sessions.NewCookieStore([]byte(appSecret))
}