You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

165 lines
3.3 KiB

package main
import (
"embed"
"log"
"math/rand"
"net/http"
"strconv"
2 years ago
"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
2 years ago
}
2 years ago
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
}
2 years ago
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 })
2 years ago
vm := viewModel{
2 years ago
Todos: renderTodos,
Only: only,
Incomplete: incomplete,
}
2 years ago
list(w, r, vm)
}
}
2 years ago
func (s *server) HandleCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
2 years ago
title := r.Form.Get("title")
s.AddTodo(Todo{
Id: strconv.Itoa(rand.Int()),
2 years ago
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)
}
}
2 years ago
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 })
2 years ago
http.Redirect(w, r, "/", http.StatusSeeOther)
}
}
2 years ago
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 })
2 years ago
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", 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())
}