From fa748328baa1e3e0fd48a0e723c34d33870dd864 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 6 Jan 2019 14:06:42 +0100 Subject: [PATCH 01/10] Performance improvements when updating task assignees, do less unnessecary stuff --- REST-Tests/lists.http | 3 +- pkg/models/list_tasks_create_update.go | 39 ++++++++++++++++++-------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/REST-Tests/lists.http b/REST-Tests/lists.http index ead1c66f2..b13d540d6 100644 --- a/REST-Tests/lists.http +++ b/REST-Tests/lists.http @@ -130,12 +130,11 @@ GET http://localhost:8080/api/v1/tasks/caldav ### # Update a task -POST http://localhost:8080/api/v1/tasks/1 +POST http://localhost:8080/api/v1/tasks/3565 Authorization: Bearer {{auth_token}} Content-Type: application/json {"assignees":[ - {"id": 1} ]} ### diff --git a/pkg/models/list_tasks_create_update.go b/pkg/models/list_tasks_create_update.go index 2378e7d0c..1c21b8bdb 100644 --- a/pkg/models/list_tasks_create_update.go +++ b/pkg/models/list_tasks_create_update.go @@ -147,22 +147,40 @@ func updateDone(oldTask *ListTask, newTask *ListTask) { // Create a bunch of task assignees func (t *ListTask) updateTaskAssignees(assignees []*User) (err error) { + // If we don't have any new assignees, delete everything right away. Saves us some hassle. + if len(assignees) == 0 && len(t.Assignees) > 0 { + _, err = x.Where("task_id = ?", t.ID). + Delete(ListTaskAssginee{}) + return err + } + + // If we didn't change anything (from 0 to zero) don't do anything. + if len(assignees) == 0 && len(t.Assignees) == 0 { + return nil + } + + // Make a hashmap of the new assignees for easier comparision + newAssignees := make(map[int64]*User, len(assignees)) + for _, newAssignee := range assignees { + newAssignees[newAssignee.ID] = newAssignee + } + // Get old assignees to delete var found bool var assigneesToDelete []int64 + oldAssignees := make(map[int64]*User, len(t.Assignees)) for _, oldAssignee := range t.Assignees { found = false - for _, newAssignee := range assignees { - if newAssignee.ID == oldAssignee.ID { - found = true // If a new assignee is already in the list with old assignees - break - } + if newAssignees[oldAssignee.ID] != nil { + found = true // If a new assignee is already in the list with old assignees } // Put all assignees which are only on the old list to the trash if !found { assigneesToDelete = append(assigneesToDelete, oldAssignee.ID) } + + oldAssignees[oldAssignee.ID] = oldAssignee } // Delete all assignees not passed @@ -177,20 +195,17 @@ func (t *ListTask) updateTaskAssignees(assignees []*User) (err error) { // Get the list to perform later checks list := List{ID: t.ListID} - err = list.ReadOne() + err = list.GetSimpleByID() if err != nil { return } // Loop through our users and add them -AddNewAssignee: for _, u := range assignees { // Check if the user is already assigned and assign him only if not - for _, oldAssignee := range t.Assignees { - if oldAssignee.ID == u.ID { - // continue outer loop - continue AddNewAssignee - } + if oldAssignees[u.ID] != nil { + // continue outer loop + continue } // Check if the user exists and has access to the list -- 2.40.1 From 59cbff2354879dae2f24fe3e9e1df8fbd2758055 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 6 Jan 2019 14:09:00 +0100 Subject: [PATCH 02/10] fixed mispell --- pkg/models/list_tasks_create_update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/models/list_tasks_create_update.go b/pkg/models/list_tasks_create_update.go index 1c21b8bdb..5cfbdda99 100644 --- a/pkg/models/list_tasks_create_update.go +++ b/pkg/models/list_tasks_create_update.go @@ -159,7 +159,7 @@ func (t *ListTask) updateTaskAssignees(assignees []*User) (err error) { return nil } - // Make a hashmap of the new assignees for easier comparision + // Make a hashmap of the new assignees for easier comparison newAssignees := make(map[int64]*User, len(assignees)) for _, newAssignee := range assignees { newAssignees[newAssignee.ID] = newAssignee -- 2.40.1 From e69ab9abd1f9a86adfc040006005f13fc5322a64 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 6 Jan 2019 14:17:49 +0100 Subject: [PATCH 03/10] update todo --- Featurecreep.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Featurecreep.md b/Featurecreep.md index 4da4bc323..22aee0898 100644 --- a/Featurecreep.md +++ b/Featurecreep.md @@ -115,8 +115,8 @@ Sorry for some of them being in German, I'll tranlate them at some point. * [x] Tasks innerhalb eines definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat" * [x] Bulk-edit -> Transactions * [x] Assignees - * [ ] Check if something changed at all before running everything - * [ ] Don't use `list.ReadOne()`, gets too much unnessecary shit + * [x] Check if something changed at all before running everything + * [x] Don't use `list.ReadOne()`, gets too much unnessecary shit * [ ] Wegen Performance auf eigene endpoints umziehen, wie labels * [ ] "One endpoint to rule them all" -> Array-addable * [x] Labels -- 2.40.1 From 46558e74f77422e0d6cdebf2b08fdf42cf88cddb Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 6 Jan 2019 15:06:36 +0100 Subject: [PATCH 04/10] Add create and delete methods for assignees --- pkg/models/list.go | 20 +++ pkg/models/list_task_assignees.go | 162 +++++++++++++++++++++++ pkg/models/list_task_assignees_rights.go | 42 ++++++ pkg/models/list_tasks.go | 29 ---- pkg/models/list_tasks_create_update.go | 85 ------------ pkg/routes/routes.go | 9 ++ 6 files changed, 233 insertions(+), 114 deletions(-) create mode 100644 pkg/models/list_task_assignees.go create mode 100644 pkg/models/list_task_assignees_rights.go diff --git a/pkg/models/list.go b/pkg/models/list.go index f4e3fb560..fe5e5a8c5 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -151,6 +151,26 @@ func (l *List) GetSimpleByID() (err error) { return } +// GetListSimplByTaskID gets a list by a task id +func GetListSimplByTaskID(taskID int64) (l *List, err error) { + // We need to re-init our list object, because otherwise xorm creates a "where for every item in that list object, + // leading to not finding anything if the id is good, but for example the title is different. + exists, err := x. + Select("lists.*"). + Join("INNER", "tasks", "list.id = tasks.list_id"). + Where("tasks.id = ?", taskID). + Get(l) + if err != nil { + return + } + + if !exists { + return &List{}, ErrListDoesNotExist{ID: l.ID} + } + + return +} + // Gets the lists only, without any tasks or so func getRawListsForUser(search string, u *User, page int) (lists []*List, err error) { fullUser, err := GetUserByID(u.ID) diff --git a/pkg/models/list_task_assignees.go b/pkg/models/list_task_assignees.go new file mode 100644 index 000000000..9c64fb45c --- /dev/null +++ b/pkg/models/list_task_assignees.go @@ -0,0 +1,162 @@ +// Vikunja is a todo-list application to facilitate your life. +// Copyright 2019 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import "code.vikunja.io/web" + +// ListTaskAssginee represents an assignment of a user to a task +type ListTaskAssginee struct { + ID int64 `xorm:"int(11) autoincr not null unique pk" json:"-"` + TaskID int64 `xorm:"int(11) INDEX not null" json:"-" param:"listtask"` + UserID int64 `xorm:"int(11) INDEX not null" json:"user_id" param:"user"` + Created int64 `xorm:"created"` + + web.CRUDable `xorm:"-" json:"-"` + web.Rights `xorm:"-" json:"-"` +} + +// TableName makes a pretty table name +func (ListTaskAssginee) TableName() string { + return "task_assignees" +} + +// ListTaskAssigneeWithUser is a helper type to deal with user joins +type ListTaskAssigneeWithUser struct { + TaskID int64 + User `xorm:"extends"` +} + +func getRawTaskAssigneesForTasks(taskIDs []int64) (taskAssignees []*ListTaskAssigneeWithUser, err error) { + taskAssignees = []*ListTaskAssigneeWithUser{nil} + err = x.Table("task_assignees"). + Select("task_id, users.*"). + In("task_id", taskIDs). + Join("INNER", "users", "task_assignees.user_id = users.id"). + Find(&taskAssignees) + return +} + +// Create or update a bunch of task assignees +func (t *ListTask) updateTaskAssignees(assignees []*User) (err error) { + + // If we don't have any new assignees, delete everything right away. Saves us some hassle. + if len(assignees) == 0 && len(t.Assignees) > 0 { + _, err = x.Where("task_id = ?", t.ID). + Delete(ListTaskAssginee{}) + return err + } + + // If we didn't change anything (from 0 to zero) don't do anything. + if len(assignees) == 0 && len(t.Assignees) == 0 { + return nil + } + + // Make a hashmap of the new assignees for easier comparison + newAssignees := make(map[int64]*User, len(assignees)) + for _, newAssignee := range assignees { + newAssignees[newAssignee.ID] = newAssignee + } + + // Get old assignees to delete + var found bool + var assigneesToDelete []int64 + oldAssignees := make(map[int64]*User, len(t.Assignees)) + for _, oldAssignee := range t.Assignees { + found = false + if newAssignees[oldAssignee.ID] != nil { + found = true // If a new assignee is already in the list with old assignees + } + + // Put all assignees which are only on the old list to the trash + if !found { + assigneesToDelete = append(assigneesToDelete, oldAssignee.ID) + } + + oldAssignees[oldAssignee.ID] = oldAssignee + } + + // Delete all assignees not passed + if len(assigneesToDelete) > 0 { + _, err = x.In("user_id", assigneesToDelete). + And("task_id = ?", t.ID). + Delete(ListTaskAssginee{}) + if err != nil { + return err + } + } + + // Get the list to perform later checks + list := List{ID: t.ListID} + err = list.GetSimpleByID() + if err != nil { + return + } + + // Loop through our users and add them + for _, u := range assignees { + // Check if the user is already assigned and assign him only if not + if oldAssignees[u.ID] != nil { + // continue outer loop + continue + } + + // Add the new assignee + err = t.addNewAssigneeByID(u.ID, &list) + if err != nil { + return err + } + } + + return +} + +// Delete a task assignee +func (la *ListTaskAssginee) Delete() (err error) { + _, err = x.Delete(&ListTaskAssginee{TaskID: la.TaskID, UserID: la.UserID}) + return +} + +// Create adds a new assignee to a task +func (la *ListTaskAssginee) Create(a web.Auth) (err error) { + + // Get the list to perform later checks + list, err := GetListSimplByTaskID(la.TaskID) + if err != nil { + return + } + + task := &ListTask{ID: la.TaskID} + return task.addNewAssigneeByID(la.UserID, list) +} + +func (t *ListTask) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) { + // Check if the user exists and has access to the list + newAssignee, err := GetUserByID(newAssigneeID) + if err != nil { + return err + } + if !list.CanRead(&newAssignee) { + return ErrUserDoesNotHaveAccessToList{list.ID, newAssigneeID} + } + + _, err = x.Insert(ListTaskAssginee{ + TaskID: t.ID, + UserID: newAssigneeID, + }) + + return +} diff --git a/pkg/models/list_task_assignees_rights.go b/pkg/models/list_task_assignees_rights.go new file mode 100644 index 000000000..a5f03f77c --- /dev/null +++ b/pkg/models/list_task_assignees_rights.go @@ -0,0 +1,42 @@ +// Vikunja is a todo-list application to facilitate your life. +// Copyright 2019 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import ( + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/web" +) + +// CanCreate checks if a user can add a new assignee +func (la *ListTaskAssginee) CanCreate(a web.Auth) bool { + return la.canDoListTaskAssingee(a) +} + +// CanDelete checks if a user can delete an assignee +func (la *ListTaskAssginee) CanDelete(a web.Auth) bool { + return la.canDoListTaskAssingee(a) +} + +func (la *ListTaskAssginee) canDoListTaskAssingee(a web.Auth) bool { + // Check if the current user can edit the list + list, err := GetListSimplByTaskID(la.TaskID) + if err != nil { + log.Log.Errorf("Error during canDoListTaskAssingee for ListTaskAssginee: %v", err) + return false + } + return list.CanCreate(a) +} diff --git a/pkg/models/list_tasks.go b/pkg/models/list_tasks.go index 586e21231..85a486a1d 100644 --- a/pkg/models/list_tasks.go +++ b/pkg/models/list_tasks.go @@ -76,25 +76,6 @@ func (ListTask) TableName() string { return "tasks" } -// ListTaskAssginee represents an assignment of a user to a task -type ListTaskAssginee struct { - ID int64 `xorm:"int(11) autoincr not null unique pk"` - TaskID int64 `xorm:"int(11) INDEX not null"` - UserID int64 `xorm:"int(11) INDEX not null"` - Created int64 `xorm:"created"` -} - -// TableName makes a pretty table name -func (ListTaskAssginee) TableName() string { - return "task_assignees" -} - -// ListTaskAssigneeWithUser is a helper type to deal with user joins -type ListTaskAssigneeWithUser struct { - TaskID int64 - User `xorm:"extends"` -} - // GetTasksByListID gets all todotasks for a list func GetTasksByListID(listID int64) (tasks []*ListTask, err error) { // make a map so we can put in a lot of other stuff more easily @@ -175,16 +156,6 @@ func GetTasksByListID(listID int64) (tasks []*ListTask, err error) { return } -func getRawTaskAssigneesForTasks(taskIDs []int64) (taskAssignees []*ListTaskAssigneeWithUser, err error) { - taskAssignees = []*ListTaskAssigneeWithUser{nil} - err = x.Table("task_assignees"). - Select("task_id, users.*"). - In("task_id", taskIDs). - Join("INNER", "users", "task_assignees.user_id = users.id"). - Find(&taskAssignees) - return -} - func getTaskByIDSimple(taskID int64) (task ListTask, err error) { if taskID < 1 { return ListTask{}, ErrListTaskDoesNotExist{taskID} diff --git a/pkg/models/list_tasks_create_update.go b/pkg/models/list_tasks_create_update.go index 5cfbdda99..c1259cd66 100644 --- a/pkg/models/list_tasks_create_update.go +++ b/pkg/models/list_tasks_create_update.go @@ -143,88 +143,3 @@ func updateDone(oldTask *ListTask, newTask *ListTask) { newTask.Done = false } } - -// Create a bunch of task assignees -func (t *ListTask) updateTaskAssignees(assignees []*User) (err error) { - - // If we don't have any new assignees, delete everything right away. Saves us some hassle. - if len(assignees) == 0 && len(t.Assignees) > 0 { - _, err = x.Where("task_id = ?", t.ID). - Delete(ListTaskAssginee{}) - return err - } - - // If we didn't change anything (from 0 to zero) don't do anything. - if len(assignees) == 0 && len(t.Assignees) == 0 { - return nil - } - - // Make a hashmap of the new assignees for easier comparison - newAssignees := make(map[int64]*User, len(assignees)) - for _, newAssignee := range assignees { - newAssignees[newAssignee.ID] = newAssignee - } - - // Get old assignees to delete - var found bool - var assigneesToDelete []int64 - oldAssignees := make(map[int64]*User, len(t.Assignees)) - for _, oldAssignee := range t.Assignees { - found = false - if newAssignees[oldAssignee.ID] != nil { - found = true // If a new assignee is already in the list with old assignees - } - - // Put all assignees which are only on the old list to the trash - if !found { - assigneesToDelete = append(assigneesToDelete, oldAssignee.ID) - } - - oldAssignees[oldAssignee.ID] = oldAssignee - } - - // Delete all assignees not passed - if len(assigneesToDelete) > 0 { - _, err = x.In("user_id", assigneesToDelete). - And("task_id = ?", t.ID). - Delete(ListTaskAssginee{}) - if err != nil { - return err - } - } - - // Get the list to perform later checks - list := List{ID: t.ListID} - err = list.GetSimpleByID() - if err != nil { - return - } - - // Loop through our users and add them - for _, u := range assignees { - // Check if the user is already assigned and assign him only if not - if oldAssignees[u.ID] != nil { - // continue outer loop - continue - } - - // Check if the user exists and has access to the list - newAssignee, err := GetUserByID(u.ID) - if err != nil { - return err - } - if !list.CanRead(&newAssignee) { - return ErrUserDoesNotHaveAccessToList{list.ID, u.ID} - } - - _, err = x.Insert(ListTaskAssginee{ - TaskID: t.ID, - UserID: u.ID, - }) - if err != nil { - return err - } - } - - return -} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 3711a1611..e233520b3 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -241,6 +241,15 @@ func RegisterRoutes(e *echo.Echo) { } a.POST("/tasks/bulk", bulkTaskHandler.UpdateWeb) + assigneeTaskHandler := &handler.WebHandler{ + EmptyStruct: func() handler.CObject { + return &models.ListTaskAssginee{} + }, + } + a.PUT("/tasks/:listtask/assignees", assigneeTaskHandler.CreateWeb) + a.DELETE("/tasks/:listtask/assignees/:user", assigneeTaskHandler.DeleteWeb) + a.GET("/tasks/:listtask/labelassigneess", assigneeTaskHandler.ReadAllWeb) + labelTaskHandler := &handler.WebHandler{ EmptyStruct: func() handler.CObject { return &models.LabelTask{} -- 2.40.1 From 5a321a39ed4096922ff7df72fc5a3a2a9df039d3 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 6 Jan 2019 15:19:12 +0100 Subject: [PATCH 05/10] Add read all endpoint for assignees --- Featurecreep.md | 2 +- REST-Tests/lists.http | 14 ++++++++++++-- pkg/models/list_task_assignees.go | 18 +++++++++++++++++- pkg/routes/routes.go | 2 +- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Featurecreep.md b/Featurecreep.md index 22aee0898..0ed53fd19 100644 --- a/Featurecreep.md +++ b/Featurecreep.md @@ -117,7 +117,7 @@ Sorry for some of them being in German, I'll tranlate them at some point. * [x] Assignees * [x] Check if something changed at all before running everything * [x] Don't use `list.ReadOne()`, gets too much unnessecary shit - * [ ] Wegen Performance auf eigene endpoints umziehen, wie labels + * [x] Wegen Performance auf eigene endpoints umziehen, wie labels * [ ] "One endpoint to rule them all" -> Array-addable * [x] Labels * [ ] Check if something changed at all before running everything diff --git a/REST-Tests/lists.http b/REST-Tests/lists.http index b13d540d6..c3fd71e91 100644 --- a/REST-Tests/lists.http +++ b/REST-Tests/lists.http @@ -134,8 +134,13 @@ POST http://localhost:8080/api/v1/tasks/3565 Authorization: Bearer {{auth_token}} Content-Type: application/json -{"assignees":[ -]} +{ + "assignees": [ + { + "id": 1 + } + ] +} ### @@ -150,3 +155,8 @@ Content-Type: application/json } ### +# Get all assignees +GET http://localhost:8080/api/v1/tasks/3565/assignees +Authorization: Bearer {{auth_token}} + +### diff --git a/pkg/models/list_task_assignees.go b/pkg/models/list_task_assignees.go index 9c64fb45c..6c8ea7ffe 100644 --- a/pkg/models/list_task_assignees.go +++ b/pkg/models/list_task_assignees.go @@ -16,7 +16,10 @@ package models -import "code.vikunja.io/web" +import ( + "code.vikunja.io/web" + "fmt" +) // ListTaskAssginee represents an assignment of a user to a task type ListTaskAssginee struct { @@ -145,6 +148,7 @@ func (la *ListTaskAssginee) Create(a web.Auth) (err error) { func (t *ListTask) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) { // Check if the user exists and has access to the list + fmt.Println("getuserbyid") newAssignee, err := GetUserByID(newAssigneeID) if err != nil { return err @@ -160,3 +164,15 @@ func (t *ListTask) addNewAssigneeByID(newAssigneeID int64, list *List) (err erro return } + +// ReadAll gets all assignees for a task +func (la *ListTaskAssginee) ReadAll(search string, a web.Auth, page int) (interface{}, error) { + taskAssignees := []*User{} + err := x.Table("task_assignees"). + Select("users.*"). + Join("INNER", "users", "task_assignees.user_id = users.id"). + Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%"). + Limit(getLimitFromPageIndex(page)). + Find(&taskAssignees) + return taskAssignees, err +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index e233520b3..e002bf50b 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -248,7 +248,7 @@ func RegisterRoutes(e *echo.Echo) { } a.PUT("/tasks/:listtask/assignees", assigneeTaskHandler.CreateWeb) a.DELETE("/tasks/:listtask/assignees/:user", assigneeTaskHandler.DeleteWeb) - a.GET("/tasks/:listtask/labelassigneess", assigneeTaskHandler.ReadAllWeb) + a.GET("/tasks/:listtask/assignees", assigneeTaskHandler.ReadAllWeb) labelTaskHandler := &handler.WebHandler{ EmptyStruct: func() handler.CObject { -- 2.40.1 From 2a0c4ea9bc52ddf2a350dfafed419e8d9e73d586 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 6 Jan 2019 17:51:20 +0100 Subject: [PATCH 06/10] improvements --- pkg/models/list_task_assignees.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/models/list_task_assignees.go b/pkg/models/list_task_assignees.go index 6c8ea7ffe..401612ba3 100644 --- a/pkg/models/list_task_assignees.go +++ b/pkg/models/list_task_assignees.go @@ -167,7 +167,7 @@ func (t *ListTask) addNewAssigneeByID(newAssigneeID int64, list *List) (err erro // ReadAll gets all assignees for a task func (la *ListTaskAssginee) ReadAll(search string, a web.Auth, page int) (interface{}, error) { - taskAssignees := []*User{} + var taskAssignees []*User err := x.Table("task_assignees"). Select("users.*"). Join("INNER", "users", "task_assignees.user_id = users.id"). -- 2.40.1 From 37ae1611a4b9e2968c200ac760a1b2443a49ff08 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 7 Jan 2019 22:48:28 +0100 Subject: [PATCH 07/10] Added swaggerdocs --- docs/docs.go | 164 +++++++++++++++++++++++++++--- docs/swagger/swagger.json | 162 ++++++++++++++++++++++++++--- docs/swagger/swagger.yaml | 119 +++++++++++++++++++--- pkg/models/list_task_assignees.go | 35 +++++++ 4 files changed, 439 insertions(+), 41 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 9d02982a3..d4dc2b46f 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 -// 2019-01-03 22:32:38.523459581 +0100 CET m=+0.109277724 +// 2019-01-07 22:44:21.325472216 +0100 CET m=+0.109716359 package docs @@ -37,7 +37,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns all labels which are either created by the user or associated with a task the user has at least read-access to.", + "description": "Returns an array with all assignees for this task.", "consumes": [ "application/json" ], @@ -45,9 +45,9 @@ var doc = `{ "application/json" ], "tags": [ - "labels" + "assignees" ], - "summary": "Get all labels a user has access to", + "summary": "Get all assignees for a task", "parameters": [ { "type": "integer", @@ -57,18 +57,18 @@ var doc = `{ }, { "type": "string", - "description": "Search labels by label text.", + "description": "Search assignees by their username.", "name": "s", "in": "query" } ], "responses": { "200": { - "description": "The labels", + "description": "The assignees", "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.Label" + "$ref": "#/definitions/models.User" } } }, @@ -391,7 +391,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns a team by its ID.", + "description": "Returns a list by its ID.", "consumes": [ "application/json" ], @@ -399,13 +399,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 @@ -413,14 +413,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" @@ -2649,6 +2649,127 @@ var doc = `{ } } }, + "/tasks/{taskID}/assignees": { + "put": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Adds a new assignee to a task. The assignee needs to have access to the list, the doer must be able to edit this task.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "assignees" + ], + "summary": "Add a new assignee to a task", + "parameters": [ + { + "description": "The assingee object", + "name": "assignee", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/models.ListTaskAssginee" + } + }, + { + "type": "integer", + "description": "Task ID", + "name": "taskID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The created assingee object.", + "schema": { + "type": "object", + "$ref": "#/definitions/models.ListTaskAssginee" + } + }, + "400": { + "description": "Invalid assignee object provided.", + "schema": { + "type": "object", + "$ref": "#/definitions/code.vikunja.io.web.HTTPError" + } + }, + "500": { + "description": "Internal error", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + } + } + } + }, + "/tasks/{taskID}/assignees/{userID}": { + "delete": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Un-assign a user from a task.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "assignees" + ], + "summary": "Delete an assignee", + "parameters": [ + { + "type": "integer", + "description": "Task ID", + "name": "taskID", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Assignee ID", + "name": "userID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The assignee was successfully deleted.", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + }, + "403": { + "description": "Not allowed to delete the assignee.", + "schema": { + "type": "object", + "$ref": "#/definitions/code.vikunja.io.web.HTTPError" + } + }, + "500": { + "description": "Internal error", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/tasks/{task}/labels": { "get": { "security": [ @@ -3795,6 +3916,17 @@ var doc = `{ } } }, + "models.ListTaskAssginee": { + "type": "object", + "properties": { + "created": { + "type": "integer" + }, + "user_id": { + "type": "integer" + } + } + }, "models.ListUser": { "type": "object", "properties": { @@ -4081,6 +4213,10 @@ var doc = `{ "team_id": { "description": "The team id.", "type": "integer" + }, + "updated": { + "description": "A unix timestamp when this relation was last updated. You cannot change this value.", + "type": "integer" } } }, diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 75c4fb1de..4e5f1cf08 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -24,7 +24,7 @@ "JWTKeyAuth": [] } ], - "description": "Returns all labels which are either created by the user or associated with a task the user has at least read-access to.", + "description": "Returns an array with all assignees for this task.", "consumes": [ "application/json" ], @@ -32,9 +32,9 @@ "application/json" ], "tags": [ - "labels" + "assignees" ], - "summary": "Get all labels a user has access to", + "summary": "Get all assignees for a task", "parameters": [ { "type": "integer", @@ -44,18 +44,18 @@ }, { "type": "string", - "description": "Search labels by label text.", + "description": "Search assignees by their username.", "name": "s", "in": "query" } ], "responses": { "200": { - "description": "The labels", + "description": "The assignees", "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.Label" + "$ref": "#/definitions/models.User" } } }, @@ -378,7 +378,7 @@ "JWTKeyAuth": [] } ], - "description": "Returns a team by its ID.", + "description": "Returns a list by its ID.", "consumes": [ "application/json" ], @@ -386,13 +386,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 @@ -400,14 +400,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" @@ -2636,6 +2636,127 @@ } } }, + "/tasks/{taskID}/assignees": { + "put": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Adds a new assignee to a task. The assignee needs to have access to the list, the doer must be able to edit this task.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "assignees" + ], + "summary": "Add a new assignee to a task", + "parameters": [ + { + "description": "The assingee object", + "name": "assignee", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/models.ListTaskAssginee" + } + }, + { + "type": "integer", + "description": "Task ID", + "name": "taskID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The created assingee object.", + "schema": { + "type": "object", + "$ref": "#/definitions/models.ListTaskAssginee" + } + }, + "400": { + "description": "Invalid assignee object provided.", + "schema": { + "type": "object", + "$ref": "#/definitions/code.vikunja.io/web.HTTPError" + } + }, + "500": { + "description": "Internal error", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + } + } + } + }, + "/tasks/{taskID}/assignees/{userID}": { + "delete": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Un-assign a user from a task.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "assignees" + ], + "summary": "Delete an assignee", + "parameters": [ + { + "type": "integer", + "description": "Task ID", + "name": "taskID", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Assignee ID", + "name": "userID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The assignee was successfully deleted.", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + }, + "403": { + "description": "Not allowed to delete the assignee.", + "schema": { + "type": "object", + "$ref": "#/definitions/code.vikunja.io/web.HTTPError" + } + }, + "500": { + "description": "Internal error", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/tasks/{task}/labels": { "get": { "security": [ @@ -3781,6 +3902,17 @@ } } }, + "models.ListTaskAssginee": { + "type": "object", + "properties": { + "created": { + "type": "integer" + }, + "user_id": { + "type": "integer" + } + } + }, "models.ListUser": { "type": "object", "properties": { @@ -4067,6 +4199,10 @@ "team_id": { "description": "The team id.", "type": "integer" + }, + "updated": { + "description": "A unix timestamp when this relation was last updated. You cannot change this value.", + "type": "integer" } } }, diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 86bd01b87..356793a45 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -244,6 +244,13 @@ definitions: change this value. type: integer type: object + models.ListTaskAssginee: + properties: + created: + type: integer + user_id: + type: integer + type: object models.ListUser: properties: created: @@ -475,6 +482,10 @@ definitions: team_id: description: The team id. type: integer + updated: + description: A unix timestamp when this relation was last updated. You cannot + change this value. + type: integer type: object models.TeamUser: properties: @@ -630,15 +641,14 @@ paths: get: consumes: - application/json - description: Returns all labels which are either created by the user or associated - with a task the user has at least read-access to. + description: Returns an array with all assignees for this task. 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 labels by label text. + - description: Search assignees by their username. in: query name: s type: string @@ -646,10 +656,10 @@ paths: - application/json responses: "200": - description: The labels + description: The assignees schema: items: - $ref: '#/definitions/models.Label' + $ref: '#/definitions/models.User' type: array "500": description: Internal error @@ -658,9 +668,9 @@ paths: type: object security: - JWTKeyAuth: [] - summary: Get all labels a user has access to + summary: Get all assignees for a task tags: - - labels + - assignees put: consumes: - application/json @@ -904,9 +914,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 @@ -915,12 +925,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 @@ -931,9 +941,9 @@ paths: type: object security: - JWTKeyAuth: [] - summary: Gets one team + summary: Gets one list tags: - - team + - list post: consumes: - application/json @@ -2341,6 +2351,87 @@ paths: summary: Remove a label from a task tags: - labels + /tasks/{taskID}/assignees: + put: + consumes: + - application/json + description: Adds a new assignee to a task. The assignee needs to have access + to the list, the doer must be able to edit this task. + parameters: + - description: The assingee object + in: body + name: assignee + required: true + schema: + $ref: '#/definitions/models.ListTaskAssginee' + type: object + - description: Task ID + in: path + name: taskID + required: true + type: integer + produces: + - application/json + responses: + "200": + description: The created assingee object. + schema: + $ref: '#/definitions/models.ListTaskAssginee' + type: object + "400": + description: Invalid assignee object provided. + schema: + $ref: '#/definitions/code.vikunja.io/web.HTTPError' + type: object + "500": + description: Internal error + schema: + $ref: '#/definitions/models.Message' + type: object + security: + - JWTKeyAuth: [] + summary: Add a new assignee to a task + tags: + - assignees + /tasks/{taskID}/assignees/{userID}: + delete: + consumes: + - application/json + description: Un-assign a user from a task. + parameters: + - description: Task ID + in: path + name: taskID + required: true + type: integer + - description: Assignee ID + in: path + name: userID + required: true + type: integer + produces: + - application/json + responses: + "200": + description: The assignee was successfully deleted. + schema: + $ref: '#/definitions/models.Message' + type: object + "403": + description: Not allowed to delete the assignee. + schema: + $ref: '#/definitions/code.vikunja.io/web.HTTPError' + type: object + "500": + description: Internal error + schema: + $ref: '#/definitions/models.Message' + type: object + security: + - JWTKeyAuth: [] + summary: Delete an assignee + tags: + - assignees /tasks/all: get: consumes: diff --git a/pkg/models/list_task_assignees.go b/pkg/models/list_task_assignees.go index 401612ba3..fcc643239 100644 --- a/pkg/models/list_task_assignees.go +++ b/pkg/models/list_task_assignees.go @@ -128,12 +128,36 @@ func (t *ListTask) updateTaskAssignees(assignees []*User) (err error) { } // Delete a task assignee +// @Summary Delete an assignee +// @Description Un-assign a user from a task. +// @tags assignees +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param taskID path int true "Task ID" +// @Param userID path int true "Assignee user ID" +// @Success 200 {object} models.Message "The assignee was successfully deleted." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to delete the assignee." +// @Failure 500 {object} models.Message "Internal error" +// @Router /tasks/{taskID}/assignees/{userID} [delete] func (la *ListTaskAssginee) Delete() (err error) { _, err = x.Delete(&ListTaskAssginee{TaskID: la.TaskID, UserID: la.UserID}) return } // Create adds a new assignee to a task +// @Summary Add a new assignee to a task +// @Description Adds a new assignee to a task. The assignee needs to have access to the list, the doer must be able to edit this task. +// @tags assignees +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param assignee body models.ListTaskAssginee true "The assingee object" +// @Param taskID path int true "Task ID" +// @Success 200 {object} models.ListTaskAssginee "The created assingee object." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid assignee object provided." +// @Failure 500 {object} models.Message "Internal error" +// @Router /tasks/{taskID}/assignees [put] func (la *ListTaskAssginee) Create(a web.Auth) (err error) { // Get the list to perform later checks @@ -166,6 +190,17 @@ func (t *ListTask) addNewAssigneeByID(newAssigneeID int64, list *List) (err erro } // ReadAll gets all assignees for a task +// @Summary Get all assignees for a task +// @Description Returns an array with all assignees for this task. +// @tags assignees +// @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 assignees by their username." +// @Security JWTKeyAuth +// @Success 200 {array} models.User "The assignees" +// @Failure 500 {object} models.Message "Internal error" +// @Router /labels [get] func (la *ListTaskAssginee) ReadAll(search string, a web.Auth, page int) (interface{}, error) { var taskAssignees []*User err := x.Table("task_assignees"). -- 2.40.1 From d0b14aa7184d2510335fdf8a2a9ec6407c33bc89 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 7 Jan 2019 23:18:38 +0100 Subject: [PATCH 08/10] Added method to bulk update assignees --- REST-Tests/lists.http | 13 +++ docs/docs.go | 104 +++++++++++++++++++---- docs/swagger/swagger.json | 102 +++++++++++++++++++--- docs/swagger/swagger.yaml | 80 ++++++++++++++--- pkg/models/list.go | 8 +- pkg/models/list_task_assignees.go | 31 +++++++ pkg/models/list_task_assignees_rights.go | 13 ++- pkg/routes/routes.go | 7 ++ 8 files changed, 308 insertions(+), 50 deletions(-) diff --git a/REST-Tests/lists.http b/REST-Tests/lists.http index c3fd71e91..8ff1d8a42 100644 --- a/REST-Tests/lists.http +++ b/REST-Tests/lists.http @@ -160,3 +160,16 @@ GET http://localhost:8080/api/v1/tasks/3565/assignees Authorization: Bearer {{auth_token}} ### + +# Add a bunch of assignees +PUT http://localhost:8080/api/v1/tasks/3565/assignees/bulk +Authorization: Bearer {{auth_token}} +Content-Type: application/json + +{ + "assignees": [ + {"id": 17} + ] +} + +### diff --git a/docs/docs.go b/docs/docs.go index d4dc2b46f..2518b5384 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 -// 2019-01-07 22:44:21.325472216 +0100 CET m=+0.109716359 +// 2019-01-07 23:16:50.581590248 +0100 CET m=+0.121922055 package docs @@ -37,7 +37,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns an array with all assignees for this task.", + "description": "Returns all labels which are either created by the user or associated with a task the user has at least read-access to.", "consumes": [ "application/json" ], @@ -45,9 +45,9 @@ var doc = `{ "application/json" ], "tags": [ - "assignees" + "labels" ], - "summary": "Get all assignees for a task", + "summary": "Get all labels a user has access to", "parameters": [ { "type": "integer", @@ -57,18 +57,18 @@ var doc = `{ }, { "type": "string", - "description": "Search assignees by their username.", + "description": "Search labels by label text.", "name": "s", "in": "query" } ], "responses": { "200": { - "description": "The assignees", + "description": "The labels", "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.User" + "$ref": "#/definitions/models.Label" } } }, @@ -391,7 +391,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns a list by its ID.", + "description": "Returns a team by its ID.", "consumes": [ "application/json" ], @@ -399,13 +399,13 @@ var doc = `{ "application/json" ], "tags": [ - "list" + "team" ], - "summary": "Gets one list", + "summary": "Gets one team", "parameters": [ { "type": "integer", - "description": "List ID", + "description": "Team ID", "name": "id", "in": "path", "required": true @@ -413,14 +413,14 @@ var doc = `{ ], "responses": { "200": { - "description": "The list", + "description": "The team", "schema": { "type": "object", - "$ref": "#/definitions/models.List" + "$ref": "#/definitions/models.Team" } }, "403": { - "description": "The user does not have access to the list", + "description": "The user does not have access to the team", "schema": { "type": "object", "$ref": "#/definitions/code.vikunja.io.web.HTTPError" @@ -2711,6 +2711,68 @@ var doc = `{ } } }, + "/tasks/{taskID}/assignees/bulk": { + "put": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Adds new assignees to a task. The assignee needs to have access to the list, the doer must be able to edit this task. Every user not in the list will be unassigned from the task, pass an empty array to unassign everyone.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "assignees" + ], + "summary": "Add new assignees to a task", + "parameters": [ + { + "description": "The array of assignees", + "name": "assignee", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/models.BulkAssignees" + } + }, + { + "type": "integer", + "description": "Task ID", + "name": "taskID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The created assingees object.", + "schema": { + "type": "object", + "$ref": "#/definitions/models.ListTaskAssginee" + } + }, + "400": { + "description": "Invalid assignee object provided.", + "schema": { + "type": "object", + "$ref": "#/definitions/code.vikunja.io.web.HTTPError" + } + }, + "500": { + "description": "Internal error", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/tasks/{taskID}/assignees/{userID}": { "delete": { "security": [ @@ -2739,7 +2801,7 @@ var doc = `{ }, { "type": "integer", - "description": "Assignee ID", + "description": "Assignee user ID", "name": "userID", "in": "path", "required": true @@ -3638,6 +3700,18 @@ var doc = `{ } } }, + "models.BulkAssignees": { + "type": "object", + "properties": { + "assignees": { + "description": "A list with all assignees", + "type": "array", + "items": { + "$ref": "#/definitions/models.User" + } + } + } + }, "models.BulkTask": { "type": "object", "properties": { diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 4e5f1cf08..a6bd0d173 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -24,7 +24,7 @@ "JWTKeyAuth": [] } ], - "description": "Returns an array with all assignees for this task.", + "description": "Returns all labels which are either created by the user or associated with a task the user has at least read-access to.", "consumes": [ "application/json" ], @@ -32,9 +32,9 @@ "application/json" ], "tags": [ - "assignees" + "labels" ], - "summary": "Get all assignees for a task", + "summary": "Get all labels a user has access to", "parameters": [ { "type": "integer", @@ -44,18 +44,18 @@ }, { "type": "string", - "description": "Search assignees by their username.", + "description": "Search labels by label text.", "name": "s", "in": "query" } ], "responses": { "200": { - "description": "The assignees", + "description": "The labels", "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.User" + "$ref": "#/definitions/models.Label" } } }, @@ -378,7 +378,7 @@ "JWTKeyAuth": [] } ], - "description": "Returns a list by its ID.", + "description": "Returns a team by its ID.", "consumes": [ "application/json" ], @@ -386,13 +386,13 @@ "application/json" ], "tags": [ - "list" + "team" ], - "summary": "Gets one list", + "summary": "Gets one team", "parameters": [ { "type": "integer", - "description": "List ID", + "description": "Team ID", "name": "id", "in": "path", "required": true @@ -400,14 +400,14 @@ ], "responses": { "200": { - "description": "The list", + "description": "The team", "schema": { "type": "object", - "$ref": "#/definitions/models.List" + "$ref": "#/definitions/models.Team" } }, "403": { - "description": "The user does not have access to the list", + "description": "The user does not have access to the team", "schema": { "type": "object", "$ref": "#/definitions/code.vikunja.io/web.HTTPError" @@ -2698,6 +2698,68 @@ } } }, + "/tasks/{taskID}/assignees/bulk": { + "put": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Adds new assignees to a task. The assignee needs to have access to the list, the doer must be able to edit this task. Every user not in the list will be unassigned from the task, pass an empty array to unassign everyone.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "assignees" + ], + "summary": "Add new assignees to a task", + "parameters": [ + { + "description": "The array of assignees", + "name": "assignee", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/models.BulkAssignees" + } + }, + { + "type": "integer", + "description": "Task ID", + "name": "taskID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The created assingees object.", + "schema": { + "type": "object", + "$ref": "#/definitions/models.ListTaskAssginee" + } + }, + "400": { + "description": "Invalid assignee object provided.", + "schema": { + "type": "object", + "$ref": "#/definitions/code.vikunja.io/web.HTTPError" + } + }, + "500": { + "description": "Internal error", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/tasks/{taskID}/assignees/{userID}": { "delete": { "security": [ @@ -2726,7 +2788,7 @@ }, { "type": "integer", - "description": "Assignee ID", + "description": "Assignee user ID", "name": "userID", "in": "path", "required": true @@ -3624,6 +3686,18 @@ } } }, + "models.BulkAssignees": { + "type": "object", + "properties": { + "assignees": { + "description": "A list with all assignees", + "type": "array", + "items": { + "$ref": "#/definitions/models.User" + } + } + } + }, "models.BulkTask": { "type": "object", "properties": { diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 356793a45..91db35028 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -21,6 +21,14 @@ definitions: minLength: 3 type: string type: object + models.BulkAssignees: + properties: + assignees: + description: A list with all assignees + items: + $ref: '#/definitions/models.User' + type: array + type: object models.BulkTask: properties: assignees: @@ -641,14 +649,15 @@ paths: get: consumes: - application/json - description: Returns an array with all assignees for this task. + description: Returns all labels which are either created by the user or associated + with a task the user has at least read-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 assignees by their username. + - description: Search labels by label text. in: query name: s type: string @@ -656,10 +665,10 @@ paths: - application/json responses: "200": - description: The assignees + description: The labels schema: items: - $ref: '#/definitions/models.User' + $ref: '#/definitions/models.Label' type: array "500": description: Internal error @@ -668,9 +677,9 @@ paths: type: object security: - JWTKeyAuth: [] - summary: Get all assignees for a task + summary: Get all labels a user has access to tags: - - assignees + - labels put: consumes: - application/json @@ -914,9 +923,9 @@ paths: get: consumes: - application/json - description: Returns a list by its ID. + description: Returns a team by its ID. parameters: - - description: List ID + - description: Team ID in: path name: id required: true @@ -925,12 +934,12 @@ paths: - application/json responses: "200": - description: The list + description: The team schema: - $ref: '#/definitions/models.List' + $ref: '#/definitions/models.Team' type: object "403": - description: The user does not have access to the list + description: The user does not have access to the team schema: $ref: '#/definitions/code.vikunja.io/web.HTTPError' type: object @@ -941,9 +950,9 @@ paths: type: object security: - JWTKeyAuth: [] - summary: Gets one list + summary: Gets one team tags: - - list + - team post: consumes: - application/json @@ -2404,7 +2413,7 @@ paths: name: taskID required: true type: integer - - description: Assignee ID + - description: Assignee user ID in: path name: userID required: true @@ -2432,6 +2441,49 @@ paths: summary: Delete an assignee tags: - assignees + /tasks/{taskID}/assignees/bulk: + put: + consumes: + - application/json + description: Adds new assignees to a task. The assignee needs to have access + to the list, the doer must be able to edit this task. Every user not in the + list will be unassigned from the task, pass an empty array to unassign everyone. + parameters: + - description: The array of assignees + in: body + name: assignee + required: true + schema: + $ref: '#/definitions/models.BulkAssignees' + type: object + - description: Task ID + in: path + name: taskID + required: true + type: integer + produces: + - application/json + responses: + "200": + description: The created assingees object. + schema: + $ref: '#/definitions/models.ListTaskAssginee' + type: object + "400": + description: Invalid assignee object provided. + schema: + $ref: '#/definitions/code.vikunja.io/web.HTTPError' + type: object + "500": + description: Internal error + schema: + $ref: '#/definitions/models.Message' + type: object + security: + - JWTKeyAuth: [] + summary: Add new assignees to a task + tags: + - assignees /tasks/all: get: consumes: diff --git a/pkg/models/list.go b/pkg/models/list.go index fe5e5a8c5..2fcd35dd1 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -155,11 +155,13 @@ func (l *List) GetSimpleByID() (err error) { func GetListSimplByTaskID(taskID int64) (l *List, err error) { // We need to re-init our list object, because otherwise xorm creates a "where for every item in that list object, // leading to not finding anything if the id is good, but for example the title is different. + var list List exists, err := x. - Select("lists.*"). + Select("list.*"). + Table(List{}). Join("INNER", "tasks", "list.id = tasks.list_id"). Where("tasks.id = ?", taskID). - Get(l) + Get(&list) if err != nil { return } @@ -168,7 +170,7 @@ func GetListSimplByTaskID(taskID int64) (l *List, err error) { return &List{}, ErrListDoesNotExist{ID: l.ID} } - return + return &list, nil } // Gets the lists only, without any tasks or so diff --git a/pkg/models/list_task_assignees.go b/pkg/models/list_task_assignees.go index fcc643239..e2ffe686a 100644 --- a/pkg/models/list_task_assignees.go +++ b/pkg/models/list_task_assignees.go @@ -211,3 +211,34 @@ func (la *ListTaskAssginee) ReadAll(search string, a web.Auth, page int) (interf Find(&taskAssignees) return taskAssignees, err } + +// BulkAssignees is a helper struct used to update multiple assignees at once. +type BulkAssignees struct { + // A list with all assignees + Assignees []*User `json:"assignees"` + TaskID int64 `json:"-" param:"listtask"` + + web.CRUDable `json:"-"` + web.Rights `json:"-"` +} + +// Create adds new assignees to a task +// @Summary Add multiple new assignees to a task +// @Description Adds multiple new assignees to a task. The assignee needs to have access to the list, the doer must be able to edit this task. Every user not in the list will be unassigned from the task, pass an empty array to unassign everyone. +// @tags assignees +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param assignee body models.BulkAssignees true "The array of assignees" +// @Param taskID path int true "Task ID" +// @Success 200 {object} models.ListTaskAssginee "The created assingees object." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid assignee object provided." +// @Failure 500 {object} models.Message "Internal error" +// @Router /tasks/{taskID}/assignees/bulk [put] +func (ba *BulkAssignees) Create(a web.Auth) (err error) { + task, err := GetListTaskByID(ba.TaskID) // We need to use the full method here because we need all current assignees. + if err != nil { + return + } + return task.updateTaskAssignees(ba.Assignees) +} diff --git a/pkg/models/list_task_assignees_rights.go b/pkg/models/list_task_assignees_rights.go index a5f03f77c..8c2496c16 100644 --- a/pkg/models/list_task_assignees_rights.go +++ b/pkg/models/list_task_assignees_rights.go @@ -23,17 +23,22 @@ import ( // CanCreate checks if a user can add a new assignee func (la *ListTaskAssginee) CanCreate(a web.Auth) bool { - return la.canDoListTaskAssingee(a) + return canDoListTaskAssingee(la.TaskID, a) +} + +// CanCreate checks if a user can add a new assignee +func (ba *BulkAssignees) CanCreate(a web.Auth) bool { + return canDoListTaskAssingee(ba.TaskID, a) } // CanDelete checks if a user can delete an assignee func (la *ListTaskAssginee) CanDelete(a web.Auth) bool { - return la.canDoListTaskAssingee(a) + return canDoListTaskAssingee(la.TaskID, a) } -func (la *ListTaskAssginee) canDoListTaskAssingee(a web.Auth) bool { +func canDoListTaskAssingee(taskID int64, a web.Auth) bool { // Check if the current user can edit the list - list, err := GetListSimplByTaskID(la.TaskID) + list, err := GetListSimplByTaskID(taskID) if err != nil { log.Log.Errorf("Error during canDoListTaskAssingee for ListTaskAssginee: %v", err) return false diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index e002bf50b..53d8b7435 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -250,6 +250,13 @@ func RegisterRoutes(e *echo.Echo) { a.DELETE("/tasks/:listtask/assignees/:user", assigneeTaskHandler.DeleteWeb) a.GET("/tasks/:listtask/assignees", assigneeTaskHandler.ReadAllWeb) + bulkAssigneeHandler := &handler.WebHandler{ + EmptyStruct: func() handler.CObject { + return &models.BulkAssignees{} + }, + } + a.PUT("/tasks/:listtask/assignees/bulk", bulkAssigneeHandler.CreateWeb) + labelTaskHandler := &handler.WebHandler{ EmptyStruct: func() handler.CObject { return &models.LabelTask{} -- 2.40.1 From 1548bbccc3d6b4f185714b4cbe5d82a468784704 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 8 Jan 2019 19:57:01 +0100 Subject: [PATCH 09/10] update todo --- Featurecreep.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Featurecreep.md b/Featurecreep.md index 0ed53fd19..4d2da85ec 100644 --- a/Featurecreep.md +++ b/Featurecreep.md @@ -118,7 +118,7 @@ Sorry for some of them being in German, I'll tranlate them at some point. * [x] Check if something changed at all before running everything * [x] Don't use `list.ReadOne()`, gets too much unnessecary shit * [x] Wegen Performance auf eigene endpoints umziehen, wie labels - * [ ] "One endpoint to rule them all" -> Array-addable + * [x] "One endpoint to rule them all" -> Array-addable * [x] Labels * [ ] Check if something changed at all before running everything * [ ] Editable via task edit, like assignees -- 2.40.1 From 954d6b117093d0748ca323f5c64d9852af84621f Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 8 Jan 2019 20:09:11 +0100 Subject: [PATCH 10/10] remove unnessecary fmt --- pkg/models/list_task_assignees.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/models/list_task_assignees.go b/pkg/models/list_task_assignees.go index e2ffe686a..0d81891cc 100644 --- a/pkg/models/list_task_assignees.go +++ b/pkg/models/list_task_assignees.go @@ -18,7 +18,6 @@ package models import ( "code.vikunja.io/web" - "fmt" ) // ListTaskAssginee represents an assignment of a user to a task @@ -172,7 +171,6 @@ func (la *ListTaskAssginee) Create(a web.Auth) (err error) { func (t *ListTask) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) { // Check if the user exists and has access to the list - fmt.Println("getuserbyid") newAssignee, err := GetUserByID(newAssigneeID) if err != nil { return err -- 2.40.1