Improve label handling (#48)

This commit is contained in:
konrad 2019-01-09 23:08:12 +00:00 committed by Gitea
parent 364a172876
commit 318920fe29
16 changed files with 515 additions and 90 deletions

View File

@ -120,9 +120,9 @@ Sorry for some of them being in German, I'll tranlate them at some point.
* [x] Wegen Performance auf eigene endpoints umziehen, wie labels
* [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
* [ ] "One endpoint to rule them all" -> Array-addable
* [x] Check if something changed at all before running everything
* [x] Editable via task edit, like assignees
* [x] "One endpoint to rule them all" -> Array-addable
* [ ] Attachments
* [ ] Task-Templates innerhalb namespaces und Listen (-> Mehrere, die auswählbar sind)
* [ ] Ein Task muss von mehreren Assignees abgehakt werden bis er als done markiert wird
@ -154,6 +154,10 @@ Sorry for some of them being in German, I'll tranlate them at some point.
* [ ] Rights methods should return errors
* [ ] Re-check all `{List|Namespace}{User|Team}` if really all parameters need to be exposed via json or are overwritten via param anyway.
### Refactor
* [ ] ListTaskRights, sollte überall gleich funktionieren, gibt ja mittlerweile auch eine Methode um liste von nem Task aus zu kriegen oder so
### Linters
* [x] goconst

View File

@ -53,4 +53,18 @@ Content-Type: application/json
DELETE http://localhost:8080/api/v1/tasks/3565/labels/1
Authorization: Bearer {{auth_token}}
###
# Add a new label to a task
POST http://localhost:8080/api/v1/tasks/3565/labels/bulk
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"labels": [
{"id": 1},
{"id": 2},
{"id": 3}
]
}
###

View File

@ -135,10 +135,9 @@ Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"assignees": [
{
"id": 1
}
"labels": [
{"id": 1},
{"id": 2}
]
}

View File

@ -1,6 +1,6 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at
// 2019-01-07 23:16:50.581590248 +0100 CET m=+0.121922055
// 2019-01-10 00:01:27.123040428 +0100 CET m=+0.110268080
package docs
@ -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"
@ -2533,7 +2533,7 @@ var doc = `{
"JWTKeyAuth": []
}
],
"description": "Updates a task. This includes marking it as done.",
"description": "Updates a task. This includes marking it as done. Assignees you pass will be updated, see their individual endpoints for more details on how this is done. To update labels, see the description of the endpoint.",
"consumes": [
"application/json"
],
@ -2712,13 +2712,13 @@ var doc = `{
}
},
"/tasks/{taskID}/assignees/bulk": {
"put": {
"post": {
"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.",
"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.",
"consumes": [
"application/json"
],
@ -2728,7 +2728,7 @@ var doc = `{
"tags": [
"assignees"
],
"summary": "Add new assignees to a task",
"summary": "Add multiple new assignees to a task",
"parameters": [
{
"description": "The array of assignees",
@ -2832,6 +2832,68 @@ var doc = `{
}
}
},
"/tasks/{taskID}/labels/bulk": {
"post": {
"security": [
{
"JWTKeyAuth": []
}
],
"description": "Adds multiple new labels to a task.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"labels"
],
"summary": "Add multiple new labels to a task",
"parameters": [
{
"description": "The array of labels",
"name": "label",
"in": "body",
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/models.LabelTaskBulk"
}
},
{
"type": "integer",
"description": "Task ID",
"name": "taskID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "The updated labels object.",
"schema": {
"type": "object",
"$ref": "#/definitions/models.LabelTaskBulk"
}
},
"400": {
"description": "Invalid label object provided.",
"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": [
@ -3858,6 +3920,18 @@ var doc = `{
}
}
},
"models.LabelTaskBulk": {
"type": "object",
"properties": {
"labels": {
"description": "All labels you want to update at once. Works exactly like you would update labels while updateing a list.",
"type": "array",
"items": {
"$ref": "#/definitions/models.Label"
}
}
}
},
"models.List": {
"type": "object",
"properties": {

View File

@ -41,4 +41,5 @@ This document describes the different errors Vikunja can return.
| 7002 | 409 | The user already has access to that list. |
| 7003 | 403 | The user does not have access to that list. |
| 8001 | 403 | This label already exists on that task. |
| 8002 | 404 | The label does not exist. |
| 8002 | 404 | The label does not exist. |
| 8003 | 403 | The user does not have access to this label. |

View File

@ -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"
@ -2520,7 +2520,7 @@
"JWTKeyAuth": []
}
],
"description": "Updates a task. This includes marking it as done.",
"description": "Updates a task. This includes marking it as done. Assignees you pass will be updated, see their individual endpoints for more details on how this is done. To update labels, see the description of the endpoint.",
"consumes": [
"application/json"
],
@ -2699,13 +2699,13 @@
}
},
"/tasks/{taskID}/assignees/bulk": {
"put": {
"post": {
"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.",
"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.",
"consumes": [
"application/json"
],
@ -2715,7 +2715,7 @@
"tags": [
"assignees"
],
"summary": "Add new assignees to a task",
"summary": "Add multiple new assignees to a task",
"parameters": [
{
"description": "The array of assignees",
@ -2819,6 +2819,68 @@
}
}
},
"/tasks/{taskID}/labels/bulk": {
"post": {
"security": [
{
"JWTKeyAuth": []
}
],
"description": "Adds multiple new labels to a task.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"labels"
],
"summary": "Add multiple new labels to a task",
"parameters": [
{
"description": "The array of labels",
"name": "label",
"in": "body",
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/models.LabelTaskBulk"
}
},
{
"type": "integer",
"description": "Task ID",
"name": "taskID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "The updated labels object.",
"schema": {
"type": "object",
"$ref": "#/definitions/models.LabelTaskBulk"
}
},
"400": {
"description": "Invalid label object provided.",
"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": [
@ -3844,6 +3906,18 @@
}
}
},
"models.LabelTaskBulk": {
"type": "object",
"properties": {
"labels": {
"description": "All labels you want to update at once. Works exactly like you would update labels while updateing a list.",
"type": "array",
"items": {
"$ref": "#/definitions/models.Label"
}
}
}
},
"models.List": {
"type": "object",
"properties": {

View File

@ -146,6 +146,15 @@ definitions:
change this value.
type: integer
type: object
models.LabelTaskBulk:
properties:
labels:
description: All labels you want to update at once. Works exactly like you
would update labels while updateing a list.
items:
$ref: '#/definitions/models.Label'
type: array
type: object
models.List:
properties:
created:
@ -923,9 +932,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
@ -934,12 +943,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
@ -950,9 +959,9 @@ paths:
type: object
security:
- JWTKeyAuth: []
summary: Gets one team
summary: Gets one list
tags:
- team
- list
post:
consumes:
- application/json
@ -2183,7 +2192,9 @@ paths:
post:
consumes:
- application/json
description: Updates a task. This includes marking it as done.
description: Updates a task. This includes marking it as done. Assignees you
pass will be updated, see their individual endpoints for more details on how
this is done. To update labels, see the description of the endpoint.
parameters:
- description: Task ID
in: path
@ -2442,12 +2453,13 @@ paths:
tags:
- assignees
/tasks/{taskID}/assignees/bulk:
put:
post:
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.
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.
parameters:
- description: The array of assignees
in: body
@ -2481,9 +2493,50 @@ paths:
type: object
security:
- JWTKeyAuth: []
summary: Add new assignees to a task
summary: Add multiple new assignees to a task
tags:
- assignees
/tasks/{taskID}/labels/bulk:
post:
consumes:
- application/json
description: Adds multiple new labels to a task.
parameters:
- description: The array of labels
in: body
name: label
required: true
schema:
$ref: '#/definitions/models.LabelTaskBulk'
type: object
- description: Task ID
in: path
name: taskID
required: true
type: integer
produces:
- application/json
responses:
"200":
description: The updated labels object.
schema:
$ref: '#/definitions/models.LabelTaskBulk'
type: object
"400":
description: Invalid label 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 multiple new labels to a task
tags:
- labels
/tasks/all:
get:
consumes:

View File

@ -951,3 +951,31 @@ func (err ErrLabelDoesNotExist) HTTPError() web.HTTPError {
Message: "This label does not exist.",
}
}
// ErrUserHasNoAccessToLabel represents an error where a user does not have the right to see a label
type ErrUserHasNoAccessToLabel struct {
LabelID int64
UserID int64
}
// IsErrUserHasNoAccessToLabel checks if an error is ErrUserHasNoAccessToLabel.
func IsErrUserHasNoAccessToLabel(err error) bool {
_, ok := err.(ErrUserHasNoAccessToLabel)
return ok
}
func (err ErrUserHasNoAccessToLabel) Error() string {
return fmt.Sprintf("The user does not have access to this label [LabelID: %v, UserID: %v]", err.LabelID, err.UserID)
}
// ErrCodeUserHasNoAccessToLabel holds the unique world-error code of this error
const ErrCodeUserHasNoAccessToLabel = 8003
// HTTPError holds the http error description
func (err ErrUserHasNoAccessToLabel) HTTPError() web.HTTPError {
return web.HTTPError{
HTTPCode: http.StatusForbidden,
Code: ErrCodeUserHasNoAccessToLabel,
Message: "You don't have access to this label.",
}
}

View File

@ -48,22 +48,3 @@ type Label struct {
func (Label) TableName() string {
return "labels"
}
// LabelTask represents a relation between a label and a task
type LabelTask struct {
// The unique, numeric id of this label.
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
TaskID int64 `xorm:"int(11) INDEX not null" json:"-" param:"listtask"`
// The label id you want to associate with a task.
LabelID int64 `xorm:"int(11) INDEX not null" json:"label_id" param:"label"`
// A unix timestamp when this task was created. You cannot change this value.
Created int64 `xorm:"created" json:"created"`
web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"`
}
// TableName makes a pretty table name
func (LabelTask) TableName() string {
return "label_task"
}

View File

@ -45,7 +45,12 @@ func (l *Label) ReadAll(search string, a web.Auth, page int) (ls interface{}, er
return nil, err
}
return getLabelsByTaskIDs(search, u, page, taskIDs, true)
return getLabelsByTaskIDs(&LabelByTaskIDsOptions{
Search: search,
User: u,
TaskIDs: taskIDs,
GetUnusedLabels: true,
})
}
// ReadOne gets one label

View File

@ -21,6 +21,25 @@ import (
"github.com/go-xorm/builder"
)
// LabelTask represents a relation between a label and a task
type LabelTask struct {
// The unique, numeric id of this label.
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
TaskID int64 `xorm:"int(11) INDEX not null" json:"-" param:"listtask"`
// The label id you want to associate with a task.
LabelID int64 `xorm:"int(11) INDEX not null" json:"label_id" param:"label"`
// A unix timestamp when this task was created. You cannot change this value.
Created int64 `xorm:"created" json:"created"`
web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"`
}
// TableName makes a pretty table name
func (LabelTask) TableName() string {
return "label_task"
}
// Delete deletes a label on a task
// @Summary Remove a label from a task
// @Description Remove a label from a task. The user needs to have write-access to the list to be able do this.
@ -35,8 +54,8 @@ import (
// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{task}/labels/{label} [delete]
func (l *LabelTask) Delete() (err error) {
_, err = x.Delete(&LabelTask{LabelID: l.LabelID, TaskID: l.TaskID})
func (lt *LabelTask) Delete() (err error) {
_, err = x.Delete(&LabelTask{LabelID: lt.LabelID, TaskID: lt.TaskID})
return err
}
@ -55,19 +74,19 @@ func (l *LabelTask) Delete() (err error) {
// @Failure 404 {object} code.vikunja.io/web.HTTPError "The label does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{task}/labels [put]
func (l *LabelTask) Create(a web.Auth) (err error) {
func (lt *LabelTask) Create(a web.Auth) (err error) {
// Check if the label is already added
exists, err := x.Exist(&LabelTask{LabelID: l.LabelID, TaskID: l.TaskID})
exists, err := x.Exist(&LabelTask{LabelID: lt.LabelID, TaskID: lt.TaskID})
if err != nil {
return err
}
if exists {
return ErrLabelIsAlreadyOnTask{l.LabelID, l.TaskID}
return ErrLabelIsAlreadyOnTask{lt.LabelID, lt.TaskID}
}
// Insert it
_, err = x.Insert(l)
return err
_, err = x.Insert(lt)
return
}
// ReadAll gets all labels on a task
@ -83,38 +102,54 @@ func (l *LabelTask) Create(a web.Auth) (err error) {
// @Success 200 {array} models.Label "The labels"
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{task}/labels [get]
func (l *LabelTask) ReadAll(search string, a web.Auth, page int) (labels interface{}, err error) {
func (lt *LabelTask) ReadAll(search string, a web.Auth, page int) (labels interface{}, err error) {
u, err := getUserWithError(a)
if err != nil {
return nil, err
}
// Check if the user has the right to see the task
task, err := GetListTaskByID(l.TaskID)
task, err := GetListTaskByID(lt.TaskID)
if err != nil {
return nil, err
}
if !task.CanRead(a) {
return nil, ErrNoRightToSeeTask{l.TaskID, u.ID}
return nil, ErrNoRightToSeeTask{lt.TaskID, u.ID}
}
return getLabelsByTaskIDs(search, u, page, []int64{l.TaskID}, false)
return getLabelsByTaskIDs(&LabelByTaskIDsOptions{
User: u,
Search: search,
Page: page,
TaskIDs: []int64{lt.TaskID},
})
}
// Helper struct, contains the label + its task ID
type labelWithTaskID struct {
TaskID int64
Label `xorm:"extends"`
}
// LabelByTaskIDsOptions is a struct to not clutter the function with too many optional parameters.
type LabelByTaskIDsOptions struct {
User *User
Search string
Page int
TaskIDs []int64
GetUnusedLabels bool
}
// Helper function to get all labels for a set of tasks
// Used when getting all labels for one task as well when getting all lables
func getLabelsByTaskIDs(search string, u *User, page int, taskIDs []int64, getUnusedLabels bool) (ls []*labelWithTaskID, err error) {
// Incl unused labels
func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err error) {
// Include unused labels. Needed to be able to show a list of all unused labels a user
// has access to.
var uidOrNil interface{}
var requestOrNil interface{}
if getUnusedLabels {
uidOrNil = u.ID
if opts.GetUnusedLabels {
uidOrNil = opts.User.ID
requestOrNil = "label_task.label_id != null OR labels.created_by_id = ?"
}
@ -124,10 +159,10 @@ func getLabelsByTaskIDs(search string, u *User, page int, taskIDs []int64, getUn
Select("labels.*, label_task.task_id").
Join("LEFT", "label_task", "label_task.label_id = labels.id").
Where(requestOrNil, uidOrNil).
Or(builder.In("label_task.task_id", taskIDs)).
And("labels.title LIKE ?", "%"+search+"%").
Or(builder.In("label_task.task_id", opts.TaskIDs)).
And("labels.title LIKE ?", "%"+opts.Search+"%").
GroupBy("labels.id").
Limit(getLimitFromPageIndex(page)).
Limit(getLimitFromPageIndex(opts.Page)).
Find(&labels)
if err != nil {
return nil, err
@ -151,3 +186,120 @@ func getLabelsByTaskIDs(search string, u *User, page int, taskIDs []int64, getUn
return labels, err
}
// Create or update a bunch of task labels
func (t *ListTask) updateTaskLabels(creator web.Auth, labels []*Label) (err error) {
// If we don't have any new labels, delete everything right away. Saves us some hassle.
if len(labels) == 0 && len(t.Labels) > 0 {
_, err = x.Where("task_id = ?", t.ID).
Delete(LabelTask{})
return err
}
// If we didn't change anything (from 0 to zero) don't do anything.
if len(labels) == 0 && len(t.Labels) == 0 {
return nil
}
// Make a hashmap of the new labels for easier comparison
newLabels := make(map[int64]*Label, len(labels))
var allLabelIDs []int64
for _, newLabel := range labels {
newLabels[newLabel.ID] = newLabel
allLabelIDs = append(allLabelIDs, newLabel.ID)
}
// Get old labels to delete
var found bool
var labelsToDelete []int64
oldLabels := make(map[int64]*Label, len(t.Labels))
allLabels := t.Labels
t.Labels = []*Label{} // We re-empty our labels struct here because we want it to be fully empty so we can put in all the actual labels.
for _, oldLabel := range allLabels {
found = false
if newLabels[oldLabel.ID] != nil {
found = true // If a new label is already in the list with old labels
}
// Put all labels which are only on the old list to the trash
if !found {
labelsToDelete = append(labelsToDelete, oldLabel.ID)
} else {
t.Labels = append(t.Labels, oldLabel)
}
// Put it in a list with all old labels, just using the loop here
oldLabels[oldLabel.ID] = oldLabel
}
// Delete all labels not passed
if len(labelsToDelete) > 0 {
_, err = x.In("label_id", labelsToDelete).
And("task_id = ?", t.ID).
Delete(LabelTask{})
if err != nil {
return err
}
}
// Loop through our labels and add them
for _, l := range labels {
// Check if the label is already added on the task and only add it if not
if oldLabels[l.ID] != nil {
// continue outer loop
continue
}
// Add the new label
label, err := getLabelByIDSimple(l.ID)
if err != nil {
return err
}
// Check if the user has the rights to see the label he is about to add
if !label.hasAccessToLabel(creator) {
user, _ := creator.(*User)
return ErrUserHasNoAccessToLabel{LabelID: l.ID, UserID: user.ID}
}
// Insert it
_, err = x.Insert(&LabelTask{LabelID: l.ID, TaskID: t.ID})
if err != nil {
return err
}
t.Labels = append(t.Labels, label)
}
return
}
// LabelTaskBulk is a helper struct to update a bunch of labels at once
type LabelTaskBulk struct {
// All labels you want to update at once. Works exactly like you would update labels while updateing a list.
Labels []*Label `json:"labels"`
TaskID int64 `json:"-" param:"listtask"`
web.CRUDable `json:"-"`
web.Rights `json:"-"`
}
// Create updates a bunch of labels on a task at once
// @Summary Add multiple new labels to a task
// @Description Adds multiple new labels to a task.
// @tags labels
// @Accept json
// @Produce json
// @Security JWTKeyAuth
// @Param label body models.LabelTaskBulk true "The array of labels"
// @Param taskID path int true "Task ID"
// @Success 200 {object} models.LabelTaskBulk "The updated labels object."
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid label object provided."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{taskID}/labels/bulk [post]
func (ltb *LabelTaskBulk) Create(a web.Auth) (err error) {
task, err := GetListTaskByID(ltb.TaskID)
if err != nil {
return
}
return task.updateTaskLabels(a, task.Labels)
}

View File

@ -29,12 +29,12 @@ func (lt *LabelTask) CanCreate(a web.Auth) bool {
return false
}
return label.hasAccessToLabel(a) && lt.canDoLabelTask(a)
return label.hasAccessToLabel(a) && canDoLabelTask(lt.TaskID, a)
}
// CanDelete checks if a user can delete a label from a task
func (lt *LabelTask) CanDelete(a web.Auth) bool {
if !lt.canDoLabelTask(a) {
if !canDoLabelTask(lt.TaskID, a) {
return false
}
@ -48,12 +48,17 @@ func (lt *LabelTask) CanDelete(a web.Auth) bool {
return exists
}
// CanCreate determines if a user can update a labeltask
func (ltb *LabelTaskBulk) CanCreate(a web.Auth) bool {
return canDoLabelTask(ltb.TaskID, a)
}
// Helper function to check if a user can write to a task
// + is able to see the label
// always the same check for either deleting or adding a label to a task
func (lt *LabelTask) canDoLabelTask(a web.Auth) bool {
func canDoLabelTask(taskID int64, a web.Auth) bool {
// A user can add a label to a task if he can write to the task
task, err := getTaskByIDSimple(lt.TaskID)
task, err := getTaskByIDSimple(taskID)
if err != nil {
log.Log.Error("Error occurred during canDoLabelTask for LabelTask: %v", err)
return false

View File

@ -232,7 +232,7 @@ type BulkAssignees struct {
// @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]
// @Router /tasks/{taskID}/assignees/bulk [post]
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 {

View File

@ -111,7 +111,7 @@ func GetTasksByListID(listID int64) (tasks []*ListTask, err error) {
}
// Get all labels for the tasks
labels, err := getLabelsByTaskIDs("", &User{}, -1, taskIDs, false)
labels, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{TaskIDs: taskIDs})
if err != nil {
return
}
@ -196,6 +196,17 @@ func GetListTaskByID(listTaskID int64) (listTask ListTask, err error) {
}
}
// Get task labels
taskLabels, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{
TaskIDs: []int64{listTaskID},
})
if err != nil {
return
}
for _, label := range taskLabels {
listTask.Labels = append(listTask.Labels, &label.Label)
}
return
}

View File

@ -77,7 +77,7 @@ func (t *ListTask) Create(a web.Auth) (err error) {
// Update updates a list task
// @Summary Update a task
// @Description Updates a task. This includes marking it as done.
// @Description Updates a task. This includes marking it as done. Assignees you pass will be updated, see their individual endpoints for more details on how this is done. To update labels, see the description of the endpoint.
// @tags task
// @Accept json
// @Produce json
@ -104,6 +104,23 @@ func (t *ListTask) Update() (err error) {
return err
}
// Update the labels
//
// Maybe FIXME:
// I've disabled this for now, because it requires significant changes in the way we do updates (using the
// Update() function. We need a user object in updateTaskLabels to check if the user has the right to see
// the label it is currently adding. To do this, we'll need to update the webhandler to let it pass the current
// user object (like it's already the case with the create method). However when we change it, that'll break
// a lot of existing code which we'll then need to refactor.
// This is why.
//
//if err := ot.updateTaskLabels(t.Labels); err != nil {
// return err
//}
// set the labels to ot.Labels because our updateTaskLabels function puts the full label objects in it pretty nicely
// We also set this here to prevent it being overwritten later on.
//t.Labels = ot.Labels
// For whatever reason, xorm dont detect if done is updated, so we need to update this every time by hand
// Which is why we merge the actual task struct with the one we got from the
// The user struct overrides values in the actual one.

View File

@ -255,7 +255,7 @@ func RegisterRoutes(e *echo.Echo) {
return &models.BulkAssignees{}
},
}
a.PUT("/tasks/:listtask/assignees/bulk", bulkAssigneeHandler.CreateWeb)
a.POST("/tasks/:listtask/assignees/bulk", bulkAssigneeHandler.CreateWeb)
labelTaskHandler := &handler.WebHandler{
EmptyStruct: func() handler.CObject {
@ -266,6 +266,13 @@ func RegisterRoutes(e *echo.Echo) {
a.DELETE("/tasks/:listtask/labels/:label", labelTaskHandler.DeleteWeb)
a.GET("/tasks/:listtask/labels", labelTaskHandler.ReadAllWeb)
bulkLabelTaskHandler := &handler.WebHandler{
EmptyStruct: func() handler.CObject {
return &models.LabelTaskBulk{}
},
}
a.POST("/tasks/:listtask/labels/bulk", bulkLabelTaskHandler.CreateWeb)
labelHandler := &handler.WebHandler{
EmptyStruct: func() handler.CObject {
return &models.Label{}