package main import ( "errors" "fmt" "log" "net/http" "net/url" "strconv" "text/template" "time" "github.com/gorilla/mux" ) type Server struct { template *template.Template srv *Service } func parseTaskFromForm(r *http.Request, useToday bool) (*Entry, error) { err := r.ParseForm() if err != nil { return nil, err } var date Date dateP, err := DateFromString(r.PostFormValue("date")) if err != nil { return nil, err } if dateP != nil { date = *dateP } else { if useToday { date = DateFromStd(time.Now()) } else { return nil, errors.New("date missing from post") } } from, err := TimeFromString(r.PostFormValue("from")) if err != nil { return nil, err } to, err := TimeFromString(r.PostFormValue("to")) if err != nil { return nil, err } var id int64 if r.PostFormValue("id") == "" { id = -1 } else { id, err = strconv.ParseInt(r.PostFormValue("id"), 10, 32) if err != nil { return nil, err } } task := &Entry{ Id: int(id), From: from, Date: date, To: to, Comment: r.PostFormValue("comment"), } return task, nil } func (s *Server) renderTemplate(w http.ResponseWriter, name string, page interface{}) { tmpl, err := template.ParseFiles("templates/index.html", "templates/parts/entry.html", "templates/parts/entryRows.html") if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } err = tmpl.ExecuteTemplate(w, name, page) if err != nil { log.Println(err) fmt.Fprintf(w, "RENDER ERROR: %s\n", err.Error()) } } func writeError(w http.ResponseWriter, err string, code int) { log.Println(err) w.WriteHeader(code) fmt.Fprintf(w, "%d: %s\n", code, err) return } func parseDateFromUrl(url *url.URL, allowToday bool) (Date, error) { date, err := DateFromString(url.Query().Get("date")) if err != nil { return "", nil } if date == nil { if allowToday { return DateFromStd(time.Now()), nil } else { return "", errors.New("date query param empty") } } return *date, nil } func (s *Server) rootHandle(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { w.WriteHeader(http.StatusNotFound) fmt.Fprintln(w, "Not found 404") return } date, err := parseDateFromUrl(r.URL, true) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } page, err := s.srv.GetRootPage(date) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } s.renderTemplate(w, "index.html", page) } func (s *Server) postStart(w http.ResponseWriter, r *http.Request) { now := time.Now() task, err := parseTaskFromForm(r, true) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } _, err = s.srv.Db.StartNewEntry(now, task) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } page, err := s.srv.GetEntryPage(nil, NewDateInfo(now)) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } w.Header().Add("HX-Trigger", "changedEntries") s.renderTemplate(w, "entry.html", page) } func (s *Server) postStop(w http.ResponseWriter, r *http.Request) { now := time.Now() task, err := parseTaskFromForm(r, true) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } _, err = s.srv.Db.SaveEntry(task) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } err = s.srv.Db.StopEntry(now) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } w.Header().Add("HX-Trigger", "changedEntries") s.getTracking(w, r) } func (s *Server) getTracking(w http.ResponseWriter, _ *http.Request) { nowDt := NewDateInfo(time.Now()) page, err := s.srv.GetEntryPage(nil, nowDt) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } s.renderTemplate(w, "entry.html", page) } func (s *Server) putSave(w http.ResponseWriter, r *http.Request) { task, err := parseTaskFromForm(r, false) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } task, err = s.srv.Db.SaveEntry(task) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } var detached *Entry = nil if r.URL.Query().Get("detached") == "true" { detached = task } page, err := s.srv.GetEntryPage(detached, NewDateInfo(task.Date.ToStd())) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } w.Header().Add("HX-Trigger", "changedEntries") s.renderTemplate(w, "entry.html", page) } func (s *Server) getNew(w http.ResponseWriter, r *http.Request) { date, err := parseDateFromUrl(r.URL, true) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } task := NewEntry(date) page, err := s.srv.GetEntryPage(task, NewDateInfo(date.ToStd())) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } s.renderTemplate(w, "entry.html", page) } func (s *Server) getEdit(w http.ResponseWriter, r *http.Request) { idLarge, err := strconv.ParseInt(r.URL.Query().Get("id"), 10, 32) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } id := int(idLarge) entries, err := s.srv.Db.QueryEntry(&id, nil) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } if len(entries) < 1 { writeError(w, "could not find entry by id", http.StatusNotFound) return } page, err := s.srv.GetEntryPage(entries[0], NewDateInfo(entries[0].Date.ToStd())) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } s.renderTemplate(w, "entry.html", page) } func (s *Server) getEntries(w http.ResponseWriter, r *http.Request) { date, err := parseDateFromUrl(r.URL, true) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } page, err := s.srv.GetRootPage(date) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } s.renderTemplate(w, "entryRows.html", page) } func (s *Server) getTasksList(w http.ResponseWriter, _ *http.Request) { page, err := s.srv.GetTasksPage() if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } s.renderTemplate(w, "tasksPage.html", page) } func main() { fmt.Println("Hello world!") s := Server{ template: nil, srv: NewService(NewInMemDb()), } r := mux.NewRouter() r.HandleFunc("/start", s.postStart).Methods("POST") r.HandleFunc("/newDetached", s.getNew).Methods("GET") r.HandleFunc("/tracking", s.getTracking).Methods("GET") r.HandleFunc("/stop", s.postStop).Methods("POST") r.HandleFunc("/save", s.putSave).Methods("PUT") r.HandleFunc("/entryRows", s.getEntries).Methods("GET") r.HandleFunc("/edit", s.getEdit).Methods("GET") r.HandleFunc("/task/list", s.getTasksList).Methods("GET") r.HandleFunc("/", s.rootHandle) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }