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