diff --git a/main.go b/main.go index 22a78fd..113db54 100644 --- a/main.go +++ b/main.go @@ -19,12 +19,6 @@ type Todo struct { Completed bool } -type ViewModel struct { - Todos []Todo - Only string - Incomplete int -} - func filterSlice[T any](xs []T, pred func(T) bool) []T { result := make([]T, 0, len(xs)) for _, element := range xs { @@ -48,113 +42,145 @@ func countSlice[T any](xs []T, pred func(T) bool) int { return count } -func main() { - todos := make([]Todo, 0, 10) +type server struct { + views *views.Views + router *http.ServeMux + todos []Todo +} - t := views.New() - list := t.Renderer("list.html") - http.Handle("/public/", http.FileServer(http.FS(publicFS))) - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func newServer() *server { + srv := &server{ + views: views.New(), + router: http.NewServeMux(), + todos: make([]Todo, 0, 10), + } + srv.routes() + + return srv +} + +func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.router.ServeHTTP(w, r) +} + +func (s *server) AddTodo(t Todo) { + s.todos = append(s.todos, t) +} + +func (s *server) HandleList() http.HandlerFunc { + type viewModel struct { + Todos []Todo + Only string + Incomplete int + } + + list := s.views.Renderer("list.html") + + return func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() - renderTodos := todos + renderTodos := s.todos only := q.Get("only") if only == "active" { - renderTodos = filterSlice(todos, func(todo Todo) bool { return !todo.Completed }) + renderTodos = filterSlice(s.todos, func(todo Todo) bool { return !todo.Completed }) } if only == "completed" { - renderTodos = filterSlice(todos, func(todo Todo) bool { return todo.Completed }) + renderTodos = filterSlice(s.todos, func(todo Todo) bool { return todo.Completed }) } - incomplete := countSlice(todos, func(todo Todo) bool { return !todo.Completed }) + incomplete := countSlice(s.todos, func(todo Todo) bool { return !todo.Completed }) - vm := ViewModel{ + vm := viewModel{ Todos: renderTodos, Only: only, Incomplete: incomplete, } list(w, r, vm) - }) - http.HandleFunc("/todo", func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - if err := r.ParseForm(); err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } + } +} +func (s *server) HandleCreate() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { title := r.Form.Get("title") - todos = append(todos, Todo{ + s.AddTodo(Todo{ Id: strconv.Itoa(rand.Int()), Title: title, }) - w.Header().Set("Location", "/") - w.WriteHeader(http.StatusSeeOther) - }) - - http.HandleFunc("/toggle", func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - if err := r.ParseForm(); err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } + http.Redirect(w, r, "/", http.StatusSeeOther) + } +} +func (s *server) HandleToggle() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { id := r.Form.Get("id") - for i, todo := range todos { + for i, todo := range s.todos { if todo.Id == id { todo.Completed = !todo.Completed - todos[i] = todo + s.todos[i] = todo } } - w.Header().Set("Location", "/") - w.WriteHeader(http.StatusSeeOther) - }) + http.Redirect(w, r, "/", http.StatusSeeOther) + } +} - http.HandleFunc("/destroy", func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } +func (s *server) HandleDestroy() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id := r.Form.Get("id") + s.todos = filterSlice(s.todos, func(t Todo) bool { return t.Id != id }) - if err := r.ParseForm(); err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } + http.Redirect(w, r, "/", http.StatusSeeOther) + } +} - id := r.Form.Get("id") - todos = filterSlice(todos, func(t Todo) bool { return t.Id != id }) +func (s *server) HandleClear() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + s.todos = filterSlice(s.todos, func(t Todo) bool { return !t.Completed }) - w.Header().Set("Location", "/") - w.WriteHeader(http.StatusSeeOther) - }) + http.Redirect(w, r, "/", http.StatusSeeOther) + } +} + +func (s *server) routes() { + s.router.Handle("/public/", http.FileServer(http.FS(publicFS))) + s.router.HandleFunc("/", s.HandleList()) + s.router.HandleFunc("/todo", parseForm(ensureMethod("POST", s.HandleCreate()))) + s.router.HandleFunc("/toggle", parseForm(ensureMethod("POST", s.HandleToggle()))) + s.router.HandleFunc("/destroy", parseForm(ensureMethod("POST", s.HandleDestroy()))) + s.router.HandleFunc("/clear", ensureMethod("POST", s.HandleClear())) +} + +func run() error { + srv := newServer() + + log.Print("Running on 8080") + return http.ListenAndServe(":8080", srv) +} + +func main() { + log.Fatal(run()) +} - http.HandleFunc("/clear", func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { +func ensureMethod(method string, next http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method != method { w.WriteHeader(http.StatusMethodNotAllowed) return } + next.ServeHTTP(w, r) + } +} +func parseForm(next http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Bad Request")) return } - todos = filterSlice(todos, func(t Todo) bool { return !t.Completed }) - - w.Header().Set("Location", "/") - w.WriteHeader(http.StatusSeeOther) - }) - - log.Print("Running on 8080") - log.Fatal(http.ListenAndServe(":8080", nil)) + next.ServeHTTP(w, r) + } }