Making some decisions and refactoring

drew/lets-go
Drew Bednar 3 months ago
parent 1b823bf1d5
commit 43b7e2d986

@ -1,4 +1,4 @@
package user
package model
import (
"context"

@ -1,4 +1,4 @@
package user
package model
import (
"testing"

@ -1,149 +0,0 @@
package server
import (
"database/sql"
"errors"
"fmt"
"log/slog"
"net/http"
"strconv"
"git.runcible.io/learning/ratchet/internal/domain/user"
"git.runcible.io/learning/ratchet/internal/model"
)
type RatchetServer struct {
http.Handler
logger *slog.Logger
//Services used by HTTP routes
snippetService *model.SnippetService
UserService user.UserService
}
func NewRatchetServer(logger *slog.Logger, db *sql.DB) *RatchetServer {
rs := new(RatchetServer)
rs.logger = logger
rs.snippetService = &model.SnippetService{DB: db}
// 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 /{$}", rs.home)
router.HandleFunc("GET /snippet/view/{id}", rs.snippetView)
router.HandleFunc("GET /snippet/create", rs.snippetCreate)
// FYI The http.HandlerFunc() adapter works by automatically adding a ServeHTTP() method to
// the passed function
router.HandleFunc("POST /snippet/create", rs.snippetCreatePost)
// Mux Router implements the Handler interface. AKA it has a ServeHTTP receiver.
rs.Handler = router
return rs
}
func (rs *RatchetServer) home(w http.ResponseWriter, r *http.Request) {
// TODO middleware should be able to print out these lines for all routes
rs.logger.Info("request received", "method", "GET", "path", "/")
w.Header().Add("Server", "Go")
// Retrieve Snippets from DB
snippets, err := rs.snippetService.Lastest()
if err != err {
rs.serverError(w, r, err)
return
}
rs.logger.Debug(fmt.Sprintf("%d snippets retrieved", len(snippets)))
for _, snippet := range snippets {
fmt.Fprintf(w, "%+v\n", snippet)
}
// // 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 {
// rs.serverError(w, r, err)
// return
// }
// // Write template content to response body
// err = ts.ExecuteTemplate(w, "base", nil)
// if err != nil {
// // This is the older more verbose way of doing what RatchetServer.serverError does
// // rs.logger.Error(err.Error())
// // http.Error(w, "Internal Server Error", http.StatusInternalServerError)
// rs.serverError(w, r, err)
// }
}
func (rs *RatchetServer) snippetView(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil || id < 1 {
// http.NotFound(w, r)
rs.clientError(w, http.StatusNotFound)
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")
snippet, err := rs.snippetService.Get(id)
if err != nil {
rs.logger.Debug(fmt.Sprintf("Failed to retrieve an active record with id: %d", id))
if errors.Is(err, model.ErrNoRecord) {
rs.clientError(w, http.StatusNotFound)
} else {
rs.serverError(w, r, err)
}
return
}
// Write the snippet data as a plain-text HTTP response body.
fmt.Fprintf(w, "%+v", snippet)
}
// snippetCreate handles display of the form used to create snippets
func (rs *RatchetServer) snippetCreate(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Create snippet form..."))
}
// snippetCreate handles display of the form used to create snippets
//
// curl -iL -d "" http://localhost:5000/snippet/create
func (rs *RatchetServer) 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")
// Create some variables holding dummy data. We'll remove these later on
// during the build.
title := "O snail"
content := "O snail\nClimb Mount Fuji,\nBut slowly, slowly!\n\n Kobayashi Issa"
expires := 7
id, err := rs.snippetService.Insert(title, content, expires)
if err != nil {
rs.serverError(w, r, err)
}
http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther)
}

@ -0,0 +1,140 @@
package server
import (
"errors"
"fmt"
"log/slog"
"net/http"
"strconv"
"git.runcible.io/learning/ratchet/internal/model"
)
// TODO function should accept and a pointer to an interface allowing for mocking in tests.
func handleHome(logger *slog.Logger, snippetService *model.SnippetService) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
logger.Info("request received", "method", "GET", "path", "/")
// Just and example of adding a header
w.Header().Add("Server", "Go")
// Retrieve Snippets from DB
snippets, err := snippetService.Lastest()
if err != err {
serverError(w, r, err)
return
}
logger.Debug(fmt.Sprintf("%d snippets retrieved", len(snippets)))
for _, snippet := range snippets {
fmt.Fprintf(w, "%+v\n", snippet)
}
// // 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 {
// rs.serverError(w, r, err)
// return
// }
// // Write template content to response body
// err = ts.ExecuteTemplate(w, "base", nil)
// if err != nil {
// // This is the older more verbose way of doing what RatchetServer.serverError does
// // rs.logger.Error(err.Error())
// // http.Error(w, "Internal Server Error", http.StatusInternalServerError)
// rs.serverError(w, r, err)
// }
})
}
func handleSnippetView(logger *slog.Logger, snippetService *model.SnippetService) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil || id < 1 {
clientError(w, http.StatusNotFound)
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")
snippet, err := snippetService.Get(id)
if err != nil {
logger.Debug(fmt.Sprintf("Failed to retrieve an active record with id: %d", id))
if errors.Is(err, model.ErrNoRecord) {
clientError(w, http.StatusNotFound)
} else {
serverError(w, r, err)
}
return
}
// Write the snippet data as a plain-text HTTP response body.
fmt.Fprintf(w, "%+v", snippet)
})
}
// // snippetCreate handles display of the form used to create snippets
// func (rs *RatchetServer) snippetCreate(w http.ResponseWriter, r *http.Request) {
// w.Write([]byte("Create snippet form..."))
// }
func handleSnippetCreateGet() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Create snippet form.."))
})
}
// snippetCreate handles display of the form used to create snippets
//
// curl -iL -d "" http://localhost:5001/snippet/create
func handleSnippetCreatePost(logger *slog.Logger, snippetService *model.SnippetService) http.Handler {
return http.HandlerFunc(
func(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")
// Create some variables holding dummy data. We'll remove these later on
// during the build.
title := "O snail"
content := "O snail\nClimb Mount Fuji,\nBut slowly, slowly!\n\n Kobayashi Issa"
expires := 7
id, err := snippetService.Insert(title, content, expires)
if err != nil {
serverError(w, r, err)
}
logger.Info(fmt.Sprintf("Inserted record. id: %d", id))
http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther)
})
}
// func (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")
// // Create some variables holding dummy data. We'll remove these later on
// // during the build.
// title := "O snail"
// content := "O snail\nClimb Mount Fuji,\nBut slowly, slowly!\n\n Kobayashi Issa"
// expires := 7
// id, err := rs.snippetService.Insert(title, content, expires)
// if err != nil {
// rs.serverError(w, r, err)
// }
// http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther)
// }

@ -1,6 +1,7 @@
package server
import (
"log/slog"
"net/http"
"runtime/debug"
)
@ -8,7 +9,8 @@ import (
// serverError helper writes a log entry at Error level (including the request
// method and URI as attributes), then sends a generic 500 Internal Server Error
// response to the user.
func (rs *RatchetServer) serverError(w http.ResponseWriter, r *http.Request, err error) {
func serverError(w http.ResponseWriter, r *http.Request, err error) {
logger := slog.Default()
var (
method = r.Method
uri = r.URL.RequestURI()
@ -17,13 +19,13 @@ func (rs *RatchetServer) serverError(w http.ResponseWriter, r *http.Request, err
trace = string(debug.Stack())
)
rs.logger.Error(err.Error(), "method", method, "uri", uri, "trace", trace)
logger.Error(err.Error(), "method", method, "uri", uri, "trace", trace)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
// clientError helper sends a specific status code and corresponding description
// to the user. We'll use this later in the book to send responses like 400 "Bad
// Request" when there's a problem with the request that the user sent
func (rs *RatchetServer) clientError(w http.ResponseWriter, status int) {
func clientError(w http.ResponseWriter, status int) {
http.Error(w, http.StatusText(status), status)
}

@ -0,0 +1,28 @@
package server
import (
"database/sql"
"log/slog"
"net/http"
"git.runcible.io/learning/ratchet/internal/model"
)
func addRoutes(mux *http.ServeMux,
logger *slog.Logger,
db *sql.DB,
snippetService *model.SnippetService) http.Handler {
// /{$} 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
mux.Handle("GET /{$}", handleHome(logger, snippetService))
mux.Handle("GET /snippet/view/{id}", handleSnippetView(logger, snippetService))
mux.Handle("GET /snippet/create", handleSnippetCreateGet())
mux.Handle("POST /snippet/create", handleSnippetCreatePost(logger, snippetService))
// mux.Handle("/something", handleSomething(logger, config))
// mux.Handle("/healthz", handleHealthzPlease(logger))
// mux.Handle("/", http.NotFoundHandler())
return mux
}

@ -0,0 +1,47 @@
package server
import (
"database/sql"
"log/slog"
"net/http"
"git.runcible.io/learning/ratchet/internal/model"
)
type RatchetServer struct {
http.Handler
logger *slog.Logger
//Services used by HTTP routes
snippetService *model.SnippetService
UserService model.UserService
}
func NewRatchetServer(logger *slog.Logger, db *sql.DB) *RatchetServer {
rs := new(RatchetServer)
rs.logger = logger
rs.snippetService = &model.SnippetService{DB: db}
// 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 /{$}", rs.home)
// router.HandleFunc("GET /snippet/view/{id}", rs.snippetView)
// router.HandleFunc("GET /snippet/create", rs.snippetCreate)
// FYI The http.HandlerFunc() adapter works by automatically adding a ServeHTTP() method to
// the passed function
// router.HandleFunc("POST /snippet/create", rs.snippetCreatePost)
// Mux Router implements the Handler interface. AKA it has a ServeHTTP receiver.
// SEE we can really clean things up by moving this into routes.go and handlers.go
rs.Handler = addRoutes(router, rs.logger, db, rs.snippetService)
return rs
}
Loading…
Cancel
Save