From 0bc80e3cd7360ce40c6785c9cb9141526ebd36f9 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sun, 12 Jan 2025 08:12:23 -0500 Subject: [PATCH] Actually following lets-go book --- .air.toml | 52 +++++++++++++ .gitignore | 1 + README.md | 2 + cmd/ratchetd/main.go | 10 ++- error.go => internal/error.go | 0 error_test.go => internal/error_test.go | 0 internal/server.go | 97 +++++++++++++++++++++++++ user.go => internal/user.go | 0 user_test.go => internal/user_test.go | 0 ui/html/base.go.tmpl | 19 +++++ ui/html/pages/home.go.tmpl | 6 ++ ui/html/partials/nav.go.tmpl | 5 ++ 12 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 .air.toml rename error.go => internal/error.go (100%) rename error_test.go => internal/error_test.go (100%) create mode 100644 internal/server.go rename user.go => internal/user.go (100%) rename user_test.go => internal/user_test.go (100%) create mode 100644 ui/html/base.go.tmpl create mode 100644 ui/html/pages/home.go.tmpl create mode 100644 ui/html/partials/nav.go.tmpl diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..d435769 --- /dev/null +++ b/.air.toml @@ -0,0 +1,52 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main cmd/ratchetd/main.go" + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html", "go.tmpl"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + silent = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.gitignore b/.gitignore index adf8f72..26ef082 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ # Go workspace file go.work +tmp/ \ No newline at end of file diff --git a/README.md b/README.md index a554b37..180ec69 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ An example web application in Golang. +https://lets-go.alexedwards.net/sample/02.09-serving-static-files.html + ## Project Structure Loosely inspired by the organization of [WTFDial](https://github.com/benbjohnson/wtf?tab=readme-ov-file#project-structure), diff --git a/cmd/ratchetd/main.go b/cmd/ratchetd/main.go index b2b8c6e..df49678 100644 --- a/cmd/ratchetd/main.go +++ b/cmd/ratchetd/main.go @@ -1,11 +1,12 @@ package main import ( - "fmt" - "os" + "log" + "net/http" "strings" "git.runcible.io/learning/ratchet" + ratchethttp "git.runcible.io/learning/ratchet/internal" ) var ( @@ -17,5 +18,8 @@ func main() { // Propagate build information to root package to share globally ratchet.Version = strings.TrimPrefix(version, "") ratchet.Commit = commit - fmt.Fprintf(os.Stdout, "Version: %s\nCommit: %s\n", ratchet.Version, ratchet.Commit) + + server := ratchethttp.NewRatchetServer() + log.Print("Listening on http://localhost:5001/") + log.Fatal(http.ListenAndServe(":5001", server)) } diff --git a/error.go b/internal/error.go similarity index 100% rename from error.go rename to internal/error.go diff --git a/error_test.go b/internal/error_test.go similarity index 100% rename from error_test.go rename to internal/error_test.go diff --git a/internal/server.go b/internal/server.go new file mode 100644 index 0000000..1b72444 --- /dev/null +++ b/internal/server.go @@ -0,0 +1,97 @@ +package ratchet + +import ( + "fmt" + "html/template" + "log" + "net/http" + "strconv" +) + +type RatchetServer struct { + http.Handler + + //Services used by HTTP routes + + UserService UserService +} + +func NewRatchetServer() *RatchetServer { + r := new(RatchetServer) + + router := http.NewServeMux() + // /{$} 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) + router.HandleFunc("POST /snippet/create", snippetCreatePost) + r.Handler = router + return r +} + +func home(w http.ResponseWriter, r *http.Request) { + 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 { + log.Print(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 { + log.Print(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...")) +} diff --git a/user.go b/internal/user.go similarity index 100% rename from user.go rename to internal/user.go diff --git a/user_test.go b/internal/user_test.go similarity index 100% rename from user_test.go rename to internal/user_test.go diff --git a/ui/html/base.go.tmpl b/ui/html/base.go.tmpl new file mode 100644 index 0000000..b017bf7 --- /dev/null +++ b/ui/html/base.go.tmpl @@ -0,0 +1,19 @@ +{{define "base" -}} + + + + + {{template "title" .}} + + +
+

Ratchet

+
+ {{template "nav" .}} +
+ {{template "main" .}} +
+ + + +{{end}} \ No newline at end of file diff --git a/ui/html/pages/home.go.tmpl b/ui/html/pages/home.go.tmpl new file mode 100644 index 0000000..4925fb7 --- /dev/null +++ b/ui/html/pages/home.go.tmpl @@ -0,0 +1,6 @@ +{{define "title"}}Home{{end}} + +{{define "main" -}} +

Latest Snippets

+

There's nothing to see yet!

+{{- end -}} diff --git a/ui/html/partials/nav.go.tmpl b/ui/html/partials/nav.go.tmpl new file mode 100644 index 0000000..256e49a --- /dev/null +++ b/ui/html/partials/nav.go.tmpl @@ -0,0 +1,5 @@ +{{define "nav" -}} + +{{- end}} \ No newline at end of file