Submit a task

master
Bel LaPointe 2019-11-13 13:17:56 -07:00
parent ee77d9d3b7
commit cf1fd1dfed
13 changed files with 259 additions and 49 deletions

View File

@ -4,6 +4,7 @@ import (
"local/storage"
"local/todo-server/config"
"net/http"
"net/url"
)
type Ajax struct {
@ -20,56 +21,63 @@ func New() (*Ajax, error) {
func (a *Ajax) HandleAjax(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
var foo func(http.ResponseWriter, *http.Request) error
if v := params.Get("loadLists"); v != "" {
if has(params, "loadLists") {
foo = a.loadLists
} else if v := params.Get("loadTasks"); v != "" {
} else if has(params, "loadTasks") {
foo = a.loadTasks
} else if v := params.Get("newTask"); v != "" {
} else if has(params, "newTask") {
foo = a.newTask
} else if v := params.Get("fullNewTask"); v != "" {
} else if has(params, "fullNewTask") {
foo = a.newTask
} else if v := params.Get("deleteTask"); v != "" {
} else if has(params, "deleteTask") {
foo = a.deleteTask
} else if v := params.Get("completeTask"); v != "" {
} else if has(params, "completeTask") {
foo = a.completeTask
} else if v := params.Get("editNote"); v != "" {
} else if has(params, "editNote") {
foo = a.editNote
} else if v := params.Get("editTask"); v != "" {
} else if has(params, "editTask") {
foo = a.editTask
} else if v := params.Get("changeOrder"); v != "" {
} else if has(params, "changeOrder") {
foo = a.changeOrder
} else if v := params.Get("suggestTags"); v != "" {
} else if has(params, "suggestTags") {
foo = a.suggestTags
} else if v := params.Get("setPrio"); v != "" {
} else if has(params, "setPrio") {
foo = a.setPrio
} else if v := params.Get("tagCloud"); v != "" {
} else if has(params, "tagCloud") {
foo = a.tagCloud
} else if v := params.Get("addList"); v != "" {
} else if has(params, "addList") {
foo = a.addList
} else if v := params.Get("renameList"); v != "" {
} else if has(params, "renameList") {
foo = a.renameList
} else if v := params.Get("deleteList"); v != "" {
} else if has(params, "deleteList") {
foo = a.deleteList
} else if v := params.Get("setSort"); v != "" {
} else if has(params, "setSort") {
foo = a.setSort
} else if v := params.Get("publishList"); v != "" {
} else if has(params, "publishList") {
foo = a.publishList
} else if v := params.Get("moveTask"); v != "" {
} else if has(params, "moveTask") {
foo = a.moveTask
} else if v := params.Get("changeListOrder"); v != "" {
} else if has(params, "changeListOrder") {
foo = a.changeListOrder
} else if v := params.Get("parseTaskStr"); v != "" {
} else if has(params, "parseTaskStr") {
foo = a.parseTaskStr
} else if v := params.Get("clearCompletedInList"); v != "" {
} else if has(params, "clearCompletedInList") {
foo = a.clearCompletedInList
} else if v := params.Get("setShowNotesInList"); v != "" {
} else if has(params, "setShowNotesInList") {
foo = a.setShowNotesInList
} else if v := params.Get("setHideList"); v != "" {
} else if has(params, "setHideList") {
foo = a.setHideList
}
if err := foo(w, r); err == storage.ErrNotFound {
if foo == nil {
http.NotFound(w, r)
} else if err := foo(w, r); err == storage.ErrNotFound {
http.Error(w, err.Error(), http.StatusNotFound)
} else if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func has(params url.Values, k string) bool {
_, ok := params[k]
return ok
}

View File

@ -1,14 +1,32 @@
package ajax
import (
"encoding/json"
"errors"
"local/todo-server/server/ajax/list"
"net/http"
)
type List struct{}
func (a *Ajax) loadLists(w http.ResponseWriter, r *http.Request) error {
return errors.New("not impl")
lists, err := a.storageListLists()
if err != nil {
return err
}
// TODO
lists = append(lists, &list.List{
ID: "Todo",
UUID: "Todo",
})
if err := a.storageSetList(lists[0].ID, lists[0]); err != nil {
return err
}
// /TODO
return json.NewEncoder(w).Encode(map[string]interface{}{
"total": len(lists),
"list": lists,
})
}
func (a *Ajax) changeOrder(w http.ResponseWriter, r *http.Request) error {
@ -16,6 +34,7 @@ func (a *Ajax) changeOrder(w http.ResponseWriter, r *http.Request) error {
}
func (a *Ajax) addList(w http.ResponseWriter, r *http.Request) error {
// {"total":1,"list":[{"id":"21","name":"abc","sort":0,"published":0,"showCompl":0,"showNotes":0,"hidden":0}]}
return errors.New("not impl")
}

View File

@ -1,11 +1,19 @@
package ajax
package list
import (
"errors"
"net/http"
)
type List struct{}
type List struct {
ID string `json:"name"`
UUID string `json:"id"`
Sort int `json:"sort"`
Published int `json:"published"`
ShowCompl int `json:"showCompl"`
ShowNotes int `json:"showNotes"`
Hidden int `json:"hidden"`
}
func New(r *http.Request) (*List, error) {
return &List{}, errors.New("not impl")

View File

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/gob"
"local/todo-server/server/ajax/form"
"local/todo-server/server/ajax/list"
"local/todo-server/server/ajax/task"
"net/http"
"path"
@ -11,12 +12,40 @@ import (
)
func (a *Ajax) Cur(r *http.Request) (string, string, []string) {
listID, _ := a.storageGetCur()
listID := form.Get(r, "list")
taskID := form.Get(r, "id")
tags, _ := a.storageGetCurTags()
tags, _ := r.URL.Query()["t"]
return listID, taskID, tags
}
func (a *Ajax) storageListLists(filters ...func(t *list.List) bool) ([]*list.List, error) {
results, err := a.DB.List(nil, "", "}")
if err != nil {
return nil, err
}
lists := []*list.List{}
for _, result := range results {
if d, f := path.Split(result); d == "" || f == "" {
continue
}
list := &list.List{
ID: result,
UUID: result,
}
filtered := true
for _, f := range filters {
if !f(list) {
filtered = false
break
}
}
if filtered {
lists = append(lists, list)
}
}
return lists, nil
}
func (a *Ajax) storageListTasks(listID string, filters ...func(t *task.Task) bool) ([]*task.Task, error) {
results, err := a.DB.List(nil, listID+"/", listID+"/}")
if err != nil {
@ -60,13 +89,13 @@ func (a *Ajax) storageDelTask(listID, taskID string) error {
return a.storageDel(path.Join(listID, taskID))
}
func (a *Ajax) storageGetList(listID string) (*List, error) {
var list List
func (a *Ajax) storageGetList(listID string) (*list.List, error) {
var list list.List
err := a.storageGet(listID, &list)
return &list, err
}
func (a *Ajax) storageSetList(listID string, list *List) error {
func (a *Ajax) storageSetList(listID string, list *list.List) error {
return a.storageSet(listID, *list)
}

View File

@ -8,9 +8,9 @@ import (
func TestAjaxStorageCur(t *testing.T) {
ajax := mockAjax()
r := httptest.NewRequest("POST", "/?id=abc", nil)
r := httptest.NewRequest("POST", "/?id=abc&list=def", nil)
listID, taskID, tags := ajax.Cur(r)
if listID != "list" {
if listID != "def" {
t.Error(listID)
}
if taskID != "abc" {

View File

@ -49,11 +49,42 @@ func (a *Ajax) loadTasks(w http.ResponseWriter, r *http.Request) error {
}
func (a *Ajax) newTask(w http.ResponseWriter, r *http.Request) error {
listID, task, err := a.makeTask(r)
listID, newTask, err := a.makeTask(r)
if err != nil {
return err
}
return a.storageSetTask(listID, task.UUID, task)
if err := a.storageSetTask(listID, newTask.UUID, newTask); err != nil {
return err
}
// {"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 "}
// ]}
return json.NewEncoder(w).Encode(map[string]interface{}{
"total": 1,
"list": []*task.Task{newTask},
})
}
func (a *Ajax) makeTask(r *http.Request) (string, *task.Task, error) {

View File

@ -1,7 +1,9 @@
package task
import (
"encoding/json"
"errors"
"fmt"
"local/todo-server/server/ajax/form"
"net/http"
"regexp"
@ -16,7 +18,7 @@ type Task struct {
UUID string
Title string
Priority int
Tags []string
Tags StrList
Created time.Time
Edited time.Time
@ -26,12 +28,19 @@ type Task struct {
Due time.Time
}
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: uuid.New().String(),
Title: form.Get(r, "title"),
Priority: form.ToInt(form.Get(r, "prio")),
Tags: form.ToStrArr(form.Get(r, "tags")),
Tags: StrList(form.ToStrArr(form.Get(r, "tags"))),
Created: time.Now(),
Edited: time.Now(),
@ -42,6 +51,71 @@ func New(r *http.Request) (*Task, error) {
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...)

View File

@ -57,3 +57,15 @@ func toReq(m map[string]interface{}) *http.Request {
b, _ := json.Marshal(m)
return httptest.NewRequest("POST", "/paht", bytes.NewReader(b))
}
func TestJSONMarshal(t *testing.T) {
task := &Task{Tags: []string{"a", "b"}}
b, _ := json.Marshal(task)
var m map[string]interface{}
json.Unmarshal(b, &m)
if v, ok := m["tags"]; !ok {
t.Error(ok, m)
} else if v != "a, b" {
t.Error(v, m)
}
}

View File

@ -14,7 +14,7 @@ func TestAjaxLoadTasks(t *testing.T) {
func() {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r := httptest.NewRequest("GET", "/?list=list", nil)
a.loadTasks(w, r)
var result struct {
List []string `json:"list"`
@ -32,10 +32,10 @@ func TestAjaxLoadTasks(t *testing.T) {
func() {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r := httptest.NewRequest("GET", "/?list=list", nil)
a.loadTasks(w, r)
var result struct {
List []task.Task `json:"list"`
List []map[string]interface{} `json:"list"`
}
if v := w.Code; v != http.StatusOK {
t.Error(v)
@ -50,7 +50,7 @@ func TestAjaxLoadTasks(t *testing.T) {
func TestAjaxNewTask(t *testing.T) {
ajax := mockAjax()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", strings.NewReader(`{
r := httptest.NewRequest("GET", "/?list=list", strings.NewReader(`{
"title":"a"
}`))
ajax.newTask(w, r)
@ -68,7 +68,7 @@ func TestAjaxNewTask(t *testing.T) {
func TestAjaxMakeTask(t *testing.T) {
ajax := mockAjax()
r := httptest.NewRequest("GET", "/", strings.NewReader(`{
r := httptest.NewRequest("GET", "/?list=list", strings.NewReader(`{
"title":"a"
}`))
listID, task, err := ajax.makeTask(r)
@ -87,7 +87,7 @@ func TestAjaxDeleteTask(t *testing.T) {
ajax := mockAjax()
ajax.storageSetTask("list", "b", &task.Task{Title: "c"})
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/?id=b", nil)
r := httptest.NewRequest("GET", "/?id=b&list=list", nil)
ajax.deleteTask(w, r)
if v := w.Code; v != http.StatusOK {
t.Error(v)
@ -109,7 +109,7 @@ func TestAjaxCompleteTask(t *testing.T) {
ajax.storageSetTask("list", "b", &task.Task{Title: "c"})
for _, state := range []string{"1", "0"} {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/?id=b&compl="+state, nil)
r := httptest.NewRequest("GET", "/?id=b&list=list&compl="+state, nil)
ajax.completeTask(w, r)
if v := w.Code; v != http.StatusOK {
t.Error(v)
@ -126,7 +126,7 @@ func TestAjaxEditNote(t *testing.T) {
ajax := mockAjax()
ajax.storageSetTask("list", "b", &task.Task{Title: "c", Note: []string{"hi", "mom"}})
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/?id=b&compl=0", strings.NewReader(`{
r := httptest.NewRequest("POST", "/?id=b&list=list&compl=0", strings.NewReader(`{
"note":"hello world i like tacos"
}`))
ajax.editNote(w, r)
@ -144,7 +144,7 @@ func TestAjaxEditTask(t *testing.T) {
ajax := mockAjax()
ajax.storageSetTask("list", "b", &task.Task{Title: "c", Note: []string{"hi"}})
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/?id=b&compl=0", strings.NewReader(`{
r := httptest.NewRequest("POST", "/?id=b&list=list&compl=0", strings.NewReader(`{
"title": "newtitle",
"note":"hello world i like tacos"
}`))
@ -163,7 +163,7 @@ func TestAjaxMoveTask(t *testing.T) {
ajax := mockAjax()
ajax.storageSetTask("list", "b", &task.Task{Title: "c"})
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/?id=b&to=listB", strings.NewReader(`{}`))
r := httptest.NewRequest("GET", "/?id=b&list=list&to=listB", strings.NewReader(`{}`))
ajax.moveTask(w, r)
if v := w.Code; v != http.StatusOK {
t.Error(v)

View File

@ -23,7 +23,7 @@ func (s *Server) Routes() error {
handler: s.phpProxy,
},
{
path: fmt.Sprintf("/ajax.php"),
path: fmt.Sprintf("ajax.php"),
handler: s.HandleAjax,
},
}

23
testdata/entrypoint.sh vendored Executable file
View File

@ -0,0 +1,23 @@
#! /bin/sh
cd /mnt
apk add --no-cache \
ca-certificates \
bash \
git
#if [ ! -d ./mytinytodo ]; then
if [ ! -d ./mytinytodo2 ]; then
#wget https://bitbucket.org/maxpozdeev/mytinytodo/downloads/mytinytodo-v1.4.3.zip
#unzip mytinytodo-v1.4.3.zip
git clone https://github.com/ptrckkk/myTinyTodo.git mytinytodo2
fi
if [ -z "$(grep invert ./mytinytodo2/themes/default/style.css)" ]; then
echo 'body { filter: invert(80%); background-color: #222; }' >> ./mytinytodo2/themes/default/style.css
fi
#cd mytinytodo
cd mytinytodo2
php -S 0.0.0.0:8080

1
testdata/mytinytodo2 vendored Submodule

@ -0,0 +1 @@
Subproject commit e21afea391ecac5c5f5b47639e5ce8cb8b7eb693

5
testdata/run_mtt.sh vendored Normal file
View File

@ -0,0 +1,5 @@
#! /bin/bash
cd "$(dirname "$BASH_SOURCE")"
cd mytinytodo2
php -S 0.0.0.0:38808