diff --git a/pkg/models/label.go b/pkg/models/label.go index c6fbaf7421..388a039c06 100644 --- a/pkg/models/label.go +++ b/pkg/models/label.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/web" + "time" ) // Label represents a label @@ -48,3 +49,158 @@ type Label struct { func (Label) TableName() string { return "labels" } + +// Create creates a new label +// @Summary Create a label +// @Description Creates a new label. +// @tags labels +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param label body models.Label true "The label object" +// @Success 200 {object} models.Label "The created label object." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid label object provided." +// @Failure 500 {object} models.Message "Internal error" +// @Router /labels [put] +func (l *Label) Create(a web.Auth) (err error) { + u, err := getUserWithError(a) + if err != nil { + return + } + + l.CreatedBy = u + l.CreatedByID = u.ID + + _, err = x.Insert(l) + return +} + +// Update updates a label +// @Summary Update a label +// @Description Update an existing label. The user needs to be the creator of the label to be able to do this. +// @tags labels +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "Label ID" +// @Param label body models.Label true "The label object" +// @Success 200 {object} models.Label "The created label object." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid label object provided." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to update the label." +// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found." +// @Failure 500 {object} models.Message "Internal error" +// @Router /labels/{id} [put] +func (l *Label) Update() (err error) { + _, err = x.ID(l.ID).Update(l) + if err != nil { + return + } + + err = l.ReadOne() + return +} + +// Delete deletes a label +// @Summary Delete a label +// @Description Delete an existing label. The user needs to be the creator of the label to be able to do this. +// @tags labels +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "Label ID" +// @Success 200 {object} models.Label "The label was successfully deleted." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to delete the label." +// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found." +// @Failure 500 {object} models.Message "Internal error" +// @Router /labels/{id} [delete] +func (l *Label) Delete() (err error) { + _, err = x.ID(l.ID).Delete(&Label{}) + return err +} + +// ReadAll gets all labels a user can use +// @Summary Get all labels a user has access to +// @Description Returns all labels which are either created by the user or associated with a task the user has at least read-access to. +// @tags labels +// @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 labels by label text." +// @Security JWTKeyAuth +// @Success 200 {array} models.Label "The labels" +// @Failure 500 {object} models.Message "Internal error" +// @Router /labels [get] +func (l *Label) ReadAll(search string, a web.Auth, page int) (ls interface{}, err error) { + + u := &User{ID: a.GetID()} + + // Get all tasks + taskIDs, err := getUserTaskIDs(u) + if err != nil { + return nil, err + } + + return getLabelsByTaskIDs(&LabelByTaskIDsOptions{ + Search: search, + User: u, + TaskIDs: taskIDs, + GetUnusedLabels: true, + }) +} + +// ReadOne gets one label +// @Summary Gets one label +// @Description Returns one label by its ID. +// @tags labels +// @Accept json +// @Produce json +// @Param id path int true "Label ID" +// @Security JWTKeyAuth +// @Success 200 {object} models.Label "The label" +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the label" +// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found" +// @Failure 500 {object} models.Message "Internal error" +// @Router /labels/{id} [get] +func (l *Label) ReadOne() (err error) { + label, err := getLabelByIDSimple(l.ID) + if err != nil { + return err + } + *l = *label + + user, err := GetUserByID(l.CreatedByID) + if err != nil { + return err + } + + l.CreatedBy = &user + return +} + +func getLabelByIDSimple(labelID int64) (*Label, error) { + label := Label{} + exists, err := x.ID(labelID).Get(&label) + if err != nil { + return &label, err + } + + if !exists { + return &Label{}, ErrLabelDoesNotExist{labelID} + } + return &label, err +} + +// Helper method to get all task ids a user has +func getUserTaskIDs(u *User) (taskIDs []int64, err error) { + tasks, err := GetTasksByUser("", u, -1, SortTasksByUnsorted, time.Unix(0, 0), time.Unix(0, 0)) + if err != nil { + return nil, err + } + + // make a slice of task ids + for _, t := range tasks { + taskIDs = append(taskIDs, t.ID) + } + + return +} diff --git a/pkg/models/label_create_update.go b/pkg/models/label_create_update.go deleted file mode 100644 index 1f8943eb4d..0000000000 --- a/pkg/models/label_create_update.go +++ /dev/null @@ -1,87 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" - -// Create creates a new label -// @Summary Create a label -// @Description Creates a new label. -// @tags labels -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param label body models.Label true "The label object" -// @Success 200 {object} models.Label "The created label object." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid label object provided." -// @Failure 500 {object} models.Message "Internal error" -// @Router /labels [put] -func (l *Label) Create(a web.Auth) (err error) { - u, err := getUserWithError(a) - if err != nil { - return - } - - l.CreatedBy = u - l.CreatedByID = u.ID - - _, err = x.Insert(l) - return -} - -// Update updates a label -// @Summary Update a label -// @Description Update an existing label. The user needs to be the creator of the label to be able to do this. -// @tags labels -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "Label ID" -// @Param label body models.Label true "The label object" -// @Success 200 {object} models.Label "The created label object." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid label object provided." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to update the label." -// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found." -// @Failure 500 {object} models.Message "Internal error" -// @Router /labels/{id} [put] -func (l *Label) Update() (err error) { - _, err = x.ID(l.ID).Update(l) - if err != nil { - return - } - - err = l.ReadOne() - return -} - -// Delete deletes a label -// @Summary Delete a label -// @Description Delete an existing label. The user needs to be the creator of the label to be able to do this. -// @tags labels -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "Label ID" -// @Success 200 {object} models.Label "The label was successfully deleted." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to delete the label." -// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found." -// @Failure 500 {object} models.Message "Internal error" -// @Router /labels/{id} [delete] -func (l *Label) Delete() (err error) { - _, err = x.ID(l.ID).Delete(&Label{}) - return err -} diff --git a/pkg/models/label_read.go b/pkg/models/label_read.go deleted file mode 100644 index 2eea6d9282..0000000000 --- a/pkg/models/label_read.go +++ /dev/null @@ -1,109 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" - "time" -) - -// ReadAll gets all labels a user can use -// @Summary Get all labels a user has access to -// @Description Returns all labels which are either created by the user or associated with a task the user has at least read-access to. -// @tags labels -// @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 labels by label text." -// @Security JWTKeyAuth -// @Success 200 {array} models.Label "The labels" -// @Failure 500 {object} models.Message "Internal error" -// @Router /labels [get] -func (l *Label) ReadAll(search string, a web.Auth, page int) (ls interface{}, err error) { - - u := &User{ID: a.GetID()} - - // Get all tasks - taskIDs, err := getUserTaskIDs(u) - if err != nil { - return nil, err - } - - return getLabelsByTaskIDs(&LabelByTaskIDsOptions{ - Search: search, - User: u, - TaskIDs: taskIDs, - GetUnusedLabels: true, - }) -} - -// ReadOne gets one label -// @Summary Gets one label -// @Description Returns one label by its ID. -// @tags labels -// @Accept json -// @Produce json -// @Param id path int true "Label ID" -// @Security JWTKeyAuth -// @Success 200 {object} models.Label "The label" -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the label" -// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found" -// @Failure 500 {object} models.Message "Internal error" -// @Router /labels/{id} [get] -func (l *Label) ReadOne() (err error) { - label, err := getLabelByIDSimple(l.ID) - if err != nil { - return err - } - *l = *label - - user, err := GetUserByID(l.CreatedByID) - if err != nil { - return err - } - - l.CreatedBy = &user - return -} - -func getLabelByIDSimple(labelID int64) (*Label, error) { - label := Label{} - exists, err := x.ID(labelID).Get(&label) - if err != nil { - return &label, err - } - - if !exists { - return &Label{}, ErrLabelDoesNotExist{labelID} - } - return &label, err -} - -// Helper method to get all task ids a user has -func getUserTaskIDs(u *User) (taskIDs []int64, err error) { - tasks, err := GetTasksByUser("", u, -1, SortTasksByUnsorted, time.Unix(0, 0), time.Unix(0, 0)) - if err != nil { - return nil, err - } - - // make a slice of task ids - for _, t := range tasks { - taskIDs = append(taskIDs, t.ID) - } - - return -} diff --git a/pkg/models/list.go b/pkg/models/list.go index f9bc4ba61f..b5e02a537d 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/metrics" "code.vikunja.io/web" ) @@ -238,3 +239,121 @@ func AddListDetails(lists []*List) (err error) { return } + +// CreateOrUpdateList updates a list or creates it if it doesn't exist +func CreateOrUpdateList(list *List) (err error) { + + // Check if the namespace exists + if list.NamespaceID != 0 { + _, err = GetNamespaceByID(list.NamespaceID) + if err != nil { + return err + } + } + + if list.ID == 0 { + _, err = x.Insert(list) + metrics.UpdateCount(1, metrics.ListCountKey) + } else { + _, err = x.ID(list.ID).Update(list) + } + + if err != nil { + return + } + + err = list.GetSimpleByID() + if err != nil { + return + } + + err = list.ReadOne() + return + +} + +// Update implements the update method of CRUDable +// @Summary Updates a list +// @Description Updates a list. This does not include adding a task (see below). +// @tags list +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "List ID" +// @Param list body models.List true "The list with updated values you want to update." +// @Success 200 {object} models.List "The updated list." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid list object provided." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{id} [post] +func (l *List) Update() (err error) { + return CreateOrUpdateList(l) +} + +func updateListLastUpdated(list *List) error { + _, err := x.ID(list.ID).Cols("updated").Update(list) + return err +} + +func updateListByTaskID(taskID int64) (err error) { + // need to get the task to update the list last updated timestamp + task, err := GetTaskByIDSimple(taskID) + if err != nil { + return err + } + + return updateListLastUpdated(&List{ID: task.ListID}) +} + +// Create implements the create method of CRUDable +// @Summary Creates a new list +// @Description Creates a new list in a given namespace. The user needs write-access to the namespace. +// @tags list +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param namespaceID path int true "Namespace ID" +// @Param list body models.List true "The list you want to create." +// @Success 200 {object} models.List "The created list." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid list object provided." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces/{namespaceID}/lists [put] +func (l *List) Create(a web.Auth) (err error) { + doer, err := getUserWithError(a) + if err != nil { + return err + } + + l.OwnerID = doer.ID + l.Owner = *doer + l.ID = 0 // Otherwise only the first time a new list would be created + + return CreateOrUpdateList(l) +} + +// Delete implements the delete method of CRUDable +// @Summary Deletes a list +// @Description Delets a list +// @tags list +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "List ID" +// @Success 200 {object} models.Message "The list was successfully deleted." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid list object provided." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{id} [delete] +func (l *List) Delete() (err error) { + + // Delete the list + _, err = x.ID(l.ID).Delete(&List{}) + if err != nil { + return + } + metrics.UpdateCount(-1, metrics.ListCountKey) + + // Delete all todotasks on that list + _, err = x.Where("list_id = ?", l.ID).Delete(&ListTask{}) + return +} diff --git a/pkg/models/list_create_update.go b/pkg/models/list_create_update.go deleted file mode 100644 index f06867c33a..0000000000 --- a/pkg/models/list_create_update.go +++ /dev/null @@ -1,114 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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/metrics" - "code.vikunja.io/web" -) - -// CreateOrUpdateList updates a list or creates it if it doesn't exist -func CreateOrUpdateList(list *List) (err error) { - - // Check if the namespace exists - if list.NamespaceID != 0 { - _, err = GetNamespaceByID(list.NamespaceID) - if err != nil { - return err - } - } - - if list.ID == 0 { - _, err = x.Insert(list) - metrics.UpdateCount(1, metrics.ListCountKey) - } else { - _, err = x.ID(list.ID).Update(list) - } - - if err != nil { - return - } - - err = list.GetSimpleByID() - if err != nil { - return - } - - err = list.ReadOne() - return - -} - -// Update implements the update method of CRUDable -// @Summary Updates a list -// @Description Updates a list. This does not include adding a task (see below). -// @tags list -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "List ID" -// @Param list body models.List true "The list with updated values you want to update." -// @Success 200 {object} models.List "The updated list." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid list object provided." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{id} [post] -func (l *List) Update() (err error) { - return CreateOrUpdateList(l) -} - -func updateListLastUpdated(list *List) error { - _, err := x.ID(list.ID).Cols("updated").Update(list) - return err -} - -func updateListByTaskID(taskID int64) (err error) { - // need to get the task to update the list last updated timestamp - task, err := GetTaskByIDSimple(taskID) - if err != nil { - return err - } - - return updateListLastUpdated(&List{ID: task.ListID}) -} - -// Create implements the create method of CRUDable -// @Summary Creates a new list -// @Description Creates a new list in a given namespace. The user needs write-access to the namespace. -// @tags list -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param namespaceID path int true "Namespace ID" -// @Param list body models.List true "The list you want to create." -// @Success 200 {object} models.List "The created list." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid list object provided." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces/{namespaceID}/lists [put] -func (l *List) Create(a web.Auth) (err error) { - doer, err := getUserWithError(a) - if err != nil { - return err - } - - l.OwnerID = doer.ID - l.Owner = *doer - l.ID = 0 // Otherwise only the first time a new list would be created - - return CreateOrUpdateList(l) -} diff --git a/pkg/models/list_delete.go b/pkg/models/list_delete.go deleted file mode 100644 index 69538af71a..0000000000 --- a/pkg/models/list_delete.go +++ /dev/null @@ -1,48 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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/metrics" - _ "code.vikunja.io/web" // For swaggerdocs generation -) - -// Delete implements the delete method of CRUDable -// @Summary Deletes a list -// @Description Delets a list -// @tags list -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "List ID" -// @Success 200 {object} models.Message "The list was successfully deleted." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid list object provided." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{id} [delete] -func (l *List) Delete() (err error) { - - // Delete the list - _, err = x.ID(l.ID).Delete(&List{}) - if err != nil { - return - } - metrics.UpdateCount(-1, metrics.ListCountKey) - - // Delete all todotasks on that list - _, err = x.Where("list_id = ?", l.ID).Delete(&ListTask{}) - return -} diff --git a/pkg/models/list_task_readall.go b/pkg/models/list_task_readall.go deleted file mode 100644 index 01b25729ba..0000000000 --- a/pkg/models/list_task_readall.go +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2018 the Vikunja Authors. All rights reserved. - * Use of this source code is governed by a LPGLv3-style - * license that can be found in the LICENSE file. - */ - -package models - -import ( - "code.vikunja.io/web" - "sort" - "time" -) - -// SortBy declares constants to sort -type SortBy int - -// These are possible sort options -const ( - SortTasksByUnsorted SortBy = -1 - SortTasksByDueDateAsc = iota - SortTasksByDueDateDesc - SortTasksByPriorityAsc - SortTasksByPriorityDesc -) - -// ReadAll gets all tasks for a user -// @Summary Get tasks -// @Description Returns all tasks on any list the user has access to. -// @tags task -// @Accept json -// @Produce json -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." -// @Param s query string false "Search tasks by task text." -// @Param sort query string false "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc." -// @Param startdate query int false "The start date parameter to filter by. Expects a unix timestamp. If no end date, but a start date is specified, the end date is set to the current time." -// @Param enddate query int false "The end date parameter to filter by. Expects a unix timestamp. If no start date, but an end date is specified, the start date is set to the current time." -// @Security JWTKeyAuth -// @Success 200 {array} models.ListTask "The tasks" -// @Failure 500 {object} models.Message "Internal error" -// @Router /tasks/all [get] -func (t *ListTask) ReadAll(search string, a web.Auth, page int) (interface{}, error) { - var sortby SortBy - switch t.Sorting { - case "priority": - sortby = SortTasksByPriorityDesc - case "prioritydesc": - sortby = SortTasksByPriorityDesc - case "priorityasc": - sortby = SortTasksByPriorityAsc - case "duedate": - sortby = SortTasksByDueDateDesc - case "duedatedesc": - sortby = SortTasksByDueDateDesc - case "duedateasc": - sortby = SortTasksByDueDateAsc - default: - sortby = SortTasksByUnsorted - } - - return GetTasksByUser(search, &User{ID: a.GetID()}, page, sortby, time.Unix(t.StartDateSortUnix, 0), time.Unix(t.EndDateSortUnix, 0)) -} - -//GetTasksByUser returns all tasks for a user -func GetTasksByUser(search string, u *User, page int, sortby SortBy, startDate time.Time, endDate time.Time) ([]*ListTask, error) { - // Get all lists - lists, err := getRawListsForUser("", u, page) - if err != nil { - return nil, err - } - - // Get all list IDs and get the tasks - var listIDs []int64 - for _, l := range lists { - listIDs = append(listIDs, l.ID) - } - - var orderby string - switch sortby { - case SortTasksByPriorityDesc: - orderby = "priority desc" - case SortTasksByPriorityAsc: - orderby = "priority asc" - case SortTasksByDueDateDesc: - orderby = "due_date_unix desc" - case SortTasksByDueDateAsc: - orderby = "due_date_unix asc" - } - - taskMap := make(map[int64]*ListTask) - - // Then return all tasks for that lists - if startDate.Unix() != 0 || endDate.Unix() != 0 { - - startDateUnix := time.Now().Unix() - if startDate.Unix() != 0 { - startDateUnix = startDate.Unix() - } - - endDateUnix := time.Now().Unix() - if endDate.Unix() != 0 { - endDateUnix = endDate.Unix() - } - - if err := x.In("list_id", listIDs). - Where("text LIKE ?", "%"+search+"%"). - And("((due_date_unix BETWEEN ? AND ?) OR "+ - "(start_date_unix BETWEEN ? and ?) OR "+ - "(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix). - And("(parent_task_id = 0 OR parent_task_id IS NULL)"). - OrderBy(orderby). - Find(&taskMap); err != nil { - return nil, err - } - } else { - if err := x.In("list_id", listIDs). - Where("text LIKE ?", "%"+search+"%"). - And("(parent_task_id = 0 OR parent_task_id IS NULL)"). - OrderBy(orderby). - Find(&taskMap); err != nil { - return nil, err - } - } - - tasks, err := addMoreInfoToTasks(taskMap) - if err != nil { - return nil, err - } - // Because the list is sorted by id which we don't want (since we're dealing with maps) - // we have to manually sort the tasks again here. - sortTasks(tasks, sortby) - - return tasks, err -} - -func sortTasks(tasks []*ListTask, by SortBy) { - switch by { - case SortTasksByPriorityDesc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority > tasks[j].Priority - }) - case SortTasksByPriorityAsc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority < tasks[j].Priority - }) - case SortTasksByDueDateDesc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix > tasks[j].DueDateUnix - }) - case SortTasksByDueDateAsc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix < tasks[j].DueDateUnix - }) - } -} diff --git a/pkg/models/list_tasks.go b/pkg/models/list_tasks.go index 85bc6c1d54..423ad25268 100644 --- a/pkg/models/list_tasks.go +++ b/pkg/models/list_tasks.go @@ -17,8 +17,12 @@ package models import ( + "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/utils" "code.vikunja.io/web" + "github.com/imdario/mergo" "sort" + "time" ) // ListTask represents an task in a todolist @@ -97,6 +101,148 @@ func (TaskReminder) TableName() string { return "task_reminders" } +// SortBy declares constants to sort +type SortBy int + +// These are possible sort options +const ( + SortTasksByUnsorted SortBy = -1 + SortTasksByDueDateAsc = iota + SortTasksByDueDateDesc + SortTasksByPriorityAsc + SortTasksByPriorityDesc +) + +// ReadAll gets all tasks for a user +// @Summary Get tasks +// @Description Returns all tasks on any list the user has access to. +// @tags task +// @Accept json +// @Produce json +// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param s query string false "Search tasks by task text." +// @Param sort query string false "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc." +// @Param startdate query int false "The start date parameter to filter by. Expects a unix timestamp. If no end date, but a start date is specified, the end date is set to the current time." +// @Param enddate query int false "The end date parameter to filter by. Expects a unix timestamp. If no start date, but an end date is specified, the start date is set to the current time." +// @Security JWTKeyAuth +// @Success 200 {array} models.ListTask "The tasks" +// @Failure 500 {object} models.Message "Internal error" +// @Router /tasks/all [get] +func (t *ListTask) ReadAll(search string, a web.Auth, page int) (interface{}, error) { + var sortby SortBy + switch t.Sorting { + case "priority": + sortby = SortTasksByPriorityDesc + case "prioritydesc": + sortby = SortTasksByPriorityDesc + case "priorityasc": + sortby = SortTasksByPriorityAsc + case "duedate": + sortby = SortTasksByDueDateDesc + case "duedatedesc": + sortby = SortTasksByDueDateDesc + case "duedateasc": + sortby = SortTasksByDueDateAsc + default: + sortby = SortTasksByUnsorted + } + + return GetTasksByUser(search, &User{ID: a.GetID()}, page, sortby, time.Unix(t.StartDateSortUnix, 0), time.Unix(t.EndDateSortUnix, 0)) +} + +//GetTasksByUser returns all tasks for a user +func GetTasksByUser(search string, u *User, page int, sortby SortBy, startDate time.Time, endDate time.Time) ([]*ListTask, error) { + // Get all lists + lists, err := getRawListsForUser("", u, page) + if err != nil { + return nil, err + } + + // Get all list IDs and get the tasks + var listIDs []int64 + for _, l := range lists { + listIDs = append(listIDs, l.ID) + } + + var orderby string + switch sortby { + case SortTasksByPriorityDesc: + orderby = "priority desc" + case SortTasksByPriorityAsc: + orderby = "priority asc" + case SortTasksByDueDateDesc: + orderby = "due_date_unix desc" + case SortTasksByDueDateAsc: + orderby = "due_date_unix asc" + } + + taskMap := make(map[int64]*ListTask) + + // Then return all tasks for that lists + if startDate.Unix() != 0 || endDate.Unix() != 0 { + + startDateUnix := time.Now().Unix() + if startDate.Unix() != 0 { + startDateUnix = startDate.Unix() + } + + endDateUnix := time.Now().Unix() + if endDate.Unix() != 0 { + endDateUnix = endDate.Unix() + } + + if err := x.In("list_id", listIDs). + Where("text LIKE ?", "%"+search+"%"). + And("((due_date_unix BETWEEN ? AND ?) OR "+ + "(start_date_unix BETWEEN ? and ?) OR "+ + "(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix). + And("(parent_task_id = 0 OR parent_task_id IS NULL)"). + OrderBy(orderby). + Find(&taskMap); err != nil { + return nil, err + } + } else { + if err := x.In("list_id", listIDs). + Where("text LIKE ?", "%"+search+"%"). + And("(parent_task_id = 0 OR parent_task_id IS NULL)"). + OrderBy(orderby). + Find(&taskMap); err != nil { + return nil, err + } + } + + tasks, err := addMoreInfoToTasks(taskMap) + if err != nil { + return nil, err + } + // Because the list is sorted by id which we don't want (since we're dealing with maps) + // we have to manually sort the tasks again here. + sortTasks(tasks, sortby) + + return tasks, err +} + +func sortTasks(tasks []*ListTask, by SortBy) { + switch by { + case SortTasksByPriorityDesc: + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Priority > tasks[j].Priority + }) + case SortTasksByPriorityAsc: + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Priority < tasks[j].Priority + }) + case SortTasksByDueDateDesc: + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DueDateUnix > tasks[j].DueDateUnix + }) + case SortTasksByDueDateAsc: + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DueDateUnix < tasks[j].DueDateUnix + }) + } +} + // 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 @@ -292,3 +438,324 @@ func addMoreInfoToTasks(taskMap map[int64]*ListTask) (tasks []*ListTask, err err return } + +// Create is the implementation to create a list task +// @Summary Create a task +// @Description Inserts a task into a list. +// @tags task +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "List ID" +// @Param task body models.ListTask true "The task object" +// @Success 200 {object} models.ListTask "The created task object." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid task object provided." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{id} [put] +func (t *ListTask) Create(a web.Auth) (err error) { + + t.ID = 0 + + // Check if we have at least a text + if t.Text == "" { + return ErrListTaskCannotBeEmpty{} + } + + // Check if the list exists + l := &List{ID: t.ListID} + if err = l.GetSimpleByID(); err != nil { + return + } + + u, err := GetUserByID(a.GetID()) + if err != nil { + return err + } + + // Generate a uuid if we don't already have one + if t.UID == "" { + t.UID = utils.MakeRandomString(40) + } + + t.CreatedByID = u.ID + t.CreatedBy = u + if _, err = x.Insert(t); err != nil { + return err + } + + // Update the assignees + if err := t.updateTaskAssignees(t.Assignees); err != nil { + return err + } + + // Update the reminders + if err := t.updateReminders(t.RemindersUnix); err != nil { + return err + } + + metrics.UpdateCount(1, metrics.TaskCountKey) + + err = updateListLastUpdated(&List{ID: t.ListID}) + return +} + +// Update updates a list task +// @Summary Update a task +// @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 +// @Security JWTKeyAuth +// @Param id path int true "Task ID" +// @Param task body models.ListTask true "The task object" +// @Success 200 {object} models.ListTask "The updated task object." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid task object provided." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the task (aka its list)" +// @Failure 500 {object} models.Message "Internal error" +// @Router /tasks/{id} [post] +func (t *ListTask) Update() (err error) { + // Check if the task exists + ot, err := GetTaskByID(t.ID) + if err != nil { + return + } + + // Parent task cannot be the same as the current task + if t.ID == t.ParentTaskID { + return ErrParentTaskCannotBeTheSame{TaskID: t.ID} + } + + // When a repeating task is marked as done, we update all deadlines and reminders and set it as undone + updateDone(&ot, t) + + // Update the assignees + if err := ot.updateTaskAssignees(t.Assignees); err != nil { + return err + } + + // Update the reminders + if err := ot.updateReminders(t.RemindersUnix); err != nil { + 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. + if err := mergo.Merge(&ot, t, mergo.WithOverride); err != nil { + return err + } + + ////// + // Mergo does ignore nil values. Because of that, we need to check all parameters and set the updated to + // nil/their nil value in the struct which is inserted. + //// + // Done + if !t.Done { + ot.Done = false + } + // Priority + if t.Priority == 0 { + ot.Priority = 0 + } + // Description + if t.Description == "" { + ot.Description = "" + } + // Due date + if t.DueDateUnix == 0 { + ot.DueDateUnix = 0 + } + // Repeat after + if t.RepeatAfter == 0 { + ot.RepeatAfter = 0 + } + // Parent task + if t.ParentTaskID == 0 { + ot.ParentTaskID = 0 + } + // Start date + if t.StartDateUnix == 0 { + ot.StartDateUnix = 0 + } + // End date + if t.EndDateUnix == 0 { + ot.EndDateUnix = 0 + } + // Color + if t.HexColor == "" { + ot.HexColor = "" + } + + _, err = x.ID(t.ID). + Cols("text", + "description", + "done", + "due_date_unix", + "repeat_after", + "parent_task_id", + "priority", + "start_date_unix", + "end_date_unix", + "hex_color", + "done_at_unix"). + Update(ot) + *t = ot + if err != nil { + return err + } + + err = updateListLastUpdated(&List{ID: t.ListID}) + return +} + +// This helper function updates the reminders and doneAtUnix of the *old* task (since that's the one we're inserting +// with updated values into the db) +func updateDone(oldTask *ListTask, newTask *ListTask) { + if !oldTask.Done && newTask.Done && oldTask.RepeatAfter > 0 { + oldTask.DueDateUnix = oldTask.DueDateUnix + oldTask.RepeatAfter // assuming we'll save the old task (merged) + + for in, r := range oldTask.RemindersUnix { + oldTask.RemindersUnix[in] = r + oldTask.RepeatAfter + } + + newTask.Done = false + } + + // Update the "done at" timestamp + if !oldTask.Done && newTask.Done { + oldTask.DoneAtUnix = time.Now().Unix() + } + // When unmarking a task as done, reset the timestamp + if oldTask.Done && !newTask.Done { + oldTask.DoneAtUnix = 0 + } +} + +// Creates or deletes all necessary remindes without unneded db operations. +// The parameter is a slice with unix dates which holds the new reminders. +func (t *ListTask) updateReminders(reminders []int64) (err error) { + + // If we're removing everything, delete all reminders right away + if len(reminders) == 0 && len(t.RemindersUnix) > 0 { + _, err = x.Where("task_id = ?", t.ID). + Delete(TaskReminder{}) + t.RemindersUnix = nil + return err + } + + // If we didn't change anything (from 0 to zero) don't do anything. + if len(reminders) == 0 && len(t.RemindersUnix) == 0 { + return nil + } + + // Make a hashmap of the new reminders for easier comparison + newReminders := make(map[int64]*TaskReminder, len(reminders)) + for _, newReminder := range reminders { + newReminders[newReminder] = &TaskReminder{ReminderUnix: newReminder} + } + + // Get old reminders to delete + var found bool + var remindersToDelete []int64 + oldReminders := make(map[int64]*TaskReminder, len(t.RemindersUnix)) + for _, oldReminder := range t.RemindersUnix { + found = false + // If a new reminder is already in the list with old reminders + if newReminders[oldReminder] != nil { + found = true + } + + // Put all reminders which are only on the old list to the trash + if !found { + remindersToDelete = append(remindersToDelete, oldReminder) + } + + oldReminders[oldReminder] = &TaskReminder{ReminderUnix: oldReminder} + } + + // Delete all reminders not passed + if len(remindersToDelete) > 0 { + _, err = x.In("reminder_unix", remindersToDelete). + And("task_id = ?", t.ID). + Delete(ListTaskAssginee{}) + if err != nil { + return err + } + } + + // Loop through our users and add them + for _, r := range reminders { + // Check if the reminder already exists and only inserts it if not + if oldReminders[r] != nil { + // continue outer loop + continue + } + + // Add the new reminder + _, err = x.Insert(TaskReminder{TaskID: t.ID, ReminderUnix: r}) + if err != nil { + return err + } + } + + t.RemindersUnix = reminders + if len(reminders) == 0 { + t.RemindersUnix = nil + } + + err = updateListLastUpdated(&List{ID: t.ListID}) + return +} + +// Delete implements the delete method for listTask +// @Summary Delete a task +// @Description Deletes a task from a list. This does not mean "mark it done". +// @tags task +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "Task ID" +// @Success 200 {object} models.Message "The created task object." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid task ID provided." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" +// @Failure 500 {object} models.Message "Internal error" +// @Router /tasks/{id} [delete] +func (t *ListTask) Delete() (err error) { + + // Check if it exists + _, err = GetTaskByID(t.ID) + if err != nil { + return + } + + if _, err = x.ID(t.ID).Delete(ListTask{}); err != nil { + return err + } + + // Delete assignees + if _, err = x.Where("task_id = ?", t.ID).Delete(ListTaskAssginee{}); err != nil { + return err + } + + metrics.UpdateCount(-1, metrics.TaskCountKey) + + err = updateListLastUpdated(&List{ID: t.ListID}) + return +} diff --git a/pkg/models/list_tasks_create_update.go b/pkg/models/list_tasks_create_update.go deleted file mode 100644 index 0f3595e5b3..0000000000 --- a/pkg/models/list_tasks_create_update.go +++ /dev/null @@ -1,311 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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/metrics" - "code.vikunja.io/api/pkg/utils" - "code.vikunja.io/web" - "github.com/imdario/mergo" - "time" -) - -// Create is the implementation to create a list task -// @Summary Create a task -// @Description Inserts a task into a list. -// @tags task -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "List ID" -// @Param task body models.ListTask true "The task object" -// @Success 200 {object} models.ListTask "The created task object." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid task object provided." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{id} [put] -func (t *ListTask) Create(a web.Auth) (err error) { - - t.ID = 0 - - // Check if we have at least a text - if t.Text == "" { - return ErrListTaskCannotBeEmpty{} - } - - // Check if the list exists - l := &List{ID: t.ListID} - if err = l.GetSimpleByID(); err != nil { - return - } - - u, err := GetUserByID(a.GetID()) - if err != nil { - return err - } - - // Generate a uuid if we don't already have one - if t.UID == "" { - t.UID = utils.MakeRandomString(40) - } - - t.CreatedByID = u.ID - t.CreatedBy = u - if _, err = x.Insert(t); err != nil { - return err - } - - // Update the assignees - if err := t.updateTaskAssignees(t.Assignees); err != nil { - return err - } - - // Update the reminders - if err := t.updateReminders(t.RemindersUnix); err != nil { - return err - } - - metrics.UpdateCount(1, metrics.TaskCountKey) - - err = updateListLastUpdated(&List{ID: t.ListID}) - return -} - -// Update updates a list task -// @Summary Update a task -// @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 -// @Security JWTKeyAuth -// @Param id path int true "Task ID" -// @Param task body models.ListTask true "The task object" -// @Success 200 {object} models.ListTask "The updated task object." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid task object provided." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the task (aka its list)" -// @Failure 500 {object} models.Message "Internal error" -// @Router /tasks/{id} [post] -func (t *ListTask) Update() (err error) { - // Check if the task exists - ot, err := GetTaskByID(t.ID) - if err != nil { - return - } - - // Parent task cannot be the same as the current task - if t.ID == t.ParentTaskID { - return ErrParentTaskCannotBeTheSame{TaskID: t.ID} - } - - // When a repeating task is marked as done, we update all deadlines and reminders and set it as undone - updateDone(&ot, t) - - // Update the assignees - if err := ot.updateTaskAssignees(t.Assignees); err != nil { - return err - } - - // Update the reminders - if err := ot.updateReminders(t.RemindersUnix); err != nil { - 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. - if err := mergo.Merge(&ot, t, mergo.WithOverride); err != nil { - return err - } - - ////// - // Mergo does ignore nil values. Because of that, we need to check all parameters and set the updated to - // nil/their nil value in the struct which is inserted. - //// - // Done - if !t.Done { - ot.Done = false - } - // Priority - if t.Priority == 0 { - ot.Priority = 0 - } - // Description - if t.Description == "" { - ot.Description = "" - } - // Due date - if t.DueDateUnix == 0 { - ot.DueDateUnix = 0 - } - // Repeat after - if t.RepeatAfter == 0 { - ot.RepeatAfter = 0 - } - // Parent task - if t.ParentTaskID == 0 { - ot.ParentTaskID = 0 - } - // Start date - if t.StartDateUnix == 0 { - ot.StartDateUnix = 0 - } - // End date - if t.EndDateUnix == 0 { - ot.EndDateUnix = 0 - } - // Color - if t.HexColor == "" { - ot.HexColor = "" - } - - _, err = x.ID(t.ID). - Cols("text", - "description", - "done", - "due_date_unix", - "repeat_after", - "parent_task_id", - "priority", - "start_date_unix", - "end_date_unix", - "hex_color", - "done_at_unix"). - Update(ot) - *t = ot - if err != nil { - return err - } - - err = updateListLastUpdated(&List{ID: t.ListID}) - return -} - -// This helper function updates the reminders and doneAtUnix of the *old* task (since that's the one we're inserting -// with updated values into the db) -func updateDone(oldTask *ListTask, newTask *ListTask) { - if !oldTask.Done && newTask.Done && oldTask.RepeatAfter > 0 { - oldTask.DueDateUnix = oldTask.DueDateUnix + oldTask.RepeatAfter // assuming we'll save the old task (merged) - - for in, r := range oldTask.RemindersUnix { - oldTask.RemindersUnix[in] = r + oldTask.RepeatAfter - } - - newTask.Done = false - } - - // Update the "done at" timestamp - if !oldTask.Done && newTask.Done { - oldTask.DoneAtUnix = time.Now().Unix() - } - // When unmarking a task as done, reset the timestamp - if oldTask.Done && !newTask.Done { - oldTask.DoneAtUnix = 0 - } -} - -// Creates or deletes all necessary remindes without unneded db operations. -// The parameter is a slice with unix dates which holds the new reminders. -func (t *ListTask) updateReminders(reminders []int64) (err error) { - - // If we're removing everything, delete all reminders right away - if len(reminders) == 0 && len(t.RemindersUnix) > 0 { - _, err = x.Where("task_id = ?", t.ID). - Delete(TaskReminder{}) - t.RemindersUnix = nil - return err - } - - // If we didn't change anything (from 0 to zero) don't do anything. - if len(reminders) == 0 && len(t.RemindersUnix) == 0 { - return nil - } - - // Make a hashmap of the new reminders for easier comparison - newReminders := make(map[int64]*TaskReminder, len(reminders)) - for _, newReminder := range reminders { - newReminders[newReminder] = &TaskReminder{ReminderUnix: newReminder} - } - - // Get old reminders to delete - var found bool - var remindersToDelete []int64 - oldReminders := make(map[int64]*TaskReminder, len(t.RemindersUnix)) - for _, oldReminder := range t.RemindersUnix { - found = false - // If a new reminder is already in the list with old reminders - if newReminders[oldReminder] != nil { - found = true - } - - // Put all reminders which are only on the old list to the trash - if !found { - remindersToDelete = append(remindersToDelete, oldReminder) - } - - oldReminders[oldReminder] = &TaskReminder{ReminderUnix: oldReminder} - } - - // Delete all reminders not passed - if len(remindersToDelete) > 0 { - _, err = x.In("reminder_unix", remindersToDelete). - And("task_id = ?", t.ID). - Delete(ListTaskAssginee{}) - if err != nil { - return err - } - } - - // Loop through our users and add them - for _, r := range reminders { - // Check if the reminder already exists and only inserts it if not - if oldReminders[r] != nil { - // continue outer loop - continue - } - - // Add the new reminder - _, err = x.Insert(TaskReminder{TaskID: t.ID, ReminderUnix: r}) - if err != nil { - return err - } - } - - t.RemindersUnix = reminders - if len(reminders) == 0 { - t.RemindersUnix = nil - } - - err = updateListLastUpdated(&List{ID: t.ListID}) - return -} diff --git a/pkg/models/list_tasks_delete.go b/pkg/models/list_tasks_delete.go deleted file mode 100644 index 8ed5279a2c..0000000000 --- a/pkg/models/list_tasks_delete.go +++ /dev/null @@ -1,57 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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/metrics" - _ "code.vikunja.io/web" // For swaggerdocs generation -) - -// Delete implements the delete method for listTask -// @Summary Delete a task -// @Description Deletes a task from a list. This does not mean "mark it done". -// @tags task -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "Task ID" -// @Success 200 {object} models.Message "The created task object." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid task ID provided." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" -// @Failure 500 {object} models.Message "Internal error" -// @Router /tasks/{id} [delete] -func (t *ListTask) Delete() (err error) { - - // Check if it exists - _, err = GetTaskByID(t.ID) - if err != nil { - return - } - - if _, err = x.ID(t.ID).Delete(ListTask{}); err != nil { - return err - } - - // Delete assignees - if _, err = x.Where("task_id = ?", t.ID).Delete(ListTaskAssginee{}); err != nil { - return err - } - - metrics.UpdateCount(-1, metrics.TaskCountKey) - - err = updateListLastUpdated(&List{ID: t.ListID}) - return -} diff --git a/pkg/models/list_users.go b/pkg/models/list_users.go index 37a9b12c20..8e530c37bb 100644 --- a/pkg/models/list_users.go +++ b/pkg/models/list_users.go @@ -50,3 +50,182 @@ type UserWithRight struct { User `xorm:"extends"` Right Right `json:"right"` } + +// Create creates a new list <-> user relation +// @Summary Add a user to a list +// @Description Gives a user access to a list. +// @tags sharing +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "List ID" +// @Param list body models.ListUser true "The user you want to add to the list." +// @Success 200 {object} models.ListUser "The created user<->list relation." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid user list object provided." +// @Failure 404 {object} code.vikunja.io/web.HTTPError "The user does not exist." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{id}/users [put] +func (lu *ListUser) Create(a web.Auth) (err error) { + + // Check if the right is valid + if err := lu.Right.isValid(); err != nil { + return err + } + + // Check if the list exists + l := &List{ID: lu.ListID} + if err = l.GetSimpleByID(); err != nil { + return + } + + // Check if the user exists + user, err := GetUserByUsername(lu.Username) + if err != nil { + return err + } + lu.UserID = user.ID + + // Check if the user already has access or is owner of that list + // We explicitly DONT check for teams here + if l.OwnerID == lu.UserID { + return ErrUserAlreadyHasAccess{UserID: lu.UserID, ListID: lu.ListID} + } + + exist, err := x.Where("list_id = ? AND user_id = ?", lu.ListID, lu.UserID).Get(&ListUser{}) + if err != nil { + return + } + if exist { + return ErrUserAlreadyHasAccess{UserID: lu.UserID, ListID: lu.ListID} + } + + // Insert user <-> list relation + _, err = x.Insert(lu) + if err != nil { + return err + } + + err = updateListLastUpdated(l) + return +} + +// Delete deletes a list <-> user relation +// @Summary Delete a user from a list +// @Description Delets a user from a list. The user won't have access to the list anymore. +// @tags sharing +// @Produce json +// @Security JWTKeyAuth +// @Param listID path int true "List ID" +// @Param userID path int true "User ID" +// @Success 200 {object} models.Message "The user was successfully removed from the list." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" +// @Failure 404 {object} code.vikunja.io/web.HTTPError "user or list does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{listID}/users/{userID} [delete] +func (lu *ListUser) Delete() (err error) { + + // Check if the user exists + user, err := GetUserByUsername(lu.Username) + if err != nil { + return + } + lu.UserID = user.ID + + // Check if the user has access to the list + has, err := x.Where("user_id = ? AND list_id = ?", lu.UserID, lu.ListID). + Get(&ListUser{}) + if err != nil { + return + } + if !has { + return ErrUserDoesNotHaveAccessToList{ListID: lu.ListID, UserID: lu.UserID} + } + + _, err = x.Where("user_id = ? AND list_id = ?", lu.UserID, lu.ListID). + Delete(&ListUser{}) + if err != nil { + return err + } + + err = updateListLastUpdated(&List{ID: lu.ListID}) + return +} + +// ReadAll gets all users who have access to a list +// @Summary Get users on a list +// @Description Returns a list with all users which have access on a given list. +// @tags sharing +// @Accept json +// @Produce json +// @Param id path int true "List ID" +// @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 users by its name." +// @Security JWTKeyAuth +// @Success 200 {array} models.UserWithRight "The users with the right they have." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the list." +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{id}/users [get] +func (lu *ListUser) ReadAll(search string, a web.Auth, page int) (interface{}, error) { + // Check if the user has access to the list + l := &List{ID: lu.ListID} + canRead, err := l.CanRead(a) + if err != nil { + return nil, err + } + if !canRead { + return nil, ErrNeedToHaveListReadAccess{UserID: a.GetID(), ListID: lu.ListID} + } + + // Get all users + all := []*UserWithRight{} + err = x. + Join("INNER", "users_list", "user_id = users.id"). + Where("users_list.list_id = ?", lu.ListID). + Limit(getLimitFromPageIndex(page)). + Where("users.username LIKE ?", "%"+search+"%"). + Find(&all) + + return all, err +} + +// Update updates a user <-> list relation +// @Summary Update a user <-> list relation +// @Description Update a user <-> list relation. Mostly used to update the right that user has. +// @tags sharing +// @Accept json +// @Produce json +// @Param listID path int true "List ID" +// @Param userID path int true "User ID" +// @Param list body models.ListUser true "The user you want to update." +// @Security JWTKeyAuth +// @Success 200 {object} models.ListUser "The updated user <-> list relation." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have admin-access to the list" +// @Failure 404 {object} code.vikunja.io/web.HTTPError "User or list does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{listID}/users/{userID} [post] +func (lu *ListUser) Update() (err error) { + + // Check if the right is valid + if err := lu.Right.isValid(); err != nil { + return err + } + + // Check if the user exists + user, err := GetUserByUsername(lu.Username) + if err != nil { + return err + } + lu.UserID = user.ID + + _, err = x. + Where("list_id = ? AND user_id = ?", lu.ListID, lu.UserID). + Cols("right"). + Update(lu) + if err != nil { + return err + } + + err = updateListLastUpdated(&List{ID: lu.ListID}) + return +} diff --git a/pkg/models/list_users_create.go b/pkg/models/list_users_create.go deleted file mode 100644 index 2b9e6a2b8e..0000000000 --- a/pkg/models/list_users_create.go +++ /dev/null @@ -1,78 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" - -// Create creates a new list <-> user relation -// @Summary Add a user to a list -// @Description Gives a user access to a list. -// @tags sharing -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "List ID" -// @Param list body models.ListUser true "The user you want to add to the list." -// @Success 200 {object} models.ListUser "The created user<->list relation." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid user list object provided." -// @Failure 404 {object} code.vikunja.io/web.HTTPError "The user does not exist." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{id}/users [put] -func (lu *ListUser) Create(a web.Auth) (err error) { - - // Check if the right is valid - if err := lu.Right.isValid(); err != nil { - return err - } - - // Check if the list exists - l := &List{ID: lu.ListID} - if err = l.GetSimpleByID(); err != nil { - return - } - - // Check if the user exists - user, err := GetUserByUsername(lu.Username) - if err != nil { - return err - } - lu.UserID = user.ID - - // Check if the user already has access or is owner of that list - // We explicitly DONT check for teams here - if l.OwnerID == lu.UserID { - return ErrUserAlreadyHasAccess{UserID: lu.UserID, ListID: lu.ListID} - } - - exist, err := x.Where("list_id = ? AND user_id = ?", lu.ListID, lu.UserID).Get(&ListUser{}) - if err != nil { - return - } - if exist { - return ErrUserAlreadyHasAccess{UserID: lu.UserID, ListID: lu.ListID} - } - - // Insert user <-> list relation - _, err = x.Insert(lu) - if err != nil { - return err - } - - err = updateListLastUpdated(l) - return -} diff --git a/pkg/models/list_users_delete.go b/pkg/models/list_users_delete.go deleted file mode 100644 index d8e64926ef..0000000000 --- a/pkg/models/list_users_delete.go +++ /dev/null @@ -1,61 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" // For swaggerdocs generation - -// Delete deletes a list <-> user relation -// @Summary Delete a user from a list -// @Description Delets a user from a list. The user won't have access to the list anymore. -// @tags sharing -// @Produce json -// @Security JWTKeyAuth -// @Param listID path int true "List ID" -// @Param userID path int true "User ID" -// @Success 200 {object} models.Message "The user was successfully removed from the list." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" -// @Failure 404 {object} code.vikunja.io/web.HTTPError "user or list does not exist." -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{listID}/users/{userID} [delete] -func (lu *ListUser) Delete() (err error) { - - // Check if the user exists - user, err := GetUserByUsername(lu.Username) - if err != nil { - return - } - lu.UserID = user.ID - - // Check if the user has access to the list - has, err := x.Where("user_id = ? AND list_id = ?", lu.UserID, lu.ListID). - Get(&ListUser{}) - if err != nil { - return - } - if !has { - return ErrUserDoesNotHaveAccessToList{ListID: lu.ListID, UserID: lu.UserID} - } - - _, err = x.Where("user_id = ? AND list_id = ?", lu.UserID, lu.ListID). - Delete(&ListUser{}) - if err != nil { - return err - } - - err = updateListLastUpdated(&List{ID: lu.ListID}) - return -} diff --git a/pkg/models/list_users_readall.go b/pkg/models/list_users_readall.go deleted file mode 100644 index 3d52349a3f..0000000000 --- a/pkg/models/list_users_readall.go +++ /dev/null @@ -1,56 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" - -// ReadAll gets all users who have access to a list -// @Summary Get users on a list -// @Description Returns a list with all users which have access on a given list. -// @tags sharing -// @Accept json -// @Produce json -// @Param id path int true "List ID" -// @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 users by its name." -// @Security JWTKeyAuth -// @Success 200 {array} models.UserWithRight "The users with the right they have." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the list." -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{id}/users [get] -func (lu *ListUser) ReadAll(search string, a web.Auth, page int) (interface{}, error) { - // Check if the user has access to the list - l := &List{ID: lu.ListID} - canRead, err := l.CanRead(a) - if err != nil { - return nil, err - } - if !canRead { - return nil, ErrNeedToHaveListReadAccess{UserID: a.GetID(), ListID: lu.ListID} - } - - // Get all users - all := []*UserWithRight{} - err = x. - Join("INNER", "users_list", "user_id = users.id"). - Where("users_list.list_id = ?", lu.ListID). - Limit(getLimitFromPageIndex(page)). - Where("users.username LIKE ?", "%"+search+"%"). - Find(&all) - - return all, err -} diff --git a/pkg/models/list_users_update.go b/pkg/models/list_users_update.go deleted file mode 100644 index 29bc6c8245..0000000000 --- a/pkg/models/list_users_update.go +++ /dev/null @@ -1,60 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" // For swaggerdocs generation - -// Update updates a user <-> list relation -// @Summary Update a user <-> list relation -// @Description Update a user <-> list relation. Mostly used to update the right that user has. -// @tags sharing -// @Accept json -// @Produce json -// @Param listID path int true "List ID" -// @Param userID path int true "User ID" -// @Param list body models.ListUser true "The user you want to update." -// @Security JWTKeyAuth -// @Success 200 {object} models.ListUser "The updated user <-> list relation." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have admin-access to the list" -// @Failure 404 {object} code.vikunja.io/web.HTTPError "User or list does not exist." -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{listID}/users/{userID} [post] -func (lu *ListUser) Update() (err error) { - - // Check if the right is valid - if err := lu.Right.isValid(); err != nil { - return err - } - - // Check if the user exists - user, err := GetUserByUsername(lu.Username) - if err != nil { - return err - } - lu.UserID = user.ID - - _, err = x. - Where("list_id = ? AND user_id = ?", lu.ListID, lu.UserID). - Cols("right"). - Update(lu) - if err != nil { - return err - } - - err = updateListLastUpdated(&List{ID: lu.ListID}) - return -} diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index f1923c42b8..3c2bb5ea70 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/metrics" "code.vikunja.io/web" "github.com/imdario/mergo" "time" @@ -252,3 +253,135 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e return all, nil } + +// Create implements the creation method via the interface +// @Summary Creates a new namespace +// @Description Creates a new namespace. +// @tags namespace +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param namespace body models.Namespace true "The namespace you want to create." +// @Success 200 {object} models.Namespace "The created namespace." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid namespace object provided." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the namespace" +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces [put] +func (n *Namespace) Create(a web.Auth) (err error) { + // Check if we have at least a name + if n.Name == "" { + return ErrNamespaceNameCannotBeEmpty{NamespaceID: 0, UserID: a.GetID()} + } + n.ID = 0 // This would otherwise prevent the creation of new lists after one was created + + // Check if the User exists + n.Owner, err = GetUserByID(a.GetID()) + if err != nil { + return + } + n.OwnerID = n.Owner.ID + + // Insert + if _, err = x.Insert(n); err != nil { + return err + } + + metrics.UpdateCount(1, metrics.NamespaceCountKey) + return +} + +// Delete deletes a namespace +// @Summary Deletes a namespace +// @Description Delets a namespace +// @tags namespace +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "Namespace ID" +// @Success 200 {object} models.Message "The namespace was successfully deleted." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid namespace object provided." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the namespace" +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces/{id} [delete] +func (n *Namespace) Delete() (err error) { + + // Check if the namespace exists + _, err = GetNamespaceByID(n.ID) + if err != nil { + return + } + + // Delete the namespace + _, err = x.ID(n.ID).Delete(&Namespace{}) + if err != nil { + return + } + + // Delete all lists with their tasks + lists, err := GetListsByNamespaceID(n.ID, &User{}) + if err != nil { + return + } + var listIDs []int64 + // We need to do that for here because we need the list ids to delete two times: + // 1) to delete the lists itself + // 2) to delete the list tasks + for _, l := range lists { + listIDs = append(listIDs, l.ID) + } + + // Delete tasks + _, err = x.In("list_id", listIDs).Delete(&ListTask{}) + if err != nil { + return + } + + // Delete the lists + _, err = x.In("id", listIDs).Delete(&List{}) + if err != nil { + return + } + + metrics.UpdateCount(-1, metrics.NamespaceCountKey) + + return +} + +// Update implements the update method via the interface +// @Summary Updates a namespace +// @Description Updates a namespace. +// @tags namespace +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "Namespace ID" +// @Param namespace body models.Namespace true "The namespace with updated values you want to update." +// @Success 200 {object} models.Namespace "The updated namespace." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid namespace object provided." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the namespace" +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespace/{id} [post] +func (n *Namespace) Update() (err error) { + // Check if we have at least a name + if n.Name == "" { + return ErrNamespaceNameCannotBeEmpty{NamespaceID: n.ID} + } + + // Check if the namespace exists + currentNamespace, err := GetNamespaceByID(n.ID) + if err != nil { + return + } + + // Check if the (new) owner exists + n.OwnerID = n.Owner.ID + if currentNamespace.OwnerID != n.OwnerID { + n.Owner, err = GetUserByID(n.OwnerID) + if err != nil { + return + } + } + + // Do the actual update + _, err = x.ID(currentNamespace.ID).Update(n) + return +} diff --git a/pkg/models/namespace_create.go b/pkg/models/namespace_create.go deleted file mode 100644 index 2493d94e3e..0000000000 --- a/pkg/models/namespace_create.go +++ /dev/null @@ -1,58 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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/metrics" - "code.vikunja.io/web" -) - -// Create implements the creation method via the interface -// @Summary Creates a new namespace -// @Description Creates a new namespace. -// @tags namespace -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param namespace body models.Namespace true "The namespace you want to create." -// @Success 200 {object} models.Namespace "The created namespace." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid namespace object provided." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the namespace" -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces [put] -func (n *Namespace) Create(a web.Auth) (err error) { - // Check if we have at least a name - if n.Name == "" { - return ErrNamespaceNameCannotBeEmpty{NamespaceID: 0, UserID: a.GetID()} - } - n.ID = 0 // This would otherwise prevent the creation of new lists after one was created - - // Check if the User exists - n.Owner, err = GetUserByID(a.GetID()) - if err != nil { - return - } - n.OwnerID = n.Owner.ID - - // Insert - if _, err = x.Insert(n); err != nil { - return err - } - - metrics.UpdateCount(1, metrics.NamespaceCountKey) - return -} diff --git a/pkg/models/namespace_delete.go b/pkg/models/namespace_delete.go deleted file mode 100644 index f1612924ce..0000000000 --- a/pkg/models/namespace_delete.go +++ /dev/null @@ -1,78 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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/metrics" - _ "code.vikunja.io/web" // For swaggerdocs generation -) - -// Delete deletes a namespace -// @Summary Deletes a namespace -// @Description Delets a namespace -// @tags namespace -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "Namespace ID" -// @Success 200 {object} models.Message "The namespace was successfully deleted." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid namespace object provided." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the namespace" -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces/{id} [delete] -func (n *Namespace) Delete() (err error) { - - // Check if the namespace exists - _, err = GetNamespaceByID(n.ID) - if err != nil { - return - } - - // Delete the namespace - _, err = x.ID(n.ID).Delete(&Namespace{}) - if err != nil { - return - } - - // Delete all lists with their tasks - lists, err := GetListsByNamespaceID(n.ID, &User{}) - if err != nil { - return - } - var listIDs []int64 - // We need to do that for here because we need the list ids to delete two times: - // 1) to delete the lists itself - // 2) to delete the list tasks - for _, l := range lists { - listIDs = append(listIDs, l.ID) - } - - // Delete tasks - _, err = x.In("list_id", listIDs).Delete(&ListTask{}) - if err != nil { - return - } - - // Delete the lists - _, err = x.In("id", listIDs).Delete(&List{}) - if err != nil { - return - } - - metrics.UpdateCount(-1, metrics.NamespaceCountKey) - - return -} diff --git a/pkg/models/namespace_update.go b/pkg/models/namespace_update.go deleted file mode 100644 index 2da2b4fcb7..0000000000 --- a/pkg/models/namespace_update.go +++ /dev/null @@ -1,59 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" // For swaggerdocs generation - -// Update implements the update method via the interface -// @Summary Updates a namespace -// @Description Updates a namespace. -// @tags namespace -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "Namespace ID" -// @Param namespace body models.Namespace true "The namespace with updated values you want to update." -// @Success 200 {object} models.Namespace "The updated namespace." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid namespace object provided." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the namespace" -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespace/{id} [post] -func (n *Namespace) Update() (err error) { - // Check if we have at least a name - if n.Name == "" { - return ErrNamespaceNameCannotBeEmpty{NamespaceID: n.ID} - } - - // Check if the namespace exists - currentNamespace, err := GetNamespaceByID(n.ID) - if err != nil { - return - } - - // Check if the (new) owner exists - n.OwnerID = n.Owner.ID - if currentNamespace.OwnerID != n.OwnerID { - n.Owner, err = GetUserByID(n.OwnerID) - if err != nil { - return - } - } - - // Do the actual update - _, err = x.ID(currentNamespace.ID).Update(n) - return -} diff --git a/pkg/models/namespace_users.go b/pkg/models/namespace_users.go index f94cdcb8c8..fd67ca57a3 100644 --- a/pkg/models/namespace_users.go +++ b/pkg/models/namespace_users.go @@ -43,3 +43,170 @@ type NamespaceUser struct { func (NamespaceUser) TableName() string { return "users_namespace" } + +// Create creates a new namespace <-> user relation +// @Summary Add a user to a namespace +// @Description Gives a user access to a namespace. +// @tags sharing +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "Namespace ID" +// @Param namespace body models.NamespaceUser true "The user you want to add to the namespace." +// @Success 200 {object} models.NamespaceUser "The created user<->namespace relation." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid user namespace object provided." +// @Failure 404 {object} code.vikunja.io/web.HTTPError "The user does not exist." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the namespace" +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces/{id}/users [put] +func (nu *NamespaceUser) Create(a web.Auth) (err error) { + // Reset the id + nu.ID = 0 + + // Check if the right is valid + if err := nu.Right.isValid(); err != nil { + return err + } + + // Check if the namespace exists + l, err := GetNamespaceByID(nu.NamespaceID) + if err != nil { + return + } + + // Check if the user exists + user, err := GetUserByUsername(nu.Username) + if err != nil { + return err + } + nu.UserID = user.ID + + // Check if the user already has access or is owner of that namespace + // We explicitly DO NOT check for teams here + if l.OwnerID == nu.UserID { + return ErrUserAlreadyHasNamespaceAccess{UserID: nu.UserID, NamespaceID: nu.NamespaceID} + } + + exist, err := x.Where("namespace_id = ? AND user_id = ?", nu.NamespaceID, nu.UserID).Get(&NamespaceUser{}) + if err != nil { + return + } + if exist { + return ErrUserAlreadyHasNamespaceAccess{UserID: nu.UserID, NamespaceID: nu.NamespaceID} + } + + // Insert user <-> namespace relation + _, err = x.Insert(nu) + + return +} + +// Delete deletes a namespace <-> user relation +// @Summary Delete a user from a namespace +// @Description Delets a user from a namespace. The user won't have access to the namespace anymore. +// @tags sharing +// @Produce json +// @Security JWTKeyAuth +// @Param namespaceID path int true "Namespace ID" +// @Param userID path int true "user ID" +// @Success 200 {object} models.Message "The user was successfully deleted." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the namespace" +// @Failure 404 {object} code.vikunja.io/web.HTTPError "user or namespace does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces/{namespaceID}/users/{userID} [delete] +func (nu *NamespaceUser) Delete() (err error) { + + // Check if the user exists + user, err := GetUserByUsername(nu.Username) + if err != nil { + return + } + nu.UserID = user.ID + + // Check if the user has access to the namespace + has, err := x.Where("user_id = ? AND namespace_id = ?", nu.UserID, nu.NamespaceID). + Get(&NamespaceUser{}) + if err != nil { + return + } + if !has { + return ErrUserDoesNotHaveAccessToNamespace{NamespaceID: nu.NamespaceID, UserID: nu.UserID} + } + + _, err = x.Where("user_id = ? AND namespace_id = ?", nu.UserID, nu.NamespaceID). + Delete(&NamespaceUser{}) + return +} + +// ReadAll gets all users who have access to a namespace +// @Summary Get users on a namespace +// @Description Returns a namespace with all users which have access on a given namespace. +// @tags sharing +// @Accept json +// @Produce json +// @Param id path int true "Namespace ID" +// @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 users by its name." +// @Security JWTKeyAuth +// @Success 200 {array} models.UserWithRight "The users with the right they have." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the namespace." +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces/{id}/users [get] +func (nu *NamespaceUser) ReadAll(search string, a web.Auth, page int) (interface{}, error) { + // Check if the user has access to the namespace + l := Namespace{ID: nu.NamespaceID} + canRead, err := l.CanRead(a) + if err != nil { + return nil, err + } + if !canRead { + return nil, ErrNeedToHaveNamespaceReadAccess{} + } + + // Get all users + all := []*UserWithRight{} + err = x. + Join("INNER", "users_namespace", "user_id = users.id"). + Where("users_namespace.namespace_id = ?", nu.NamespaceID). + Limit(getLimitFromPageIndex(page)). + Where("users.username LIKE ?", "%"+search+"%"). + Find(&all) + + return all, err +} + +// Update updates a user <-> namespace relation +// @Summary Update a user <-> namespace relation +// @Description Update a user <-> namespace relation. Mostly used to update the right that user has. +// @tags sharing +// @Accept json +// @Produce json +// @Param namespaceID path int true "Namespace ID" +// @Param userID path int true "User ID" +// @Param namespace body models.NamespaceUser true "The user you want to update." +// @Security JWTKeyAuth +// @Success 200 {object} models.NamespaceUser "The updated user <-> namespace relation." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have admin-access to the namespace" +// @Failure 404 {object} code.vikunja.io/web.HTTPError "User or namespace does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces/{namespaceID}/users/{userID} [post] +func (nu *NamespaceUser) Update() (err error) { + + // Check if the right is valid + if err := nu.Right.isValid(); err != nil { + return err + } + + // Check if the user exists + user, err := GetUserByUsername(nu.Username) + if err != nil { + return err + } + nu.UserID = user.ID + + _, err = x. + Where("namespace_id = ? AND user_id = ?", nu.NamespaceID, nu.UserID). + Cols("right"). + Update(nu) + return +} diff --git a/pkg/models/namespace_users_create.go b/pkg/models/namespace_users_create.go deleted file mode 100644 index 8953058bb1..0000000000 --- a/pkg/models/namespace_users_create.go +++ /dev/null @@ -1,76 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" - -// Create creates a new namespace <-> user relation -// @Summary Add a user to a namespace -// @Description Gives a user access to a namespace. -// @tags sharing -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "Namespace ID" -// @Param namespace body models.NamespaceUser true "The user you want to add to the namespace." -// @Success 200 {object} models.NamespaceUser "The created user<->namespace relation." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid user namespace object provided." -// @Failure 404 {object} code.vikunja.io/web.HTTPError "The user does not exist." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the namespace" -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces/{id}/users [put] -func (nu *NamespaceUser) Create(a web.Auth) (err error) { - // Reset the id - nu.ID = 0 - - // Check if the right is valid - if err := nu.Right.isValid(); err != nil { - return err - } - - // Check if the namespace exists - l, err := GetNamespaceByID(nu.NamespaceID) - if err != nil { - return - } - - // Check if the user exists - user, err := GetUserByUsername(nu.Username) - if err != nil { - return err - } - nu.UserID = user.ID - - // Check if the user already has access or is owner of that namespace - // We explicitly DO NOT check for teams here - if l.OwnerID == nu.UserID { - return ErrUserAlreadyHasNamespaceAccess{UserID: nu.UserID, NamespaceID: nu.NamespaceID} - } - - exist, err := x.Where("namespace_id = ? AND user_id = ?", nu.NamespaceID, nu.UserID).Get(&NamespaceUser{}) - if err != nil { - return - } - if exist { - return ErrUserAlreadyHasNamespaceAccess{UserID: nu.UserID, NamespaceID: nu.NamespaceID} - } - - // Insert user <-> namespace relation - _, err = x.Insert(nu) - - return -} diff --git a/pkg/models/namespace_users_delete.go b/pkg/models/namespace_users_delete.go deleted file mode 100644 index 12f81b521b..0000000000 --- a/pkg/models/namespace_users_delete.go +++ /dev/null @@ -1,56 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" // For swaggerdocs generation - -// Delete deletes a namespace <-> user relation -// @Summary Delete a user from a namespace -// @Description Delets a user from a namespace. The user won't have access to the namespace anymore. -// @tags sharing -// @Produce json -// @Security JWTKeyAuth -// @Param namespaceID path int true "Namespace ID" -// @Param userID path int true "user ID" -// @Success 200 {object} models.Message "The user was successfully deleted." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the namespace" -// @Failure 404 {object} code.vikunja.io/web.HTTPError "user or namespace does not exist." -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces/{namespaceID}/users/{userID} [delete] -func (nu *NamespaceUser) Delete() (err error) { - - // Check if the user exists - user, err := GetUserByUsername(nu.Username) - if err != nil { - return - } - nu.UserID = user.ID - - // Check if the user has access to the namespace - has, err := x.Where("user_id = ? AND namespace_id = ?", nu.UserID, nu.NamespaceID). - Get(&NamespaceUser{}) - if err != nil { - return - } - if !has { - return ErrUserDoesNotHaveAccessToNamespace{NamespaceID: nu.NamespaceID, UserID: nu.UserID} - } - - _, err = x.Where("user_id = ? AND namespace_id = ?", nu.UserID, nu.NamespaceID). - Delete(&NamespaceUser{}) - return -} diff --git a/pkg/models/namespace_users_readall.go b/pkg/models/namespace_users_readall.go deleted file mode 100644 index e292cfb905..0000000000 --- a/pkg/models/namespace_users_readall.go +++ /dev/null @@ -1,56 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" - -// ReadAll gets all users who have access to a namespace -// @Summary Get users on a namespace -// @Description Returns a namespace with all users which have access on a given namespace. -// @tags sharing -// @Accept json -// @Produce json -// @Param id path int true "Namespace ID" -// @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 users by its name." -// @Security JWTKeyAuth -// @Success 200 {array} models.UserWithRight "The users with the right they have." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the namespace." -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces/{id}/users [get] -func (nu *NamespaceUser) ReadAll(search string, a web.Auth, page int) (interface{}, error) { - // Check if the user has access to the namespace - l := Namespace{ID: nu.NamespaceID} - canRead, err := l.CanRead(a) - if err != nil { - return nil, err - } - if !canRead { - return nil, ErrNeedToHaveNamespaceReadAccess{} - } - - // Get all users - all := []*UserWithRight{} - err = x. - Join("INNER", "users_namespace", "user_id = users.id"). - Where("users_namespace.namespace_id = ?", nu.NamespaceID). - Limit(getLimitFromPageIndex(page)). - Where("users.username LIKE ?", "%"+search+"%"). - Find(&all) - - return all, err -} diff --git a/pkg/models/namespace_users_update.go b/pkg/models/namespace_users_update.go deleted file mode 100644 index bcaa55d142..0000000000 --- a/pkg/models/namespace_users_update.go +++ /dev/null @@ -1,55 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" // For swaggerdocs generation - -// Update updates a user <-> namespace relation -// @Summary Update a user <-> namespace relation -// @Description Update a user <-> namespace relation. Mostly used to update the right that user has. -// @tags sharing -// @Accept json -// @Produce json -// @Param namespaceID path int true "Namespace ID" -// @Param userID path int true "User ID" -// @Param namespace body models.NamespaceUser true "The user you want to update." -// @Security JWTKeyAuth -// @Success 200 {object} models.NamespaceUser "The updated user <-> namespace relation." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have admin-access to the namespace" -// @Failure 404 {object} code.vikunja.io/web.HTTPError "User or namespace does not exist." -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces/{namespaceID}/users/{userID} [post] -func (nu *NamespaceUser) Update() (err error) { - - // Check if the right is valid - if err := nu.Right.isValid(); err != nil { - return err - } - - // Check if the user exists - user, err := GetUserByUsername(nu.Username) - if err != nil { - return err - } - nu.UserID = user.ID - - _, err = x. - Where("namespace_id = ? AND user_id = ?", nu.NamespaceID, nu.UserID). - Cols("right"). - Update(nu) - return -} diff --git a/pkg/models/team_list.go b/pkg/models/team_list.go index 106e893dbb..9115e2b84f 100644 --- a/pkg/models/team_list.go +++ b/pkg/models/team_list.go @@ -48,3 +48,173 @@ type TeamWithRight struct { Team `xorm:"extends"` Right Right `json:"right"` } + +// Create creates a new team <-> list relation +// @Summary Add a team to a list +// @Description Gives a team access to a list. +// @tags sharing +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "List ID" +// @Param list body models.TeamList true "The team you want to add to the list." +// @Success 200 {object} models.TeamList "The created team<->list relation." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid team list object provided." +// @Failure 404 {object} code.vikunja.io/web.HTTPError "The team does not exist." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{id}/teams [put] +func (tl *TeamList) Create(a web.Auth) (err error) { + + // Check if the rights are valid + if err = tl.Right.isValid(); err != nil { + return + } + + // Check if the team exists + _, err = GetTeamByID(tl.TeamID) + if err != nil { + return + } + + // Check if the list exists + l := &List{ID: tl.ListID} + if err := l.GetSimpleByID(); err != nil { + return err + } + + // Check if the team is already on the list + exists, err := x.Where("team_id = ?", tl.TeamID). + And("list_id = ?", tl.ListID). + Get(&TeamList{}) + if err != nil { + return + } + if exists { + return ErrTeamAlreadyHasAccess{tl.TeamID, tl.ListID} + } + + // Insert the new team + _, err = x.Insert(tl) + if err != nil { + return err + } + + err = updateListLastUpdated(l) + return +} + +// Delete deletes a team <-> list relation based on the list & team id +// @Summary Delete a team from a list +// @Description Delets a team from a list. The team won't have access to the list anymore. +// @tags sharing +// @Produce json +// @Security JWTKeyAuth +// @Param listID path int true "List ID" +// @Param teamID path int true "Team ID" +// @Success 200 {object} models.Message "The team was successfully deleted." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" +// @Failure 404 {object} code.vikunja.io/web.HTTPError "Team or list does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{listID}/teams/{teamID} [delete] +func (tl *TeamList) Delete() (err error) { + + // Check if the team exists + _, err = GetTeamByID(tl.TeamID) + if err != nil { + return + } + + // Check if the team has access to the list + has, err := x.Where("team_id = ? AND list_id = ?", tl.TeamID, tl.ListID). + Get(&TeamList{}) + if err != nil { + return + } + if !has { + return ErrTeamDoesNotHaveAccessToList{TeamID: tl.TeamID, ListID: tl.ListID} + } + + // Delete the relation + _, err = x.Where("team_id = ?", tl.TeamID). + And("list_id = ?", tl.ListID). + Delete(TeamList{}) + if err != nil { + return err + } + + err = updateListLastUpdated(&List{ID: tl.ListID}) + return +} + +// ReadAll implements the method to read all teams of a list +// @Summary Get teams on a list +// @Description Returns a list with all teams which have access on a given list. +// @tags sharing +// @Accept json +// @Produce json +// @Param id path int true "List ID" +// @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 teams by its name." +// @Security JWTKeyAuth +// @Success 200 {array} models.TeamWithRight "The teams with their right." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the list." +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{id}/teams [get] +func (tl *TeamList) ReadAll(search string, a web.Auth, page int) (interface{}, error) { + // Check if the user can read the namespace + l := &List{ID: tl.ListID} + canRead, err := l.CanRead(a) + if err != nil { + return nil, err + } + if !canRead { + return nil, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: a.GetID()} + } + + // Get the teams + all := []*TeamWithRight{} + err = x. + Table("teams"). + Join("INNER", "team_list", "team_id = teams.id"). + Where("team_list.list_id = ?", tl.ListID). + Limit(getLimitFromPageIndex(page)). + Where("teams.name LIKE ?", "%"+search+"%"). + Find(&all) + + return all, err +} + +// Update updates a team <-> list relation +// @Summary Update a team <-> list relation +// @Description Update a team <-> list relation. Mostly used to update the right that team has. +// @tags sharing +// @Accept json +// @Produce json +// @Param listID path int true "List ID" +// @Param teamID path int true "Team ID" +// @Param list body models.TeamList true "The team you want to update." +// @Security JWTKeyAuth +// @Success 200 {object} models.TeamList "The updated team <-> list relation." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have admin-access to the list" +// @Failure 404 {object} code.vikunja.io/web.HTTPError "Team or list does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{listID}/teams/{teamID} [post] +func (tl *TeamList) Update() (err error) { + + // Check if the right is valid + if err := tl.Right.isValid(); err != nil { + return err + } + + _, err = x. + Where("list_id = ? AND team_id = ?", tl.ListID, tl.TeamID). + Cols("right"). + Update(tl) + if err != nil { + return err + } + + err = updateListLastUpdated(&List{ID: tl.ListID}) + return +} diff --git a/pkg/models/team_list_create.go b/pkg/models/team_list_create.go deleted file mode 100644 index ab72263a9b..0000000000 --- a/pkg/models/team_list_create.go +++ /dev/null @@ -1,74 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" - -// Create creates a new team <-> list relation -// @Summary Add a team to a list -// @Description Gives a team access to a list. -// @tags sharing -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "List ID" -// @Param list body models.TeamList true "The team you want to add to the list." -// @Success 200 {object} models.TeamList "The created team<->list relation." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid team list object provided." -// @Failure 404 {object} code.vikunja.io/web.HTTPError "The team does not exist." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{id}/teams [put] -func (tl *TeamList) Create(a web.Auth) (err error) { - - // Check if the rights are valid - if err = tl.Right.isValid(); err != nil { - return - } - - // Check if the team exists - _, err = GetTeamByID(tl.TeamID) - if err != nil { - return - } - - // Check if the list exists - l := &List{ID: tl.ListID} - if err := l.GetSimpleByID(); err != nil { - return err - } - - // Check if the team is already on the list - exists, err := x.Where("team_id = ?", tl.TeamID). - And("list_id = ?", tl.ListID). - Get(&TeamList{}) - if err != nil { - return - } - if exists { - return ErrTeamAlreadyHasAccess{tl.TeamID, tl.ListID} - } - - // Insert the new team - _, err = x.Insert(tl) - if err != nil { - return err - } - - err = updateListLastUpdated(l) - return -} diff --git a/pkg/models/team_list_delete.go b/pkg/models/team_list_delete.go deleted file mode 100644 index ccae1701a4..0000000000 --- a/pkg/models/team_list_delete.go +++ /dev/null @@ -1,62 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" // For swaggerdocs generation - -// Delete deletes a team <-> list relation based on the list & team id -// @Summary Delete a team from a list -// @Description Delets a team from a list. The team won't have access to the list anymore. -// @tags sharing -// @Produce json -// @Security JWTKeyAuth -// @Param listID path int true "List ID" -// @Param teamID path int true "Team ID" -// @Success 200 {object} models.Message "The team was successfully deleted." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" -// @Failure 404 {object} code.vikunja.io/web.HTTPError "Team or list does not exist." -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{listID}/teams/{teamID} [delete] -func (tl *TeamList) Delete() (err error) { - - // Check if the team exists - _, err = GetTeamByID(tl.TeamID) - if err != nil { - return - } - - // Check if the team has access to the list - has, err := x.Where("team_id = ? AND list_id = ?", tl.TeamID, tl.ListID). - Get(&TeamList{}) - if err != nil { - return - } - if !has { - return ErrTeamDoesNotHaveAccessToList{TeamID: tl.TeamID, ListID: tl.ListID} - } - - // Delete the relation - _, err = x.Where("team_id = ?", tl.TeamID). - And("list_id = ?", tl.ListID). - Delete(TeamList{}) - if err != nil { - return err - } - - err = updateListLastUpdated(&List{ID: tl.ListID}) - return -} diff --git a/pkg/models/team_list_readall.go b/pkg/models/team_list_readall.go deleted file mode 100644 index 8a3ac791a1..0000000000 --- a/pkg/models/team_list_readall.go +++ /dev/null @@ -1,57 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" - -// ReadAll implements the method to read all teams of a list -// @Summary Get teams on a list -// @Description Returns a list with all teams which have access on a given list. -// @tags sharing -// @Accept json -// @Produce json -// @Param id path int true "List ID" -// @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 teams by its name." -// @Security JWTKeyAuth -// @Success 200 {array} models.TeamWithRight "The teams with their right." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the list." -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{id}/teams [get] -func (tl *TeamList) ReadAll(search string, a web.Auth, page int) (interface{}, error) { - // Check if the user can read the namespace - l := &List{ID: tl.ListID} - canRead, err := l.CanRead(a) - if err != nil { - return nil, err - } - if !canRead { - return nil, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: a.GetID()} - } - - // Get the teams - all := []*TeamWithRight{} - err = x. - Table("teams"). - Join("INNER", "team_list", "team_id = teams.id"). - Where("team_list.list_id = ?", tl.ListID). - Limit(getLimitFromPageIndex(page)). - Where("teams.name LIKE ?", "%"+search+"%"). - Find(&all) - - return all, err -} diff --git a/pkg/models/team_list_update.go b/pkg/models/team_list_update.go deleted file mode 100644 index 17bb260a2e..0000000000 --- a/pkg/models/team_list_update.go +++ /dev/null @@ -1,53 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" // For swaggerdocs generation - -// Update updates a team <-> list relation -// @Summary Update a team <-> list relation -// @Description Update a team <-> list relation. Mostly used to update the right that team has. -// @tags sharing -// @Accept json -// @Produce json -// @Param listID path int true "List ID" -// @Param teamID path int true "Team ID" -// @Param list body models.TeamList true "The team you want to update." -// @Security JWTKeyAuth -// @Success 200 {object} models.TeamList "The updated team <-> list relation." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have admin-access to the list" -// @Failure 404 {object} code.vikunja.io/web.HTTPError "Team or list does not exist." -// @Failure 500 {object} models.Message "Internal error" -// @Router /lists/{listID}/teams/{teamID} [post] -func (tl *TeamList) Update() (err error) { - - // Check if the right is valid - if err := tl.Right.isValid(); err != nil { - return err - } - - _, err = x. - Where("list_id = ? AND team_id = ?", tl.ListID, tl.TeamID). - Cols("right"). - Update(tl) - if err != nil { - return err - } - - err = updateListLastUpdated(&List{ID: tl.ListID}) - return -} diff --git a/pkg/models/team_members_create.go b/pkg/models/team_members.go similarity index 68% rename from pkg/models/team_members_create.go rename to pkg/models/team_members.go index 811729cc56..8f756ca09c 100644 --- a/pkg/models/team_members_create.go +++ b/pkg/models/team_members.go @@ -61,3 +61,35 @@ func (tm *TeamMember) Create(a web.Auth) (err error) { _, err = x.Insert(tm) return } + +// Delete deletes a user from a team +// @Summary Remove a user from a team +// @Description Remove a user from a team. This will also revoke any access this user might have via that team. +// @tags team +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "Team ID" +// @Param userID path int true "User ID" +// @Success 200 {object} models.Message "The user was successfully removed from the team." +// @Failure 500 {object} models.Message "Internal error" +// @Router /teams/{id}/members/{userID} [delete] +func (tm *TeamMember) Delete() (err error) { + + total, err := x.Where("team_id = ?", tm.TeamID).Count(&TeamMember{}) + if err != nil { + return + } + if total == 1 { + return ErrCannotDeleteLastTeamMember{tm.TeamID, tm.UserID} + } + + // Find the numeric user id + user, err := GetUserByUsername(tm.Username) + if err != nil { + return + } + tm.UserID = user.ID + + _, err = x.Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID).Delete(&TeamMember{}) + return +} diff --git a/pkg/models/team_members_delete.go b/pkg/models/team_members_delete.go deleted file mode 100644 index 824307740e..0000000000 --- a/pkg/models/team_members_delete.go +++ /dev/null @@ -1,49 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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 - -// Delete deletes a user from a team -// @Summary Remove a user from a team -// @Description Remove a user from a team. This will also revoke any access this user might have via that team. -// @tags team -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "Team ID" -// @Param userID path int true "User ID" -// @Success 200 {object} models.Message "The user was successfully removed from the team." -// @Failure 500 {object} models.Message "Internal error" -// @Router /teams/{id}/members/{userID} [delete] -func (tm *TeamMember) Delete() (err error) { - - total, err := x.Where("team_id = ?", tm.TeamID).Count(&TeamMember{}) - if err != nil { - return - } - if total == 1 { - return ErrCannotDeleteLastTeamMember{tm.TeamID, tm.UserID} - } - - // Find the numeric user id - user, err := GetUserByUsername(tm.Username) - if err != nil { - return - } - tm.UserID = user.ID - - _, err = x.Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID).Delete(&TeamMember{}) - return -} diff --git a/pkg/models/team_namespace.go b/pkg/models/team_namespace.go index 659f12c67e..bb7afb99f3 100644 --- a/pkg/models/team_namespace.go +++ b/pkg/models/team_namespace.go @@ -42,3 +42,159 @@ type TeamNamespace struct { func (TeamNamespace) TableName() string { return "team_namespaces" } + +// Create creates a new team <-> namespace relation +// @Summary Add a team to a namespace +// @Description Gives a team access to a namespace. +// @tags sharing +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "Namespace ID" +// @Param namespace body models.TeamNamespace true "The team you want to add to the namespace." +// @Success 200 {object} models.TeamNamespace "The created team<->namespace relation." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid team namespace object provided." +// @Failure 404 {object} code.vikunja.io/web.HTTPError "The team does not exist." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The team does not have access to the namespace" +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces/{id}/teams [put] +func (tn *TeamNamespace) Create(a web.Auth) (err error) { + + // Check if the rights are valid + if err = tn.Right.isValid(); err != nil { + return + } + + // Check if the team exists + _, err = GetTeamByID(tn.TeamID) + if err != nil { + return + } + + // Check if the namespace exists + _, err = GetNamespaceByID(tn.NamespaceID) + if err != nil { + return + } + + // Check if the team already has access to the namespace + exists, err := x.Where("team_id = ?", tn.TeamID). + And("namespace_id = ?", tn.NamespaceID). + Get(&TeamNamespace{}) + if err != nil { + return + } + if exists { + return ErrTeamAlreadyHasAccess{tn.TeamID, tn.NamespaceID} + } + + // Insert the new team + _, err = x.Insert(tn) + return +} + +// Delete deletes a team <-> namespace relation based on the namespace & team id +// @Summary Delete a team from a namespace +// @Description Delets a team from a namespace. The team won't have access to the namespace anymore. +// @tags sharing +// @Produce json +// @Security JWTKeyAuth +// @Param namespaceID path int true "Namespace ID" +// @Param teamID path int true "team ID" +// @Success 200 {object} models.Message "The team was successfully deleted." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The team does not have access to the namespace" +// @Failure 404 {object} code.vikunja.io/web.HTTPError "team or namespace does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces/{namespaceID}/teams/{teamID} [delete] +func (tn *TeamNamespace) Delete() (err error) { + + // Check if the team exists + _, err = GetTeamByID(tn.TeamID) + if err != nil { + return + } + + // Check if the team has access to the namespace + has, err := x.Where("team_id = ? AND namespace_id = ?", tn.TeamID, tn.NamespaceID). + Get(&TeamNamespace{}) + if err != nil { + return + } + if !has { + return ErrTeamDoesNotHaveAccessToNamespace{TeamID: tn.TeamID, NamespaceID: tn.NamespaceID} + } + + // Delete the relation + _, err = x.Where("team_id = ?", tn.TeamID). + And("namespace_id = ?", tn.NamespaceID). + Delete(TeamNamespace{}) + + return +} + +// ReadAll implements the method to read all teams of a namespace +// @Summary Get teams on a namespace +// @Description Returns a namespace with all teams which have access on a given namespace. +// @tags sharing +// @Accept json +// @Produce json +// @Param id path int true "Namespace ID" +// @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 teams by its name." +// @Security JWTKeyAuth +// @Success 200 {array} models.TeamWithRight "The teams with the right they have." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the namespace." +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces/{id}/teams [get] +func (tn *TeamNamespace) ReadAll(search string, a web.Auth, page int) (interface{}, error) { + // Check if the user can read the namespace + n := Namespace{ID: tn.NamespaceID} + canRead, err := n.CanRead(a) + if err != nil { + return nil, err + } + if !canRead { + return nil, ErrNeedToHaveNamespaceReadAccess{NamespaceID: tn.NamespaceID, UserID: a.GetID()} + } + + // Get the teams + all := []*TeamWithRight{} + + err = x.Table("teams"). + Join("INNER", "team_namespaces", "team_id = teams.id"). + Where("team_namespaces.namespace_id = ?", tn.NamespaceID). + Limit(getLimitFromPageIndex(page)). + Where("teams.name LIKE ?", "%"+search+"%"). + Find(&all) + + return all, err +} + +// Update updates a team <-> namespace relation +// @Summary Update a team <-> namespace relation +// @Description Update a team <-> namespace relation. Mostly used to update the right that team has. +// @tags sharing +// @Accept json +// @Produce json +// @Param namespaceID path int true "Namespace ID" +// @Param teamID path int true "Team ID" +// @Param namespace body models.TeamNamespace true "The team you want to update." +// @Security JWTKeyAuth +// @Success 200 {object} models.TeamNamespace "The updated team <-> namespace relation." +// @Failure 403 {object} code.vikunja.io/web.HTTPError "The team does not have admin-access to the namespace" +// @Failure 404 {object} code.vikunja.io/web.HTTPError "Team or namespace does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /namespaces/{namespaceID}/teams/{teamID} [post] +func (tn *TeamNamespace) Update() (err error) { + + // Check if the right is valid + if err := tn.Right.isValid(); err != nil { + return err + } + + _, err = x. + Where("namespace_id = ? AND team_id = ?", tn.TeamID, tn.TeamID). + Cols("right"). + Update(tn) + return +} diff --git a/pkg/models/team_namespace_create.go b/pkg/models/team_namespace_create.go deleted file mode 100644 index a7099dacae..0000000000 --- a/pkg/models/team_namespace_create.go +++ /dev/null @@ -1,69 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" - -// Create creates a new team <-> namespace relation -// @Summary Add a team to a namespace -// @Description Gives a team access to a namespace. -// @tags sharing -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "Namespace ID" -// @Param namespace body models.TeamNamespace true "The team you want to add to the namespace." -// @Success 200 {object} models.TeamNamespace "The created team<->namespace relation." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid team namespace object provided." -// @Failure 404 {object} code.vikunja.io/web.HTTPError "The team does not exist." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The team does not have access to the namespace" -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces/{id}/teams [put] -func (tn *TeamNamespace) Create(a web.Auth) (err error) { - - // Check if the rights are valid - if err = tn.Right.isValid(); err != nil { - return - } - - // Check if the team exists - _, err = GetTeamByID(tn.TeamID) - if err != nil { - return - } - - // Check if the namespace exists - _, err = GetNamespaceByID(tn.NamespaceID) - if err != nil { - return - } - - // Check if the team already has access to the namespace - exists, err := x.Where("team_id = ?", tn.TeamID). - And("namespace_id = ?", tn.NamespaceID). - Get(&TeamNamespace{}) - if err != nil { - return - } - if exists { - return ErrTeamAlreadyHasAccess{tn.TeamID, tn.NamespaceID} - } - - // Insert the new team - _, err = x.Insert(tn) - return -} diff --git a/pkg/models/team_namespace_delete.go b/pkg/models/team_namespace_delete.go deleted file mode 100644 index 0ee7d38052..0000000000 --- a/pkg/models/team_namespace_delete.go +++ /dev/null @@ -1,58 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" // For swaggerdocs generation - -// Delete deletes a team <-> namespace relation based on the namespace & team id -// @Summary Delete a team from a namespace -// @Description Delets a team from a namespace. The team won't have access to the namespace anymore. -// @tags sharing -// @Produce json -// @Security JWTKeyAuth -// @Param namespaceID path int true "Namespace ID" -// @Param teamID path int true "team ID" -// @Success 200 {object} models.Message "The team was successfully deleted." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The team does not have access to the namespace" -// @Failure 404 {object} code.vikunja.io/web.HTTPError "team or namespace does not exist." -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces/{namespaceID}/teams/{teamID} [delete] -func (tn *TeamNamespace) Delete() (err error) { - - // Check if the team exists - _, err = GetTeamByID(tn.TeamID) - if err != nil { - return - } - - // Check if the team has access to the namespace - has, err := x.Where("team_id = ? AND namespace_id = ?", tn.TeamID, tn.NamespaceID). - Get(&TeamNamespace{}) - if err != nil { - return - } - if !has { - return ErrTeamDoesNotHaveAccessToNamespace{TeamID: tn.TeamID, NamespaceID: tn.NamespaceID} - } - - // Delete the relation - _, err = x.Where("team_id = ?", tn.TeamID). - And("namespace_id = ?", tn.NamespaceID). - Delete(TeamNamespace{}) - - return -} diff --git a/pkg/models/team_namespace_readall.go b/pkg/models/team_namespace_readall.go deleted file mode 100644 index 3557cc47eb..0000000000 --- a/pkg/models/team_namespace_readall.go +++ /dev/null @@ -1,57 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" - -// ReadAll implements the method to read all teams of a namespace -// @Summary Get teams on a namespace -// @Description Returns a namespace with all teams which have access on a given namespace. -// @tags sharing -// @Accept json -// @Produce json -// @Param id path int true "Namespace ID" -// @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 teams by its name." -// @Security JWTKeyAuth -// @Success 200 {array} models.TeamWithRight "The teams with the right they have." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the namespace." -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces/{id}/teams [get] -func (tn *TeamNamespace) ReadAll(search string, a web.Auth, page int) (interface{}, error) { - // Check if the user can read the namespace - n := Namespace{ID: tn.NamespaceID} - canRead, err := n.CanRead(a) - if err != nil { - return nil, err - } - if !canRead { - return nil, ErrNeedToHaveNamespaceReadAccess{NamespaceID: tn.NamespaceID, UserID: a.GetID()} - } - - // Get the teams - all := []*TeamWithRight{} - - err = x.Table("teams"). - Join("INNER", "team_namespaces", "team_id = teams.id"). - Where("team_namespaces.namespace_id = ?", tn.NamespaceID). - Limit(getLimitFromPageIndex(page)). - Where("teams.name LIKE ?", "%"+search+"%"). - Find(&all) - - return all, err -} diff --git a/pkg/models/team_namespace_update.go b/pkg/models/team_namespace_update.go deleted file mode 100644 index af6e080cd7..0000000000 --- a/pkg/models/team_namespace_update.go +++ /dev/null @@ -1,48 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" // For swaggerdocs generation - -// Update updates a team <-> namespace relation -// @Summary Update a team <-> namespace relation -// @Description Update a team <-> namespace relation. Mostly used to update the right that team has. -// @tags sharing -// @Accept json -// @Produce json -// @Param namespaceID path int true "Namespace ID" -// @Param teamID path int true "Team ID" -// @Param namespace body models.TeamNamespace true "The team you want to update." -// @Security JWTKeyAuth -// @Success 200 {object} models.TeamNamespace "The updated team <-> namespace relation." -// @Failure 403 {object} code.vikunja.io/web.HTTPError "The team does not have admin-access to the namespace" -// @Failure 404 {object} code.vikunja.io/web.HTTPError "Team or namespace does not exist." -// @Failure 500 {object} models.Message "Internal error" -// @Router /namespaces/{namespaceID}/teams/{teamID} [post] -func (tn *TeamNamespace) Update() (err error) { - - // Check if the right is valid - if err := tn.Right.isValid(); err != nil { - return err - } - - _, err = x. - Where("namespace_id = ? AND team_id = ?", tn.TeamID, tn.TeamID). - Cols("right"). - Update(tn) - return -} diff --git a/pkg/models/teams.go b/pkg/models/teams.go index 947ac7d133..f0ad3817bc 100644 --- a/pkg/models/teams.go +++ b/pkg/models/teams.go @@ -16,7 +16,10 @@ package models -import "code.vikunja.io/web" +import ( + "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/web" +) // Team holds a team object type Team struct { @@ -150,3 +153,121 @@ func (t *Team) ReadAll(search string, a web.Auth, page int) (interface{}, error) return all, err } + +// Create is the handler to create a team +// @Summary Creates a new team +// @Description Creates a new team in a given namespace. The user needs write-access to the namespace. +// @tags team +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param team body models.Team true "The team you want to create." +// @Success 200 {object} models.Team "The created team." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid team object provided." +// @Failure 500 {object} models.Message "Internal error" +// @Router /teams [put] +func (t *Team) Create(a web.Auth) (err error) { + doer, err := getUserWithError(a) + if err != nil { + return err + } + + // Check if we have a name + if t.Name == "" { + return ErrTeamNameCannotBeEmpty{} + } + + t.CreatedByID = doer.ID + t.CreatedBy = *doer + + _, err = x.Insert(t) + if err != nil { + return + } + + // Insert the current user as member and admin + tm := TeamMember{TeamID: t.ID, Username: doer.Username, Admin: true} + if err = tm.Create(doer); err != nil { + return err + } + + metrics.UpdateCount(1, metrics.TeamCountKey) + return +} + +// Delete deletes a team +// @Summary Deletes a team +// @Description Delets a team. This will also remove the access for all users in that team. +// @tags team +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "Team ID" +// @Success 200 {object} models.Message "The team was successfully deleted." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid team object provided." +// @Failure 500 {object} models.Message "Internal error" +// @Router /teams/{id} [delete] +func (t *Team) Delete() (err error) { + + // Delete the team + _, err = x.ID(t.ID).Delete(&Team{}) + if err != nil { + return + } + + // Delete team members + _, err = x.Where("team_id = ?", t.ID).Delete(&TeamMember{}) + if err != nil { + return + } + + // Delete team <-> namespace relations + _, err = x.Where("team_id = ?", t.ID).Delete(&TeamNamespace{}) + if err != nil { + return + } + + // Delete team <-> lists relations + _, err = x.Where("team_id = ?", t.ID).Delete(&TeamList{}) + if err != nil { + return + } + + metrics.UpdateCount(-1, metrics.TeamCountKey) + return +} + +// Update is the handler to create a team +// @Summary Updates a team +// @Description Updates a team. +// @tags team +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param id path int true "Team ID" +// @Param team body models.Team true "The team with updated values you want to update." +// @Success 200 {object} models.Team "The updated team." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid team object provided." +// @Failure 500 {object} models.Message "Internal error" +// @Router /teams/{id} [post] +func (t *Team) Update() (err error) { + // Check if we have a name + if t.Name == "" { + return ErrTeamNameCannotBeEmpty{} + } + + // Check if the team exists + _, err = GetTeamByID(t.ID) + if err != nil { + return + } + + _, err = x.ID(t.ID).Update(t) + if err != nil { + return + } + + // Get the newly updated team + *t, err = GetTeamByID(t.ID) + + return +} diff --git a/pkg/models/teams_create.go b/pkg/models/teams_create.go deleted file mode 100644 index 774beef4c1..0000000000 --- a/pkg/models/teams_create.go +++ /dev/null @@ -1,63 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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/metrics" - "code.vikunja.io/web" -) - -// Create is the handler to create a team -// @Summary Creates a new team -// @Description Creates a new team in a given namespace. The user needs write-access to the namespace. -// @tags team -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param team body models.Team true "The team you want to create." -// @Success 200 {object} models.Team "The created team." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid team object provided." -// @Failure 500 {object} models.Message "Internal error" -// @Router /teams [put] -func (t *Team) Create(a web.Auth) (err error) { - doer, err := getUserWithError(a) - if err != nil { - return err - } - - // Check if we have a name - if t.Name == "" { - return ErrTeamNameCannotBeEmpty{} - } - - t.CreatedByID = doer.ID - t.CreatedBy = *doer - - _, err = x.Insert(t) - if err != nil { - return - } - - // Insert the current user as member and admin - tm := TeamMember{TeamID: t.ID, Username: doer.Username, Admin: true} - if err = tm.Create(doer); err != nil { - return err - } - - metrics.UpdateCount(1, metrics.TeamCountKey) - return -} diff --git a/pkg/models/teams_delete.go b/pkg/models/teams_delete.go deleted file mode 100644 index 095dc3caa1..0000000000 --- a/pkg/models/teams_delete.go +++ /dev/null @@ -1,63 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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/metrics" - _ "code.vikunja.io/web" // For swaggerdocs generation -) - -// Delete deletes a team -// @Summary Deletes a team -// @Description Delets a team. This will also remove the access for all users in that team. -// @tags team -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "Team ID" -// @Success 200 {object} models.Message "The team was successfully deleted." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid team object provided." -// @Failure 500 {object} models.Message "Internal error" -// @Router /teams/{id} [delete] -func (t *Team) Delete() (err error) { - - // Delete the team - _, err = x.ID(t.ID).Delete(&Team{}) - if err != nil { - return - } - - // Delete team members - _, err = x.Where("team_id = ?", t.ID).Delete(&TeamMember{}) - if err != nil { - return - } - - // Delete team <-> namespace relations - _, err = x.Where("team_id = ?", t.ID).Delete(&TeamNamespace{}) - if err != nil { - return - } - - // Delete team <-> lists relations - _, err = x.Where("team_id = ?", t.ID).Delete(&TeamList{}) - if err != nil { - return - } - - metrics.UpdateCount(-1, metrics.TeamCountKey) - return -} diff --git a/pkg/models/teams_update.go b/pkg/models/teams_update.go deleted file mode 100644 index af8e53a881..0000000000 --- a/pkg/models/teams_update.go +++ /dev/null @@ -1,55 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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" // For swaggerdocs generation - -// Update is the handler to create a team -// @Summary Updates a team -// @Description Updates a team. -// @tags team -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param id path int true "Team ID" -// @Param team body models.Team true "The team with updated values you want to update." -// @Success 200 {object} models.Team "The updated team." -// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid team object provided." -// @Failure 500 {object} models.Message "Internal error" -// @Router /teams/{id} [post] -func (t *Team) Update() (err error) { - // Check if we have a name - if t.Name == "" { - return ErrTeamNameCannotBeEmpty{} - } - - // Check if the team exists - _, err = GetTeamByID(t.ID) - if err != nil { - return - } - - _, err = x.ID(t.ID).Update(t) - if err != nil { - return - } - - // Get the newly updated team - *t, err = GetTeamByID(t.ID) - - return -} diff --git a/pkg/models/user.go b/pkg/models/user.go index d189265a68..559554fdbb 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -17,6 +17,8 @@ package models import ( + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/mail" "code.vikunja.io/api/pkg/metrics" "code.vikunja.io/api/pkg/utils" "code.vikunja.io/web" @@ -212,3 +214,179 @@ func UpdateActiveUsersFromContext(c echo.Context) (err error) { return metrics.SetActiveUsers(allActiveUsers) } + +// CreateUser creates a new user and inserts it into the database +func CreateUser(user User) (newUser User, err error) { + + newUser = user + + // Check if we have all needed informations + if newUser.Password == "" || newUser.Username == "" || newUser.Email == "" { + return User{}, ErrNoUsernamePassword{} + } + + // Check if the user already existst with that username + exists := true + existingUser, err := GetUserByUsername(newUser.Username) + if err != nil { + if IsErrUserDoesNotExist(err) { + exists = false + } else { + return User{}, err + } + } + if exists { + return User{}, ErrUsernameExists{newUser.ID, newUser.Username} + } + + // Check if the user already existst with that email + exists = true + existingUser, err = GetUser(User{Email: newUser.Email}) + if err != nil { + if IsErrUserDoesNotExist(err) { + exists = false + } else { + return User{}, err + } + } + if exists { + return User{}, ErrUserEmailExists{existingUser.ID, existingUser.Email} + } + + // Hash the password + newUser.Password, err = hashPassword(user.Password) + if err != nil { + return User{}, err + } + + newUser.IsActive = true + if config.MailerEnabled.GetBool() { + // The new user should not be activated until it confirms his mail address + newUser.IsActive = false + // Generate a confirm token + newUser.EmailConfirmToken = utils.MakeRandomString(400) + } + + // Insert it + _, err = x.Insert(newUser) + if err != nil { + return User{}, err + } + + // Update the metrics + metrics.UpdateCount(1, metrics.ActiveUsersKey) + + // Get the full new User + newUserOut, err := GetUser(newUser) + if err != nil { + return User{}, err + } + + // Create the user's namespace + newN := &Namespace{Name: newUserOut.Username, Description: newUserOut.Username + "'s namespace.", Owner: newUserOut} + err = newN.Create(&newUserOut) + if err != nil { + return User{}, err + } + + // Dont send a mail if we're testing + if !config.MailerEnabled.GetBool() { + return newUserOut, err + } + + // Send the user a mail with a link to confirm the mail + data := map[string]interface{}{ + "User": newUserOut, + } + + mail.SendMailWithTemplate(user.Email, newUserOut.Username+" + Vikunja = <3", "confirm-email", data) + + return newUserOut, err +} + +// HashPassword hashes a password +func hashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 11) + return string(bytes), err +} + +// UpdateUser updates a user +func UpdateUser(user User) (updatedUser User, err error) { + + // Check if it exists + theUser, err := GetUserByID(user.ID) + if err != nil { + return User{}, err + } + + // Check if we have at least a username + if user.Username == "" { + //return User{}, ErrNoUsername{user.ID} + user.Username = theUser.Username // Dont change the username if we dont have one + } + + user.Password = theUser.Password // set the password to the one in the database to not accedently resetting it + + // Update it + _, err = x.Id(user.ID).Update(user) + if err != nil { + return User{}, err + } + + // Get the newly updated user + updatedUser, err = GetUserByID(user.ID) + if err != nil { + return User{}, err + } + + return updatedUser, err +} + +// UpdateUserPassword updates the password of a user +func UpdateUserPassword(user *User, newPassword string) (err error) { + + if newPassword == "" { + return ErrEmptyNewPassword{} + } + + // Get all user details + theUser, err := GetUserByID(user.ID) + if err != nil { + return err + } + + // Hash the new password and set it + hashed, err := hashPassword(newPassword) + if err != nil { + return err + } + theUser.Password = hashed + + // Update it + _, err = x.Id(user.ID).Update(theUser) + if err != nil { + return err + } + + return err +} + +// DeleteUserByID deletes a user by its ID +func DeleteUserByID(id int64, doer *User) error { + // Check if the id is 0 + if id == 0 { + return ErrIDCannotBeZero{} + } + + // Delete the user + _, err := x.Id(id).Delete(&User{}) + + if err != nil { + return err + } + + // Update the metrics + metrics.UpdateCount(-1, metrics.ActiveUsersKey) + + return err +} diff --git a/pkg/models/user_add_update.go b/pkg/models/user_add_update.go deleted file mode 100644 index 9598d6ed78..0000000000 --- a/pkg/models/user_add_update.go +++ /dev/null @@ -1,181 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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/config" - "code.vikunja.io/api/pkg/mail" - "code.vikunja.io/api/pkg/metrics" - "code.vikunja.io/api/pkg/utils" - "golang.org/x/crypto/bcrypt" -) - -// CreateUser creates a new user and inserts it into the database -func CreateUser(user User) (newUser User, err error) { - - newUser = user - - // Check if we have all needed informations - if newUser.Password == "" || newUser.Username == "" || newUser.Email == "" { - return User{}, ErrNoUsernamePassword{} - } - - // Check if the user already existst with that username - exists := true - existingUser, err := GetUserByUsername(newUser.Username) - if err != nil { - if IsErrUserDoesNotExist(err) { - exists = false - } else { - return User{}, err - } - } - if exists { - return User{}, ErrUsernameExists{newUser.ID, newUser.Username} - } - - // Check if the user already existst with that email - exists = true - existingUser, err = GetUser(User{Email: newUser.Email}) - if err != nil { - if IsErrUserDoesNotExist(err) { - exists = false - } else { - return User{}, err - } - } - if exists { - return User{}, ErrUserEmailExists{existingUser.ID, existingUser.Email} - } - - // Hash the password - newUser.Password, err = hashPassword(user.Password) - if err != nil { - return User{}, err - } - - newUser.IsActive = true - if config.MailerEnabled.GetBool() { - // The new user should not be activated until it confirms his mail address - newUser.IsActive = false - // Generate a confirm token - newUser.EmailConfirmToken = utils.MakeRandomString(400) - } - - // Insert it - _, err = x.Insert(newUser) - if err != nil { - return User{}, err - } - - // Update the metrics - metrics.UpdateCount(1, metrics.ActiveUsersKey) - - // Get the full new User - newUserOut, err := GetUser(newUser) - if err != nil { - return User{}, err - } - - // Create the user's namespace - newN := &Namespace{Name: newUserOut.Username, Description: newUserOut.Username + "'s namespace.", Owner: newUserOut} - err = newN.Create(&newUserOut) - if err != nil { - return User{}, err - } - - // Dont send a mail if we're testing - if !config.MailerEnabled.GetBool() { - return newUserOut, err - } - - // Send the user a mail with a link to confirm the mail - data := map[string]interface{}{ - "User": newUserOut, - } - - mail.SendMailWithTemplate(user.Email, newUserOut.Username+" + Vikunja = <3", "confirm-email", data) - - return newUserOut, err -} - -// HashPassword hashes a password -func hashPassword(password string) (string, error) { - bytes, err := bcrypt.GenerateFromPassword([]byte(password), 11) - return string(bytes), err -} - -// UpdateUser updates a user -func UpdateUser(user User) (updatedUser User, err error) { - - // Check if it exists - theUser, err := GetUserByID(user.ID) - if err != nil { - return User{}, err - } - - // Check if we have at least a username - if user.Username == "" { - //return User{}, ErrNoUsername{user.ID} - user.Username = theUser.Username // Dont change the username if we dont have one - } - - user.Password = theUser.Password // set the password to the one in the database to not accedently resetting it - - // Update it - _, err = x.Id(user.ID).Update(user) - if err != nil { - return User{}, err - } - - // Get the newly updated user - updatedUser, err = GetUserByID(user.ID) - if err != nil { - return User{}, err - } - - return updatedUser, err -} - -// UpdateUserPassword updates the password of a user -func UpdateUserPassword(user *User, newPassword string) (err error) { - - if newPassword == "" { - return ErrEmptyNewPassword{} - } - - // Get all user details - theUser, err := GetUserByID(user.ID) - if err != nil { - return err - } - - // Hash the new password and set it - hashed, err := hashPassword(newPassword) - if err != nil { - return err - } - theUser.Password = hashed - - // Update it - _, err = x.Id(user.ID).Update(theUser) - if err != nil { - return err - } - - return err -} diff --git a/pkg/models/user_delete.go b/pkg/models/user_delete.go deleted file mode 100644 index 077ba84e25..0000000000 --- a/pkg/models/user_delete.go +++ /dev/null @@ -1,39 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018 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/metrics" - -// DeleteUserByID deletes a user by its ID -func DeleteUserByID(id int64, doer *User) error { - // Check if the id is 0 - if id == 0 { - return ErrIDCannotBeZero{} - } - - // Delete the user - _, err := x.Id(id).Delete(&User{}) - - if err != nil { - return err - } - - // Update the metrics - metrics.UpdateCount(-1, metrics.ActiveUsersKey) - - return err -}