package task import ( "encoding/json" "errors" "fmt" "local/todo-server/server/ajax/form" "net/http" "regexp" "strings" "time" ) type Task struct { UUID string Title string Priority int Tags StrList Created time.Time Edited time.Time Completed time.Time Complete bool Note []string Due time.Time Index int } type StrList []string func (sl StrList) MarshalJSON() ([]byte, error) { s := strings.Join(sl, ", ") return json.Marshal(s) } func New(r *http.Request) (*Task, error) { task := &Task{ UUID: form.NewUUID(), Title: form.Get(r, "title"), Priority: form.ToInt(form.Get(r, "prio")), Tags: StrList(form.ToStrArr(form.Get(r, "tags"))), Created: time.Now(), Edited: time.Now(), Due: form.ToTime(form.Get(r, "duedate")), } task.SetNote(form.Get(r, "note")) return task, task.validate() } func (t *Task) MarshalJSON() ([]byte, error) { // {"total":4,"list":[ // {"id":"3455", // "title":"redo qvolution", // "listId":"18", // "date":"14 Oct 2019 12:56 PM", // "dateInt":1571079392, // "dateInline":"14 Oct", // "dateInlineTitle":"created at 14 Oct 2019 12:56 PM", // "dateEditedInt":1571079401, // "dateCompleted":"", // "dateCompletedInline":"", // "dateCompletedInlineTitle":"Completed at ", // "compl":0, // "prio":"0", // "note":"", // "noteText":"", // "ow":4, // "tags":"work", // "tags_ids":"138", // "duedate":"", // "dueClass":"", // "dueStr":"", // "dueInt":33330000, // "dueTitle":"Due "} // ]} fullFormat := "02 Jan 2006 03:04 PM" shortFormat := "02 Jan" compl := 0 if t.Complete { compl = 1 } m := map[string]interface{}{ "id": t.UUID, "title": t.Title, "listId": "list", "date": t.Created.Format(fullFormat), "dateInt": t.Created.Unix(), "dateInline": t.Created.Format(shortFormat), "dateInlineTitle": fmt.Sprintf("created at %s", t.Created.Format(fullFormat)), "dateEditedInt": t.Edited.Unix(), "dateCompleted": "", "dateCompletedInline": "", "dateCompletedInlineTitle": "", "compl": compl, "prio": t.Priority, "note": strings.Join(t.Note, "\n"), "noteText": strings.Join(t.Note, "\n"), "ow": 0, "tags": strings.Join([]string(t.Tags), ", "), "tags_ids": "", "duedate": t.Due.Format(fullFormat), "dueClass": "", "dueStr": t.Due.Format(shortFormat), "dueInt": t.Due.Unix(), "dueTitle": "Due ", } if t.Complete { m["dateCompleted"] = t.Completed.Format(fullFormat) m["dateCompletedInline"] = t.Completed.Format(shortFormat) m["dateCompletedInlineTitle"] = "Completed at " } return json.Marshal(m) } func (t *Task) AppendTags(tags []string) { t.touch() t.Tags = append(t.Tags, tags...) } func (t *Task) SetComplete(state bool) { t.touch() t.Complete = state if t.Complete { t.Completed = time.Now() } else { t.Completed = time.Time{} } } func (t *Task) SetPrio(prio int) { t.touch() t.Priority = prio } func (t *Task) SetNote(note string) { t.touch() t.Note = strings.Split(note, "\n") } func (t *Task) touch() { t.Edited = time.Now() } func (t *Task) validate() error { if t.Title == "" { return errors.New("task cannot have nil title") } if err := t.smartSyntax(); err != nil { return err } return nil } func (t *Task) smartSyntax() error { re := regexp.MustCompile(`^(/([+-]{0,1}\d+)?/)?(.*?)(\s+/([^/]*)/$)?$|`) matches := re.FindAllStringSubmatch(t.Title, 1)[0] if len(matches) != 6 { return nil } if matches[1] != "" { t.Priority = form.ToInt(matches[1]) } if matches[3] != "" { t.Title = matches[3] } if matches[5] != "" { t.Tags = form.ToStrArr(matches[5]) } return nil }