diff --git a/README.md b/README.md
new file mode 100644
index 0000000..023dc1d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,421 @@
+# 🍣 Sushi
+
+A fast, raw, tasty framework for simplifying basic web apps!
+
+## Quick Start
+
+```go
+package main
+
+import (
+ "git.sharkk.net/Sharkk/Sushi"
+ "git.sharkk.net/Sharkk/Sushi/session"
+)
+
+func main() {
+ app := sushi.New()
+
+ // Initialize sessions
+ session.InitSessions("sessions.json")
+
+ app.Get("/", func(ctx sushi.Ctx, params []any) {
+ sushi.SendHTML(ctx, "
Hello Sushi!
")
+ })
+
+ app.Listen(":8080")
+}
+```
+
+## Routing
+
+```go
+// Basic routes
+app.Get("/users/:id", getUserHandler)
+app.Post("/users", createUserHandler)
+app.Put("/users/:id", updateUserHandler)
+app.Delete("/users/:id", deleteUserHandler)
+
+// Wildcards
+app.Get("/files/*path", serveFilesHandler)
+
+// Route groups
+api := app.Group("/api/v1")
+api.Get("/users", listUsersHandler)
+api.Post("/users", createUserHandler)
+```
+
+## Parameters
+
+URL parameters are automatically converted to the appropriate type:
+
+```go
+// Numeric parameters become integers
+app.Get("/users/:id", func(ctx sushi.Ctx, params []any) {
+ userID := params[0].(int) // /users/123 -> 123
+ // ...
+})
+
+// String parameters stay strings
+app.Get("/users/:name", func(ctx sushi.Ctx, params []any) {
+ name := params[0].(string) // /users/john -> "john"
+ // ...
+})
+
+// Mixed types
+app.Get("/users/:id/posts/:slug", func(ctx sushi.Ctx, params []any) {
+ userID := params[0].(int) // 123
+ slug := params[1].(string) // "my-post"
+ // ...
+})
+```
+
+## Response Helpers
+
+```go
+func myHandler(ctx sushi.Ctx, params []any) {
+ // JSON responses
+ sushi.SendJSON(ctx, map[string]string{"message": "success"})
+
+ // HTML responses
+ sushi.SendHTML(ctx, "Welcome
")
+
+ // Text responses
+ sushi.SendText(ctx, "Plain text")
+
+ // Error responses
+ sushi.SendError(ctx, 404, "Not Found")
+
+ // Redirects
+ sushi.SendRedirect(ctx, "/login")
+
+ // Status only
+ sushi.SendStatus(ctx, 204)
+}
+```
+
+## Middleware
+
+```go
+// Custom middleware
+func loggingMiddleware() sushi.Middleware {
+ return func(ctx sushi.Ctx, params []any, next func()) {
+ println("Request:", string(ctx.Method()), string(ctx.Path()))
+ next()
+ println("Status:", ctx.Response.StatusCode())
+ }
+}
+
+app.Use(loggingMiddleware())
+
+// Group middleware
+admin := app.Group("/admin")
+admin.Use(auth.RequireAuth("/login"))
+```
+
+## Authentication Workflow
+
+### 1. Setup Password Hashing
+
+```go
+import "git.sharkk.net/Sharkk/Sushi/password"
+
+// Hash password for storage
+hashedPassword := password.HashPassword("userpassword123")
+
+// Verify password during login
+isValid, err := password.VerifyPassword("userpassword123", hashedPassword)
+```
+
+### 2. User Structure
+
+```go
+type User struct {
+ ID int `json:"id"`
+ Email string `json:"email"`
+ Username string `json:"username"`
+ Password string `json:"password"` // Store hashed password
+}
+
+// User lookup function for auth middleware
+func getUserByID(userID int) any {
+ // Query your database for user by ID
+ // Return nil if not found
+ return &User{ID: userID, Email: "user@example.com"}
+}
+```
+
+### 3. Session & Auth Middleware
+
+```go
+import (
+ "git.sharkk.net/Sharkk/Sushi/session"
+ "git.sharkk.net/Sharkk/Sushi/auth"
+)
+
+func main() {
+ app := sushi.New()
+
+ // Initialize sessions
+ session.InitSessions("sessions.json")
+
+ // Add session middleware
+ app.Use(session.Middleware())
+
+ // Add auth middleware with user lookup
+ app.Use(auth.Middleware(getUserByID))
+
+ // Public routes
+ app.Get("/login", loginPageHandler)
+ app.Post("/login", loginHandler)
+ app.Post("/logout", logoutHandler)
+
+ // Protected routes
+ protected := app.Group("/dashboard")
+ protected.Use(auth.RequireAuth("/login"))
+ protected.Get("/", dashboardHandler)
+}
+```
+
+### 4. Login Handler
+
+```go
+func loginHandler(ctx sushi.Ctx, params []string) {
+ email := string(ctx.PostArgs().Peek("email"))
+ password := string(ctx.PostArgs().Peek("password"))
+
+ // Find user by email/username
+ user := findUserByEmail(email)
+ if user == nil {
+ sushi.SendError(ctx, 401, "Invalid credentials")
+ return
+ }
+
+ // Verify password
+ isValid, err := password.VerifyPassword(password, user.Password)
+ if err != nil || !isValid {
+ sushi.SendError(ctx, 401, "Invalid credentials")
+ return
+ }
+
+ // Log the user in
+ auth.Login(ctx, user.ID, user)
+
+ sushi.SendRedirect(ctx, "/dashboard")
+}
+```
+
+### 5. Logout Handler
+
+```go
+func logoutHandler(ctx sushi.Ctx, params []string) {
+ auth.Logout(ctx)
+ sushi.SendRedirect(ctx, "/")
+}
+```
+
+### 6. Getting Current User
+
+```go
+func dashboardHandler(ctx sushi.Ctx, params []string) {
+ user := auth.GetCurrentUser(ctx).(*User)
+
+ html := fmt.Sprintf("Welcome, %s!
", user.Username)
+ sushi.SendHTML(ctx, html)
+}
+```
+
+## CSRF Protection
+
+```go
+import "git.sharkk.net/Sharkk/Sushi/csrf"
+
+// Add CSRF middleware to forms
+app.Use(csrf.Middleware())
+
+// In your form template
+func loginPageHandler(ctx sushi.Ctx, params []string) {
+ csrfField := csrf.CSRFHiddenField(ctx)
+
+ html := fmt.Sprintf(`
+
+ `, csrfField)
+
+ sushi.SendHTML(ctx, html)
+}
+```
+
+## Static Files
+
+```go
+// Serve static files
+app.Get("/static/*path", sushi.Static("./public"))
+
+// Serve single file
+app.Get("/favicon.ico", sushi.StaticFile("./public/favicon.ico"))
+
+// Embedded files
+files := map[string][]byte{
+ "/style.css": cssData,
+ "/app.js": jsData,
+}
+app.Get("/assets/*path", sushi.StaticEmbed(files))
+```
+
+## Sessions
+
+```go
+func someHandler(ctx sushi.Ctx, params []string) {
+ sess := session.GetCurrentSession(ctx)
+
+ // Set session data
+ sess.Set("user_preference", "dark_mode")
+
+ // Get session data
+ if pref, exists := sess.Get("user_preference"); exists {
+ preference := pref.(string)
+ }
+
+ // Flash messages (one-time)
+ sess.SetFlash("success", "Profile updated!")
+
+ // Get flash message
+ message := sess.GetFlashMessage("success")
+}
+```
+
+## Server Configuration
+
+```go
+app := sushi.New(sushi.ServerOptions{
+ ReadTimeout: 30 * time.Second,
+ WriteTimeout: 30 * time.Second,
+ MaxRequestBodySize: 10 * 1024 * 1024, // 10MB
+})
+
+// TLS
+app.ListenTLS(":443", "cert.pem", "key.pem")
+```
+
+## Complete Auth Example
+
+```go
+package main
+
+import (
+ "fmt"
+ sushi "git.sharkk.net/Sharkk/Sushi"
+ "git.sharkk.net/Sharkk/Sushi/auth"
+ "git.sharkk.net/Sharkk/Sushi/csrf"
+ "git.sharkk.net/Sharkk/Sushi/password"
+ "git.sharkk.net/Sharkk/Sushi/session"
+)
+
+type User struct {
+ ID int
+ Email string
+ Password string
+}
+
+var users = map[int]*User{
+ 1: {ID: 1, Email: "admin@example.com", Password: password.HashPassword("admin123")},
+}
+
+func getUserByID(userID int) any {
+ return users[userID]
+}
+
+func findUserByEmail(email string) *User {
+ for _, user := range users {
+ if user.Email == email {
+ return user
+ }
+ }
+ return nil
+}
+
+func main() {
+ app := sushi.New()
+
+ session.InitSessions("sessions.json")
+ app.Use(session.Middleware())
+ app.Use(auth.Middleware(getUserByID))
+
+ // Public routes
+ app.Get("/", homeHandler)
+ app.Get("/login", loginPageHandler)
+ app.Post("/login", loginHandler)
+
+ // Protected routes
+ protected := app.Group("/dashboard")
+ protected.Use(auth.RequireAuth("/login"))
+ protected.Use(csrf.Middleware())
+ protected.Get("/", dashboardHandler)
+ protected.Post("/logout", logoutHandler)
+
+ app.Listen(":8080")
+}
+
+func homeHandler(ctx sushi.Ctx, params []string) {
+ if auth.IsAuthenticated(ctx) {
+ sushi.SendRedirect(ctx, "/dashboard")
+ return
+ }
+ sushi.SendHTML(ctx, `Login`)
+}
+
+func loginPageHandler(ctx sushi.Ctx, params []string) {
+ html := fmt.Sprintf(`
+
+ `, csrf.CSRFHiddenField(ctx))
+
+ sushi.SendHTML(ctx, html)
+}
+
+func loginHandler(ctx sushi.Ctx, params []string) {
+ email := string(ctx.PostArgs().Peek("email"))
+ pass := string(ctx.PostArgs().Peek("password"))
+
+ user := findUserByEmail(email)
+ if user == nil {
+ sushi.SendError(ctx, 401, "Invalid credentials")
+ return
+ }
+
+ if valid, _ := password.VerifyPassword(pass, user.Password); !valid {
+ sushi.SendError(ctx, 401, "Invalid credentials")
+ return
+ }
+
+ auth.Login(ctx, user.ID, user)
+ sushi.SendRedirect(ctx, "/dashboard")
+}
+
+func dashboardHandler(ctx sushi.Ctx, params []string) {
+ user := auth.GetCurrentUser(ctx).(*User)
+
+ html := fmt.Sprintf(`
+ Welcome, %s!
+
+ `, user.Email, csrf.CSRFHiddenField(ctx))
+
+ sushi.SendHTML(ctx, html)
+}
+
+func logoutHandler(ctx sushi.Ctx, params []string) {
+ auth.Logout(ctx)
+ sushi.SendRedirect(ctx, "/")
+}
+```
diff --git a/auth/auth.go b/auth/auth.go
index 5084724..63fe9cd 100644
--- a/auth/auth.go
+++ b/auth/auth.go
@@ -10,7 +10,7 @@ const UserCtxKey = "user"
// Middleware adds authentication handling
func Middleware(userLookup func(int) any) sushi.Middleware {
- return func(ctx sushi.Ctx, params []string, next func()) {
+ return func(ctx sushi.Ctx, params []any, next func()) {
sess := session.GetCurrentSession(ctx)
if sess != nil && sess.UserID > 0 && userLookup != nil {
user := userLookup(sess.UserID)
@@ -32,7 +32,7 @@ func RequireAuth(redirectPath ...string) sushi.Middleware {
redirect = redirectPath[0]
}
- return func(ctx sushi.Ctx, params []string, next func()) {
+ return func(ctx sushi.Ctx, params []any, next func()) {
if !IsAuthenticated(ctx) {
ctx.Redirect(redirect, fasthttp.StatusFound)
return
@@ -48,7 +48,7 @@ func RequireGuest(redirectPath ...string) sushi.Middleware {
redirect = redirectPath[0]
}
- return func(ctx sushi.Ctx, params []string, next func()) {
+ return func(ctx sushi.Ctx, params []any, next func()) {
if IsAuthenticated(ctx) {
ctx.Redirect(redirect, fasthttp.StatusFound)
return
diff --git a/csrf/csrf.go b/csrf/csrf.go
index d56c6e5..44293f9 100644
--- a/csrf/csrf.go
+++ b/csrf/csrf.go
@@ -119,7 +119,7 @@ func ValidateFormCSRFToken(ctx sushi.Ctx) bool {
// Middleware returns middleware that automatically validates CSRF tokens
func Middleware() sushi.Middleware {
- return func(ctx sushi.Ctx, params []string, next func()) {
+ return func(ctx sushi.Ctx, params []any, next func()) {
method := string(ctx.Method())
if method == "POST" || method == "PUT" || method == "PATCH" || method == "DELETE" {
diff --git a/fs.go b/fs.go
index 52eaf4e..776b98d 100644
--- a/fs.go
+++ b/fs.go
@@ -50,7 +50,7 @@ func StaticFS(fsOptions StaticOptions) Handler {
fsHandler := fs.NewRequestHandler()
- return func(ctx Ctx, params []string) {
+ return func(ctx Ctx, params []any) {
fsHandler(ctx)
}
}
@@ -62,14 +62,14 @@ func Static(root string) Handler {
// StaticFile serves a single file
func StaticFile(filePath string) Handler {
- return func(ctx Ctx, params []string) {
+ return func(ctx Ctx, params []any) {
fasthttp.ServeFile(ctx, filePath)
}
}
// StaticEmbed creates a handler for embedded files
func StaticEmbed(files map[string][]byte) Handler {
- return func(ctx Ctx, params []string) {
+ return func(ctx Ctx, params []any) {
requestPath := string(ctx.Path())
// Try to find the file
diff --git a/router.go b/router.go
index d188abb..ea68302 100644
--- a/router.go
+++ b/router.go
@@ -22,7 +22,7 @@ type Router struct {
patch *node
delete *node
middleware []Middleware
- paramsBuffer []string
+ paramsBuffer []any
}
type Group struct {
@@ -40,7 +40,7 @@ func NewRouter() *Router {
patch: &node{},
delete: &node{},
middleware: []Middleware{},
- paramsBuffer: make([]string, 64),
+ paramsBuffer: make([]any, 64),
}
}
@@ -145,7 +145,7 @@ func applyMiddleware(h Handler, mw []Middleware) Handler {
return h
}
- return func(ctx Ctx, params []string) {
+ return func(ctx Ctx, params []any) {
var index int
var next func()
@@ -229,7 +229,7 @@ func (r *Router) addRoute(root *node, path string, h Handler, mw []Middleware) e
}
// Lookup finds a handler matching method and path
-func (r *Router) Lookup(method, path string) (Handler, []string, bool) {
+func (r *Router) Lookup(method, path string) (Handler, []any, bool) {
root := r.methodNode(method)
if root == nil {
return nil, nil, false
@@ -240,7 +240,7 @@ func (r *Router) Lookup(method, path string) (Handler, []string, bool) {
buffer := r.paramsBuffer
if cap(buffer) < int(root.maxParams) {
- buffer = make([]string, root.maxParams)
+ buffer = make([]any, root.maxParams)
r.paramsBuffer = buffer
}
buffer = buffer[:0]
@@ -253,7 +253,7 @@ func (r *Router) Lookup(method, path string) (Handler, []string, bool) {
return h, buffer[:paramCount], true
}
-func match(current *node, path string, start int, params *[]string) (Handler, int, bool) {
+func match(current *node, path string, start int, params *[]any) (Handler, int, bool) {
paramCount := 0
for _, c := range current.children {
diff --git a/session/middleware.go b/session/middleware.go
index 52b9b37..2272e70 100644
--- a/session/middleware.go
+++ b/session/middleware.go
@@ -4,7 +4,7 @@ import sushi "git.sharkk.net/Sharkk/Sushi"
// Middleware provides session handling
func Middleware() sushi.Middleware {
- return func(ctx sushi.Ctx, params []string, next func()) {
+ return func(ctx sushi.Ctx, params []any, next func()) {
sessionID := sushi.GetCookie(ctx, SessionCookieName)
var sess *Session
diff --git a/sushi.go b/sushi.go
index bb3876c..64614f3 100644
--- a/sushi.go
+++ b/sushi.go
@@ -8,7 +8,7 @@ import (
"github.com/valyala/fasthttp"
)
-func (h Handler) Serve(ctx Ctx, params []string) {
+func (h Handler) Serve(ctx Ctx, params []any) {
h(ctx, params)
}
@@ -20,7 +20,7 @@ func IsHTTPS(ctx Ctx) bool {
// StandardHandler adapts a standard fasthttp.RequestHandler to the router's Handler
func StandardHandler(handler fasthttp.RequestHandler) Handler {
- return func(ctx Ctx, _ []string) {
+ return func(ctx Ctx, _ []any) {
handler(ctx)
}
}
diff --git a/timing/timing.go b/timing/timing.go
index 1c668d7..cd82553 100644
--- a/timing/timing.go
+++ b/timing/timing.go
@@ -11,7 +11,7 @@ const RequestTimerKey = "request_start_time"
// Middleware adds request timing functionality
func Middleware() sushi.Middleware {
- return func(ctx sushi.Ctx, params []string, next func()) {
+ return func(ctx sushi.Ctx, params []any, next func()) {
startTime := time.Now()
ctx.SetUserValue(RequestTimerKey, startTime)
next()
diff --git a/types.go b/types.go
index 34eed04..cd08b4a 100644
--- a/types.go
+++ b/types.go
@@ -3,5 +3,5 @@ package sushi
import "github.com/valyala/fasthttp"
type Ctx = *fasthttp.RequestCtx
-type Handler func(ctx Ctx, params []string)
-type Middleware func(ctx Ctx, params []string, next func())
+type Handler func(ctx Ctx, params []any)
+type Middleware func(ctx Ctx, params []any, next func())