Sort Order for tasks (#110)

This commit is contained in:
konrad 2019-12-07 14:30:51 +00:00
parent e890001ee1
commit d8399e374c
22 changed files with 2080 additions and 994 deletions

View file

@ -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. |

13
go.mod
View file

@ -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

119
go.sum
View file

@ -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=

View file

@ -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}}]`)
})

View file

@ -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
// =================

View file

@ -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,
})

View file

@ -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)

View file

@ -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 <https://www.gnu.org/licenses/>.
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
})
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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)
})
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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)
}
})
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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)
}
})
}
}

View file

@ -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

View file

@ -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",

View file

@ -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",

View file

@ -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}:

26
vendor/github.com/mohae/deepcopy/.gitignore generated vendored Normal file
View file

@ -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

11
vendor/github.com/mohae/deepcopy/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,11 @@
language: go
go:
- tip
matrix:
allow_failures:
- go: tip
script:
- go test ./...

21
vendor/github.com/mohae/deepcopy/LICENSE generated vendored Normal file
View file

@ -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.

8
vendor/github.com/mohae/deepcopy/README.md generated vendored Normal file
View file

@ -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)

125
vendor/github.com/mohae/deepcopy/deepcopy.go generated vendored Normal file
View file

@ -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)
}
}

View file

@ -1,3 +1 @@
module github.com/prometheus/procfs
go 1.12

2
vendor/modules.txt vendored
View file

@ -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