From d8399e374c862e6593e8eba7554e3b1689d106e0 Mon Sep 17 00:00:00 2001 From: konrad Date: Sat, 7 Dec 2019 14:30:51 +0000 Subject: [PATCH] Sort Order for tasks (#110) --- docs/content/doc/usage/errors.md | 2 + go.mod | 13 +- go.sum | 119 +-- pkg/integrations/task_collection_test.go | 51 +- pkg/models/error.go | 54 ++ pkg/models/label.go | 1 - pkg/models/task_collection.go | 52 +- pkg/models/task_collection_sort.go | 233 ++++++ pkg/models/task_collection_sort_test.go | 829 +++++++++++++++++++ pkg/models/task_collection_test.go | 635 ++++++++++++++ pkg/models/task_readall_test.go | 728 ---------------- pkg/models/tasks.go | 58 +- pkg/swagger/docs.go | 35 +- pkg/swagger/swagger.json | 33 +- pkg/swagger/swagger.yaml | 36 +- vendor/github.com/mohae/deepcopy/.gitignore | 26 + vendor/github.com/mohae/deepcopy/.travis.yml | 11 + vendor/github.com/mohae/deepcopy/LICENSE | 21 + vendor/github.com/mohae/deepcopy/README.md | 8 + vendor/github.com/mohae/deepcopy/deepcopy.go | 125 +++ vendor/github.com/prometheus/procfs/go.mod | 2 - vendor/modules.txt | 2 + 22 files changed, 2080 insertions(+), 994 deletions(-) create mode 100644 pkg/models/task_collection_sort.go create mode 100644 pkg/models/task_collection_sort_test.go create mode 100644 pkg/models/task_collection_test.go delete mode 100644 pkg/models/task_readall_test.go create mode 100644 vendor/github.com/mohae/deepcopy/.gitignore create mode 100644 vendor/github.com/mohae/deepcopy/.travis.yml create mode 100644 vendor/github.com/mohae/deepcopy/LICENSE create mode 100644 vendor/github.com/mohae/deepcopy/README.md create mode 100644 vendor/github.com/mohae/deepcopy/deepcopy.go diff --git a/docs/content/doc/usage/errors.md b/docs/content/doc/usage/errors.md index 6d926784..cc89e1b4 100644 --- a/docs/content/doc/usage/errors.md +++ b/docs/content/doc/usage/errors.md @@ -45,6 +45,8 @@ This document describes the different errors Vikunja can return. | 4010 | 400 | Cannot relate a task with itself. | | 4011 | 404 | The task attachment does not exist. | | 4012 | 400 | The task attachment is too large. | +| 4013 | 400 | The task sort param is invalid. | +| 4014 | 400 | The task sort order is invalid. | | 5001 | 404 | The namspace does not exist. | | 5003 | 403 | The user does not have access to the specified namespace. | | 5006 | 400 | The namespace name cannot be empty. | diff --git a/go.mod b/go.mod index 539a2bf1..84f5fcbd 100644 --- a/go.mod +++ b/go.mod @@ -25,15 +25,12 @@ require ( github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae github.com/client9/misspell v0.3.4 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect - github.com/creack/pty v1.1.9 // indirect github.com/d4l3k/messagediff v1.2.1 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 github.com/garyburd/redigo v1.6.0 // indirect - github.com/gin-gonic/gin v1.5.0 // indirect github.com/go-openapi/jsonreference v0.19.3 // indirect github.com/go-openapi/spec v0.19.4 // indirect - github.com/go-playground/universal-translator v0.17.0 // indirect github.com/go-redis/redis v6.15.2+incompatible github.com/go-sql-driver/mysql v1.4.1 github.com/go-xorm/builder v0.3.4 @@ -41,22 +38,21 @@ require ( github.com/go-xorm/tests v0.5.6 // indirect github.com/go-xorm/xorm v0.7.1 github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2 + github.com/golang/protobuf v1.3.2 // indirect github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc github.com/imdario/mergo v0.3.7 github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb - github.com/json-iterator/go v1.1.8 // indirect - github.com/kr/pty v1.1.8 // indirect github.com/labstack/echo/v4 v4.1.11 github.com/labstack/gommon v0.3.0 github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef - github.com/leodido/go-urn v1.2.0 // indirect github.com/mailru/easyjson v0.7.0 // indirect github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.10 // indirect github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 // indirect github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mattn/go-sqlite3 v1.10.0 + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/olekukonko/tablewriter v0.0.1 github.com/onsi/ginkgo v1.7.0 // indirect github.com/onsi/gomega v1.4.3 // indirect @@ -79,15 +75,12 @@ require ( golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1 golang.org/x/lint v0.0.0-20190409202823-959b441ac422 golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d // indirect - golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect google.golang.org/appengine v1.5.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/d4l3k/messagediff.v1 v1.2.1 - gopkg.in/go-playground/validator.v9 v9.30.2 // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/testfixtures.v2 v2.5.3 gopkg.in/yaml.v2 v2.2.7 // indirect @@ -97,3 +90,5 @@ require ( ) replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7 + +go 1.13 diff --git a/go.sum b/go.sum index 37ba03dd..f591dffc 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,6 @@ cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -code.vikunja.io/web v0.0.0-20190507193736-edb39812af9c h1:L8aPCsaLQe9qytRavkRqipse64EbDK8mFijm+9SKf7I= -code.vikunja.io/web v0.0.0-20190507193736-edb39812af9c/go.mod h1:9dOotUqYZJhDhimNh4Xo4e2i+8cR+qPFEQNCUzaplsI= -code.vikunja.io/web v0.0.0-20190628071027-b5c16e24b0a7 h1:P9ncMaJE7RbYqBXF9lwT0hab7EPwuHOPslz3k1VxFs8= -code.vikunja.io/web v0.0.0-20190628071027-b5c16e24b0a7/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20190628075253-b457b5a1a332 h1:gXxyLkjhgN+vqrLvPyqyScyG5fbu44FJp61TvntWM24= -code.vikunja.io/web v0.0.0-20190628075253-b457b5a1a332/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191021211916-f7834b02a174 h1:hBY+r6bzGEfHxolaXbiVoz2LBNNnyHZK7d7Ga4Jowu8= -code.vikunja.io/web v0.0.0-20191021211916-f7834b02a174/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191022193355-23a3d145177a h1:exDC9eZ+SK0GT3zB/5f3OBahWzbTZlvX9OfZWgqlbeI= -code.vikunja.io/web v0.0.0-20191022193355-23a3d145177a/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191022195605-8edfc5d33c79 h1:U2px27G/b082nUu8vO21wFNKF9BM+5YQJj4XRZiyn2I= -code.vikunja.io/web v0.0.0-20191022195605-8edfc5d33c79/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191023144416-3ee093147b6d h1:zhNidbAwqJSnkql03i7aHDUMyQo1vM8yR1Ks495FKvc= -code.vikunja.io/web v0.0.0-20191023144416-3ee093147b6d/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191023145656-bce8b505205d h1:Fw5eiTr4p82l4PLaML1ARgx3fjyebxVNvPsCz727brk= -code.vikunja.io/web v0.0.0-20191023145656-bce8b505205d/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191023190415-502bbbbd9dfa h1:rtYKpdT/6wGgxGNFUzl9Q/AHgS778+rSC20AcBPNu/I= -code.vikunja.io/web v0.0.0-20191023190415-502bbbbd9dfa/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= code.vikunja.io/web v0.0.0-20191023202526-f337750c3573 h1:q+nf3ao4vLpoAaksuk6lkRAMAcD2grOPNj/HwjejLl4= code.vikunja.io/web v0.0.0-20191023202526-f337750c3573/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -31,8 +13,6 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -55,9 +35,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cweill/gotests v1.5.3 h1:k3t4wW/x/YNixWZJhUIn+mivmK5iV1tJVOwVYkx0UcU= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -81,42 +58,27 @@ github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NB github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.0 h1:FTUMcX77w5rQkClIzDtTxvn6Bsa894CcrzNj2MMfeg8= -github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8= -github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-redis/redis v6.14.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= @@ -147,7 +109,6 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY= github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -169,63 +130,42 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible h1:PkEEpmbrFXlMul8cOplR8nkcIM/NDbx+H6fq2+vaKAA= github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw= -github.com/kolaente/echo/v4 v4.0.0-20190507190305-3725a216d803/go.mod h1:3LbYC6VkwmUnmLPZ8WFdHdQHG77e9GQbjyhWdb1QvC4= -github.com/kolaente/echo/v4 v4.0.0-20190621113036-3b0700f6d073 h1:vOueKBVhcaAusqPDK/g7lRYOwqfQ0YI3oLrBV+iGZV8= -github.com/kolaente/echo/v4 v4.0.0-20190621113036-3b0700f6d073/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= -github.com/kolaente/echo/v4 v4.0.0-20190622213746-34000ea0f7d3 h1:5RyXbSrJtkFl1EGFgyDYwiBQMbj8sMtP33aFHnT+5YE= -github.com/kolaente/echo/v4 v4.0.0-20190622213746-34000ea0f7d3/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= -github.com/labstack/echo/v4 v4.1.5/go.mod h1:3LbYC6VkwmUnmLPZ8WFdHdQHG77e9GQbjyhWdb1QvC4= -github.com/labstack/echo/v4 v4.1.6 h1:WOvLa4T1KzWCRpANwz0HGgWDelXSSGwIKtKBbFdHTv4= -github.com/labstack/echo/v4 v4.1.6/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f h1:fNJtR+TNyxTdYCZU40fc8Or8RyBqMOKYNv+Zay5gjvk= github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= -github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= -github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU= github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4= github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef/go.mod h1:4LATl0uhhtytR6p9n1AlktDyIz4u2iUnWEdI3L/hXiw= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983 h1:wL11wNW7dhKIcRCHSm4sHKPWz0tt4mwBsVodG7+Xyqg= -github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= @@ -245,10 +185,11 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -314,31 +255,20 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= -github.com/swaggo/gin-swagger v1.1.0/go.mod h1:FQlm07YuT1glfN3hQiO11UQ2m39vOCZ/aa3WWr5E+XU= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= -github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM= -github.com/swaggo/swag v1.5.0 h1:haK8VG3hj+v/c8hQ4f3U+oYpkdI/26m9LAUTXHOv+2U= -github.com/swaggo/swag v1.5.0/go.mod h1:+xZrnu5Ut3GcUkKAJm9spnOooIS1WB1cUOkLNPrvrE0= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.6.3 h1:N+uVPGP4H2hXoss2pt5dctoSUPKKRInr6qcTMOm0usI= github.com/swaggo/swag v1.6.3/go.mod h1:wcc83tB4Mb2aNiL/HP4MFeQdpHUrca+Rp/DRNgWAUio= github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ulule/limiter/v3 v3.3.0 h1:DuMRthpkl1wW9Em6xOVw5HMHnbDumSIDydiMqP0PTXs= github.com/ulule/limiter/v3 v3.3.0/go.mod h1:E6sfg3hfRgW+yFvkE/rZf6YLqXYFMWTmZaZKvdEiQsA= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -354,16 +284,11 @@ golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 h1:rlLehGeYg6jfoyz/eDqDU1iRXLKfR42nnNh57ytKEWo= -golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1 h1:anGSYQpPhQwXlwsu5wmfq0nWkCNaMEMUwAv13Y92hd8= golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= @@ -376,9 +301,7 @@ golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -388,10 +311,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191011234655-491137f69257 h1:ry8e2D+cwaV6hk7lb3aRTjjZo24shrbK0e11QEOkTIg= -golang.org/x/net v0.0.0-20191011234655-491137f69257/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8= -golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk= golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -399,34 +318,20 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTm golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190609082536-301114b31cce/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190621203818-d432491b9138 h1:t8BZD9RDjkm9/h7yYN6kE8oaeov5r9aztkB7zKA5Tkg= -golang.org/x/sys v0.0.0-20190621203818-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191023145028-b69606af412f h1:HNixo/W24k2W4EliZfUFl5ApIz/dMDShw52wmWfJ8/s= -golang.org/x/sys v0.0.0-20191023145028-b69606af412f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 h1:I7efaDQAsIQmkTF+WSdcydwVWzK07Yuz8IFF8rNkDe0= -golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= @@ -434,31 +339,16 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190110015856-aa033095749b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac h1:MQEvx39qSf8vyrx3XRaOe+j1UDIzKwkYOVObRgGPVqI= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628034336-212fb13d595e h1:ZlQjfVdpDxeqxRfmO30CdqWWzTvgRCj0MxaUVfxEG1k= golang.org/x/tools v0.0.0-20190628034336-212fb13d595e/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a h1:TwMENskLwU2NnWBzrJGEWHqSiGUkO/B4rfyhwqDxDYQ= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191022174149-ab6dbf99d100 h1:OT2Y8iVtXGHPODZd6iwpndJmAYRiZc75IYxlufvlkLg= -golang.org/x/tools v0.0.0-20191022174149-ab6dbf99d100/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191023143423-ff611c50cd12 h1:s9/f9YHBWfC3jIKMbJElk5+EwgC58Khn6t1EdLnQ9+k= -golang.org/x/tools v0.0.0-20191023143423-ff611c50cd12/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191023163450-98e333b8b3a3 h1:4haCIJia9wHJUU7z9f7PTC8Nf599Ok93njSCHb5gJas= -golang.org/x/tools v0.0.0-20191023163450-98e333b8b3a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3 h1:0vQisIa3mUFShxg7Xyq8WFt/ArQ1soDk5A5uF62IJCc= -golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d h1:/iIZNFGxc/a7C3yWjGcnboV+Tkc7mxr+p6fDztwoxuM= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -470,6 +360,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/d4l3k/messagediff.v1 v1.2.1 h1:70AthpjunwzUiarMHyED52mj9UwtAnE89l1Gmrt3EU0= gopkg.in/d4l3k/messagediff.v1 v1.2.1/go.mod h1:EUzikiKadqXWcD1AzJLagx0j/BeeWGtn++04Xniyg44= @@ -477,8 +368,6 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= -gopkg.in/go-playground/validator.v9 v9.30.2/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M= @@ -491,8 +380,6 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA= diff --git a/pkg/integrations/task_collection_test.go b/pkg/integrations/task_collection_test.go index 58655661..e9f3e497 100644 --- a/pkg/integrations/task_collection_test.go +++ b/pkg/integrations/task_collection_test.go @@ -90,38 +90,49 @@ func TestTaskCollection(t *testing.T) { assert.NotContains(t, rec.Body.String(), `task #14`) }) t.Run("Sort Order", func(t *testing.T) { - // should equal priority desc + // TODO: Add more cases + // should equal priority asc t.Run("by priority", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"priority"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`) + assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) t.Run("by priority desc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"prioritydesc"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`) }) t.Run("by priority asc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"priorityasc"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) - // should equal duedate desc + // should equal duedate asc t.Run("by duedate", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedate"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`) + assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) t.Run("by duedate desc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedatedesc"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"desc"}}, urlParams) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`) }) t.Run("by duedate asc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedateasc"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"asc"}}, urlParams) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) + t.Run("invalid sort parameter", func(t *testing.T) { + _, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams) + assert.Error(t, err) + assertHandlerErrorCode(t, err, models.ErrCodeInvalidSortParam) + }) + t.Run("invalid sort order", func(t *testing.T) { + _, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"id"}, "order_by": []string{"loremipsum"}}, urlParams) + assert.Error(t, err) + assertHandlerErrorCode(t, err, models.ErrCodeInvalidSortOrder) + }) t.Run("invalid parameter", func(t *testing.T) { // Invalid parameter should not sort at all rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"loremipsum"}}, urlParams) @@ -234,35 +245,35 @@ func TestTaskCollection(t *testing.T) { assert.NotContains(t, rec.Body.String(), `task #14`) }) t.Run("Sort Order", func(t *testing.T) { - // should equal priority desc + // should equal priority asc t.Run("by priority", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"priority"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`) + assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) t.Run("by priority desc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"prioritydesc"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`) }) t.Run("by priority asc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"priorityasc"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) - // should equal duedate desc + // should equal duedate asc t.Run("by duedate", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedate"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`) + assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) t.Run("by duedate desc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedatedesc"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"desc"}}, nil) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`) }) t.Run("by duedate asc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedateasc"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"asc"}}, nil) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) diff --git a/pkg/models/error.go b/pkg/models/error.go index c42af27a..0af2b978 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -788,6 +788,60 @@ func (err ErrTaskAttachmentIsTooLarge) HTTPError() web.HTTPError { } } +// ErrInvalidSortParam represents an error where the provided sort param is invalid +type ErrInvalidSortParam struct { + SortBy sortProperty +} + +// IsErrInvalidSortParam checks if an error is ErrInvalidSortParam. +func IsErrInvalidSortParam(err error) bool { + _, ok := err.(ErrInvalidSortParam) + return ok +} + +func (err ErrInvalidSortParam) Error() string { + return fmt.Sprintf("Sort param is invalid [SortBy: %s]", err.SortBy) +} + +// ErrCodeInvalidSortParam holds the unique world-error code of this error +const ErrCodeInvalidSortParam = 4013 + +// HTTPError holds the http error description +func (err ErrInvalidSortParam) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeInvalidSortParam, + Message: fmt.Sprintf("The task sort param '%s' is invalid.", err.SortBy), + } +} + +// ErrInvalidSortOrder represents an error where the provided sort order is invalid +type ErrInvalidSortOrder struct { + OrderBy sortOrder +} + +// IsErrInvalidSortOrder checks if an error is ErrInvalidSortOrder. +func IsErrInvalidSortOrder(err error) bool { + _, ok := err.(ErrInvalidSortOrder) + return ok +} + +func (err ErrInvalidSortOrder) Error() string { + return fmt.Sprintf("Sort order is invalid [OrderBy: %s]", err.OrderBy) +} + +// ErrCodeInvalidSortOrder holds the unique world-error code of this error +const ErrCodeInvalidSortOrder = 4014 + +// HTTPError holds the http error description +func (err ErrInvalidSortOrder) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeInvalidSortOrder, + Message: fmt.Sprintf("The task sort order '%s' is invalid. Allowed is either asc or desc.", err.OrderBy), + } +} + // ================= // Namespace errors // ================= diff --git a/pkg/models/label.go b/pkg/models/label.go index 13799f8c..14cab109 100644 --- a/pkg/models/label.go +++ b/pkg/models/label.go @@ -209,7 +209,6 @@ func getUserTaskIDs(u *User) (taskIDs []int64, err error) { tasks, _, _, err := getRawTasksForLists(lists, &taskOptions{ startDate: time.Unix(0, 0), endDate: time.Unix(0, 0), - sortby: SortTasksByUnsorted, page: -1, perPage: 0, }) diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index db79f953..16cc1062 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -24,18 +24,22 @@ import ( // 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 { - ListID int64 `param:"list"` - Sorting string `query:"sort"` // Parameter to sort by - StartDateSortUnix int64 `query:"startdate"` - EndDateSortUnix int64 `query:"enddate"` + ListID int64 `param:"list"` + StartDateSortUnix int64 `query:"startdate"` + EndDateSortUnix int64 `query:"enddate"` Lists []*List + // The query parameter to sort by. This is for ex. done, priority, etc. + SortBy []string `query:"sort_by"` + // The query parameter to order the items by. This can be either asc or desc, with asc being the default. + OrderBy []string `query:"order_by"` + web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` } // ReadAll gets all tasks for a collection -// @Summary Get tasks on a list +// @Summary Get tasks in a list // @Description Returns all tasks for the current list. // @tags task // @Accept json @@ -44,7 +48,8 @@ type TaskCollection struct { // @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." -// @Param sort query string false "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc." +// @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`, `text`, `description`, `done`, `done_at_unix`, `due_date_unix`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date_unix`, `end_date_unix`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`." +// @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`." // @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 @@ -52,31 +57,32 @@ type TaskCollection struct { // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{listID}/tasks [get] func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { - var sortby SortBy - switch tf.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 + + var sort = make([]*sortParam, 0, len(tf.SortBy)) + for i, s := range tf.SortBy { + param := &sortParam{ + sortBy: sortProperty(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, 0, 0, err + } + sort = append(sort, param) } taskopts := &taskOptions{ search: search, - sortby: sortby, startDate: time.Unix(tf.StartDateSortUnix, 0), endDate: time.Unix(tf.EndDateSortUnix, 0), page: page, perPage: perPage, + sortby: sort, } shareAuth, is := a.(*LinkSharing) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go new file mode 100644 index 00000000..f2cae7b7 --- /dev/null +++ b/pkg/models/task_collection_sort.go @@ -0,0 +1,233 @@ +// Vikunja is a todo-list application to facilitate your life. +// Copyright 2019 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 ( + "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() + } + + 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("DoneAtUnix"), + taskPropertyDueDateUnix: mustMakeComparator("DueDateUnix"), + taskPropertyCreatedByID: mustMakeComparator("CreatedByID"), + taskPropertyListID: mustMakeComparator("ListID"), + taskPropertyRepeatAfter: mustMakeComparator("RepeatAfter"), + taskPropertyPriority: mustMakeComparator("Priority"), + taskPropertyStartDateUnix: mustMakeComparator("StartDateUnix"), + taskPropertyEndDateUnix: mustMakeComparator("EndDateUnix"), + 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 + }) +} diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go new file mode 100644 index 00000000..d009e99c --- /dev/null +++ b/pkg/models/task_collection_sort_test.go @@ -0,0 +1,829 @@ +// Vikunja is a todo-list application to facilitate your life. +// Copyright 2019 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 ( + "github.com/mohae/deepcopy" + "github.com/stretchr/testify/assert" + "math/rand" + "reflect" + "testing" +) + +func TestSortParamValidation(t *testing.T) { + t.Run("Test valid order by", func(t *testing.T) { + t.Run(orderAscending.String(), func(t *testing.T) { + s := &sortParam{ + orderBy: orderAscending, + sortBy: "id", + } + err := s.validate() + assert.NoError(t, err) + }) + t.Run(orderDescending.String(), func(t *testing.T) { + s := &sortParam{ + orderBy: orderDescending, + sortBy: "id", + } + err := s.validate() + assert.NoError(t, err) + }) + }) + t.Run("Test valid sort by", func(t *testing.T) { + for _, test := range []sortProperty{ + taskPropertyID, + taskPropertyText, + taskPropertyDescription, + taskPropertyDone, + taskPropertyDoneAtUnix, + taskPropertyDueDateUnix, + taskPropertyCreatedByID, + taskPropertyListID, + taskPropertyRepeatAfter, + taskPropertyPriority, + taskPropertyStartDateUnix, + taskPropertyEndDateUnix, + taskPropertyHexColor, + taskPropertyPercentDone, + taskPropertyUID, + taskPropertyCreated, + taskPropertyUpdated, + } { + t.Run(test.String(), func(t *testing.T) { + s := &sortParam{ + orderBy: orderAscending, + sortBy: test, + } + err := s.validate() + assert.NoError(t, err) + }) + } + }) + t.Run("Test invalid order by", func(t *testing.T) { + s := &sortParam{ + orderBy: "somethingInvalid", + sortBy: "id", + } + err := s.validate() + assert.Error(t, err) + assert.True(t, IsErrInvalidSortOrder(err)) + }) + t.Run("Test invalid sort by", func(t *testing.T) { + s := &sortParam{ + orderBy: orderAscending, + sortBy: "somethingInvalid", + } + err := s.validate() + assert.Error(t, err) + assert.True(t, IsErrInvalidSortParam(err)) + }) +} + +var ( + task1 = &Task{ + ID: 1, + Text: "aaa", + Description: "Lorem Ipsum", + Done: true, + DoneAtUnix: 1543626000, + ListID: 1, + UID: "JywtBPCESImlyKugvaZWrxmXAFAWXFISMeXYImEh", + Created: 1543626724, + Updated: 1543626724, + } + task2 = &Task{ + ID: 2, + Text: "bbb", + Description: "Arem Ipsum", + Done: true, + DoneAtUnix: 1543626724, + CreatedByID: 1, + ListID: 2, + PercentDone: 0.3, + StartDateUnix: 1543626724, + Created: 1553626724, + Updated: 1553626724, + } + task3 = &Task{ + ID: 3, + Text: "ccc", + DueDateUnix: 1583626724, + Priority: 100, + ListID: 3, + HexColor: "000000", + PercentDone: 0.1, + Updated: 1555555555, + } + task4 = &Task{ + ID: 4, + Text: "ddd", + Priority: 1, + StartDateUnix: 1643626724, + ListID: 1, + } + task5 = &Task{ + ID: 5, + Text: "eef", + Priority: 50, + UID: "shggzCHQWLhGNMNsOGOCOjcVkInOYjTAnORqTkdL", + DueDateUnix: 1543636724, + Updated: 1565555555, + } + task6 = &Task{ + ID: 6, + Text: "eef", + DueDateUnix: 1543616724, + RepeatAfter: 6400, + CreatedByID: 2, + HexColor: "ffffff", + } + task7 = &Task{ + ID: 7, + Text: "mmmn", + Description: "Zoremis", + StartDateUnix: 1544600000, + EndDateUnix: 1584600000, + UID: "tyzCZuLMSKhwclJOsDyDcUdyVAPBDOPHNTBOLTcW", + } + task8 = &Task{ + ID: 8, + Text: "b123", + EndDateUnix: 1544700000, + } + task9 = &Task{ + ID: 9, + Done: true, + DoneAtUnix: 1573626724, + Text: "a123", + RepeatAfter: 86000, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + } + task10 = &Task{ + ID: 10, + Text: "zzz", + Priority: 10, + PercentDone: 1, + } +) + +type taskSortTestCase struct { + name string + wantAsc []*Task + wantDesc []*Task + sortProperty sortProperty +} + +var taskSortTestCases = []taskSortTestCase{ + { + name: "id", + sortProperty: taskPropertyID, + wantAsc: []*Task{ + task1, + task2, + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + }, + wantDesc: []*Task{ + task10, + task9, + task8, + task7, + task6, + task5, + task4, + task3, + task2, + task1, + }, + }, + { + name: "text", + sortProperty: taskPropertyText, + wantAsc: []*Task{ + task9, + task1, + task8, + task2, + task3, + task4, + task5, + task6, + task7, + task10, + }, + wantDesc: []*Task{ + task10, + task7, + task5, + task6, + task4, + task3, + task2, + task8, + task1, + task9, + }, + }, + { + name: "description", + sortProperty: taskPropertyDescription, + wantAsc: []*Task{ + task3, + task4, + task5, + task6, + task8, + task9, + task10, + task2, + task1, + task7, + }, + wantDesc: []*Task{ + task7, + task1, + task2, + task3, + task4, + task5, + task6, + task8, + task9, + task10, + }, + }, + { + name: "done", + sortProperty: taskPropertyDone, + wantAsc: []*Task{ + // These are done + task1, + task2, + task9, + // These are not + task3, + task4, + task5, + task6, + task7, + task8, + task10, + }, + wantDesc: []*Task{ + // These are not + task3, + task4, + task5, + task6, + task7, + task8, + task10, + // These are done + task1, + task2, + task9, + }, + }, + { + name: "done at", + sortProperty: taskPropertyDoneAtUnix, + wantAsc: []*Task{ + task3, + task4, + task5, + task6, + task7, + task8, + task10, + task1, + task2, + task9, + }, + wantDesc: []*Task{ + task9, + task2, + task1, + task3, + task4, + task5, + task6, + task7, + task8, + task10, + }, + }, + { + name: "due date", + sortProperty: taskPropertyDueDateUnix, + wantAsc: []*Task{ + task1, + task2, + task4, + task7, + task8, + task9, + task10, + task6, + task5, + task3, + }, + wantDesc: []*Task{ + task3, + task5, + task6, + task1, + task2, + task4, + task7, + task8, + task9, + task10, + }, + }, + { + name: "created by id", + sortProperty: taskPropertyCreatedByID, + wantAsc: []*Task{ + task1, + task3, + task4, + task5, + task7, + task8, + task9, + task10, + task2, + task6, + }, + wantDesc: []*Task{ + task6, + task2, + task1, + task3, + task4, + task5, + task7, + task8, + task9, + task10, + }, + }, + { + name: "list id", + sortProperty: taskPropertyListID, + wantAsc: []*Task{ + task5, + task6, + task7, + task8, + task9, + task10, + task1, + task4, + task2, + task3, + }, + wantDesc: []*Task{ + task3, + task2, + task1, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + }, + }, + { + name: "repeat after", + sortProperty: taskPropertyRepeatAfter, + wantAsc: []*Task{ + task1, + task2, + task3, + task4, + task5, + task7, + task8, + task10, + task6, + task9, + }, + wantDesc: []*Task{ + task9, + task6, + task1, + task2, + task3, + task4, + task5, + task7, + task8, + task10, + }, + }, + { + name: "priority", + sortProperty: taskPropertyPriority, + wantAsc: []*Task{ + task1, + task2, + task6, + task7, + task8, + task9, + task4, + task10, + task5, + task3, + }, + wantDesc: []*Task{ + task3, + task5, + task10, + task4, + task1, + task2, + task6, + task7, + task8, + task9, + }, + }, + { + name: "start date", + sortProperty: taskPropertyStartDateUnix, + wantAsc: []*Task{ + task1, + task3, + task5, + task6, + task8, + task10, + task2, + task7, + task9, + task4, + }, + wantDesc: []*Task{ + task4, + task7, + task9, + task2, + task1, + task3, + task5, + task6, + task8, + task10, + }, + }, + { + name: "end date", + sortProperty: taskPropertyEndDateUnix, + wantAsc: []*Task{ + task1, + task2, + task3, + task4, + task5, + task6, + task10, + task8, + task9, + task7, + }, + wantDesc: []*Task{ + task7, + task8, + task9, + task1, + task2, + task3, + task4, + task5, + task6, + task10, + }, + }, + { + name: "hex color", + sortProperty: taskPropertyHexColor, + wantAsc: []*Task{ + task1, + task2, + task4, + task5, + task7, + task8, + task9, + task10, + task3, + task6, + }, + wantDesc: []*Task{ + task6, + task3, + task1, + task2, + task4, + task5, + task7, + task8, + task9, + task10, + }, + }, + { + name: "percent done", + sortProperty: taskPropertyPercentDone, + wantAsc: []*Task{ + task1, + task4, + task5, + task6, + task7, + task8, + task9, + task3, + task2, + task10, + }, + wantDesc: []*Task{ + task10, + task2, + task3, + task1, + task4, + task5, + task6, + task7, + task8, + task9, + }, + }, + { + name: "uid", + sortProperty: taskPropertyUID, + wantAsc: []*Task{ + task2, + task3, + task4, + task6, + task8, + task9, + task10, + task1, + task5, + task7, + }, + wantDesc: []*Task{ + task7, + task5, + task1, + task2, + task3, + task4, + task6, + task8, + task9, + task10, + }, + }, + { + name: "created", + sortProperty: taskPropertyCreated, + wantAsc: []*Task{ + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + task1, + task2, + }, + wantDesc: []*Task{ + task2, + task1, + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + }, + }, + { + name: "updated", + sortProperty: taskPropertyUpdated, + wantAsc: []*Task{ + task4, + task6, + task7, + task8, + task9, + task10, + task1, + task2, + task3, + task5, + }, + wantDesc: []*Task{ + task5, + task3, + task2, + task1, + task4, + task6, + task7, + task8, + task9, + task10, + }, + }, +} + +func TestTaskSort(t *testing.T) { + + assertTestSliceMatch := func(t *testing.T, got, want []*Task) { + if !reflect.DeepEqual(got, want) { + t.Error("Slices do not match in order") + t.Error("Got\t| Want") + for in, task := range got { + fail := "" + if task.ID != want[in].ID { + fail = "wrong" + } + t.Errorf("\t%d\t| %d \t%s", task.ID, want[in].ID, fail) + } + } + } + + for _, testCase := range taskSortTestCases { + t.Run(testCase.name, func(t *testing.T) { + t.Run("asc default", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: testCase.sortProperty, + }, + } + + got := deepcopy.Copy(testCase.wantAsc).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, by) + + assertTestSliceMatch(t, got, testCase.wantAsc) + }) + t.Run("asc", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: testCase.sortProperty, + orderBy: orderAscending, + }, + } + + got := deepcopy.Copy(testCase.wantAsc).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, by) + + assertTestSliceMatch(t, got, testCase.wantAsc) + }) + t.Run("desc", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: testCase.sortProperty, + orderBy: orderDescending, + }, + } + + got := deepcopy.Copy(testCase.wantDesc).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, by) + + assertTestSliceMatch(t, got, testCase.wantDesc) + }) + }) + } + + // Other cases + t.Run("Order by Done Ascending and Text Descending", func(t *testing.T) { + want := []*Task{ + // Done + task2, + task1, + task9, + + // Not done + task10, + task7, + task5, + task6, + task4, + task3, + task8, + } + sortParams := []*sortParam{ + { + sortBy: taskPropertyDone, + orderBy: orderAscending, + }, + { + sortBy: taskPropertyText, + orderBy: orderDescending, + }, + } + + got := deepcopy.Copy(want).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, sortParams) + + assertTestSliceMatch(t, got, want) + }) + t.Run("Order by Done Descending and Text Ascending", func(t *testing.T) { + want := []*Task{ + // Not done + task8, + task3, + task4, + task5, + task6, + task7, + task10, + + // Done + task9, + task1, + task2, + } + sortParams := []*sortParam{ + { + sortBy: taskPropertyDone, + orderBy: orderDescending, + }, + { + sortBy: taskPropertyText, + orderBy: orderAscending, + }, + } + + got := deepcopy.Copy(want).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, sortParams) + + assertTestSliceMatch(t, got, want) + + }) +} diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go new file mode 100644 index 00000000..effe5e5e --- /dev/null +++ b/pkg/models/task_collection_test.go @@ -0,0 +1,635 @@ +// Vikunja is a todo-list application to facilitate your life. +// Copyright 2019 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/db" + "code.vikunja.io/api/pkg/files" + "code.vikunja.io/web" + "github.com/stretchr/testify/assert" + "gopkg.in/d4l3k/messagediff.v1" + "testing" +) + +func TestTaskCollection_ReadAll(t *testing.T) { + assert.NoError(t, db.LoadFixtures()) + + // Dummy users + user1 := &User{ + ID: 1, + Username: "user1", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", // hash for "" + } + user2 := &User{ + ID: 2, + Username: "user2", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + AvatarURL: "ab53a2911ddf9b4817ac01ddcd3d975f", // hash for "" + } + user6 := &User{ + ID: 6, + Username: "user6", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "3efbe51f864c6666bc27caf4c6ff90ed", // hash for "" + } + + // We use individual variables for the tasks here to be able to rearrange or remove ones more easily + task1 := &Task{ + ID: 1, + Text: "task #1", + Description: "Lorem Ipsum", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + Labels: []*Label{ + { + ID: 4, + Title: "Label #4 - visible via other task", + CreatedByID: 2, + CreatedBy: user2, + Updated: 0, + Created: 0, + }, + }, + RelatedTasks: map[RelationKind][]*Task{ + RelationKindSubtask: { + { + ID: 29, + Text: "task #29 with parent task (1)", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + }, + }, + }, + Attachments: []*TaskAttachment{ + { + ID: 1, + TaskID: 1, + FileID: 1, + CreatedByID: 1, + CreatedBy: user1, + File: &files.File{ + ID: 1, + Name: "test", + Size: 100, + CreatedUnix: 1570998791, + CreatedByID: 1, + }, + }, + { + ID: 2, + TaskID: 1, + FileID: 9999, + CreatedByID: 1, + CreatedBy: user1, + }, + }, + Created: 1543626724, + Updated: 1543626724, + } + task2 := &Task{ + ID: 2, + Text: "task #2 done", + Done: true, + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + Labels: []*Label{ + { + ID: 4, + Title: "Label #4 - visible via other task", + CreatedByID: 2, + CreatedBy: user2, + Updated: 0, + Created: 0, + }, + }, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task3 := &Task{ + ID: 3, + Text: "task #3 high prio", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + Priority: 100, + } + task4 := &Task{ + ID: 4, + Text: "task #4 low prio", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + Priority: 1, + } + task5 := &Task{ + ID: 5, + Text: "task #5 higher due date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + DueDateUnix: 1543636724, + } + task6 := &Task{ + ID: 6, + Text: "task #6 lower due date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + DueDateUnix: 1543616724, + } + task7 := &Task{ + ID: 7, + Text: "task #7 with start date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + } + task8 := &Task{ + ID: 8, + Text: "task #8 with end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + EndDateUnix: 1544700000, + } + task9 := &Task{ + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + } + task10 := &Task{ + ID: 10, + Text: "task #10 basic", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task11 := &Task{ + ID: 11, + Text: "task #11 basic", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task12 := &Task{ + ID: 12, + Text: "task #12 basic", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task15 := &Task{ + ID: 15, + Text: "task #15", + CreatedByID: 6, + CreatedBy: user6, + ListID: 6, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task16 := &Task{ + ID: 16, + Text: "task #16", + CreatedByID: 6, + CreatedBy: user6, + ListID: 7, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task17 := &Task{ + ID: 17, + Text: "task #17", + CreatedByID: 6, + CreatedBy: user6, + ListID: 8, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task18 := &Task{ + ID: 18, + Text: "task #18", + CreatedByID: 6, + CreatedBy: user6, + ListID: 9, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task19 := &Task{ + ID: 19, + Text: "task #19", + CreatedByID: 6, + CreatedBy: user6, + ListID: 10, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task20 := &Task{ + ID: 20, + Text: "task #20", + CreatedByID: 6, + CreatedBy: user6, + ListID: 11, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task21 := &Task{ + ID: 21, + Text: "task #21", + CreatedByID: 6, + CreatedBy: user6, + ListID: 12, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task22 := &Task{ + ID: 22, + Text: "task #22", + CreatedByID: 6, + CreatedBy: user6, + ListID: 13, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task23 := &Task{ + ID: 23, + Text: "task #23", + CreatedByID: 6, + CreatedBy: user6, + ListID: 14, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task24 := &Task{ + ID: 24, + Text: "task #24", + CreatedByID: 6, + CreatedBy: user6, + ListID: 15, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task25 := &Task{ + ID: 25, + Text: "task #25", + CreatedByID: 6, + CreatedBy: user6, + ListID: 16, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task26 := &Task{ + ID: 26, + Text: "task #26", + CreatedByID: 6, + CreatedBy: user6, + ListID: 17, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task27 := &Task{ + ID: 27, + Text: "task #27 with reminders", + CreatedByID: 1, + CreatedBy: user1, + RemindersUnix: []int64{1543626724, 1543626824}, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task28 := &Task{ + ID: 28, + Text: "task #28 with repeat after", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + RepeatAfter: 3600, + Created: 1543626724, + Updated: 1543626724, + } + task29 := &Task{ + ID: 29, + Text: "task #29 with parent task (1)", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{ + RelationKindParenttask: { + { + ID: 1, + Text: "task #1", + Description: "Lorem Ipsum", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + }, + }, + }, + Created: 1543626724, + Updated: 1543626724, + } + task30 := &Task{ + ID: 30, + Text: "task #30 with assignees", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + Assignees: []*User{ + user1, + user2, + }, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task31 := &Task{ + ID: 31, + Text: "task #31 with color", + HexColor: "f0f0f0", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task32 := &Task{ + ID: 32, + Text: "task #32", + CreatedByID: 1, + CreatedBy: user1, + ListID: 3, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task33 := &Task{ + ID: 33, + Text: "task #33 with percent done", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + PercentDone: 0.5, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + + type fields struct { + ListID int64 + StartDateSortUnix int64 + EndDateSortUnix int64 + Lists []*List + SortBy []string // Is a string, since this is the place where a query string comes from the user + OrderBy []string + CRUDable web.CRUDable + Rights web.Rights + } + type args struct { + search string + a web.Auth + page int + } + type testcase struct { + name string + fields fields + args args + want interface{} + wantErr bool + } + tests := []testcase{ + { + name: "ReadAll Tasks normally", + fields: fields{}, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*Task{ + task1, + task2, + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + task11, + task12, + task15, + task16, + task17, + task18, + task19, + task20, + task21, + task22, + task23, + task24, + task25, + task26, + task27, + task28, + task29, + task30, + task31, + task32, + task33, + }, + wantErr: false, + }, + { + // For more sorting tests see task_collection_sort_test.go + name: "ReadAll Tasks sorted by done asc and id desc", + fields: fields{ + SortBy: []string{"done", "id"}, + OrderBy: []string{"asc", "desc"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*Task{ + task2, + task33, + task32, + task31, + task30, + task29, + task28, + task27, + task26, + task25, + task24, + task23, + task22, + task21, + task20, + task19, + task18, + task17, + task16, + task15, + task12, + task11, + task10, + task9, + task8, + task7, + task6, + task5, + task4, + task3, + task1, + }, + wantErr: false, + }, + { + name: "ReadAll Tasks with range", + fields: fields{ + StartDateSortUnix: 1544500000, + EndDateSortUnix: 1544600000, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*Task{ + task7, + task9, + }, + wantErr: false, + }, + { + name: "ReadAll Tasks with range", + fields: fields{ + StartDateSortUnix: 1544700000, + EndDateSortUnix: 1545000000, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*Task{ + task8, + task9, + }, + wantErr: false, + }, + { + name: "ReadAll Tasks with range without end date", + fields: fields{ + StartDateSortUnix: 1544700000, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*Task{ + task8, + task9, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + lt := &TaskCollection{ + ListID: tt.fields.ListID, + StartDateSortUnix: tt.fields.StartDateSortUnix, + EndDateSortUnix: tt.fields.EndDateSortUnix, + SortBy: tt.fields.SortBy, + OrderBy: tt.fields.OrderBy, + CRUDable: tt.fields.CRUDable, + Rights: tt.fields.Rights, + } + got, _, _, err := lt.ReadAll(tt.args.a, tt.args.search, tt.args.page, 50) + if (err != nil) != tt.wantErr { + t.Errorf("Test %s, Task.ReadAll() error = %v, wantErr %v", tt.name, err, tt.wantErr) + return + } + if diff, equal := messagediff.PrettyDiff(got, tt.want); !equal { + t.Errorf("Test %s, LabelTask.ReadAll() = %v, want %v, \ndiff: %v", tt.name, got, tt.want, diff) + } + }) + } +} diff --git a/pkg/models/task_readall_test.go b/pkg/models/task_readall_test.go deleted file mode 100644 index 6e869ae2..00000000 --- a/pkg/models/task_readall_test.go +++ /dev/null @@ -1,728 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018-2019 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/db" - "code.vikunja.io/api/pkg/files" - "github.com/stretchr/testify/assert" - "gopkg.in/d4l3k/messagediff.v1" - "sort" - "testing" - - "code.vikunja.io/web" -) - -func sortTasksForTesting(by SortBy) (tasks []*Task) { - user1 := &User{ - ID: 1, - Username: "user1", - Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", - IsActive: true, - AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", // hash for "" - } - user2 := &User{ - ID: 2, - Username: "user2", - Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", - AvatarURL: "ab53a2911ddf9b4817ac01ddcd3d975f", // hash for "" - } - user6 := &User{ - ID: 6, - Username: "user6", - Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", - IsActive: true, - AvatarURL: "3efbe51f864c6666bc27caf4c6ff90ed", // hash for "" - } - - tasks = []*Task{ - { - ID: 1, - Text: "task #1", - Description: "Lorem Ipsum", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - Labels: []*Label{ - { - ID: 4, - Title: "Label #4 - visible via other task", - CreatedByID: 2, - CreatedBy: user2, - Updated: 0, - Created: 0, - }, - }, - RelatedTasks: map[RelationKind][]*Task{ - RelationKindSubtask: { - { - ID: 29, - Text: "task #29 with parent task (1)", - CreatedByID: 1, - ListID: 1, - Created: 1543626724, - Updated: 1543626724, - }, - }, - }, - Attachments: []*TaskAttachment{ - { - ID: 1, - TaskID: 1, - FileID: 1, - CreatedByID: 1, - CreatedBy: user1, - File: &files.File{ - ID: 1, - Name: "test", - Size: 100, - CreatedUnix: 1570998791, - CreatedByID: 1, - }, - }, - { - ID: 2, - TaskID: 1, - FileID: 9999, - CreatedByID: 1, - CreatedBy: user1, - }, - }, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 2, - Text: "task #2 done", - Done: true, - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - Labels: []*Label{ - { - ID: 4, - Title: "Label #4 - visible via other task", - CreatedByID: 2, - CreatedBy: user2, - Updated: 0, - Created: 0, - }, - }, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 3, - Text: "task #3 high prio", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - Priority: 100, - }, - { - ID: 4, - Text: "task #4 low prio", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - Priority: 1, - }, - { - ID: 5, - Text: "task #5 higher due date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - DueDateUnix: 1543636724, - }, - { - ID: 6, - Text: "task #6 lower due date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - DueDateUnix: 1543616724, - }, - { - ID: 7, - Text: "task #7 with start date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - }, - { - ID: 8, - Text: "task #8 with end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - EndDateUnix: 1544700000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, - { - ID: 10, - Text: "task #10 basic", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 11, - Text: "task #11 basic", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 12, - Text: "task #12 basic", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 15, - Text: "task #15", - CreatedByID: 6, - CreatedBy: user6, - ListID: 6, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 16, - Text: "task #16", - CreatedByID: 6, - CreatedBy: user6, - ListID: 7, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 17, - Text: "task #17", - CreatedByID: 6, - CreatedBy: user6, - ListID: 8, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 18, - Text: "task #18", - CreatedByID: 6, - CreatedBy: user6, - ListID: 9, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 19, - Text: "task #19", - CreatedByID: 6, - CreatedBy: user6, - ListID: 10, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 20, - Text: "task #20", - CreatedByID: 6, - CreatedBy: user6, - ListID: 11, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 21, - Text: "task #21", - CreatedByID: 6, - CreatedBy: user6, - ListID: 12, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 22, - Text: "task #22", - CreatedByID: 6, - CreatedBy: user6, - ListID: 13, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 23, - Text: "task #23", - CreatedByID: 6, - CreatedBy: user6, - ListID: 14, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 24, - Text: "task #24", - CreatedByID: 6, - CreatedBy: user6, - ListID: 15, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 25, - Text: "task #25", - CreatedByID: 6, - CreatedBy: user6, - ListID: 16, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 26, - Text: "task #26", - CreatedByID: 6, - CreatedBy: user6, - ListID: 17, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 27, - Text: "task #27 with reminders", - CreatedByID: 1, - CreatedBy: user1, - RemindersUnix: []int64{1543626724, 1543626824}, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 28, - Text: "task #28 with repeat after", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - RepeatAfter: 3600, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 29, - Text: "task #29 with parent task (1)", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{ - RelationKindParenttask: { - { - ID: 1, - Text: "task #1", - Description: "Lorem Ipsum", - CreatedByID: 1, - ListID: 1, - Created: 1543626724, - Updated: 1543626724, - }, - }, - }, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 30, - Text: "task #30 with assignees", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - Assignees: []*User{ - user1, - user2, - }, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 31, - Text: "task #31 with color", - HexColor: "f0f0f0", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 32, - Text: "task #32", - CreatedByID: 1, - CreatedBy: user1, - ListID: 3, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 33, - Text: "task #33 with percent done", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - PercentDone: 0.5, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - } - - 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 - }) - } - - return -} - -func TestTask_ReadAll(t *testing.T) { - assert.NoError(t, db.LoadFixtures()) - - // Dummy users - user1 := &User{ - ID: 1, - Username: "user1", - Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", - IsActive: true, - AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", // hash for "" - } - - type fields struct { - ListID int64 - Sorting string - StartDateSortUnix int64 - EndDateSortUnix int64 - Lists []*List - CRUDable web.CRUDable - Rights web.Rights - } - type args struct { - search string - a web.Auth - page int - } - tests := []struct { - name string - fields fields - args args - want interface{} - wantErr bool - }{ - { - name: "ReadAll Tasks normally", - fields: fields{}, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting(SortTasksByUnsorted), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by priority (desc)", - fields: fields{ - Sorting: "priority", - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting(SortTasksByPriorityDesc), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by priority asc", - fields: fields{ - Sorting: "priorityasc", - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting(SortTasksByPriorityAsc), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by priority desc", - fields: fields{ - Sorting: "prioritydesc", - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting(SortTasksByPriorityDesc), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by due date default desc", - fields: fields{ - Sorting: "duedate", - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting(SortTasksByDueDateDesc), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by due date asc", - fields: fields{ - Sorting: "duedateasc", - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting(SortTasksByDueDateAsc), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by due date desc", - fields: fields{ - Sorting: "duedatedesc", - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - - want: sortTasksForTesting(SortTasksByDueDateDesc), - wantErr: false, - }, - { - name: "ReadAll Tasks with range", - fields: fields{ - StartDateSortUnix: 1544500000, - EndDateSortUnix: 1544600000, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: []*Task{ - { - ID: 7, - Text: "task #7 with start date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, - }, - wantErr: false, - }, - { - name: "ReadAll Tasks with range", - fields: fields{ - StartDateSortUnix: 1544700000, - EndDateSortUnix: 1545000000, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: []*Task{ - { - ID: 8, - Text: "task #8 with end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - EndDateUnix: 1544700000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, - }, - wantErr: false, - }, - { - name: "ReadAll Tasks with range without end date", - fields: fields{ - StartDateSortUnix: 1544700000, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: []*Task{ - { - ID: 8, - Text: "task #8 with end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - EndDateUnix: 1544700000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - lt := &TaskCollection{ - ListID: tt.fields.ListID, - Sorting: tt.fields.Sorting, - StartDateSortUnix: tt.fields.StartDateSortUnix, - EndDateSortUnix: tt.fields.EndDateSortUnix, - CRUDable: tt.fields.CRUDable, - Rights: tt.fields.Rights, - } - got, _, _, err := lt.ReadAll(tt.args.a, tt.args.search, tt.args.page, 50) - if (err != nil) != tt.wantErr { - t.Errorf("Test %s, Task.ReadAll() error = %v, wantErr %v", tt.name, err, tt.wantErr) - return - } - if diff, equal := messagediff.PrettyDiff(got, tt.want); !equal { - t.Errorf("Test %s, LabelTask.ReadAll() = %v, want %v, \ndiff: %v", tt.name, got, tt.want, diff) - } - }) - } -} diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 3da801cd..a4c9d428 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -105,25 +105,13 @@ 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 -) - type taskOptions struct { search string - sortby SortBy startDate time.Time endDate time.Time page int perPage int + sortby []*sortParam } // ReadAll is a dummy function to still have that endpoint documented @@ -154,16 +142,19 @@ func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*T listIDs = append(listIDs, l.ID) } + // Since xorm does not use placeholders for order by, it is possible to expose this with sql injection if we're directly + // passing user input to the db. + // As a workaround to prevent this, we check for valid column names here prior to passing it to the db. var orderby string - switch opts.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" + for i, param := range opts.sortby { + // Validate the params + if err := param.validate(); err != nil { + return nil, 0, 0, err + } + orderby += param.sortBy.String() + " " + param.orderBy.String() + if (i + 1) < len(opts.sortby) { + orderby += ", " + } } taskMap = make(map[int64]*Task) @@ -232,34 +223,13 @@ func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCo if err != nil { return nil, 0, 0, err } - // Because the list is sorted by id which we don't want (since we're dealing with maps) + // Because the list is fully unsorted (since we're dealing with maps) // we have to manually sort the tasks again here. sortTasks(tasks, opts.sortby) return tasks, resultCount, totalItems, err } -func sortTasks(tasks []*Task, 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 []*Task, err error) { // make a map so we can put in a lot of other stuff more easily diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 5d7f0b66..76873197 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -1,6 +1,6 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2019-12-01 13:20:36.822585642 +0100 CET m=+0.129079038 +// 2019-12-05 22:15:49.761451764 +0100 CET m=+0.171539379 package swagger @@ -407,7 +407,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns a list by its ID.", + "description": "Returns a team by its ID.", "consumes": [ "application/json" ], @@ -415,13 +415,13 @@ var doc = `{ "application/json" ], "tags": [ - "list" + "team" ], - "summary": "Gets one list", + "summary": "Gets one team", "parameters": [ { "type": "integer", - "description": "List ID", + "description": "Team ID", "name": "id", "in": "path", "required": true @@ -429,13 +429,13 @@ var doc = `{ ], "responses": { "200": { - "description": "The list", + "description": "The team", "schema": { - "$ref": "#/definitions/models.List" + "$ref": "#/definitions/models.Team" } }, "403": { - "description": "The user does not have access to the list", + "description": "The user does not have access to the team", "schema": { "$ref": "#/definitions/code.vikunja.io.web.HTTPError" } @@ -984,7 +984,7 @@ var doc = `{ "tags": [ "task" ], - "summary": "Get tasks on a list", + "summary": "Get tasks in a list", "parameters": [ { "type": "integer", @@ -1013,8 +1013,14 @@ var doc = `{ }, { "type": "string", - "description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc.", - "name": "sort", + "description": "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` + "`" + `, ` + "`" + `text` + "`" + `, ` + "`" + `description` + "`" + `, ` + "`" + `done` + "`" + `, ` + "`" + `done_at_unix` + "`" + `, ` + "`" + `due_date_unix` + "`" + `, ` + "`" + `created_by_id` + "`" + `, ` + "`" + `list_id` + "`" + `, ` + "`" + `repeat_after` + "`" + `, ` + "`" + `priority` + "`" + `, ` + "`" + `start_date_unix` + "`" + `, ` + "`" + `end_date_unix` + "`" + `, ` + "`" + `hex_color` + "`" + `, ` + "`" + `percent_done` + "`" + `, ` + "`" + `uid` + "`" + `, ` + "`" + `created` + "`" + `, ` + "`" + `updated` + "`" + `. Default is ` + "`" + `id` + "`" + `.", + "name": "sort_by", + "in": "query" + }, + { + "type": "string", + "description": "The ordering parameter. Possible values to order by are ` + "`" + `asc` + "`" + ` or ` + "`" + `desc` + "`" + `. Default is ` + "`" + `asc` + "`" + `.", + "name": "order_by", "in": "query" }, { @@ -4633,13 +4639,6 @@ var doc = `{ "type": "object", "$ref": "#/definitions/models.User" }, - "tasks": { - "description": "An array of tasks which belong to the list.", - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - }, "title": { "description": "The title of the list. You'll see this in the namespace overview.", "type": "string", diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index af8c048e..a3c54a4d 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -389,7 +389,7 @@ "JWTKeyAuth": [] } ], - "description": "Returns a list by its ID.", + "description": "Returns a team by its ID.", "consumes": [ "application/json" ], @@ -397,13 +397,13 @@ "application/json" ], "tags": [ - "list" + "team" ], - "summary": "Gets one list", + "summary": "Gets one team", "parameters": [ { "type": "integer", - "description": "List ID", + "description": "Team ID", "name": "id", "in": "path", "required": true @@ -411,13 +411,13 @@ ], "responses": { "200": { - "description": "The list", + "description": "The team", "schema": { - "$ref": "#/definitions/models.List" + "$ref": "#/definitions/models.Team" } }, "403": { - "description": "The user does not have access to the list", + "description": "The user does not have access to the team", "schema": { "$ref": "#/definitions/code.vikunja.io/web.HTTPError" } @@ -966,7 +966,7 @@ "tags": [ "task" ], - "summary": "Get tasks on a list", + "summary": "Get tasks in a list", "parameters": [ { "type": "integer", @@ -995,8 +995,14 @@ }, { "type": "string", - "description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc.", - "name": "sort", + "description": "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`, `text`, `description`, `done`, `done_at_unix`, `due_date_unix`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date_unix`, `end_date_unix`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`.", + "name": "sort_by", + "in": "query" + }, + { + "type": "string", + "description": "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`.", + "name": "order_by", "in": "query" }, { @@ -4614,13 +4620,6 @@ "type": "object", "$ref": "#/definitions/models.User" }, - "tasks": { - "description": "An array of tasks which belong to the list.", - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - }, "title": { "description": "The title of the list. You'll see this in the namespace overview.", "type": "string", diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index 5c7ea2c8..b4c9d314 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -236,11 +236,6 @@ definitions: $ref: '#/definitions/models.User' description: The user who created this list. type: object - tasks: - description: An array of tasks which belong to the list. - items: - $ref: '#/definitions/models.Task' - type: array title: description: The title of the list. You'll see this in the namespace overview. maxLength: 250 @@ -1138,9 +1133,9 @@ paths: get: consumes: - application/json - description: Returns a list by its ID. + description: Returns a team by its ID. parameters: - - description: List ID + - description: Team ID in: path name: id required: true @@ -1149,11 +1144,11 @@ paths: - application/json responses: "200": - description: The list + description: The team schema: - $ref: '#/definitions/models.List' + $ref: '#/definitions/models.Team' "403": - description: The user does not have access to the list + description: The user does not have access to the team schema: $ref: '#/definitions/code.vikunja.io/web.HTTPError' "500": @@ -1162,9 +1157,9 @@ paths: $ref: '#/definitions/models.Message' security: - JWTKeyAuth: [] - summary: Gets one list + summary: Gets one team tags: - - list + - team post: consumes: - application/json @@ -1667,10 +1662,19 @@ paths: in: query name: s type: string - - description: The sorting parameter. Possible values to sort by are priority, - prioritydesc, priorityasc, duedate, duedatedesc, duedateasc. + - description: 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`, `text`, `description`, `done`, `done_at_unix`, + `due_date_unix`, `created_by_id`, `list_id`, `repeat_after`, `priority`, + `start_date_unix`, `end_date_unix`, `hex_color`, `percent_done`, `uid`, + `created`, `updated`. Default is `id`. in: query - name: sort + name: sort_by + type: string + - description: The ordering parameter. Possible values to order by are `asc` + or `desc`. Default is `asc`. + in: query + name: order_by type: string - description: 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 @@ -1699,7 +1703,7 @@ paths: $ref: '#/definitions/models.Message' security: - JWTKeyAuth: [] - summary: Get tasks on a list + summary: Get tasks in a list tags: - task /lists/{listID}/teams/{teamID}: diff --git a/vendor/github.com/mohae/deepcopy/.gitignore b/vendor/github.com/mohae/deepcopy/.gitignore new file mode 100644 index 00000000..5846dd15 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/.gitignore @@ -0,0 +1,26 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*~ +*.out +*.log diff --git a/vendor/github.com/mohae/deepcopy/.travis.yml b/vendor/github.com/mohae/deepcopy/.travis.yml new file mode 100644 index 00000000..fd47a8cf --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - tip + +matrix: + allow_failures: + - go: tip + +script: + - go test ./... diff --git a/vendor/github.com/mohae/deepcopy/LICENSE b/vendor/github.com/mohae/deepcopy/LICENSE new file mode 100644 index 00000000..419673f0 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Joel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mohae/deepcopy/README.md b/vendor/github.com/mohae/deepcopy/README.md new file mode 100644 index 00000000..f8184188 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/README.md @@ -0,0 +1,8 @@ +deepCopy +======== +[![GoDoc](https://godoc.org/github.com/mohae/deepcopy?status.svg)](https://godoc.org/github.com/mohae/deepcopy)[![Build Status](https://travis-ci.org/mohae/deepcopy.png)](https://travis-ci.org/mohae/deepcopy) + +DeepCopy makes deep copies of things: unexported field values are not copied. + +## Usage + cpy := deepcopy.Copy(orig) diff --git a/vendor/github.com/mohae/deepcopy/deepcopy.go b/vendor/github.com/mohae/deepcopy/deepcopy.go new file mode 100644 index 00000000..ba763ad0 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/deepcopy.go @@ -0,0 +1,125 @@ +// deepcopy makes deep copies of things. A standard copy will copy the +// pointers: deep copy copies the values pointed to. Unexported field +// values are not copied. +// +// Copyright (c)2014-2016, Joel Scoble (github.com/mohae), all rights reserved. +// License: MIT, for more details check the included LICENSE file. +package deepcopy + +import ( + "reflect" + "time" +) + +// Interface for delegating copy process to type +type Interface interface { + DeepCopy() interface{} +} + +// Iface is an alias to Copy; this exists for backwards compatibility reasons. +func Iface(iface interface{}) interface{} { + return Copy(iface) +} + +// Copy creates a deep copy of whatever is passed to it and returns the copy +// in an interface{}. The returned value will need to be asserted to the +// correct type. +func Copy(src interface{}) interface{} { + if src == nil { + return nil + } + + // Make the interface a reflect.Value + original := reflect.ValueOf(src) + + // Make a copy of the same type as the original. + cpy := reflect.New(original.Type()).Elem() + + // Recursively copy the original. + copyRecursive(original, cpy) + + // Return the copy as an interface. + return cpy.Interface() +} + +// copyRecursive does the actual copying of the interface. It currently has +// limited support for what it can handle. Add as needed. +func copyRecursive(original, cpy reflect.Value) { + // check for implement deepcopy.Interface + if original.CanInterface() { + if copier, ok := original.Interface().(Interface); ok { + cpy.Set(reflect.ValueOf(copier.DeepCopy())) + return + } + } + + // handle according to original's Kind + switch original.Kind() { + case reflect.Ptr: + // Get the actual value being pointed to. + originalValue := original.Elem() + + // if it isn't valid, return. + if !originalValue.IsValid() { + return + } + cpy.Set(reflect.New(originalValue.Type())) + copyRecursive(originalValue, cpy.Elem()) + + case reflect.Interface: + // If this is a nil, don't do anything + if original.IsNil() { + return + } + // Get the value for the interface, not the pointer. + originalValue := original.Elem() + + // Get the value by calling Elem(). + copyValue := reflect.New(originalValue.Type()).Elem() + copyRecursive(originalValue, copyValue) + cpy.Set(copyValue) + + case reflect.Struct: + t, ok := original.Interface().(time.Time) + if ok { + cpy.Set(reflect.ValueOf(t)) + return + } + // Go through each field of the struct and copy it. + for i := 0; i < original.NumField(); i++ { + // The Type's StructField for a given field is checked to see if StructField.PkgPath + // is set to determine if the field is exported or not because CanSet() returns false + // for settable fields. I'm not sure why. -mohae + if original.Type().Field(i).PkgPath != "" { + continue + } + copyRecursive(original.Field(i), cpy.Field(i)) + } + + case reflect.Slice: + if original.IsNil() { + return + } + // Make a new slice and copy each element. + cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap())) + for i := 0; i < original.Len(); i++ { + copyRecursive(original.Index(i), cpy.Index(i)) + } + + case reflect.Map: + if original.IsNil() { + return + } + cpy.Set(reflect.MakeMap(original.Type())) + for _, key := range original.MapKeys() { + originalValue := original.MapIndex(key) + copyValue := reflect.New(originalValue.Type()).Elem() + copyRecursive(originalValue, copyValue) + copyKey := Copy(key.Interface()) + cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue) + } + + default: + cpy.Set(original) + } +} diff --git a/vendor/github.com/prometheus/procfs/go.mod b/vendor/github.com/prometheus/procfs/go.mod index 14631543..e89ee6c9 100644 --- a/vendor/github.com/prometheus/procfs/go.mod +++ b/vendor/github.com/prometheus/procfs/go.mod @@ -1,3 +1 @@ module github.com/prometheus/procfs - -go 1.12 diff --git a/vendor/modules.txt b/vendor/modules.txt index 078cb301..57b04dae 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -114,6 +114,8 @@ github.com/mattn/go-sqlite3 github.com/matttproud/golang_protobuf_extensions/pbutil # github.com/mitchellh/mapstructure v1.1.2 github.com/mitchellh/mapstructure +# github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 +github.com/mohae/deepcopy # github.com/olekukonko/tablewriter v0.0.1 github.com/olekukonko/tablewriter # github.com/op/go-logging v0.0.0-20160315200505-970db520ece7