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 parseIdInt(str string) (int, error) { var id int = -1 if str != "" { idLong, err := strconv.ParseInt(str, 10, 32) if err != nil { return -1, err } id = int(idLong) } return id, nil } func parseTaskFromForm(r *http.Request) (*Task, error) { err := r.ParseForm() if err != nil { return nil, err } id, err := parseIdInt(r.PostFormValue("id")) if err != nil { return nil, err } var group *string { groupStr := r.PostFormValue("group") if groupStr != "" { group = &groupStr } } var ident *string { identStr := r.PostFormValue("ident") if identStr != "" { ident = &identStr } } return &Task{ Id: int(id), Group: group, Ident: ident, Text: r.PostFormValue("text"), }, nil } func readTaskSelectFromPost(r *http.Request, index string) (int, *time.Duration, bool, error) { idS := r.PostFormValue(fmt.Sprintf("task-%s-id", index)) if idS == "" || idS == "-" { return -1, nil, false, nil } id, err := parseIdInt(idS) if err != nil { return -1, nil, false, err } durS := r.PostFormValue(fmt.Sprintf("task-%s-part", index)) if durS == "" { return id, nil, true, nil } dur, err := time.ParseDuration(durS) if err != nil { return -1, nil, false, err } return id, &dur, true, nil } func parseEntryFromForm(r *http.Request, useToday bool) (*Entry, error) { err := r.ParseForm() if err != nil { return nil, err } tasks := map[int]*time.Duration{} taskCount, err := strconv.ParseInt(r.PostFormValue("task-count"), 10, 32) if err != nil { return nil, fmt.Errorf("Error reading task-count: %w", err) } for i := 0; i < int(taskCount); i++ { id, dur, ok, err := readTaskSelectFromPost(r, fmt.Sprintf("%d", i)) if err != nil { return nil, err } if !ok { continue } tasks[id] = dur } { id, dur, ok, err := readTaskSelectFromPost(r, "new") if err != nil { return nil, fmt.Errorf("Error reading new task select: %w", err) } if ok { tasks[id] = dur } } 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 } id, err := parseIdInt(r.PostFormValue("id")) if err != nil { return nil, err } log.Printf("hej hej %+v\n", tasks) task := &Entry{ Id: int(id), From: from, Date: date, To: to, Tasks: tasks, Comment: r.PostFormValue("comment"), } return task, nil } func (s *Server) renderTemplate(w http.ResponseWriter, name string, page interface{}) { tmpl := template.New("").Funcs(template.FuncMap { "arr": (func (els ...interface{}) []interface{} { return els }), "taskMapToArr": (func (taskMap map[int]*time.Duration) [](struct {Key int; Id int; Sel *string}) { res := make([](struct {Key int; Id int; Sel *string}), 0, len(taskMap)) count := 0 for taskId, sel := range taskMap { thing := struct {Key int; Id int; Sel *string}{ Key: count, Id: taskId, Sel: nil, } if sel != nil { formatted := sel.String() thing.Sel = &formatted } res = append(res, thing) count += 1 } fmt.Printf("Hej, %+v, lul: %+v\n", res, taskMap) return res }), }) tmpl, err := tmpl.ParseFiles("templates/index.html", "templates/parts/entry.html", "templates/parts/entryRows.html", "templates/parts/task.html", "templates/parts/taskRows.html", "templates/parts/taskEntry.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 := parseEntryFromForm(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 := parseEntryFromForm(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 := parseEntryFromForm(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) { id, err := parseIdInt(r.URL.Query().Get("id")) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } 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, "taskRows.html", page) } func (s *Server) postTaskSave(w http.ResponseWriter, r *http.Request) { task, err := parseTaskFromForm(r) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } task, err = s.srv.Db.SaveTask(task) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } log.Printf("Hej %+v\n", task) page, err := s.srv.GetTaskPage(nil, task) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } w.Header().Add("HX-Trigger", "changedTasks") s.renderTemplate(w, "task.html", page) } func (s *Server) getTaskEmpty(w http.ResponseWriter, _ *http.Request) { task := NewTask() page, err := s.srv.GetTaskPage(nil, task) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } s.renderTemplate(w, "task.html", page) } func (s *Server) getTaskEdit(w http.ResponseWriter, r *http.Request) { id, err := parseIdInt(r.URL.Query().Get("id")) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } page, err := s.srv.GetTaskPage(&id, nil) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } s.renderTemplate(w, "task.html", page) } func (s *Server) getTaskSelect(w http.ResponseWriter, r *http.Request) { page, err := s.srv.GetTasksPage() if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } var selected int = -1 { selectedStr := r.URL.Query().Get("selected") if selectedStr != "" { selectedLong, err := strconv.ParseInt(selectedStr, 10, 32) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } selected = int(selectedLong) } } args := []interface{}{ &page.GroupToTasks, selected, } s.renderTemplate(w, "taskEntry.html", args) } 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("/task/save", s.postTaskSave).Methods("POST") r.HandleFunc("/task/empty", s.getTaskEmpty).Methods("GET") r.HandleFunc("/task/edit", s.getTaskEdit).Methods("GET") r.HandleFunc("/task/select", s.getTaskSelect).Methods("GET") r.HandleFunc("/", s.rootHandle) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }