diff --git a/Featurecreep.md b/Featurecreep.md index 6ea6189c4..2d88c3635 100644 --- a/Featurecreep.md +++ b/Featurecreep.md @@ -160,13 +160,13 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten. ## Feature-Ideen -* [ ] Labels -* [ ] Priorities -* [ ] Assignees -* [x] Subtasks -* [ ] Attachments +* [x] Priorities * [x] Repeating tasks * [x] Tagesübersicht ("Was ist heute/diese Woche due?") -> Machen letztenendes die Clients, wir brauchen nur nen endpoint, der alle tasks auskotzt, der Client macht dann die Sortierung. +* [x] Subtasks +* [ ] Assignees +* [ ] Attachments +* [ ] Labels * [ ] Tasks innerhalb eines definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat" * [ ] Namespaces in Namespaces (in Namespaces in Namespaces in Namespaces...) * Rechtemanagement dafür wird schwierig diff --git a/README.md b/README.md index e59f42f3c..bc8a0f7af 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,14 @@ Try it under [try.vikunja.io](https://try.vikunja.io)! > I know, it's still a long way to go. I'm currently working on a lot of "basic" features, the exiting things will come later. Don't worry, they'll come. -* [ ] Labels for todo lists and tasks -* [ ] Prioritize tasks -* [ ] Assign users to tasks +* [x] Prioritize tasks * [x] Subtasks * [x] Repeating tasks -* [ ] Attachments on tasks -* [ ] Get all tasks for you per interval (day/month/period) * [x] Get tasks via caldav +* [ ] Labels for todo lists and tasks +* [ ] Assign users to tasks +* [ ] Attachments on tasks +* [ ] Get all your tasks for an interval (day/month/period) * [ ] More sharing features * [x] Share with individual users * [ ] Share via a world-readable link with or without password, like Nextcloud diff --git a/REST-Tests/lists.http b/REST-Tests/lists.http index 8dae649a6..2513207ab 100644 --- a/REST-Tests/lists.http +++ b/REST-Tests/lists.http @@ -112,6 +112,12 @@ Authorization: Bearer {{auth_token}} ### +# Get all pending tasks with priorities +GET http://localhost:8080/api/v1/tasks/desc +Authorization: Bearer {{auth_token}} + +### + # Get all pending tasks in caldav GET http://localhost:8080/api/v1/tasks/caldav #Authorization: Bearer {{auth_token}} diff --git a/docs/docs.go b/docs/docs.go index ee9af7659..84b99d0d9 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,6 +1,6 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2018-12-01 03:02:40.853807498 +0100 CET m=+0.099265725 +// 2018-12-02 01:35:21.999635022 +0100 CET m=+0.131973644 package docs @@ -91,7 +91,7 @@ var doc = `{ "ApiKeyAuth": [] } ], - "description": "Returns a team by its ID.", + "description": "Returns a list by its ID.", "consumes": [ "application/json" ], @@ -99,13 +99,13 @@ var doc = `{ "application/json" ], "tags": [ - "team" + "list" ], - "summary": "Gets one team", + "summary": "Gets one list", "parameters": [ { "type": "integer", - "description": "Team ID", + "description": "List ID", "name": "id", "in": "path", "required": true @@ -113,14 +113,14 @@ var doc = `{ ], "responses": { "200": { - "description": "The team", + "description": "The list", "schema": { "type": "object", - "$ref": "#/definitions/models.Team" + "$ref": "#/definitions/models.List" } }, "403": { - "description": "The user does not have access to the team", + "description": "The user does not have access to the list", "schema": { "type": "object", "$ref": "#/definitions/code.vikunja.io/web.HTTPError" @@ -2155,6 +2155,65 @@ var doc = `{ } } }, + "/tasks/{sortby}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns all tasks on any list the user has access to.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "task" + ], + "summary": "Get tasks sorted", + "parameters": [ + { + "type": "integer", + "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", + "name": "p", + "in": "query" + }, + { + "type": "string", + "description": "Search tasks by task text.", + "name": "s", + "in": "query" + }, + { + "type": "string", + "description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, dueadate, dueadatedesc, dueadateasc.", + "name": "sortby", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The tasks", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.List" + } + } + }, + "500": { + "description": "Internal error", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/teams": { "get": { "security": [ @@ -2880,6 +2939,9 @@ var doc = `{ "parentTaskID": { "type": "integer" }, + "priority": { + "type": "integer" + }, "reminderDates": { "type": "array", "items": { diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index e416f5b71..92bfd27d3 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -78,7 +78,7 @@ "ApiKeyAuth": [] } ], - "description": "Returns a team by its ID.", + "description": "Returns a list by its ID.", "consumes": [ "application/json" ], @@ -86,13 +86,13 @@ "application/json" ], "tags": [ - "team" + "list" ], - "summary": "Gets one team", + "summary": "Gets one list", "parameters": [ { "type": "integer", - "description": "Team ID", + "description": "List ID", "name": "id", "in": "path", "required": true @@ -100,14 +100,14 @@ ], "responses": { "200": { - "description": "The team", + "description": "The list", "schema": { "type": "object", - "$ref": "#/definitions/models.Team" + "$ref": "#/definitions/models.List" } }, "403": { - "description": "The user does not have access to the team", + "description": "The user does not have access to the list", "schema": { "type": "object", "$ref": "#/definitions/code.vikunja.io/web.HTTPError" @@ -2142,6 +2142,65 @@ } } }, + "/tasks/{sortby}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns all tasks on any list the user has access to.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "task" + ], + "summary": "Get tasks sorted", + "parameters": [ + { + "type": "integer", + "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", + "name": "p", + "in": "query" + }, + { + "type": "string", + "description": "Search tasks by task text.", + "name": "s", + "in": "query" + }, + { + "type": "string", + "description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, dueadate, dueadatedesc, dueadateasc.", + "name": "sortby", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The tasks", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.List" + } + } + }, + "500": { + "description": "Internal error", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/teams": { "get": { "security": [ @@ -2867,6 +2926,9 @@ "parentTaskID": { "type": "integer" }, + "priority": { + "type": "integer" + }, "reminderDates": { "type": "array", "items": { diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 99ad22ca6..cc23bc3df 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -55,6 +55,8 @@ definitions: type: integer parentTaskID: type: integer + priority: + type: integer reminderDates: items: type: integer @@ -390,9 +392,9 @@ paths: get: consumes: - application/json - description: Returns a team by its ID. + description: Returns a list by its ID. parameters: - - description: Team ID + - description: List ID in: path name: id required: true @@ -401,12 +403,12 @@ paths: - application/json responses: "200": - description: The team + description: The list schema: - $ref: '#/definitions/models.Team' + $ref: '#/definitions/models.List' type: object "403": - description: The user does not have access to the team + description: The user does not have access to the list schema: $ref: '#/definitions/code.vikunja.io/web.HTTPError' type: object @@ -417,9 +419,9 @@ paths: type: object security: - ApiKeyAuth: [] - summary: Gets one team + summary: Gets one list tags: - - team + - list post: consumes: - application/json @@ -1726,6 +1728,46 @@ paths: summary: Update a task tags: - task + /tasks/{sortby}: + get: + consumes: + - application/json + description: Returns all tasks on any list the user has access to. + parameters: + - description: The page number. Used for pagination. If not provided, the first + page of results is returned. + in: query + name: p + type: integer + - description: Search tasks by task text. + in: query + name: s + type: string + - description: The sorting parameter. Possible values to sort by are priority, + prioritydesc, priorityasc, dueadate, dueadatedesc, dueadateasc. + in: path + name: sortby + required: true + type: string + produces: + - application/json + responses: + "200": + description: The tasks + schema: + items: + $ref: '#/definitions/models.List' + type: array + "500": + description: Internal error + schema: + $ref: '#/definitions/models.Message' + type: object + security: + - ApiKeyAuth: [] + summary: Get tasks sorted + tags: + - task /tasks/caldav: get: description: Returns a calDAV-parsable format with all tasks as calendar events. diff --git a/pkg/models/list.go b/pkg/models/list.go index 463110ebb..9b315e6a7 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -18,7 +18,6 @@ package models import ( "code.vikunja.io/web" - "sort" ) // List represents a list of tasks @@ -201,51 +200,3 @@ func AddListDetails(lists []*List) (err error) { return } - -// ReadAll gets all tasks for a user -// @Summary Get tasks -// @Description Returns all tasks on any list the user has access to. -// @tags task -// @Accept json -// @Produce json -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." -// @Param s query string false "Search tasks by task text." -// @Security ApiKeyAuth -// @Success 200 {array} models.List "The tasks" -// @Failure 500 {object} models.Message "Internal error" -// @Router /tasks [get] -func (lt *ListTask) ReadAll(search string, a web.Auth, page int) (interface{}, error) { - u, err := getUserWithError(a) - if err != nil { - return nil, err - } - - return GetTasksByUser(search, u, page) -} - -//GetTasksByUser returns all tasks for a user -func GetTasksByUser(search string, u *User, page int) (tasks []*ListTask, err error) { - // Get all lists - lists, err := getRawListsForUser("", u, page) - if err != nil { - return nil, err - } - - // Get all list IDs and get the tasks - var listIDs []int64 - for _, l := range lists { - listIDs = append(listIDs, l.ID) - } - - // Then return all tasks for that lists - if err := x.In("list_id", listIDs).Where("text LIKE ?", "%"+search+"%").Find(&tasks); err != nil { - return nil, err - } - - // Sort it by due date - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix > tasks[j].DueDateUnix - }) - - return tasks, err -} diff --git a/pkg/models/list_task_readall.go b/pkg/models/list_task_readall.go new file mode 100644 index 000000000..762892ee7 --- /dev/null +++ b/pkg/models/list_task_readall.go @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2018 the Vikunja Authors. All rights reserved. + * Use of this source code is governed by a LPGLv3-style + * license that can be found in the LICENSE file. + */ + +package models + +import "code.vikunja.io/web" + +// SortBy declares constants to sort +type SortBy int + +// These are possible sort options +const ( + SortTasksByUnsorted SortBy = -1 + SortTasksByDueDateAsc = iota + SortTasksByDueDateDesc + SortTasksByPriorityAsc + SortTasksByPriorityDesc +) + +// ReadAllWithPriority gets all tasks for a user, sorted +// @Summary Get tasks sorted +// @Description Returns all tasks on any list the user has access to. +// @tags task +// @Accept json +// @Produce json +// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param s query string false "Search tasks by task text." +// @Param sortby path string true "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, dueadate, dueadatedesc, dueadateasc." +// @Security ApiKeyAuth +// @Success 200 {array} models.List "The tasks" +// @Failure 500 {object} models.Message "Internal error" +// @Router /tasks/{sortby} [get] +func dummy() { + // Dummy function for swaggo to pick up the docs comment +} + +// ReadAll gets all tasks for a user +// @Summary Get tasks +// @Description Returns all tasks on any list the user has access to. +// @tags task +// @Accept json +// @Produce json +// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param s query string false "Search tasks by task text." +// @Security ApiKeyAuth +// @Success 200 {array} models.List "The tasks" +// @Failure 500 {object} models.Message "Internal error" +// @Router /tasks [get] +func (lt *ListTask) ReadAll(search string, a web.Auth, page int) (interface{}, error) { + u, err := getUserWithError(a) + if err != nil { + return nil, err + } + + var sortby SortBy + switch lt.Sorting { + case "priority": + sortby = SortTasksByPriorityDesc + case "prioritydesc": + sortby = SortTasksByPriorityDesc + case "priorityasc": + sortby = SortTasksByPriorityAsc + case "dueadate": + sortby = SortTasksByDueDateDesc + case "dueadatedesc": + sortby = SortTasksByDueDateDesc + case "duedateasc": + sortby = SortTasksByDueDateAsc + default: + sortby = SortTasksByUnsorted + } + + return GetTasksByUser(search, u, page, sortby) +} + +//GetTasksByUser returns all tasks for a user +func GetTasksByUser(search string, u *User, page int, sortby SortBy) (tasks []*ListTask, err error) { + // Get all lists + lists, err := getRawListsForUser("", u, page) + if err != nil { + return nil, err + } + + // Get all list IDs and get the tasks + var listIDs []int64 + for _, l := range lists { + listIDs = append(listIDs, l.ID) + } + + var orderby string + switch sortby { + case SortTasksByPriorityDesc: + orderby = "priority desc" + case SortTasksByPriorityAsc: + orderby = "priority asc" + case SortTasksByDueDateDesc: + orderby = "due_date_unix desc" + case SortTasksByDueDateAsc: + orderby = "due_date_unix asc" + } + + // 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 + } + + return tasks, err +} diff --git a/pkg/models/list_tasks.go b/pkg/models/list_tasks.go index a2cc26d6f..2d5f35654 100644 --- a/pkg/models/list_tasks.go +++ b/pkg/models/list_tasks.go @@ -16,7 +16,9 @@ package models -import "code.vikunja.io/web" +import ( + "code.vikunja.io/web" +) // ListTask represents an task in a todolist type ListTask struct { @@ -30,6 +32,8 @@ type ListTask struct { ListID int64 `xorm:"int(11) INDEX" json:"listID" param:"list"` 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 Subtasks []*ListTask `xorm:"-" json:"subtasks"` diff --git a/pkg/routes/api/v1/caldav.go b/pkg/routes/api/v1/caldav.go index 4a1956bb2..1f43700f8 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) + tasks, err := models.GetTasksByUser("", &u, -1, models.SortTasksByUnsorted) if err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 586d5ae4b..8d1d6c45e 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -133,7 +133,7 @@ func RegisterRoutes(e *echo.Echo) { return models.GetCurrentUser(c) }, }) - c.Set("LoggingProvider", &log.Log) + c.Set("LoggingProvider", log.Log) return next(c) } }) @@ -163,6 +163,7 @@ func RegisterRoutes(e *echo.Echo) { } a.PUT("/lists/:list", taskHandler.CreateWeb) a.GET("/tasks", taskHandler.ReadAllWeb) + a.GET("/tasks/:sort", taskHandler.ReadAllWeb) a.DELETE("/tasks/:listtask", taskHandler.DeleteWeb) a.POST("/tasks/:listtask", taskHandler.UpdateWeb)