db2d868eed
Remove traces of unix timestamp Revert renaming reminder table column Fix staticcheck Remove unused table call Add migration for renaming reminders table Fix issues with using TimeStamp Fix lint Updated all created / updated fields to use TimeStamps Add comments Convert all created / updated fields to datetime Add time util package Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/130
241 lines
7.3 KiB
Go
241 lines
7.3 KiB
Go
// Vikunja is a to-do list application to facilitate your life.
|
|
// Copyright 2018-2020 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 <https://www.gnu.org/licenses/>.
|
|
|
|
package models
|
|
|
|
import (
|
|
"code.vikunja.io/api/pkg/timeutil"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
)
|
|
|
|
type (
|
|
sortParam struct {
|
|
sortBy sortProperty
|
|
orderBy sortOrder // asc or desc
|
|
}
|
|
|
|
sortProperty string
|
|
|
|
sortOrder string
|
|
)
|
|
|
|
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"
|
|
)
|
|
|
|
func (p sortProperty) String() string {
|
|
return string(p)
|
|
}
|
|
|
|
const (
|
|
orderInvalid sortOrder = "invalid"
|
|
orderAscending sortOrder = "asc"
|
|
orderDescending sortOrder = "desc"
|
|
)
|
|
|
|
func (o sortOrder) String() string {
|
|
return string(o)
|
|
}
|
|
|
|
func getSortOrderFromString(s string) sortOrder {
|
|
if s == "asc" {
|
|
return orderAscending
|
|
}
|
|
if s == "desc" {
|
|
return orderDescending
|
|
}
|
|
return orderInvalid
|
|
}
|
|
|
|
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}
|
|
}
|
|
|
|
type taskComparator func(lhs, rhs *Task) int64
|
|
|
|
func mustMakeComparator(fieldName string) taskComparator {
|
|
field, ok := reflect.TypeOf(&Task{}).Elem().FieldByName(fieldName)
|
|
if !ok {
|
|
panic(fmt.Sprintf("Field '%s' has not been found on Task", fieldName))
|
|
}
|
|
|
|
extractProp := func(task *Task) interface{} {
|
|
return reflect.ValueOf(task).Elem().FieldByIndex(field.Index).Interface()
|
|
}
|
|
|
|
// Special case for handling TimeStamp types
|
|
if field.Type.Name() == "TimeStamp" {
|
|
return func(lhs, rhs *Task) int64 {
|
|
return int64(extractProp(lhs).(timeutil.TimeStamp)) - int64(extractProp(rhs).(timeutil.TimeStamp))
|
|
}
|
|
}
|
|
|
|
switch field.Type.Kind() {
|
|
case reflect.Int64:
|
|
return func(lhs, rhs *Task) int64 {
|
|
return extractProp(lhs).(int64) - extractProp(rhs).(int64)
|
|
}
|
|
case reflect.Float64:
|
|
return func(lhs, rhs *Task) int64 {
|
|
floatLHS, floatRHS := extractProp(lhs).(float64), extractProp(rhs).(float64)
|
|
if floatLHS > floatRHS {
|
|
return 1
|
|
} else if floatLHS < floatRHS {
|
|
return -1
|
|
}
|
|
return 0
|
|
}
|
|
case reflect.String:
|
|
return func(lhs, rhs *Task) int64 {
|
|
strLHS, strRHS := extractProp(lhs).(string), extractProp(rhs).(string)
|
|
if strLHS > strRHS {
|
|
return 1
|
|
} else if strLHS < strRHS {
|
|
return -1
|
|
}
|
|
return 0
|
|
}
|
|
case reflect.Bool:
|
|
return func(lhs, rhs *Task) int64 {
|
|
boolLHS, boolRHS := extractProp(lhs).(bool), extractProp(rhs).(bool)
|
|
if !boolLHS && boolRHS {
|
|
return -1
|
|
} else if boolLHS && !boolRHS {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("Unsupported type for sorting: %s", field.Type.Name()))
|
|
}
|
|
}
|
|
|
|
// 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{
|
|
taskPropertyID: mustMakeComparator("ID"),
|
|
taskPropertyText: mustMakeComparator("Text"),
|
|
taskPropertyDescription: mustMakeComparator("Description"),
|
|
taskPropertyDone: mustMakeComparator("Done"),
|
|
taskPropertyDoneAtUnix: mustMakeComparator("DoneAt"),
|
|
taskPropertyDueDateUnix: mustMakeComparator("DueDate"),
|
|
taskPropertyCreatedByID: mustMakeComparator("CreatedByID"),
|
|
taskPropertyListID: mustMakeComparator("ListID"),
|
|
taskPropertyRepeatAfter: mustMakeComparator("RepeatAfter"),
|
|
taskPropertyPriority: mustMakeComparator("Priority"),
|
|
taskPropertyStartDateUnix: mustMakeComparator("StartDate"),
|
|
taskPropertyEndDateUnix: mustMakeComparator("EndDate"),
|
|
taskPropertyHexColor: mustMakeComparator("HexColor"),
|
|
taskPropertyPercentDone: mustMakeComparator("PercentDone"),
|
|
taskPropertyUID: mustMakeComparator("UID"),
|
|
taskPropertyCreated: mustMakeComparator("Created"),
|
|
taskPropertyUpdated: mustMakeComparator("Updated"),
|
|
}
|
|
|
|
// Creates a taskComparator that sorts by the first comparator and falls back to
|
|
// the second one (and so on...) if the properties were equal.
|
|
func combineComparators(comparators ...taskComparator) taskComparator {
|
|
return func(lhs, rhs *Task) int64 {
|
|
for _, compare := range comparators {
|
|
res := compare(lhs, rhs)
|
|
if res != 0 {
|
|
return res
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func sortTasks(tasks []*Task, by []*sortParam) {
|
|
|
|
// Always sort at least by id asc so we have a consistent order of items every time
|
|
// 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})
|
|
}
|
|
|
|
comparators := make([]taskComparator, 0, len(by))
|
|
for _, param := range by {
|
|
comparator, ok := propertyComparators[param.sortBy]
|
|
if !ok {
|
|
panic("No suitable comparator for sortBy found! Param was " + param.sortBy)
|
|
}
|
|
|
|
// This is a descending sort, so we need to negate the comparator (i.e. switch the inputs).
|
|
if param.orderBy == orderDescending {
|
|
oldComparator := comparator
|
|
comparator = func(lhs, rhs *Task) int64 {
|
|
return oldComparator(lhs, rhs) * -1
|
|
}
|
|
}
|
|
|
|
comparators = append(comparators, comparator)
|
|
}
|
|
|
|
combinedComparator := combineComparators(comparators...)
|
|
|
|
sort.Slice(tasks, func(i, j int) bool {
|
|
lhs, rhs := tasks[i], tasks[j]
|
|
|
|
res := combinedComparator(lhs, rhs)
|
|
return res <= 0
|
|
})
|
|
}
|