Priorites for tasks (#31)

This commit is contained in:
konrad 2018-12-02 00:49:30 +00:00 committed by Gitea
parent f2f881f505
commit cc6b35e314
11 changed files with 323 additions and 84 deletions

View file

@ -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

View file

@ -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

View file

@ -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}}

View file

@ -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": {

View file

@ -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": {

View file

@ -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.

View file

@ -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
}

View file

@ -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
}

View file

@ -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"`

View file

@ -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)
}

View file

@ -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)