diff --git a/.gitignore b/.gitignore index e5b39f05e6..fa6ba8cbea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .idea/ .idea/*/* +.idea/httpRequests config.yml config.yaml *.db diff --git a/Featurecreep.md b/Featurecreep.md index 5cd65e6a38..2214b5a9be 100644 --- a/Featurecreep.md +++ b/Featurecreep.md @@ -255,6 +255,9 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten. ### Later/Nice to have * [x] Deps nach mod umziehen +* [x] Start/Enddatum für Tasks +* [x] Timeline/Calendar view -> Dazu tasks die in einem Bestimmten Bereich due sind, macht dann das Frontend +* [x] Tasks innerhalb eines definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat" * [ ] Websockets * Nur lesend? (-> Updates wie bisher) * sollen den geupdaten Kram an alle anderen user schicken @@ -268,15 +271,12 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten. * [ ] Mgl. zum Accountlöschen haben (so richtig krass mit emailverifiezierung und dass alle Privaten Listen gelöscht werden und man alle geteilten entweder wem übertragen muss oder auf privat stellen) * [ ] IMAP-Integration -> Man schickt eine email an Vikunja und es macht daraus dann nen task -> Achtung missbrauchsmöglichkeiten * [ ] In und Out webhooks, mit Templates vom Payload -* [x] Start/Enddatum für Tasks -* [ ] Timeline/Calendar view -> Dazu tasks die in einem Bestimmten Bereich due sind, macht dann das Frontend * [ ] "Smart Lists", Listen nach bestimmten Kriterien gefiltert -> nur UI? * [ ] "Performance-Statistik" -> Wie viele Tasks man in bestimmten Zeiträumen so geschafft hat etc * [ ] Activity Feed, so à la "der und der hat das und das gemacht etc" * [ ] Assignees * [ ] Attachments * [ ] Labels -* [ ] Tasks innerhalb eines definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat" * [ ] Task-Templates innerhalb namespaces und Listen (-> Mehrere, die auswählbar sind) * [ ] Bulk-edit -> Transactions * [ ] Ein Task muss von mehreren Assignees abgehakt werden bis er als done markiert wird \ No newline at end of file diff --git a/REST-Tests/lists.http b/REST-Tests/lists.http index aa0ee4f036..b0926768e0 100644 --- a/REST-Tests/lists.http +++ b/REST-Tests/lists.http @@ -118,6 +118,12 @@ Authorization: Bearer {{auth_token}} ### +# Get all pending tasks in a range +GET http://localhost:8080/api/v1/tasks/all/dueadateasc/1546784000/1548784000 +Authorization: Bearer {{auth_token}} + +### + # Get all pending tasks in caldav GET http://localhost:8080/api/v1/tasks/caldav #Authorization: Bearer {{auth_token}} @@ -125,10 +131,10 @@ GET http://localhost:8080/api/v1/tasks/caldav ### # Update a task -POST http://localhost:8080/api/v1/tasks/32 +POST http://localhost:8080/api/v1/tasks/3491 Authorization: Bearer {{auth_token}} Content-Type: application/json -{"done":true} +{"startDate":1546804000, "endDate": 1546805000} ### \ No newline at end of file diff --git a/pkg/models/fixtures/tasks.yml b/pkg/models/fixtures/tasks.yml index 03578a5f92..05e54b9c8e 100644 --- a/pkg/models/fixtures/tasks.yml +++ b/pkg/models/fixtures/tasks.yml @@ -38,4 +38,26 @@ list_id: 1 created: 1543626724 updated: 1543626724 - due_date_unix: 1543616724 \ No newline at end of file + due_date_unix: 1543616724 +- id: 7 + text: 'task #7 with start date' + created_by_id: 1 + list_id: 1 + created: 1543626724 + updated: 1543626724 + start_date_unix: 1544600000 +- id: 8 + text: 'task #8 with end date' + created_by_id: 1 + list_id: 1 + created: 1543626724 + updated: 1543626724 + end_date_unix: 1544700000 +- id: 9 + text: 'task #9 with start and end date' + created_by_id: 1 + list_id: 1 + created: 1543626724 + updated: 1543626724 + start_date_unix: 1544600000 + end_date_unix: 1544700000 \ No newline at end of file diff --git a/pkg/models/list_task_readall.go b/pkg/models/list_task_readall.go index 762892ee7f..e452a3f47a 100644 --- a/pkg/models/list_task_readall.go +++ b/pkg/models/list_task_readall.go @@ -6,7 +6,10 @@ package models -import "code.vikunja.io/web" +import ( + "code.vikunja.io/web" + "time" +) // SortBy declares constants to sort type SortBy int @@ -73,11 +76,11 @@ func (lt *ListTask) ReadAll(search string, a web.Auth, page int) (interface{}, e sortby = SortTasksByUnsorted } - return GetTasksByUser(search, u, page, sortby) + return GetTasksByUser(search, u, page, sortby, time.Unix(lt.StartDateSortUnix, 0), time.Unix(lt.EndDateSortUnix, 0)) } //GetTasksByUser returns all tasks for a user -func GetTasksByUser(search string, u *User, page int, sortby SortBy) (tasks []*ListTask, err error) { +func GetTasksByUser(search string, u *User, page int, sortby SortBy, startDate time.Time, endDate time.Time) (tasks []*ListTask, err error) { // Get all lists lists, err := getRawListsForUser("", u, page) if err != nil { @@ -103,8 +106,36 @@ func GetTasksByUser(search string, u *User, page int, sortby SortBy) (tasks []*L } // Then return all tasks for that lists - if err := x.In("list_id", listIDs).Where("text LIKE ?", "%"+search+"%").OrderBy(orderby).Find(&tasks); err != nil { - return nil, err + if startDate.Unix() != 0 || endDate.Unix() != 0 { + + startDateUnix := time.Now().Unix() + if startDate.Unix() != 0 { + startDateUnix = startDate.Unix() + } + + endDateUnix := time.Now().Unix() + if endDate.Unix() != 0 { + endDateUnix = endDate.Unix() + } + + if err := x.In("list_id", listIDs). + Where("text LIKE ?", "%"+search+"%"). + And("((due_date_unix BETWEEN ? AND ?) OR "+ + "(start_date_unix BETWEEN ? and ?) OR "+ + "(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix). + And("(parent_task_id = 0 OR parent_task_id IS NULL)"). + OrderBy(orderby). + Find(&tasks); err != nil { + return nil, err + } + } else { + if err := x.In("list_id", listIDs). + Where("text LIKE ?", "%"+search+"%"). + And("(parent_task_id = 0 OR parent_task_id IS NULL)"). + OrderBy(orderby). + Find(&tasks); err != nil { + return nil, err + } } return tasks, err diff --git a/pkg/models/list_task_readall_test.go b/pkg/models/list_task_readall_test.go index 8ada568180..86b638b3a5 100644 --- a/pkg/models/list_task_readall_test.go +++ b/pkg/models/list_task_readall_test.go @@ -69,6 +69,34 @@ func sortTasksForTesting(by SortBy) (tasks []*ListTask) { Updated: 1543626724, DueDateUnix: 1543616724, }, + { + ID: 7, + Text: "task #7 with start date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + }, + { + ID: 8, + Text: "task #8 with end date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + EndDateUnix: 1544700000, + }, + { + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + }, } switch by { @@ -95,24 +123,26 @@ func sortTasksForTesting(by SortBy) (tasks []*ListTask) { func TestListTask_ReadAll(t *testing.T) { type fields struct { - ID int64 - Text string - Description string - Done bool - DueDateUnix int64 - RemindersUnix []int64 - CreatedByID int64 - ListID int64 - RepeatAfter int64 - ParentTaskID int64 - Priority int64 - Sorting string - Subtasks []*ListTask - Created int64 - Updated int64 - CreatedBy User - CRUDable web.CRUDable - Rights web.Rights + ID int64 + Text string + Description string + Done bool + DueDateUnix int64 + RemindersUnix []int64 + CreatedByID int64 + ListID int64 + RepeatAfter int64 + ParentTaskID int64 + Priority int64 + Sorting string + StartDateSortUnix int64 + EndDateSortUnix int64 + Subtasks []*ListTask + Created int64 + Updated int64 + CreatedBy User + CRUDable web.CRUDable + Rights web.Rights } type args struct { search string @@ -160,7 +190,89 @@ func TestListTask_ReadAll(t *testing.T) { a: &User{ID: 1}, page: 0, }, - want: sortTasksForTesting(SortTasksByPriorityAsc), + want: []*ListTask{ + { + ID: 1, + Text: "task #1", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 2, + Text: "task #2 done", + Done: true, + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 5, + Text: "task #5 higher due date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + DueDateUnix: 1543636724, + }, + { + ID: 6, + Text: "task #6 lower due date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + DueDateUnix: 1543616724, + }, + { + ID: 7, + Text: "task #7 with start date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + }, + { + ID: 8, + Text: "task #8 with end date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + EndDateUnix: 1544700000, + }, + { + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + }, + { + ID: 4, + Text: "task #4 low prio", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + Priority: 1, + }, + { + ID: 3, + Text: "task #3 high prio", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + Priority: 100, + }, + }, wantErr: false, }, { @@ -177,7 +289,7 @@ func TestListTask_ReadAll(t *testing.T) { wantErr: false, }, { - name: "ReadAll ListTasks sorted by due date default (desc)", + name: "ReadAll ListTasks sorted by due date default desc", fields: fields{ Sorting: "dueadate", }, @@ -215,28 +327,131 @@ func TestListTask_ReadAll(t *testing.T) { want: sortTasksForTesting(SortTasksByDueDateDesc), wantErr: false, }, + { + name: "ReadAll ListTasks with range", + fields: fields{ + StartDateSortUnix: 1544500000, + EndDateSortUnix: 1544600000, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*ListTask{ + { + ID: 7, + Text: "task #7 with start date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + }, + { + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + }, + }, + wantErr: false, + }, + { + name: "ReadAll ListTasks with range", + fields: fields{ + StartDateSortUnix: 1544700000, + EndDateSortUnix: 1545000000, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*ListTask{ + { + ID: 8, + Text: "task #8 with end date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + EndDateUnix: 1544700000, + }, + { + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + }, + }, + wantErr: false, + }, + { + name: "ReadAll ListTasks with range without end date", + fields: fields{ + StartDateSortUnix: 1544700000, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*ListTask{ + { + ID: 8, + Text: "task #8 with end date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + EndDateUnix: 1544700000, + }, + { + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { lt := &ListTask{ - ID: tt.fields.ID, - Text: tt.fields.Text, - Description: tt.fields.Description, - Done: tt.fields.Done, - DueDateUnix: tt.fields.DueDateUnix, - RemindersUnix: tt.fields.RemindersUnix, - CreatedByID: tt.fields.CreatedByID, - ListID: tt.fields.ListID, - RepeatAfter: tt.fields.RepeatAfter, - ParentTaskID: tt.fields.ParentTaskID, - Priority: tt.fields.Priority, - Sorting: tt.fields.Sorting, - Subtasks: tt.fields.Subtasks, - Created: tt.fields.Created, - Updated: tt.fields.Updated, - CreatedBy: tt.fields.CreatedBy, - CRUDable: tt.fields.CRUDable, - Rights: tt.fields.Rights, + ID: tt.fields.ID, + Text: tt.fields.Text, + Description: tt.fields.Description, + Done: tt.fields.Done, + DueDateUnix: tt.fields.DueDateUnix, + RemindersUnix: tt.fields.RemindersUnix, + CreatedByID: tt.fields.CreatedByID, + ListID: tt.fields.ListID, + RepeatAfter: tt.fields.RepeatAfter, + ParentTaskID: tt.fields.ParentTaskID, + Priority: tt.fields.Priority, + Sorting: tt.fields.Sorting, + StartDateSortUnix: tt.fields.StartDateSortUnix, + EndDateSortUnix: tt.fields.EndDateSortUnix, + Subtasks: tt.fields.Subtasks, + Created: tt.fields.Created, + Updated: tt.fields.Updated, + CreatedBy: tt.fields.CreatedBy, + CRUDable: tt.fields.CRUDable, + Rights: tt.fields.Rights, } got, err := lt.ReadAll(tt.args.search, tt.args.a, tt.args.page) if (err != nil) != tt.wantErr { @@ -245,6 +460,20 @@ func TestListTask_ReadAll(t *testing.T) { } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ListTask.ReadAll() = %v, want %v", got, tt.want) + /*fmt.Println("Got:") + gotslice := got.([]*ListTask) + for _, g := range gotslice { + fmt.Println(g.Priority, g.Text) + //fmt.Println(g.StartDateUnix) + //fmt.Println(g.EndDateUnix) + } + fmt.Println("Want:") + wantslice := tt.want.([]*ListTask) + for _, w := range wantslice { + fmt.Println(w.Priority, w.Text) + //fmt.Println(w.StartDateUnix) + //fmt.Println(w.EndDateUnix) + }*/ } }) } diff --git a/pkg/models/list_tasks.go b/pkg/models/list_tasks.go index 3cbf714046..f973b3a64e 100644 --- a/pkg/models/list_tasks.go +++ b/pkg/models/list_tasks.go @@ -34,10 +34,13 @@ type ListTask struct { RepeatAfter int64 `xorm:"int(11) INDEX" json:"repeatAfter"` ParentTaskID int64 `xorm:"int(11) INDEX" json:"parentTaskID"` Priority int64 `xorm:"int(11)" json:"priority"` - Sorting string `xorm:"-" json:"-" param:"sort"` // Parameter to sort by StartDateUnix int64 `xorm:"int(11) INDEX" json:"startDate"` EndDateUnix int64 `xorm:"int(11) INDEX" json:"endDate"` + Sorting string `xorm:"-" json:"-" param:"sort"` // Parameter to sort by + StartDateSortUnix int64 `xorm:"-" json:"-" param:"startdatefilter"` + EndDateSortUnix int64 `xorm:"-" json:"-" param:"enddatefilter"` + Subtasks []*ListTask `xorm:"-" json:"subtasks"` Created int64 `xorm:"created" json:"created"` diff --git a/pkg/models/list_tasks_create_update.go b/pkg/models/list_tasks_create_update.go index 6e462aa988..f3bea0a200 100644 --- a/pkg/models/list_tasks_create_update.go +++ b/pkg/models/list_tasks_create_update.go @@ -114,7 +114,18 @@ func (i *ListTask) Update() (err error) { ot.Done = false } - _, err = x.ID(i.ID).Cols("text", "description", "done", "due_date_unix", "reminders_unix", "repeat_after").Update(ot) + _, err = x.ID(i.ID). + Cols("text", + "description", + "done", + "due_date_unix", + "reminders_unix", + "repeat_after", + "parent_task_id", + "priority", + "start_date_unix", + "end_date_unix"). + Update(ot) *i = ot return } diff --git a/pkg/routes/api/v1/caldav.go b/pkg/routes/api/v1/caldav.go index 1f43700f83..3c5780d446 100644 --- a/pkg/routes/api/v1/caldav.go +++ b/pkg/routes/api/v1/caldav.go @@ -52,7 +52,7 @@ func Caldav(c echo.Context) error { } // Get all tasks for that user - tasks, err := models.GetTasksByUser("", &u, -1, models.SortTasksByUnsorted) + tasks, err := models.GetTasksByUser("", &u, -1, models.SortTasksByUnsorted, time.Now(), time.Now().Add(24*356*time.Hour)) if err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 4b486150cb..dc42536d3a 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -218,6 +218,7 @@ func RegisterRoutes(e *echo.Echo) { a.PUT("/lists/:list", taskHandler.CreateWeb) a.GET("/tasks/all", taskHandler.ReadAllWeb) a.GET("/tasks/all/:sort", taskHandler.ReadAllWeb) + a.GET("/tasks/all/:sort/:startdatefilter/:enddatefilter", taskHandler.ReadAllWeb) a.DELETE("/tasks/:listtask", taskHandler.DeleteWeb) a.POST("/tasks/:listtask", taskHandler.UpdateWeb)