package ratchet import ( "fmt" "html/template" "log/slog" "net/http" "os" "strconv" "strings" ) type RatchetServer struct { http.Handler //Services used by HTTP routes UserService UserService } func parseLogLevel(levelStr string) slog.Level { switch strings.ToUpper(levelStr) { case "DEBUG": return slog.LevelDebug case "INFO": return slog.LevelInfo case "WARN": return slog.LevelWarn case "ERROR": return slog.LevelError default: return slog.LevelInfo // Default level } } func InitLogging(level string) { // Use os.Stderr // // Stderr is used for diagnostics and logging. Stdout is used for program // output. Stderr also have greater likely hood of being seen if a programs // output is being redirected. parsedLogLevel := parseLogLevel(level) loggerHandler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: parsedLogLevel, AddSource: true}) logger := slog.New(loggerHandler) slog.SetDefault(logger) } func NewRatchetServer() *RatchetServer { r := new(RatchetServer) // TODO implement middleware that disables directory listings fileServer := http.FileServer(http.Dir("./ui/static/")) router := http.NewServeMux() // Subtree pattern for static assets router.Handle("GET /static/", http.StripPrefix("/static/", fileServer)) // /{$} is used to prevent subtree path patterns from acting like a wildcard // resulting in this route requiring an exact match on "/" only // You can only include one HTTP method in a route pattern if you choose // GET will match GET & HEAD http request methods router.HandleFunc("GET /{$}", home) router.HandleFunc("GET /snippet/view/{id}", snippetView) router.HandleFunc("GET /snippet/create", snippetCreate) // FYI The http.HandlerFunc() adapter works by automatically adding a ServeHTTP() method to // the passed function router.HandleFunc("POST /snippet/create", snippetCreatePost) // Mux Router implements the Handler interface. AKA it has a ServeHTTP receiver. r.Handler = router return r } func home(w http.ResponseWriter, r *http.Request) { // TODO middleware should be able to print out these lines for all routes slog.Info("request received", "method", "GET", "path", "/") w.Header().Add("Server", "Go") // Initialize a slice containing the paths to the two files. It's important // to note that the file containing our base template must be the *first* // file in the slice. files := []string{ "./ui/html/base.go.tmpl", "./ui/html/partials/nav.go.tmpl", "./ui/html/pages/home.go.tmpl", } // read template file into template set. ts, err := template.ParseFiles(files...) if err != nil { slog.Error(err.Error()) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Write template content to response body err = ts.ExecuteTemplate(w, "base", nil) if err != nil { slog.Error(err.Error()) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } } func snippetView(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil || id < 1 { http.NotFound(w, r) return } // Set a new cache-control header. If an existing "Cache-Control" header exists // it will be overwritten. w.Header().Set("Cache-Control", "public, max-age=31536000") // msg := fmt.Sprintf("Snippet %d...", id) // w.Write([]byte(msg)) // we can rely on the Write() interface to use a differnent // function to write out our response fmt.Fprintf(w, "Snippet %d...", id) } // snippetCreate handles display of the form used to create snippets func snippetCreate(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Displaying snippetCreate form...")) } // snippetCreate handles display of the form used to create snippets func snippetCreatePost(w http.ResponseWriter, r *http.Request) { // example of a custom header. Must be done before calling WriteHeader // or they will fail to take effect. w.Header().Add("Server", "Dirp") w.WriteHeader(http.StatusCreated) w.Write([]byte("Created snippet...")) }