* feat: add middleware example demonstrating route groups Self-contained example covering v.Use(), v.Group(), nested groups, Group.Use(), and middleware chaining with role-based access control. * feat: add per-action middleware via WithMiddleware ActionOption Reuses the existing Middleware type so the same auth/logging functions work at both page and action level. Middleware runs after CSRF and rate-limit checks, with full access to session and signals. * feat: add RedirectView helper and refactor session example to use middleware RedirectView lets middleware abort and redirect in one step. The session example now uses an authRequired middleware on a route group instead of an inline check inside the view. * fix: remove dead code, fix double Load and extractParams mismatch - Remove componentRegistry (written, never read) - Remove unused signal methods: Bytes, Int64, Float - Remove unreachable nil check in registerCtx - Simplify injectRouteParams (extractParams already returns fresh map) - Fix double sync.Map.Load in injectSignals - Merge Shutdown/shutdown into single method - Inline currSessionNum - Fix extractParams: mismatched literal segment now returns nil - Minor: new(bytes.Buffer), go c.Sync(), genRandID reads 4 bytes
113 lines
2.5 KiB
Go
113 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"log"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
"github.com/ryanhamamura/via"
|
|
"github.com/ryanhamamura/via/h"
|
|
)
|
|
|
|
func main() {
|
|
// Open SQLite database for persistent sessions
|
|
db, err := sql.Open("sqlite3", "sessions.db")
|
|
if err != nil {
|
|
log.Fatalf("failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Create session manager with SQLite store
|
|
sm, err := via.NewSQLiteSessionManager(db)
|
|
if err != nil {
|
|
log.Fatalf("failed to create session manager: %v", err)
|
|
}
|
|
|
|
v := via.New()
|
|
v.Config(via.Options{
|
|
ServerAddress: ":7331",
|
|
SessionManager: sm,
|
|
})
|
|
|
|
// Auth middleware — redirects unauthenticated users to /login
|
|
authRequired := func(c *via.Context, next func()) {
|
|
if c.Session().GetString("username") == "" {
|
|
c.Session().Set("flash", "Please log in first")
|
|
c.RedirectView("/login")
|
|
return
|
|
}
|
|
next()
|
|
}
|
|
|
|
// Login page (public)
|
|
v.Page("/login", func(c *via.Context) {
|
|
flash := c.Session().PopString("flash")
|
|
usernameInput := c.Signal("")
|
|
|
|
login := c.Action(func() {
|
|
name := usernameInput.String()
|
|
if name != "" {
|
|
c.Session().Set("username", name)
|
|
c.Session().Set("flash", "Welcome, "+name+"!")
|
|
c.Session().RenewToken()
|
|
c.Redirect("/dashboard")
|
|
}
|
|
})
|
|
|
|
c.View(func() h.H {
|
|
// Already logged in? Redirect to dashboard
|
|
if c.Session().GetString("username") != "" {
|
|
c.Redirect("/dashboard")
|
|
return h.Div()
|
|
}
|
|
|
|
var flashMsg h.H
|
|
if flash != "" {
|
|
flashMsg = h.P(h.Text(flash), h.Style("color: green"))
|
|
}
|
|
return h.Div(
|
|
flashMsg,
|
|
h.H1(h.Text("Login")),
|
|
h.Input(h.Type("text"), h.Placeholder("Username"), usernameInput.Bind()),
|
|
h.Button(h.Text("Login"), login.OnClick()),
|
|
)
|
|
})
|
|
})
|
|
|
|
// Protected pages
|
|
protected := v.Group("", authRequired)
|
|
|
|
protected.Page("/dashboard", func(c *via.Context) {
|
|
logout := c.Action(func() {
|
|
c.Session().Set("flash", "Goodbye!")
|
|
c.Session().Delete("username")
|
|
c.Redirect("/login")
|
|
})
|
|
|
|
c.View(func() h.H {
|
|
username := c.Session().GetString("username")
|
|
flash := c.Session().PopString("flash")
|
|
var flashMsg h.H
|
|
if flash != "" {
|
|
flashMsg = h.P(h.Text(flash), h.Style("color: green"))
|
|
}
|
|
return h.Div(
|
|
flashMsg,
|
|
h.H1(h.Textf("Dashboard - Hello, %s!", username)),
|
|
h.P(h.Text("Your session persists across page refreshes.")),
|
|
h.Button(h.Text("Logout"), logout.OnClick()),
|
|
)
|
|
})
|
|
})
|
|
|
|
// Redirect root to login
|
|
v.Page("/", func(c *via.Context) {
|
|
c.View(func() h.H {
|
|
c.Redirect("/login")
|
|
return h.Div()
|
|
})
|
|
})
|
|
|
|
v.Start()
|
|
}
|