# 🍣 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, params []any) { 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 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 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) } ``` ## 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 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, params []any) { email := string(ctx.PostArgs().Peek("email")) password := string(ctx.PostArgs().Peek("password")) // 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, params []any) { ctx.Logout() ctx.Redirect("/") } ``` ### 6. Getting Current User ```go func dashboardHandler(ctx sushi.Ctx, params []any) { 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, params []any) { 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, params []any) { 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, params []any) { if ctx.IsAuthenticated() { ctx.Redirect("/dashboard") return } ctx.SendHTML(`Login`) } func loginPageHandler(ctx sushi.Ctx, params []any) { html := fmt.Sprintf(`
%s

`, csrf.HiddenField(ctx)) ctx.SendHTML(html) } func loginHandler(ctx sushi.Ctx, params []any) { email := string(ctx.PostArgs().Peek("email")) pass := string(ctx.PostArgs().Peek("password")) 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, params []any) { user := ctx.GetCurrentUser().(*User) html := fmt.Sprintf(`

Welcome, %s!

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