# 🍣 Sushi A fast, raw, tasty framework for simplifying basic web apps! ## Quick Start ```go package main import "git.sharkk.net/Sharkk/Sushi" func main() { app := sushi.New() // Initialize sessions sushi.InitSessions("sessions.json") app.Get("/", func(ctx sushi.Ctx) { ctx.SendHTML("

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 and Queries Sushi provides a fluent API for accessing URL parameters, query strings, and form data with automatic type conversion: ### URL Parameters (Named Routes) ```go app.Get("/users/:id", func(ctx sushi.Ctx) { // Get URL parameter with fluent API userID := ctx.Param("id").Int() // /users/123 -> 123 userIDStr := ctx.Param("id").String() // /users/123 -> "123" // With defaults limit := ctx.Param("limit").IntDefault(10) }) app.Get("/users/:id/posts/:slug", func(ctx sushi.Ctx) { userID := ctx.Param("id").Int() slug := ctx.Param("slug").String() // Check if parameter exists if ctx.Param("optional").Exists() { // Handle optional parameter } }) ``` ### 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 ```go func myHandler(ctx sushi.Ctx) { // JSON responses ctx.SendJSON(map[string]string{"message": "success"}) // HTML responses ctx.SendHTML("

Welcome

") // Text responses ctx.SendText("Plain text") // Error responses ctx.SendError(404, "Not Found") // Redirects ctx.Redirect("/login") // Status only ctx.SendStatus(204) // File responses ctx.SendFile("/path/to/file.pdf") // Raw bytes ctx.SendBytes(imageData, "image/png") } ## Middleware ```go // Custom middleware func loggingMiddleware() sushi.Middleware { return func(ctx sushi.Ctx, 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. Password Hashing The password package supports both Argon2 (default) and Bcrypt algorithms with configurable settings: ```go import "git.sharkk.net/Sharkk/Sushi/password" // Using default Argon2 configuration hashedPassword := password.HashPassword("userpassword123") // Verify password (auto-detects algorithm) isValid, err := password.VerifyPassword("userpassword123", hashedPassword) // Configure for Bcrypt password.SetConfig(password.Config{ Algorithm: password.Bcrypt, Bcrypt: password.BcryptConfig{ Cost: 12, // Higher = more secure but slower }, }) // Configure for Argon2 with custom settings password.SetConfig(password.Config{ Algorithm: password.Argon2, Argon2: password.Argon2Config{ Time: 3, // Iterations Memory: 128 * 1024, // 128 MB Threads: 4, KeyLen: 32, }, }) // Use preset configurations password.SetConfig(password.Config{ Algorithm: password.Argon2, Argon2: password.SecureArgon2Config(), // More secure, slower // Or: password.FastArgon2Config() for development }) // Check if password needs rehashing (algorithm or params changed) if password.NeedsRehash(userHashedPassword) { // Rehash password after successful verification newHash := password.HashPassword(plainPassword) // Update stored hash in database } ``` ### 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 sushi.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) { // Use fluent API for form data 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 user := findUserByEmail(email) if user == nil { ctx.SendError(401, "Invalid credentials") return } // Verify password isValid, err := password.VerifyPassword(password, user.Password) if err != nil || !isValid { ctx.SendError(401, "Invalid credentials") return } // Log the user in ctx.Login(user.ID, user) ctx.Redirect("/dashboard") } ``` ### 5. Logout Handler ```go func logoutHandler(ctx sushi.Ctx) { ctx.Logout() ctx.Redirect("/") } ``` ### 6. Getting Current User ```go func dashboardHandler(ctx sushi.Ctx) { user := ctx.GetCurrentUser().(*User) html := fmt.Sprintf("

Welcome, %s!

", user.Username) ctx.SendHTML(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) { csrfField := csrf.CSRFHiddenField(ctx) html := fmt.Sprintf(`
%s
`, csrfField) ctx.SendHTML(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) { sess := ctx.GetCurrentSession() // 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() sushi.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) { if ctx.IsAuthenticated() { ctx.Redirect("/dashboard") return } ctx.SendHTML(`Login`) } func loginPageHandler(ctx sushi.Ctx) { html := fmt.Sprintf(`
%s

`, csrf.HiddenField(ctx)) ctx.SendHTML(html) } func loginHandler(ctx sushi.Ctx) { // Use fluent API for form data email := ctx.Input("email").String() pass := ctx.Input("password").String() user := findUserByEmail(email) if user == nil { ctx.SendError(401, "Invalid credentials") return } if valid, _ := password.VerifyPassword(pass, user.Password); !valid { ctx.SendError(401, "Invalid credentials") return } ctx.Login(user.ID, user) ctx.Redirect("/dashboard") } func dashboardHandler(ctx sushi.Ctx) { user := ctx.GetCurrentUser().(*User) html := fmt.Sprintf(`

Welcome, %s!

%s
`, user.Email, csrf.HiddenField(ctx)) ctx.SendHTML(html) } func logoutHandler(ctx sushi.Ctx) { ctx.Logout() ctx.Redirect("/") } ```