Add parsing filter query parameters into task filters
continuous-integration/drone/pr Build is failing
Details
continuous-integration/drone/pr Build is failing
Details
Signed-off-by: kolaente <k@knt.li>
This commit is contained in:
parent
3842feef49
commit
cbb7c53dd1
|
@ -653,6 +653,60 @@ func (err ErrTaskCommentDoesNotExist) HTTPError() web.HTTPError {
|
|||
}
|
||||
}
|
||||
|
||||
// ErrInvalidTaskField represents an error where the provided task field is invalid
|
||||
type ErrInvalidTaskField struct {
|
||||
TaskField string
|
||||
}
|
||||
|
||||
// IsErrInvalidTaskField checks if an error is ErrInvalidTaskField.
|
||||
func IsErrInvalidTaskField(err error) bool {
|
||||
_, ok := err.(ErrInvalidTaskField)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidTaskField) Error() string {
|
||||
return fmt.Sprintf("Task Field is invalid [TaskField: %s]", err.TaskField)
|
||||
}
|
||||
|
||||
// ErrCodeInvalidTaskField holds the unique world-error code of this error
|
||||
const ErrCodeInvalidTaskField = 4016
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidTaskField) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeInvalidTaskField,
|
||||
Message: fmt.Sprintf("The task field '%s' is invalid.", err.TaskField),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrInvalidTaskFilterComparator represents an error where the provided task field is invalid
|
||||
type ErrInvalidTaskFilterComparator struct {
|
||||
Comparator taskFilterComparator
|
||||
}
|
||||
|
||||
// IsErrInvalidTaskFilterComparator checks if an error is ErrInvalidTaskFilterComparator.
|
||||
func IsErrInvalidTaskFilterComparator(err error) bool {
|
||||
_, ok := err.(ErrInvalidTaskFilterComparator)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidTaskFilterComparator) Error() string {
|
||||
return fmt.Sprintf("Task filter comparator is invalid [Comparator: %s]", err.Comparator)
|
||||
}
|
||||
|
||||
// ErrCodeInvalidTaskFilterComparator holds the unique world-error code of this error
|
||||
const ErrCodeInvalidTaskFilterComparator = 4017
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidTaskFilterComparator) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeInvalidTaskFilterComparator,
|
||||
Message: fmt.Sprintf("The task filter comparator '%s' is invalid.", err.Comparator),
|
||||
}
|
||||
}
|
||||
|
||||
// =================
|
||||
// Namespace errors
|
||||
// =================
|
||||
|
|
|
@ -43,6 +43,9 @@ type TaskCollection struct {
|
|||
// The value of the field name to filter by
|
||||
FilterValue []string `query:"filter_value"`
|
||||
FilterValueArr []string `query:"filter_value[]"`
|
||||
// The comparator for field and value
|
||||
FilterComparator []string `query:"filter_comparator"`
|
||||
FilterComparatorArr []string `query:"filter_comparator[]"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -70,7 +73,7 @@ func validateTaskField(fieldName string) error {
|
|||
taskPropertyUpdated:
|
||||
return nil
|
||||
}
|
||||
return ErrInvalidSortParam{SortBy: sp.sortBy}
|
||||
return ErrInvalidTaskField{TaskField: fieldName}
|
||||
|
||||
}
|
||||
|
||||
|
@ -110,6 +113,10 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i
|
|||
tf.FilterValue = append(tf.FilterValue, tf.FilterValueArr...)
|
||||
}
|
||||
|
||||
if len(tf.FilterComparatorArr) > 0 {
|
||||
tf.FilterValue = append(tf.FilterValue, tf.FilterComparatorArr...)
|
||||
}
|
||||
|
||||
var sort = make([]*sortParam, 0, len(tf.SortBy))
|
||||
for i, s := range tf.SortBy {
|
||||
param := &sortParam{
|
||||
|
@ -128,6 +135,31 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i
|
|||
sort = append(sort, param)
|
||||
}
|
||||
|
||||
var filters = make([]*taskFilter, 0, len(tf.FilterBy))
|
||||
for i, f := range tf.FilterBy {
|
||||
filter := &taskFilter{
|
||||
field: f,
|
||||
}
|
||||
|
||||
if len(tf.FilterValue) > i {
|
||||
filter.value = tf.FilterValue[i]
|
||||
}
|
||||
|
||||
if len(tf.FilterComparator) > i {
|
||||
filter.comparator, err = getFilterComparatorFromString(tf.FilterComparator[i])
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
err = filter.validate()
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
filters = append(filters, filter)
|
||||
}
|
||||
|
||||
taskopts := &taskOptions{
|
||||
search: search,
|
||||
startDate: time.Unix(tf.StartDateSortUnix, 0),
|
||||
|
@ -135,6 +167,7 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i
|
|||
page: page,
|
||||
perPage: perPage,
|
||||
sortby: sort,
|
||||
filters: filters,
|
||||
}
|
||||
|
||||
shareAuth, is := a.(*LinkSharing)
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2020 Vikunja and contriubtors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja 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.
|
||||
//
|
||||
// Vikunja 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 Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package models
|
||||
|
||||
type taskFilterComparator string
|
||||
|
||||
const (
|
||||
taskFilterComparatorInvalid taskFilterComparator = "invalid"
|
||||
|
||||
taskFilterComparatorEquals taskFilterComparator = "="
|
||||
taskFilterComparatorGreater taskFilterComparator = ">"
|
||||
taskFilterComparatorGreateEquals taskFilterComparator = ">="
|
||||
taskFilterComparatorLess taskFilterComparator = "<"
|
||||
taskFilterComparatorLessEquals taskFilterComparator = "<="
|
||||
)
|
||||
|
||||
type taskFilter struct {
|
||||
field string
|
||||
value string
|
||||
comparator taskFilterComparator
|
||||
}
|
||||
|
||||
func (tf *taskFilter) validate() (err error) {
|
||||
err = validateTaskField(tf.field)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch tf.comparator {
|
||||
case
|
||||
taskFilterComparatorEquals,
|
||||
taskFilterComparatorGreater,
|
||||
taskFilterComparatorGreateEquals,
|
||||
taskFilterComparatorLess,
|
||||
taskFilterComparatorLessEquals:
|
||||
return nil
|
||||
default:
|
||||
return ErrInvalidTaskFilterComparator{Comparator: tf.comparator}
|
||||
}
|
||||
}
|
||||
|
||||
func getFilterComparatorFromString(comparator string) (taskFilterComparator, error) {
|
||||
switch comparator {
|
||||
case "equals":
|
||||
return taskFilterComparatorEquals, nil
|
||||
case "greater":
|
||||
return taskFilterComparatorGreater, nil
|
||||
case "greater_equals":
|
||||
return taskFilterComparatorGreateEquals, nil
|
||||
case "less":
|
||||
return taskFilterComparatorLess, nil
|
||||
case "less_equals":
|
||||
return taskFilterComparatorLessEquals, nil
|
||||
default:
|
||||
return taskFilterComparatorInvalid, ErrInvalidTaskFilterComparator{Comparator: taskFilterComparator(comparator)}
|
||||
}
|
||||
}
|
|
@ -35,23 +35,23 @@ type (
|
|||
)
|
||||
|
||||
const (
|
||||
taskPropertyID sortProperty = "id"
|
||||
taskPropertyText sortProperty = "text"
|
||||
taskPropertyDescription sortProperty = "description"
|
||||
taskPropertyDone sortProperty = "done"
|
||||
taskPropertyDoneAtUnix sortProperty = "done_at_unix"
|
||||
taskPropertyDueDateUnix sortProperty = "due_date_unix"
|
||||
taskPropertyCreatedByID sortProperty = "created_by_id"
|
||||
taskPropertyListID sortProperty = "list_id"
|
||||
taskPropertyRepeatAfter sortProperty = "repeat_after"
|
||||
taskPropertyPriority sortProperty = "priority"
|
||||
taskPropertyStartDateUnix sortProperty = "start_date_unix"
|
||||
taskPropertyEndDateUnix sortProperty = "end_date_unix"
|
||||
taskPropertyHexColor sortProperty = "hex_color"
|
||||
taskPropertyPercentDone sortProperty = "percent_done"
|
||||
taskPropertyUID sortProperty = "uid"
|
||||
taskPropertyCreated sortProperty = "created"
|
||||
taskPropertyUpdated sortProperty = "updated"
|
||||
taskPropertyID string = "id"
|
||||
taskPropertyText string = "text"
|
||||
taskPropertyDescription string = "description"
|
||||
taskPropertyDone string = "done"
|
||||
taskPropertyDoneAtUnix string = "done_at_unix"
|
||||
taskPropertyDueDateUnix string = "due_date_unix"
|
||||
taskPropertyCreatedByID string = "created_by_id"
|
||||
taskPropertyListID string = "list_id"
|
||||
taskPropertyRepeatAfter string = "repeat_after"
|
||||
taskPropertyPriority string = "priority"
|
||||
taskPropertyStartDateUnix string = "start_date_unix"
|
||||
taskPropertyEndDateUnix string = "end_date_unix"
|
||||
taskPropertyHexColor string = "hex_color"
|
||||
taskPropertyPercentDone string = "percent_done"
|
||||
taskPropertyUID string = "uid"
|
||||
taskPropertyCreated string = "created"
|
||||
taskPropertyUpdated string = "updated"
|
||||
)
|
||||
|
||||
func (p sortProperty) String() string {
|
||||
|
@ -82,28 +82,7 @@ func (sp *sortParam) validate() error {
|
|||
if sp.orderBy != orderDescending && sp.orderBy != orderAscending {
|
||||
return ErrInvalidSortOrder{OrderBy: sp.orderBy}
|
||||
}
|
||||
switch sp.sortBy {
|
||||
case
|
||||
taskPropertyID,
|
||||
taskPropertyText,
|
||||
taskPropertyDescription,
|
||||
taskPropertyDone,
|
||||
taskPropertyDoneAtUnix,
|
||||
taskPropertyDueDateUnix,
|
||||
taskPropertyCreatedByID,
|
||||
taskPropertyListID,
|
||||
taskPropertyRepeatAfter,
|
||||
taskPropertyPriority,
|
||||
taskPropertyStartDateUnix,
|
||||
taskPropertyEndDateUnix,
|
||||
taskPropertyHexColor,
|
||||
taskPropertyPercentDone,
|
||||
taskPropertyUID,
|
||||
taskPropertyCreated,
|
||||
taskPropertyUpdated:
|
||||
return nil
|
||||
}
|
||||
return ErrInvalidSortParam{SortBy: sp.sortBy}
|
||||
return validateTaskField(string(sp.sortBy))
|
||||
}
|
||||
|
||||
type taskComparator func(lhs, rhs *Task) int64
|
||||
|
@ -168,7 +147,7 @@ func mustMakeComparator(fieldName string) taskComparator {
|
|||
// This is a map of properties that can be sorted by
|
||||
// and their appropriate comparator function.
|
||||
// The comparator function sorts in ascending mode.
|
||||
var propertyComparators = map[sortProperty]taskComparator{
|
||||
var propertyComparators = map[string]taskComparator{
|
||||
taskPropertyID: mustMakeComparator("ID"),
|
||||
taskPropertyText: mustMakeComparator("Text"),
|
||||
taskPropertyDescription: mustMakeComparator("Description"),
|
||||
|
@ -208,13 +187,13 @@ func sortTasks(tasks []*Task, by []*sortParam) {
|
|||
// If we would not do this, we would get a different order for items with the same content every time
|
||||
// the slice is sorted. To circumvent this, we always order at least by ID.
|
||||
if len(by) == 0 ||
|
||||
(len(by) > 0 && by[len(by)-1].sortBy != taskPropertyID) { // Don't sort by ID last if the id parameter is already passed as the last parameter.
|
||||
by = append(by, &sortParam{sortBy: taskPropertyID, orderBy: orderAscending})
|
||||
(len(by) > 0 && by[len(by)-1].sortBy != sortProperty(taskPropertyID)) { // Don't sort by ID last if the id parameter is already passed as the last parameter.
|
||||
by = append(by, &sortParam{sortBy: sortProperty(taskPropertyID), orderBy: orderAscending})
|
||||
}
|
||||
|
||||
comparators := make([]taskComparator, 0, len(by))
|
||||
for _, param := range by {
|
||||
comparator, ok := propertyComparators[param.sortBy]
|
||||
comparator, ok := propertyComparators[string(param.sortBy)]
|
||||
if !ok {
|
||||
panic("No suitable comparator for sortBy found! Param was " + param.sortBy)
|
||||
}
|
||||
|
|
|
@ -116,6 +116,7 @@ type taskOptions struct {
|
|||
page int
|
||||
perPage int
|
||||
sortby []*sortParam
|
||||
filters []*taskFilter
|
||||
}
|
||||
|
||||
// ReadAll is a dummy function to still have that endpoint documented
|
||||
|
|
Loading…
Reference in New Issue