2020-12-29 01:04:20 +00:00
// Vikunja is a to-do list application to facilitate your life.
2023-09-01 06:32:28 +00:00
// Copyright 2018-present Vikunja and contributors. All rights reserved.
2019-11-29 22:59:20 +00:00
//
2020-12-29 01:04:20 +00:00
// This program is free software: you can redistribute it and/or modify
2020-12-23 15:41:52 +00:00
// it under the terms of the GNU Affero General Public Licensee as published by
2019-11-29 22:59:20 +00:00
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
2020-12-29 01:04:20 +00:00
// This program is distributed in the hope that it will be useful,
2019-11-29 22:59:20 +00:00
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2020-12-23 15:41:52 +00:00
// GNU Affero General Public Licensee for more details.
2019-11-29 22:59:20 +00:00
//
2020-12-23 15:41:52 +00:00
// You should have received a copy of the GNU Affero General Public Licensee
2020-12-29 01:04:20 +00:00
// along with this program. If not, see <https://www.gnu.org/licenses/>.
2019-11-29 22:59:20 +00:00
package models
import (
2020-01-26 17:08:06 +00:00
"code.vikunja.io/api/pkg/user"
2019-11-29 22:59:20 +00:00
"code.vikunja.io/web"
2020-12-23 15:32:28 +00:00
"xorm.io/xorm"
2019-11-29 22:59:20 +00:00
)
// TaskCollection is a struct used to hold filter details and not clutter the Task struct with information not related to actual tasks.
type TaskCollection struct {
2022-12-29 17:49:48 +00:00
ProjectID int64 ` param:"project" json:"-" `
2019-11-29 22:59:20 +00:00
2019-12-07 14:30:51 +00:00
// The query parameter to sort by. This is for ex. done, priority, etc.
2020-09-26 21:02:17 +00:00
SortBy [ ] string ` query:"sort_by" json:"sort_by" `
SortByArr [ ] string ` query:"sort_by[]" json:"-" `
2019-12-07 14:30:51 +00:00
// The query parameter to order the items by. This can be either asc or desc, with asc being the default.
2020-09-26 21:02:17 +00:00
OrderBy [ ] string ` query:"order_by" json:"order_by" `
OrderByArr [ ] string ` query:"order_by[]" json:"-" `
2019-12-07 14:30:51 +00:00
2024-03-11 15:13:42 +00:00
// The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation of the feature.
2023-11-09 12:34:31 +00:00
Filter string ` query:"filter" json:"filter" `
2024-03-11 15:13:42 +00:00
// The time zone which should be used for date match (statements like "now" resolve to different actual times)
FilterTimezone string ` query:"filter_timezone" json:"filter_timezone" `
2023-11-09 12:34:31 +00:00
2020-06-27 17:04:01 +00:00
// If set to true, the result will also include null values
2020-09-26 21:02:17 +00:00
FilterIncludeNulls bool ` query:"filter_include_nulls" json:"filter_include_nulls" `
2020-04-11 14:20:33 +00:00
2019-11-29 22:59:20 +00:00
web . CRUDable ` xorm:"-" json:"-" `
web . Rights ` xorm:"-" json:"-" `
}
2020-04-11 14:20:33 +00:00
func validateTaskField ( fieldName string ) error {
switch fieldName {
case
taskPropertyID ,
2020-05-16 10:17:44 +00:00
taskPropertyTitle ,
2020-04-11 14:20:33 +00:00
taskPropertyDescription ,
taskPropertyDone ,
2020-06-27 17:04:01 +00:00
taskPropertyDoneAt ,
taskPropertyDueDate ,
2020-04-11 14:20:33 +00:00
taskPropertyCreatedByID ,
2022-11-13 16:07:01 +00:00
taskPropertyProjectID ,
2020-04-11 14:20:33 +00:00
taskPropertyRepeatAfter ,
taskPropertyPriority ,
2020-06-27 17:04:01 +00:00
taskPropertyStartDate ,
taskPropertyEndDate ,
2020-04-11 14:20:33 +00:00
taskPropertyHexColor ,
taskPropertyPercentDone ,
taskPropertyUID ,
taskPropertyCreated ,
2020-04-24 15:23:03 +00:00
taskPropertyUpdated ,
2021-03-10 10:59:10 +00:00
taskPropertyPosition ,
2021-07-28 19:06:40 +00:00
taskPropertyKanbanPosition ,
2022-11-09 13:43:31 +00:00
taskPropertyBucketID ,
taskPropertyIndex :
2020-04-11 14:20:33 +00:00
return nil
}
return ErrInvalidTaskField { TaskField : fieldName }
}
2023-08-28 10:14:50 +00:00
func getTaskFilterOptsFromCollection ( tf * TaskCollection ) ( opts * taskSearchOptions , err error ) {
2021-03-10 10:59:10 +00:00
if len ( tf . SortByArr ) > 0 {
tf . SortBy = append ( tf . SortBy , tf . SortByArr ... )
}
if len ( tf . OrderByArr ) > 0 {
tf . OrderBy = append ( tf . OrderBy , tf . OrderByArr ... )
}
var sort = make ( [ ] * sortParam , 0 , len ( tf . SortBy ) )
for i , s := range tf . SortBy {
param := & sortParam {
sortBy : s ,
orderBy : orderAscending ,
}
// This checks if tf.OrderBy has an entry with the same index as the current entry from tf.SortBy
// Taken from https://stackoverflow.com/a/27252199/10924593
if len ( tf . OrderBy ) > i {
param . orderBy = getSortOrderFromString ( tf . OrderBy [ i ] )
}
// Param validation
if err := param . validate ( ) ; err != nil {
return nil , err
}
sort = append ( sort , param )
}
2023-08-28 10:14:50 +00:00
opts = & taskSearchOptions {
2021-03-10 10:59:10 +00:00
sortby : sort ,
filterIncludeNulls : tf . FilterIncludeNulls ,
2023-11-09 12:34:31 +00:00
filter : tf . Filter ,
2024-03-11 15:13:42 +00:00
filterTimezone : tf . FilterTimezone ,
2021-03-10 10:59:10 +00:00
}
2024-03-11 15:13:42 +00:00
opts . parsedFilters , err = getTaskFiltersFromFilterString ( tf . Filter , tf . FilterTimezone )
2021-03-10 10:59:10 +00:00
return opts , err
}
2019-11-29 22:59:20 +00:00
// ReadAll gets all tasks for a collection
2022-11-13 16:07:01 +00:00
// @Summary Get tasks in a project
// @Description Returns all tasks for the current project.
2019-11-29 22:59:20 +00:00
// @tags task
// @Accept json
// @Produce json
2023-11-16 12:06:53 +00:00
// @Param id path int true "The project ID."
2019-11-29 22:59:20 +00:00
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search tasks by task text."
2022-11-13 16:07:01 +00:00
// @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `project_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`."
2019-12-07 14:30:51 +00:00
// @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`."
2024-03-09 17:25:14 +00:00
// @Param filter query string false "The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation of the feature."
2024-03-11 15:13:42 +00:00
// @Param filter_timezone query string false "The time zone which should be used for date match (statements like "now" resolve to different actual times)"
2020-06-27 17:04:01 +00:00
// @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`."
2019-11-29 22:59:20 +00:00
// @Security JWTKeyAuth
// @Success 200 {array} models.Task "The tasks"
// @Failure 500 {object} models.Message "Internal error"
2023-11-16 12:06:53 +00:00
// @Router /projects/{id}/tasks [get]
2020-12-23 15:32:28 +00:00
func ( tf * TaskCollection ) ReadAll ( s * xorm . Session , a web . Auth , search string , page int , perPage int ) ( result interface { } , resultCount int , totalItems int64 , err error ) {
2019-12-07 14:30:51 +00:00
2022-11-13 16:07:01 +00:00
// If the project id is < -1 this means we're dealing with a saved filter - in that case we get and populate the filter
// -1 is the favorites project which works as intended
if tf . ProjectID < - 1 {
sf , err := getSavedFilterSimpleByID ( s , getSavedFilterIDFromProjectID ( tf . ProjectID ) )
2020-09-26 21:02:17 +00:00
if err != nil {
return nil , 0 , 0 , err
}
2023-06-21 12:59:40 +00:00
// By prepending sort options before the saved ones from the filter, we make sure the supplied sort
// options via query take precedence over the rest.
sortby := append ( tf . SortBy , tf . SortByArr ... )
sortby = append ( sortby , sf . Filters . SortBy ... )
sortby = append ( sortby , sf . Filters . SortByArr ... )
orderby := append ( tf . OrderBy , tf . OrderByArr ... )
orderby = append ( orderby , sf . Filters . OrderBy ... )
orderby = append ( orderby , sf . Filters . OrderByArr ... )
sf . Filters . SortBy = sortby
sf . Filters . SortByArr = nil
sf . Filters . OrderBy = orderby
sf . Filters . OrderByArr = nil
2021-11-14 20:03:55 +00:00
2020-12-23 15:32:28 +00:00
return sf . getTaskCollection ( ) . ReadAll ( s , a , search , page , perPage )
2020-09-26 21:02:17 +00:00
}
2021-03-10 10:59:10 +00:00
taskopts , err := getTaskFilterOptsFromCollection ( tf )
2020-04-11 14:20:33 +00:00
if err != nil {
2021-03-10 10:59:10 +00:00
return nil , 0 , 0 , err
2019-11-29 22:59:20 +00:00
}
2021-03-10 10:59:10 +00:00
taskopts . search = search
taskopts . page = page
taskopts . perPage = perPage
2019-11-29 22:59:20 +00:00
shareAuth , is := a . ( * LinkSharing )
if is {
2022-11-13 16:07:01 +00:00
project , err := GetProjectSimpleByID ( s , shareAuth . ProjectID )
2019-11-29 22:59:20 +00:00
if err != nil {
return nil , 0 , 0 , err
}
2022-11-13 16:07:01 +00:00
return getTasksForProjects ( s , [ ] * Project { project } , a , taskopts )
2019-11-29 22:59:20 +00:00
}
2022-11-13 16:07:01 +00:00
// If the project ID is not set, we get all tasks for the user.
2019-11-29 22:59:20 +00:00
// This allows to use this function in Task.ReadAll with a possibility to deprecate the latter at some point.
2022-12-29 17:49:48 +00:00
var projects [ ] * Project
2022-11-13 16:07:01 +00:00
if tf . ProjectID == 0 {
2023-01-07 11:41:11 +00:00
projects , _ , _ , err = getRawProjectsForUser (
2020-12-23 15:32:28 +00:00
s ,
2022-11-13 16:07:01 +00:00
& projectOptions {
2020-12-23 15:32:28 +00:00
user : & user . User { ID : a . GetID ( ) } ,
page : - 1 ,
} ,
)
2019-11-29 22:59:20 +00:00
if err != nil {
return nil , 0 , 0 , err
}
} else {
2022-12-29 17:54:06 +00:00
// Check the project exists and the user has access on it
2022-11-13 16:07:01 +00:00
project := & Project { ID : tf . ProjectID }
canRead , _ , err := project . CanRead ( s , a )
2019-11-29 22:59:20 +00:00
if err != nil {
return nil , 0 , 0 , err
}
if ! canRead {
2024-01-31 12:50:07 +00:00
return nil , 0 , 0 , ErrUserDoesNotHaveAccessToProject {
ProjectID : tf . ProjectID ,
UserID : a . GetID ( ) ,
}
2019-11-29 22:59:20 +00:00
}
2022-12-29 17:49:48 +00:00
projects = [ ] * Project { { ID : tf . ProjectID } }
2019-11-29 22:59:20 +00:00
}
2022-12-29 17:49:48 +00:00
return getTasksForProjects ( s , projects , a , taskopts )
2019-11-29 22:59:20 +00:00
}