update readme for new data access patterns

This commit is contained in:
Sky Johnson 2025-09-17 10:03:28 -05:00
parent a2c782c54a
commit c962f05c81

134
README.md
View File

@ -15,7 +15,7 @@ func main() {
// Initialize sessions // Initialize sessions
sushi.InitSessions("sessions.json") sushi.InitSessions("sessions.json")
app.Get("/", func(ctx sushi.Ctx, params []any) { app.Get("/", func(ctx sushi.Ctx) {
ctx.SendHTML("<h1>Hello Sushi!</h1>") ctx.SendHTML("<h1>Hello Sushi!</h1>")
}) })
@ -41,35 +41,86 @@ api.Get("/users", listUsersHandler)
api.Post("/users", createUserHandler) api.Post("/users", createUserHandler)
``` ```
## Parameters ## Parameters and Queries
URL parameters are automatically converted to the appropriate type: Sushi provides a fluent API for accessing URL parameters, query strings, and form data with automatic type conversion:
### URL Parameters (Named Routes)
```go ```go
// Numeric parameters become integers app.Get("/users/:id", func(ctx sushi.Ctx) {
app.Get("/users/:id", func(ctx sushi.Ctx, params []any) { // Get URL parameter with fluent API
userID := params[0].(int) // /users/123 -> 123 userID := ctx.Param("id").Int() // /users/123 -> 123
// ... userIDStr := ctx.Param("id").String() // /users/123 -> "123"
// With defaults
limit := ctx.Param("limit").IntDefault(10)
}) })
// String parameters stay strings app.Get("/users/:id/posts/:slug", func(ctx sushi.Ctx) {
app.Get("/users/:name", func(ctx sushi.Ctx, params []any) { userID := ctx.Param("id").Int()
name := params[0].(string) // /users/john -> "john" slug := ctx.Param("slug").String()
// ...
})
// Mixed types // Check if parameter exists
app.Get("/users/:id/posts/:slug", func(ctx sushi.Ctx, params []any) { if ctx.Param("optional").Exists() {
userID := params[0].(int) // 123 // Handle optional parameter
slug := params[1].(string) // "my-post" }
// ... })
```
### Query Parameters
```go
app.Get("/search", func(ctx sushi.Ctx) {
// Get query parameters with fluent API
query := ctx.Query("q").String() // ?q=hello -> "hello"
page := ctx.Query("page").IntDefault(1) // ?page=2 -> 2
limit := ctx.Query("limit").IntDefault(20) // Default to 20 if not present
sortBy := ctx.Query("sort").StringDefault("name") // Default to "name"
// Boolean query params
includeDeleted := ctx.Query("deleted").Bool() // ?deleted=true -> true
// Float values
minPrice := ctx.Query("min_price").Float() // ?min_price=19.99 -> 19.99
// Check existence
if ctx.Query("filter").Exists() {
filter := ctx.Query("filter").String()
// Apply filter
}
})
```
## Form Data
Access form data with the fluent API:
```go
app.Post("/users", func(ctx sushi.Ctx) {
// Get form fields with fluent API
name := ctx.Input("name").String()
email := ctx.Input("email").String()
age := ctx.Input("age").IntDefault(0)
// Check if checkbox is checked
agreeToTerms := ctx.Input("terms").Bool() // Checks for "true", "on", "1", "yes"
// Validate required fields
if ctx.Input("email").IsEmpty() {
ctx.SendError(400, "Email is required")
return
}
// Get array of values (for multiple select, checkboxes)
tags := ctx.GetFormArray("tags[]")
}) })
``` ```
## Response Helpers ## Response Helpers
```go ```go
func myHandler(ctx sushi.Ctx, params []any) { func myHandler(ctx sushi.Ctx) {
// JSON responses // JSON responses
ctx.SendJSON(map[string]string{"message": "success"}) ctx.SendJSON(map[string]string{"message": "success"})
@ -87,15 +138,20 @@ func myHandler(ctx sushi.Ctx, params []any) {
// Status only // Status only
ctx.SendStatus(204) ctx.SendStatus(204)
// File responses
ctx.SendFile("/path/to/file.pdf")
// Raw bytes
ctx.SendBytes(imageData, "image/png")
} }
```
## Middleware ## Middleware
```go ```go
// Custom middleware // Custom middleware
func loggingMiddleware() sushi.Middleware { func loggingMiddleware() sushi.Middleware {
return func(ctx sushi.Ctx, params []any, next func()) { return func(ctx sushi.Ctx, next func()) {
println("Request:", string(ctx.Method()), string(ctx.Path())) println("Request:", string(ctx.Method()), string(ctx.Path()))
next() next()
println("Status:", ctx.Response.StatusCode()) println("Status:", ctx.Response.StatusCode())
@ -111,7 +167,7 @@ admin.Use(auth.RequireAuth("/login"))
## Authentication Workflow ## Authentication Workflow
### 1. Setup Password Hashing ### 1. Password Hashing
```go ```go
import "git.sharkk.net/Sharkk/Sushi/password" import "git.sharkk.net/Sharkk/Sushi/password"
@ -176,9 +232,16 @@ func main() {
### 4. Login Handler ### 4. Login Handler
```go ```go
func loginHandler(ctx sushi.Ctx, params []any) { func loginHandler(ctx sushi.Ctx) {
email := string(ctx.PostArgs().Peek("email")) // Use fluent API for form data
password := string(ctx.PostArgs().Peek("password")) email := ctx.Input("email").String()
password := ctx.Input("password").String()
// Validate inputs
if ctx.Input("email").IsEmpty() || ctx.Input("password").IsEmpty() {
ctx.SendError(400, "Email and password are required")
return
}
// Find user by email/username // Find user by email/username
user := findUserByEmail(email) user := findUserByEmail(email)
@ -204,7 +267,7 @@ func loginHandler(ctx sushi.Ctx, params []any) {
### 5. Logout Handler ### 5. Logout Handler
```go ```go
func logoutHandler(ctx sushi.Ctx, params []any) { func logoutHandler(ctx sushi.Ctx) {
ctx.Logout() ctx.Logout()
ctx.Redirect("/") ctx.Redirect("/")
} }
@ -213,7 +276,7 @@ func logoutHandler(ctx sushi.Ctx, params []any) {
### 6. Getting Current User ### 6. Getting Current User
```go ```go
func dashboardHandler(ctx sushi.Ctx, params []any) { func dashboardHandler(ctx sushi.Ctx) {
user := ctx.GetCurrentUser().(*User) user := ctx.GetCurrentUser().(*User)
html := fmt.Sprintf("<h1>Welcome, %s!</h1>", user.Username) html := fmt.Sprintf("<h1>Welcome, %s!</h1>", user.Username)
@ -230,7 +293,7 @@ import "git.sharkk.net/Sharkk/Sushi/csrf"
app.Use(csrf.Middleware()) app.Use(csrf.Middleware())
// In your form template // In your form template
func loginPageHandler(ctx sushi.Ctx, params []any) { func loginPageHandler(ctx sushi.Ctx) {
csrfField := csrf.CSRFHiddenField(ctx) csrfField := csrf.CSRFHiddenField(ctx)
html := fmt.Sprintf(` html := fmt.Sprintf(`
@ -266,7 +329,7 @@ app.Get("/assets/*path", sushi.StaticEmbed(files))
## Sessions ## Sessions
```go ```go
func someHandler(ctx sushi.Ctx, params []any) { func someHandler(ctx sushi.Ctx) {
sess := ctx.GetCurrentSession() sess := ctx.GetCurrentSession()
// Set session data // Set session data
@ -357,7 +420,7 @@ func main() {
app.Listen(":8080") app.Listen(":8080")
} }
func homeHandler(ctx sushi.Ctx, params []any) { func homeHandler(ctx sushi.Ctx) {
if ctx.IsAuthenticated() { if ctx.IsAuthenticated() {
ctx.Redirect("/dashboard") ctx.Redirect("/dashboard")
return return
@ -365,7 +428,7 @@ func homeHandler(ctx sushi.Ctx, params []any) {
ctx.SendHTML(`<a href="/login">Login</a>`) ctx.SendHTML(`<a href="/login">Login</a>`)
} }
func loginPageHandler(ctx sushi.Ctx, params []any) { func loginPageHandler(ctx sushi.Ctx) {
html := fmt.Sprintf(` html := fmt.Sprintf(`
<form method="POST" action="/login"> <form method="POST" action="/login">
%s %s
@ -378,9 +441,10 @@ func loginPageHandler(ctx sushi.Ctx, params []any) {
ctx.SendHTML(html) ctx.SendHTML(html)
} }
func loginHandler(ctx sushi.Ctx, params []any) { func loginHandler(ctx sushi.Ctx) {
email := string(ctx.PostArgs().Peek("email")) // Use fluent API for form data
pass := string(ctx.PostArgs().Peek("password")) email := ctx.Input("email").String()
pass := ctx.Input("password").String()
user := findUserByEmail(email) user := findUserByEmail(email)
if user == nil { if user == nil {
@ -397,7 +461,7 @@ func loginHandler(ctx sushi.Ctx, params []any) {
ctx.Redirect("/dashboard") ctx.Redirect("/dashboard")
} }
func dashboardHandler(ctx sushi.Ctx, params []any) { func dashboardHandler(ctx sushi.Ctx) {
user := ctx.GetCurrentUser().(*User) user := ctx.GetCurrentUser().(*User)
html := fmt.Sprintf(` html := fmt.Sprintf(`
@ -411,7 +475,7 @@ func dashboardHandler(ctx sushi.Ctx, params []any) {
ctx.SendHTML(html) ctx.SendHTML(html)
} }
func logoutHandler(ctx sushi.Ctx, params []any) { func logoutHandler(ctx sushi.Ctx) {
ctx.Logout() ctx.Logout()
ctx.Redirect("/") ctx.Redirect("/")
} }