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