package main import ( "embed" "log" "math/rand" "net/http" "strconv" "jxs.me/todogo/views" ) //go:embed public/* var publicFS embed.FS type Todo struct { Id string Title string Completed bool } func filterSlice[T any](xs []T, pred func(T) bool) []T { result := make([]T, 0, len(xs)) for _, element := range xs { if pred(element) { result = append(result, element) } } return result } func countSlice[T any](xs []T, pred func(T) bool) int { count := 0 for _, element := range xs { if pred(element) { count += 1 } } return count } type server struct { views *views.Views router *http.ServeMux todos []Todo } 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 := s.todos only := q.Get("only") if only == "active" { renderTodos = filterSlice(s.todos, func(todo Todo) bool { return !todo.Completed }) } if only == "completed" { renderTodos = filterSlice(s.todos, func(todo Todo) bool { return todo.Completed }) } incomplete := countSlice(s.todos, func(todo Todo) bool { return !todo.Completed }) vm := viewModel{ Todos: renderTodos, Only: only, Incomplete: incomplete, } list(w, r, vm) } } func (s *server) HandleCreate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { title := r.Form.Get("title") s.AddTodo(Todo{ Id: strconv.Itoa(rand.Int()), Title: title, }) 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 s.todos { if todo.Id == id { todo.Completed = !todo.Completed s.todos[i] = todo } } http.Redirect(w, r, "/", http.StatusSeeOther) } } 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 }) http.Redirect(w, r, "/", http.StatusSeeOther) } } 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 }) http.Redirect(w, r, "/", http.StatusSeeOther) } } func (s *server) routes() { s.router.Handle("/public/", http.FileServer(http.FS(publicFS))) s.router.HandleFunc("/", WithMethod("GET", s.HandleList())) s.router.HandleFunc("/todo", WithForm(WithMethod("POST", s.HandleCreate()))) s.router.HandleFunc("/toggle", WithForm(WithMethod("POST", s.HandleToggle()))) s.router.HandleFunc("/destroy", WithForm(WithMethod("POST", s.HandleDestroy()))) s.router.HandleFunc("/clear", WithMethod("POST", s.HandleClear())) } func run() error { srv := newServer() log.Print("Running on 8080") return http.ListenAndServe(":8080", srv) } func main() { log.Fatal(run()) }