Compare commits

...

60 Commits
v0.1 ... v0.3

Author SHA1 Message Date
konrad
14207a0fa8 Fix tests in drone (#13) 2018-11-02 14:37:11 +00:00
konrad
e906184bf7 updated version in readme 2018-11-02 15:29:01 +01:00
kolaente
1f41db9239 Hotfix: fix duplicate header in html mails with templates 2018-11-02 11:23:41 +01:00
konrad
4f8a0f8739 Fixed no root path for templates and static assets (#12) 2018-11-02 10:01:28 +00:00
konrad
c51ca4dd26 Fixed dockerfile (#11) 2018-11-02 09:18:38 +00:00
konrad
9e539b8c40 Fix checking for an email verification token (#10) 2018-11-01 22:59:20 +00:00
konrad
9a5eb577b8 Fix not checking for user credentials upon login (#9) 2018-11-01 22:51:05 +00:00
konrad
4713023a97 Prevent login from inactive (aka non-verified) users (#8) 2018-11-01 22:47:41 +00:00
konrad
301a4eedda New structure (#7) 2018-10-31 12:42:38 +00:00
kolaente
3f9fad0e2a use go modules from vendor when building the docker image 2018-10-28 18:38:40 +01:00
kolaente
990740fc19 fixed fmt 2018-10-28 17:35:48 +01:00
kolaente
960d72aa1b Fixed tests 2018-10-28 17:30:10 +01:00
kolaente
f94bbf023c Merge branch 'master' of ssh://git.kolaente.de:9022/vikunja/api 2018-10-28 17:24:13 +01:00
kolaente
2556ef7624 added vendor 2018-10-28 17:24:10 +01:00
konrad
321c0f2404 Fix build when using go modules (#6) 2018-10-28 16:11:13 +00:00
konrad
738c22b5f9 Add user email verification when registering (#5) 2018-10-27 13:14:55 +00:00
konrad
d0c30cb089 Password reset with token only (#4) 2018-10-27 13:12:15 +00:00
konrad
0cfea682ea Add password reset (#3) 2018-10-27 09:33:28 +00:00
konrad
95197ec6ed moar featurecreep 2018-10-22 22:20:45 +02:00
konrad
eecace511d updated todo 2018-10-21 22:22:56 +02:00
konrad
6793e13e6d updated todo 2018-10-21 22:21:37 +02:00
konrad
85da2d72aa updated todo 2018-10-21 22:19:51 +02:00
kolaente
206705d2e9 updated makefile to use new golint url 2018-10-15 12:54:39 +02:00
kolaente
522e647db0 Added config for database max connections 2018-10-11 18:39:53 +02:00
kolaente
bbd90eb976 Fixed not setting http code on error 2018-10-11 18:18:44 +02:00
kolaente
d8f6a628db Replaced CObject with a function returning an object 2018-10-11 17:53:59 +02:00
kolaente
a612037ee1 added go mod 2018-10-11 13:28:41 +02:00
kolaente
224cebfd10 Fixed tests failing 2018-10-06 22:26:17 +02:00
kolaente
f4ac036f27 removed unused errors 2018-10-06 18:55:14 +02:00
kolaente
c626b05af6 Improved log error messages 2018-10-06 18:44:04 +02:00
kolaente
56dc781594 added docs for error codes 2018-10-06 18:32:33 +02:00
kolaente
1ddb819c31 updated docs 2018-10-06 15:47:26 +02:00
kolaente
75b611de48 updated docs 2018-10-06 15:41:57 +02:00
konrad
c5100aad42 updated todo 2018-10-06 15:08:38 +02:00
konrad
f969593c0a Improved error handling 2018-10-06 15:04:14 +02:00
konrad
a4137b3d6f Added more checks to not leave an error unpassed when checking for rights 2018-10-06 13:25:37 +02:00
konrad
06638fccc8 Fixed lint & misspell 2018-10-06 13:07:42 +02:00
konrad
a108ed689d Added extra function to get a list without tasks or user objects 2018-10-06 13:05:29 +02:00
kolaente
53a7f2e6a7 cleanup 2018-10-05 19:17:39 +02:00
kolaente
3819927dd2 updated todo 2018-10-05 19:16:31 +02:00
kolaente
cca677878d Optimized used sql queries when getting lists or namespaces with lists 2018-10-05 19:16:14 +02:00
kolaente
a1a77a9b75 improved logging format 2018-10-05 18:49:56 +02:00
kolaente
6ea4f0598c fmt 2018-10-05 18:46:05 +02:00
kolaente
358b569bf1 Addded indecies everywhere 2018-10-05 18:46:01 +02:00
kolaente
1139eee2ad Improved update password method to ask the current password 2018-10-04 07:53:45 +02:00
konrad
a6d49a5e70 Fixed lint 2018-10-03 19:32:05 +02:00
kolaente
cd4fca19fd updated swagger spec 2018-10-03 19:30:43 +02:00
konrad
85c9fba808 Added the ability to update a users password 2018-10-03 19:28:17 +02:00
konrad
17368dea9a Cleanup 2018-10-03 19:00:09 +02:00
konrad
43c67d8c29 added go report badge 2018-09-23 21:16:56 +02:00
konrad
e724aeb206 Added new checks (#2)
* added gocyclo-check
* Added new tests to drone
* Added new checks to makefile
2018-09-23 19:15:14 +00:00
konrad
b912ff4176 updated drone config 2018-09-22 23:52:19 +02:00
konrad
849b64fc0c updated drone 2018-09-22 22:55:06 +02:00
konrad
ce661f1c6e updated drone 2018-09-22 22:38:25 +02:00
konrad
06a4ede4a8 fmt 2018-09-22 11:06:39 +02:00
kolaente
6519c62ade fixed a bug where xorm treid to insert something with an id wich already exist 2018-09-21 08:41:28 +02:00
kolaente
bfc783d1d0 updated swagger docs 2018-09-20 22:06:19 +02:00
kolaente
e7d43567aa updated readme 2018-09-20 21:59:43 +02:00
kolaente
6c35db57c7 updated todo 2018-09-20 19:44:43 +02:00
kolaente
64d290bcae Added endpoint to search for users 2018-09-20 19:42:01 +02:00
931 changed files with 195772 additions and 17398 deletions

View File

@@ -10,26 +10,24 @@ clone:
pipeline:
build:
image: webhippie/golang:edge
image: vikunja/golang-build:latest
pull: true
group: build
environment:
TAGS: bindata sqlite
GOPATH: /srv/app
commands:
- make clean
- make lint
- make fmt-check
- make swagger-check
# - make swagger-check # Inactive until go-swagger works with go modules
- make ineffassign-check
- make misspell-check
- make build
when:
event: [ push, tag, pull_request ]
test:
image: webhippie/golang:edge
image: vikunja/golang-build:latest
pull: true
environment:
GOPATH: /srv/app
commands:
- make test
when:
@@ -155,4 +153,14 @@ pipeline:
confirm: true
when:
event: [ push, tag ]
branch: [ master ]
branch: [ master ]
# Tell people vikunja was updated
telegram:
image: appleboy/drone-telegram
secrets: [ TELEGRAM_TOKEN, TELEGRAM_TO ]
message: >
{{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}}
{{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}`
Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}.

View File

@@ -5,6 +5,8 @@ FROM golang:1.11-alpine3.7 AS build-env
ARG VIKUNJA_VERSION
ENV TAGS "sqlite"
ENV GO111MODULE=on
ENV GOFLAGS=-mod=vendor
#Build deps
RUN apk --no-cache add build-base git
@@ -34,8 +36,11 @@ RUN apk --no-cache add \
tzdata
COPY docker /
COPY --from=build-env /go/src/code.vikunja.io/api/public /app/vikunja/
COPY --from=build-env /go/src/code.vikunja.io/api/public /app/vikunja/public
COPY --from=build-env /go/src/code.vikunja.io/api/templates /app/vikunja/templates
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja /app/vikunja/vikunja
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
CMD []

View File

@@ -167,6 +167,9 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
* [ ] Attachments
* [ ] Repeating tasks
* [ ] Tagesübersicht ("Was ist heute/diese Woche due?") -> Machen letztenendes die Clients, wir brauchen nur nen endpoint, der alle tasks auskotzt, der Client macht dann die Sortierung.
* [ ] Mit definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat"
* [ ] Namespaces in Namespaces (in Namespaces in Namespaces in Namespaces...)
* [ ] Rechtemanagement dafür wird schwierig
## Clients
@@ -191,7 +194,7 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
+ [x] CORS fixen
* [x] Überall echo.NewHTTPError statt c.JSON(Message{}) benutzen
* [x] Bessere Fehlermeldungen wenn das Model was ankommt falsch ist und nicht geparst werden kann
* [ ] Fehlerhandling irgendwie besser machen. Zb mit "World error messages"? Sprich, die Methode ruft einfach auf obs die entsprechende Fehlermeldung gibt und zeigt sonst 500 an.
* [x] Fehlerhandling irgendwie besser machen. Zb mit "World error messages"? Sprich, die Methode ruft einfach auf obs die entsprechende Fehlermeldung gibt und zeigt sonst 500 an.
* [ ] Endpoints neu organisieren? Also zb `namespaces/:nID/lists/:lID/items/:iID` statt einzelnen Endpoints für alles
* [x] Viper für config einbauen und ini rauswerfen
* [x] Docs für installationsanleitung
@@ -211,11 +214,28 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
* [x] Nen endpoint um /teams/members /list/users etc die Rechte updazudaten ohne erst zu löschen und dann neu einzufügen
* [x] namespaces & listen updaten geht nicht, gibt nen 500er zurück
* [x] Logging für alle Fehler irgendwohin, da gibts bestimmt ne coole library für
* [ ] Search endpoints /users?s=name und /teams?s=name, erstmal nur mit Namen suchen. -> Interface erweitern mit ner Funktion Search?
* [ ] Bei den Structs "AfterLoad" raus, das verbraucht bei Gruppenabfragen zu viele SQL-Abfragen -> Die sollen einfach die entsprechenden Read()-Methoden verwenden (Krassestes bsp. ist GET /namespaces mit so ca 50 Abfragen)
* [x] Ne extra funktion für list exists machen, damit die nicht immer über GetListByID gehen, um sql-abfragen zu sparen
* [x] Rausfinden warum xorm teilweise beim einfügen IDs mit einfügen will -> Das schlägt dann wegen duplicate fehl
* [x] Bei den Structs "AfterLoad" raus, das verbraucht bei Gruppenabfragen zu viele SQL-Abfragen -> Die sollen einfach die entsprechenden Read()-Methoden verwenden (Krassestes bsp. ist GET /namespaces mit so ca 50 Abfragen)
* [ ] Search endpoints /users?s=name und /teams?s=name, erstmal nur mit Namen suchen.
-> Search methode in den Handler einbauen und dann die Endpoints entsprechend anpassen
-> Macht Sinn das in ne eigene Methode umzubauen weil der query dazu leicht anders ist und man das auch nicht sooo einfach integrieren kann (so nach dem Motto einfach nen Searchstring anhängen)
* [ ] Methode einbauen, um mit einem gültigen token ein neues gültiges zu kriegen
* [ ] Wir brauchen noch ne gute idee, wie man die listen kriegt, auf die man nur so Zugriff hat (ohne namespace)
* [ ] Validation der ankommenden structs, am besten mit https://github.com/go-validator/validator
* [ ] Pagination
* Sollte in der Config definierbar sein, wie viel pro Seite angezeigt werden soll, die CRUD-Methoden übergeben dann ein "gibt mir die Seite sowieso" an die CRUDable-Funktionenen, die müssen das dann Auswerten. Geht leider nicht anders, wenn man erst 2342352 Einträge hohlt und die dann nachträglich auf 200 begrenzt ist das ne massive Ressourcenverschwendung.
* [ ] Testing mit locust: https://locust.io/
#### Userstuff
* [ ] Userstuff aufräumen
-> Soweit es geht und Sinnvoll ist auf den neuen Handler umziehen
-> Login/Register/Password-reset geht natürlich nicht
-> Bleibt noch Profile abrufen und Einstellungen -> Macht also keinen Sinn das auf den neuen Handler umzuziehen
* [x] Email-Verifizierung beim Registrieren
* [x] Password Reset -> Link via email oder so
* [ ] Settings
### Later/Nice to have

324
Gopkg.lock generated
View File

@@ -1,324 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
digest = "1:6cb5bf34f3e3bb61e76d976f5bd29d245b567f208e1fd7cb8104b736a5eb649d"
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
pruneopts = "UT"
revision = "a539ee1a749a2b895533f979515ac7e6e0f5b650"
[[projects]]
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
name = "github.com/fsnotify/fsnotify"
packages = ["."]
pruneopts = "UT"
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
version = "v1.4.7"
[[projects]]
digest = "1:0594af97b2f4cec6554086eeace6597e20a4b69466eb4ada25adf9f4300dddd2"
name = "github.com/garyburd/redigo"
packages = [
"internal",
"redis",
]
pruneopts = "UT"
revision = "a69d19351219b6dd56f274f96d85a7014a2ec34e"
version = "v1.6.0"
[[projects]]
digest = "1:d2b67246c3fa959edfa1be84b407cf0a0a4cabbace0843814b1729b5af808d9e"
name = "github.com/go-sql-driver/mysql"
packages = ["."]
pruneopts = "UT"
revision = "ee359f95877bdef36cbb602711e49b6f0becfca9"
[[projects]]
digest = "1:3f854136f2da9cde22fa0dd29e74fb7c920c5d79d060847fe9b96f2d3cfab746"
name = "github.com/go-xorm/builder"
packages = ["."]
pruneopts = "UT"
revision = "c8871c857d2555fbfbd8524f895be5386d3d8836"
[[projects]]
digest = "1:ee0b218753df985dd6633b13a894a69f0c1ea385f092f4afa093d407bb165cc5"
name = "github.com/go-xorm/core"
packages = ["."]
pruneopts = "UT"
revision = "f43c33d9a48db006417a7ac4c16b08897e3e1458"
version = "v0.5.8"
[[projects]]
digest = "1:8e0764e74dd6cf539d3c035772d3fd0009b89fff9889a1131139be6d871c90dd"
name = "github.com/go-xorm/xorm"
packages = ["."]
pruneopts = "UT"
revision = "29d4a0330a00b9be468b70e3fb0f74109348c358"
[[projects]]
branch = "master"
digest = "1:d4a957ebe4ccebc299c1f6b6a78a0713af379721980cf14990b8ab8fa93cd92d"
name = "github.com/go-xorm/xorm-redis-cache"
packages = ["."]
pruneopts = "UT"
revision = "859b313566b2ef090319245fd4fe7692f25dbd79"
[[projects]]
digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10"
name = "github.com/hashicorp/hcl"
packages = [
".",
"hcl/ast",
"hcl/parser",
"hcl/printer",
"hcl/scanner",
"hcl/strconv",
"hcl/token",
"json/parser",
"json/scanner",
"json/token",
]
pruneopts = "UT"
revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241"
version = "v1.0.0"
[[projects]]
digest = "1:8eb1de8112c9924d59bf1d3e5c26f5eaa2bfc2a5fcbb92dc1c2e4546d695f277"
name = "github.com/imdario/mergo"
packages = ["."]
pruneopts = "UT"
revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4"
version = "v0.3.6"
[[projects]]
digest = "1:82c5850e702ccef3976cdfdf737725013587c64e02e5ecae57e86431163b27aa"
name = "github.com/labstack/echo"
packages = [
".",
"middleware",
]
pruneopts = "UT"
revision = "1049c9613cd371b7ea8f219404c9a821734781ed"
version = "v3.1.0"
[[projects]]
digest = "1:ef6158d11f87c8036114cf3d2bf5c50edb4aa0b4aab5deef26716a8bb674a9ad"
name = "github.com/labstack/gommon"
packages = [
"bytes",
"color",
"log",
"random",
]
pruneopts = "UT"
revision = "57409ada9da0f2afad6664c49502f8c50fbd8476"
version = "0.2.3"
[[projects]]
digest = "1:c568d7727aa262c32bdf8a3f7db83614f7af0ed661474b24588de635c20024c7"
name = "github.com/magiconair/properties"
packages = ["."]
pruneopts = "UT"
revision = "c2353362d570a7bfa228149c62842019201cfb71"
version = "v1.8.0"
[[projects]]
digest = "1:a36a1febe1240bb79a208390ad17e1080555b0031a9ed42a41eae173cca3fd74"
name = "github.com/mattn/go-colorable"
packages = ["."]
pruneopts = "UT"
revision = "ad5389df28cdac544c99bd7b9161a0b5b6ca9d1b"
[[projects]]
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = "UT"
revision = "a5cdd64afdee435007ee3e9f6ed4684af949d568"
[[projects]]
digest = "1:3cafc6a5a1b8269605d9df4c6956d43d8011fc57f266ca6b9d04da6c09dee548"
name = "github.com/mattn/go-sqlite3"
packages = ["."]
pruneopts = "UT"
revision = "25ecb14adfc7543176f7d85291ec7dba82c6f7e4"
version = "v1.9.0"
[[projects]]
digest = "1:645110e089152bd0f4a011a2648fbb0e4df5977be73ca605781157ac297f50c4"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
pruneopts = "UT"
revision = "fa473d140ef3c6adf42d6b391fe76707f1f243c8"
version = "v1.0.0"
[[projects]]
digest = "1:5b3b29ce0e569f62935d9541dff2e16cc09df981ebde48e82259076a73a3d0c7"
name = "github.com/op/go-logging"
packages = ["."]
pruneopts = "UT"
revision = "b2cb9fa56473e98db8caba80237377e83fe44db5"
version = "v1"
[[projects]]
digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e"
name = "github.com/pelletier/go-toml"
packages = ["."]
pruneopts = "UT"
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
version = "v1.2.0"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd"
name = "github.com/spf13/afero"
packages = [
".",
"mem",
]
pruneopts = "UT"
revision = "d40851caa0d747393da1ffb28f7f9d8b4eeffebd"
version = "v1.1.2"
[[projects]]
digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f"
name = "github.com/spf13/cast"
packages = ["."]
pruneopts = "UT"
revision = "8965335b8c7107321228e3e3702cab9832751bac"
version = "v1.2.0"
[[projects]]
digest = "1:68ea4e23713989dc20b1bded5d9da2c5f9be14ff9885beef481848edd18c26cb"
name = "github.com/spf13/jwalterweatherman"
packages = ["."]
pruneopts = "UT"
revision = "4a4406e478ca629068e7768fc33f3f044173c0a6"
version = "v1.0.0"
[[projects]]
digest = "1:dab83a1bbc7ad3d7a6ba1a1cc1760f25ac38cdf7d96a5cdd55cd915a4f5ceaf9"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "9a97c102cda95a86cec2345a6f09f55a939babf5"
version = "v1.0.2"
[[projects]]
digest = "1:6e30a27eac59a148b3f7a32e0ba54706b31dcde5a42f63b22cb47873b62fa343"
name = "github.com/spf13/viper"
packages = ["."]
pruneopts = "UT"
revision = "8fb642006536c8d3760c99d4fa2389f5e2205631"
version = "v1.2.0"
[[projects]]
digest = "1:a7496c8d47f794d15a6e41f8ada426d4a41c038fac9bd75c1abc30faacf469c9"
name = "github.com/stretchr/testify"
packages = ["assert"]
pruneopts = "UT"
revision = "87b1dfb5b2fa649f52695dd9eae19abe404a4308"
[[projects]]
branch = "master"
digest = "1:c468422f334a6b46a19448ad59aaffdfc0a36b08fdcc1c749a0b29b6453d7e59"
name = "github.com/valyala/bytebufferpool"
packages = ["."]
pruneopts = "UT"
revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
[[projects]]
branch = "master"
digest = "1:268b8bce0064e8c057d7b913605459f9a26dcab864c0886a56d196540fbf003f"
name = "github.com/valyala/fasttemplate"
packages = ["."]
pruneopts = "UT"
revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
[[projects]]
digest = "1:456c96b87c9481d98aac8ef9862053a8fc97b981d26f6cd8d63de783dd24acec"
name = "golang.org/x/crypto"
packages = [
"acme",
"acme/autocert",
"bcrypt",
"blowfish",
]
pruneopts = "UT"
revision = "9419663f5a44be8b34ca85f08abc5fe1be11f8a3"
[[projects]]
digest = "1:f1275b0d1ed7d43eeb7a0464a148258f04e66a4783514d5c790d4e970cc7df6d"
name = "golang.org/x/sys"
packages = ["unix"]
pruneopts = "UT"
revision = "314a259e304ff91bd6985da2a7149bbf91237993"
[[projects]]
digest = "1:8029e9743749d4be5bc9f7d42ea1659471767860f0cdc34d37c3111bd308a295"
name = "golang.org/x/text"
packages = [
"internal/gen",
"internal/triegen",
"internal/ucd",
"transform",
"unicode/cldr",
"unicode/norm",
]
pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
digest = "1:59c645bd5db5ec4a889a1ac62ce9cb3c4483cc24c66907ea75de2516d6fe4716"
name = "gopkg.in/testfixtures.v2"
packages = ["."]
pruneopts = "UT"
revision = "1d98c34adfb14dbedeef37127968233b5d960f02"
version = "v2.4.5"
[[projects]]
digest = "1:73e6fda93622790d2371344759df06ff5ff2fac64a6b6e8832b792e7402956e7"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
version = "v2.0.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/dgrijalva/jwt-go",
"github.com/go-sql-driver/mysql",
"github.com/go-xorm/core",
"github.com/go-xorm/xorm",
"github.com/go-xorm/xorm-redis-cache",
"github.com/imdario/mergo",
"github.com/labstack/echo",
"github.com/labstack/echo/middleware",
"github.com/mattn/go-sqlite3",
"github.com/op/go-logging",
"github.com/spf13/viper",
"github.com/stretchr/testify/assert",
"golang.org/x/crypto/bcrypt",
"gopkg.in/testfixtures.v2",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -16,7 +16,7 @@ endif
GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go")
GOFMT ?= gofmt -s
GOFLAGS := -i -v
GOFLAGS := -v -mod=vendor
EXTRA_GOFLAGS ?=
LDFLAGS := -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')" -X "main.Tags=$(TAGS)"
@@ -44,12 +44,14 @@ else
endif
endif
VERSION := $(shell echo $(VERSION) | sed 's/\//\-/g')
.PHONY: all
all: build
.PHONY: clean
clean:
go clean -i ./...
go clean ./...
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA)
.PHONY: test
@@ -58,12 +60,12 @@ test:
go tool cover -html=cover.out -o cover.html
required-gofmt-version:
@go version | grep -q '\(1.7\|1.8\|1.9\|1.10\|1.11\)' || { echo "We require go version 1.7, 1.8, 1.9 or 1.10 to format code" >&2 && exit 1; }
@go version | grep -q '\(1.7\|1.8\|1.9\|1.10\|1.11\)' || { echo "We require go version 1.7, 1.8, 1.9, 1.10 or 1.11 to format code" >&2 && exit 1; }
.PHONY: lint
lint:
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/golang/lint/golint; \
go install $(GOFLAGS) golang.org/x/lint/golint; \
fi
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
@@ -101,7 +103,7 @@ release-dirs:
.PHONY: release-windows
release-windows:
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/karalabe/xgo; \
go install $(GOFLAGS) github.com/karalabe/xgo; \
fi
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out vikunja-$(VERSION) .
ifeq ($(CI),drone)
@@ -111,7 +113,7 @@ endif
.PHONY: release-linux
release-linux:
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/karalabe/xgo; \
go install $(GOFLAGS) github.com/karalabe/xgo; \
fi
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out vikunja-$(VERSION) .
ifeq ($(CI),drone)
@@ -121,7 +123,7 @@ endif
.PHONY: release-darwin
release-darwin:
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/karalabe/xgo; \
go install $(GOFLAGS) github.com/karalabe/xgo; \
fi
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out vikunja-$(VERSION) .
ifeq ($(CI),drone)
@@ -133,6 +135,8 @@ release-copy:
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
mkdir $(DIST)/release/public
cp public/ $(DIST)/release/ -R
mkdir $(DIST)/release/templates
cp templates/ $(DIST)/templates/ -R
.PHONY: release-check
release-check:
@@ -141,7 +145,7 @@ release-check:
.PHONY: release-os-package
release-os-package:
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp $(DIST)/release/public $(file)-full/ -R; cp LICENSE $(file)-full/; )
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp $(DIST)/release/public $(file)-full/ -R; cp $(DIST)/release/templates $(file)-full/ -R; cp LICENSE $(file)-full/; )
rm $(DIST)/release/public -rf
.PHONY: release-zip
@@ -151,7 +155,7 @@ release-zip:
.PHONY: generate-swagger
generate-swagger:
@hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/go-swagger/go-swagger/cmd/swagger; \
go install $(GOFLAGS) github.com/go-swagger/go-swagger/cmd/swagger; \
fi
swagger generate spec -o ./public/swagger/swagger.v1.json
@@ -167,6 +171,27 @@ swagger-check: generate-swagger
.PHONY: swagger-validate
swagger-validate:
@hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/go-swagger/go-swagger/cmd/swagger; \
go install $(GOFLAGS) github.com/go-swagger/go-swagger/cmd/swagger; \
fi
swagger validate ./public/swagger/swagger.v1.json
.PHONY: misspell-check
misspell-check:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install $(GOFLAGS) github.com/client9/misspell/cmd/misspell; \
fi
for S in $(GOFILES); do misspell -error $$S || exit 1; done;
.PHONY: ineffassign-check
ineffassign-check:
@hash ineffassign > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install $(GOFLAGS) github.com/gordonklaus/ineffassign; \
fi
for S in $(GOFILES); do ineffassign $$S || exit 1; done;
.PHONY: gocyclo-check
gocyclo-check:
@hash gocyclo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install $(GOFLAGS) github.com/fzipp/gocyclo; \
fi
for S in $(GOFILES); do gocyclo -over 14 $$S || exit 1; done;

View File

@@ -4,8 +4,11 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone.kolaente.de/vikunja/api)
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.1-brightgreen.svg)](https://storage.kolaente.de/minio/vikunja/)
[![Download](https://img.shields.io/badge/download-v0.3-brightgreen.svg)](https://storage.kolaente.de/minio/vikunja/)
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/swagger)
[![Go Report Card](https://goreportcard.com/badge/git.kolaente.de/vikunja/api)](https://goreportcard.com/report/git.kolaente.de/vikunja/api
)
## Features
* Create TODO lists with tasks
@@ -29,6 +32,8 @@ Try it under [try.vikunja.io](https://try.vikunja.io)!
## Development
We use go modules to vendor libraries for Vikunja, so you'll need at least go `1.11`.
To contribute to Vikunja, fork the project and work on the master branch.
Some internal packages are referenced using their respective package URL. This can become problematic. To “trick” the Go tool into thinking this is a clone from the official repository, download the source code into `$GOPATH/code.vikunja.io/api`. Fork the Vikunja repository, it should then be possible to switch the source directory on the command line.
@@ -41,7 +46,7 @@ To be able to create pull requests, the forked repository should be added as a r
```bash
git remote rename origin upstream
git remote add origin git@git.kolaente.de:<USERNAME>/vikunja.git
git remote add origin git@git.kolaente.de:<USERNAME>/api.git
git fetch --all --prune
```

View File

@@ -3,23 +3,21 @@ POST http://localhost:8080/api/v1/login
Content-Type: application/json
{
"user": "user",
"username": "user",
"password": "1234"
}
> {% client.global.set("auth_token", response.body.token); %}
###
## Register
### Register
POST http://localhost:8080/api/v1/register
Content-Type: application/json
{
"username": "user3",
"username": "user4",
"password": "1234",
"email": "3@knt.li"
"email": "4@knt.li"
}
###

View File

@@ -5,7 +5,7 @@ Authorization: Bearer {{auth_token}}
###
# Get one list
GET http://localhost:8080/api/v1/lists/13
GET http://localhost:8080/api/v1/lists/2
Authorization: Bearer {{auth_token}}
###

View File

@@ -5,7 +5,7 @@ Authorization: Bearer {{auth_token}}
###
# Get one namespaces
GET http://localhost:8080/api/v1/namespaces/12
GET http://localhost:8080/api/v1/namespaces/125476
Authorization: Bearer {{auth_token}}
###

53
REST-Tests/users.http Normal file
View File

@@ -0,0 +1,53 @@
# Get all users
GET http://localhost:8080/api/v1/users
Authorization: Bearer {{auth_token}}
######
# Search for a user
GET http://localhost:8080/api/v1/users?s=3
Authorization: Bearer {{auth_token}}
###
## Update password
POST http://localhost:8080/api/v1/user/password
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"old_password": "1234",
"new_password": "1234"
}
### Request a password to reset a password
POST http://localhost:8080/api/v1/user/password/token
Content-Type: application/json
Accept: application/json
{
"user_name": "user"
}
### Request a token to reset a password
POST http://localhost:8080/api/v1/user/password/reset
Content-Type: application/json
Accept: application/json
{
"token": "eAsZzakgqARnjzXHqsHqZtSUKuiOhoJjHANhgTxUIDBSalhbtdpAdLeywGXzVDBuRQGNpHdMxoHXhLVSlzpJsFvuoJgMdkhRhkNhaQXfufuZCdtUlerZHSJQLgYMUryHIxIREcmZLtWoZVrYyARkCvkyFhcGtoCwQOEjAOEZMQQuxTVoGYfAqcfNggQnerUcXCiRIgRtkusXSnltomhaeyRwAbrckXFeXxUjslgplSGqSTOqJTYuhrSzAVTwNvuYyvuXLaZoNnJEyeVDWlRydnxfgUQjQZOKwCBRWVQPKpZhlslLUyUAMsRQkHITkruQCjDnOGCCRsSNplbNCEuDmMfpWYHSQAcQIDZtbQWkxzpfmHDMQvvKPPrxEnrTErlvTfKDKICFYPQxXNpNE",
"new_password": "1234"
}
### Confirm a users email address
POST http://localhost:8080/api/v1/user/confirm
Content-Type: application/json
Accept: application/json
{
"token": ""
}
###

View File

@@ -5,6 +5,8 @@ service:
JWTSecret: "cei6gaezoosah2bao3ieZohkae5aicah"
# The interface on which to run the webserver
interface: ":3456"
# The URL of the frontend, used to send password reset emails.
frontendurl: ""
database:
# Database type to use. Supported types are mysql and sqlite.
@@ -33,3 +35,17 @@ cache:
redishost: 'localhost:6379'
# When using redis, this is the password used to authenicate against the redis server
redispassword: ''
mailer:
# SMTP Host
host: ''
# SMTP Host port
port: 587
# SMTP username
username: 'user'
# SMTP password
password: ''
# Wether to skip verification of the tls certificate on the server
skiptlsverify: false
# The default from address when sending emails
fromemail: 'mail@vikunja'

View File

@@ -6,7 +6,7 @@ functions and respective web handlers, all represented through interfaces.
## CRUDable
This interface defines methods to Create/Read/ReadAll/Update/Delete something. In order to use the common web
handler, the struct must implement this and the `rights` interface.
handler, the struct must implement this and the `Rights` interface.
The interface is defined as followed:
@@ -54,9 +54,23 @@ type Rights interface {
```
When using the standard web handler, all methods except `CanRead()` are called before their `CRUD` counterparts. `CanRead()`
is called after `Read()` was invoked as this would otherwise mean getting an object from the db to check if the user has the
is called after `ReadOne()` was invoked as this would otherwise mean getting an object from the db to check if the user has the
right to see it and then getting it again if thats the case. Calling the function afterwards means we only have to get the
object once.
## Standard web handler
## Errors
Error types with their messages and http-codes are set in `models/error.go`. If the error type implements `HTTPError`, the server returns a user-friendly error message when this error occours. This means it returns a good HTTP status code, a message, and an error code. The error code should be unique across all error codes and can be used on the client to show a localized error message or do other stuff based on the exact error the server returns. That way the client won't have to "guess" that the error message remains the same over multiple versions of Vikunja.
An `HTTPError` is defined as follows:
```go
type HTTPError struct {
HTTPCode int `json:"-"` // Can be any valid HTTP status code, I'd reccomend to use the constants of the http package.
Code int `json:"code"` // Must be a uniqe int identifier for this specific error. I'd reccomend defining a constant for this.
Message string `json:"message"` // A user-readable message what went wrong.
}
```

View File

@@ -29,6 +29,10 @@ service:
JWTSecret: "cei6gaezoosah2bao3ieZohkae5aicah"
# The interface on which to run the webserver
interface: ":3456"
# The URL of the frontend, used to send password reset emails.
frontendurl: ""
# The base path on the file system where the binary and assets are
rootpath: <the path of the executable>
database:
# Database type to use. Supported types are mysql and sqlite.
@@ -45,6 +49,9 @@ database:
Path: "./vikunja.db"
# Whether to show mysql queries or not. Useful for debugging.
showqueries: "false"
# Sets the max open connections to the database. Only used when using mysql.
openconnections: 100
cache:
# If cache is enabled or not
@@ -57,4 +64,22 @@ cache:
redishost: 'localhost:6379'
# When using redis, this is the password used to authenicate against the redis server
redispassword: ''
mailer:
# SMTP Host
host: ""
# SMTP Host port
port: 587
# SMTP username
username: "user"
# SMTP password
password: ""
# Wether to skip verification of the tls certificate on the server
skiptlsverify: false
# The default from address when sending emails
fromemail: "mail@vikunja"
# The length of the mail queue.
queuelength: 100
# The timeout in seconds after which the current open connection to the mailserver will be closed.
queuetimeout: 30
```

38
docs/errors.md Normal file
View File

@@ -0,0 +1,38 @@
# Errors
This document describes the different errors Vikunja can return.
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 1001 | 400 | A user with this username already exists. |
| 1002 | 400 | A user with this email address already exists. |
| 1004 | 400 | No username and password specified. |
| 1005 | 404 | The user does not exist. |
| 1006 | 400 | Could not get the user id. |
| 1008 | 412 | No password reset token provided. |
| 1009 | 412 | Invalid password reset token. |
| 1010 | 412 | Invalid email confirm token. |
| 1011 | 412 | Wrong username or password. |
| 1012 | 412 | Email address of the user not confirmed. |
| 2001 | 400 | ID cannot be empty or 0. |
| 3001 | 404 | The list does not exist. |
| 3004 | 403 | The user needs to have read permissions on that list to perform that action. |
| 3005 | 400 | The list title cannot be empty. |
| 4001 | 400 | The list task text cannot be empty. |
| 4002 | 404 | The list task does not exist. |
| 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. |
| 5009 | 403 | The user needs to have namespace read access to perform that action. |
| 5010 | 403 | This team does not have access to that namespace. |
| 5011 | 409 | This user has already access to that namespace. |
| 6001 | 400 | The team name cannot be emtpy. |
| 6002 | 404 | The team does not exist. |
| 6003 | 400 | The provided team right is invalid. |
| 6004 | 409 | The team already has access to that namespace or list. |
| 6005 | 409 | The user is already a member of that team. |
| 6006 | 400 | Cannot delete the last team member. |
| 6007 | 403 | The team does not have access to the list to perform that action. |
| 7001 | 400 | The user right is invalid. |
| 7002 | 409 | The user already has access to that list. |
| 7003 | 403 | The user does not have access to that list. |

55
go.mod Normal file
View File

@@ -0,0 +1,55 @@
module code.vikunja.io/api
require (
cloud.google.com/go v0.30.0 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/client9/misspell v0.3.4
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 // indirect
github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
github.com/garyburd/redigo v1.6.0 // indirect
github.com/go-openapi/analysis v0.17.2 // indirect
github.com/go-openapi/errors v0.17.2 // indirect
github.com/go-openapi/inflect v0.17.2 // indirect
github.com/go-openapi/loads v0.17.2 // indirect
github.com/go-openapi/runtime v0.17.2 // indirect
github.com/go-openapi/spec v0.17.2 // indirect
github.com/go-openapi/strfmt v0.17.2 // indirect
github.com/go-openapi/swag v0.17.2 // indirect
github.com/go-openapi/validate v0.17.2 // indirect
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b
github.com/go-swagger/go-swagger v0.17.2
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 // indirect
github.com/go-xorm/core v0.5.8
github.com/go-xorm/tests v0.5.6 // indirect
github.com/go-xorm/xorm v0.0.0-20170930012613-29d4a0330a00
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2
github.com/google/go-cmp v0.2.0 // indirect
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc
github.com/gorilla/handlers v1.4.0 // indirect
github.com/imdario/mergo v0.3.6
github.com/jessevdk/go-flags v1.4.0 // indirect
github.com/joho/godotenv v1.3.0 // indirect
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970
github.com/kr/pretty v0.1.0 // indirect
github.com/labstack/echo v3.1.0+incompatible
github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0
github.com/lib/pq v1.0.0 // indirect
github.com/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd // indirect
github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee // indirect
github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5 // indirect
github.com/mattn/go-sqlite3 v1.9.0
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473
github.com/spf13/viper v1.2.0
github.com/stretchr/testify v1.2.2
github.com/toqueteos/webbrowser v1.1.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 // indirect
github.com/ziutek/mymysql v1.5.4 // indirect
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/testfixtures.v2 v2.4.5
)

165
go.sum Normal file
View File

@@ -0,0 +1,165 @@
cloud.google.com/go v0.30.0 h1:xKvyLgk56d0nksWq49J0UyGEeUIicTl4+UBiX1NPX9g=
cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
github.com/PuerkitoBio/purell v1.1.0/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/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 h1:BZGp1dbKFjqlGmxEpwkDpCWNxVwEYnUPoncIzLiHlPo=
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a h1:nmYyGtn9AO7FCeZ2tHr1ZWjJAHi6SfGB3o80F8o7EbA=
github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 h1:roDmqJ4Qes7hrDOsWsMCce0vQHz3xiMPjJ9m4c2eeNs=
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835/go.mod h1:BjL/N0+C+j9uNX+1xcNuM9vdSIcXCZrQZUYbXOFbgN8=
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.17.2 h1:eYp14J1o8TTSCzndHBtsNuckikV1PfZOSnx4BcBeu0c=
github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.17.2 h1:azEQ8Fnx0jmtFF2fxsnmd6I0x6rsweUF63qqSO1NmKk=
github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/inflect v0.17.2 h1:4Zg/XLOwxsyKGFHxCI9e4AkxUFSjLKaoY+jnStSJAfw=
github.com/go-openapi/inflect v0.17.2/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
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/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.17.2 h1:tEXYu6Xc0pevpzzQx5ghrMN9F7IVpN/+u4iD3rkYE5o=
github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.17.2 h1:/ZK67ikFhQAMFFH/aPu2MaGH7QjP4wHBvHYOVIzDAw0=
github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.17.2 h1:eb2NbuCnoe8cWAxhtK6CfMWUYmiFEZJ9Hx3Z2WRwJ5M=
github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.17.2 h1:2KDns36DMHXG9/iYkOjiX+/8fKK9GCU5ELZ+J6qcRVA=
github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.17.2 h1:K/ycE/XTUDFltNHSO32cGRUhrVGJD64o8WgAIZNyc3k=
github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.17.2 h1:lwFfiS4sv5DvOrsYDsYq4N7UU8ghXiYtPJ+VcQnC3Xg=
github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b h1:/CMGgAYard7jx9+bI7tUIqafFDR7Pv2BRu2Tb5dDaqM=
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-swagger/go-swagger v0.17.2 h1:eizwRyO8THHMA4kXyM5Z1UTPslZGE8VsfJC0jJqsRI8=
github.com/go-swagger/go-swagger v0.17.2/go.mod h1:fOcXeMI1KPNv3uk4u7cR4VSyq0NyrYx4SS1/ajuTWDg=
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 h1:jUX9yw6+iKrs/WuysV2M6ap/ObK/07SE/a7I2uxitwM=
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25/go.mod h1:M+P3wv0K2C+ynucGDEqJCeOTc+6DcAtiiqU8GrCksXY=
github.com/go-xorm/core v0.5.8 h1:vQ0ghlVGnlnFmm4SpHY+xNnPlH810paMcw+Hwz9BCqE=
github.com/go-xorm/core v0.5.8/go.mod h1:d8FJ9Br8OGyQl12MCclmYBuBqqxsyeedpXciV5Myih8=
github.com/go-xorm/tests v0.5.6 h1:E4nmVkKfHQAm+i2/pmOJ5JUej6sORVcvwl6/LQybif4=
github.com/go-xorm/tests v0.5.6/go.mod h1:s8J/EnVBcXQR93dN7Jy6Dwlo92HUP5nTgKWF1wGeCDg=
github.com/go-xorm/xorm v0.0.0-20170930012613-29d4a0330a00 h1:jlA1XEj8QHl6my6FUkHwRCGu/J5hQ1zkW7RqULZ2XGc=
github.com/go-xorm/xorm v0.0.0-20170930012613-29d4a0330a00/go.mod h1:i7qRPD38xj/v75UV+a9pEzr5tfRaH2ndJfwt/fGbQhs=
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2 h1:57QbyUkFcFjipHJQstYR5owRxsQzgD8/OAO/hr4yl/E=
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2/go.mod h1:xxK9FGkFXrau9/vGdDYSOyQfSgKXBV7iHXpQfNuv6B0=
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/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970 h1:0+1ZURVRim6FxA/jhWhJklsgoWc69q1sxlIu2Ztnhy0=
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970/go.mod h1:iYGcTYIPUvEWhFo6aKUuLchs+AV4ssYdyuBbQJZGcBk=
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/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.1.0+incompatible h1:kb0CCZ0boaiGsZOqR9E9+GDpQEoIaKClVqqo0+/hzbM=
github.com/labstack/echo v3.1.0+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0 h1:kcJPx2Ug9owxOsVfuXPCludLaIudyI57YQd6ocyrO4o=
github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
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/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd h1:eYiiP5pgdf+n78BU5JFWt7yI2bpxW31L/R5Rrk8vLgs=
github.com/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee h1:tUyoJR5V1TdXnTh9v8c1YAHvDdut2+zkuyUX3gAY/wI=
github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5 h1:+IPgoz43mdEYG5lrqNcjr3DQpAE38SqHtyx1IsqqQGM=
github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473 h1:J1QZwDXgZ4dJD2s19iqR9+U00OWM2kDzbf1O/fmvCWg=
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.2.0 h1:M4Rzxlu+RgU4pyBRKhKaVN1VeYOm8h2jgyXnAseDgCc=
github.com/spf13/viper v1.2.0/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/toqueteos/webbrowser v1.1.0 h1:Prj1okiysRgHPoe3B1bOIVxcv+UuSt525BDQmR5W0x0=
github.com/toqueteos/webbrowser v1.1.0/go.mod h1:Hqqqmzj8AHn+VlZyVjaRWY20i25hoOZGAABCcg2el4A=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 h1:9lP3x0pW80sDI6t1UMSLA4to18W7R7imwAI/sWS9S8Q=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg=
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 h1:QJP9sxq2/KbTxFnGduVryxJOt6r/UVGyom3tLaqu7tc=
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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/testfixtures.v2 v2.4.5 h1:mnfYPBNoJnis+4crs6UzC4lv4GjTVoLXE9B/tW802q0=
gopkg.in/testfixtures.v2 v2.4.5/go.mod h1:vyAq+MYCgNpR29qitQdLZhdbLFf4mR/2MFJRFoQZZ2M=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

22
main.go
View File

@@ -1,8 +1,11 @@
package main
import (
"code.vikunja.io/api/models"
"code.vikunja.io/api/routes"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/mail"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/routes"
"context"
"github.com/spf13/viper"
@@ -17,24 +20,27 @@ var Version = "0.1"
func main() {
// Init logging
models.InitLogger()
log.InitLogger()
// Init Config
err := models.InitConfig()
err := config.InitConfig()
if err != nil {
models.Log.Error(err.Error())
log.Log.Error(err.Error())
os.Exit(1)
}
// Set Engine
err = models.SetEngine()
if err != nil {
models.Log.Error(err.Error())
log.Log.Error(err.Error())
os.Exit(1)
}
// Start the mail daemon
mail.StartMailDaemon()
// Version notification
models.Log.Infof("Vikunja version %s", Version)
log.Log.Infof("Vikunja version %s", Version)
// Start the webserver
e := routes.NewEcho()
@@ -53,7 +59,7 @@ func main() {
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
models.Log.Infof("Sutting down...")
log.Log.Infof("Shutting down...")
if err := e.Shutdown(ctx); err != nil {
e.Logger.Fatal(err)
}

View File

@@ -1,596 +0,0 @@
package models
import "fmt"
// =====================
// User Operation Errors
// =====================
// ErrUsernameExists represents a "UsernameAlreadyExists" kind of error.
type ErrUsernameExists struct {
UserID int64
Username string
}
// IsErrUsernameExists checks if an error is a ErrUsernameExists.
func IsErrUsernameExists(err error) bool {
_, ok := err.(ErrUsernameExists)
return ok
}
func (err ErrUsernameExists) Error() string {
return fmt.Sprintf("a user with this username does already exist [user id: %d, username: %s]", err.UserID, err.Username)
}
// ErrUserEmailExists represents a "UserEmailExists" kind of error.
type ErrUserEmailExists struct {
UserID int64
Email string
}
// IsErrUserEmailExists checks if an error is a ErrUserEmailExists.
func IsErrUserEmailExists(err error) bool {
_, ok := err.(ErrUserEmailExists)
return ok
}
func (err ErrUserEmailExists) Error() string {
return fmt.Sprintf("a user with this email does already exist [user id: %d, email: %s]", err.UserID, err.Email)
}
// ErrNoUsername represents a "UsernameAlreadyExists" kind of error.
type ErrNoUsername struct {
UserID int64
}
// IsErrNoUsername checks if an error is a ErrUsernameExists.
func IsErrNoUsername(err error) bool {
_, ok := err.(ErrNoUsername)
return ok
}
func (err ErrNoUsername) Error() string {
return fmt.Sprintf("you need to specify a username [user id: %d]", err.UserID)
}
// ErrNoUsernamePassword represents a "NoUsernamePassword" kind of error.
type ErrNoUsernamePassword struct{}
// IsErrNoUsernamePassword checks if an error is a ErrNoUsernamePassword.
func IsErrNoUsernamePassword(err error) bool {
_, ok := err.(ErrNoUsernamePassword)
return ok
}
func (err ErrNoUsernamePassword) Error() string {
return fmt.Sprintf("you need to specify a username and a password")
}
// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error.
type ErrUserDoesNotExist struct {
UserID int64
}
// IsErrUserDoesNotExist checks if an error is a ErrUserDoesNotExist.
func IsErrUserDoesNotExist(err error) bool {
_, ok := err.(ErrUserDoesNotExist)
return ok
}
func (err ErrUserDoesNotExist) Error() string {
return fmt.Sprintf("this user does not exist [user id: %d]", err.UserID)
}
// ErrCouldNotGetUserID represents a "ErrCouldNotGetUserID" kind of error.
type ErrCouldNotGetUserID struct{}
// IsErrCouldNotGetUserID checks if an error is a ErrCouldNotGetUserID.
func IsErrCouldNotGetUserID(err error) bool {
_, ok := err.(ErrCouldNotGetUserID)
return ok
}
func (err ErrCouldNotGetUserID) Error() string {
return fmt.Sprintf("could not get user ID")
}
// ErrCannotDeleteLastUser represents a "ErrCannotDeleteLastUser" kind of error.
type ErrCannotDeleteLastUser struct{}
// IsErrCannotDeleteLastUser checks if an error is a ErrCannotDeleteLastUser.
func IsErrCannotDeleteLastUser(err error) bool {
_, ok := err.(ErrCannotDeleteLastUser)
return ok
}
func (err ErrCannotDeleteLastUser) Error() string {
return fmt.Sprintf("cannot delete last user")
}
// ===================
// Empty things errors
// ===================
// ErrIDCannotBeZero represents a "IDCannotBeZero" kind of error. Used if an ID (of something, not defined) is 0 where it should not.
type ErrIDCannotBeZero struct{}
// IsErrIDCannotBeZero checks if an error is a ErrIDCannotBeZero.
func IsErrIDCannotBeZero(err error) bool {
_, ok := err.(ErrIDCannotBeZero)
return ok
}
func (err ErrIDCannotBeZero) Error() string {
return fmt.Sprintf("ID cannot be 0")
}
// ===========
// List errors
// ===========
// ErrListDoesNotExist represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
type ErrListDoesNotExist struct {
ID int64
}
// IsErrListDoesNotExist checks if an error is a ErrListDoesNotExist.
func IsErrListDoesNotExist(err error) bool {
_, ok := err.(ErrListDoesNotExist)
return ok
}
func (err ErrListDoesNotExist) Error() string {
return fmt.Sprintf("List does not exist [ID: %d]", err.ID)
}
// ErrNeedToBeListAdmin represents an error, where the user is not the owner of that list (used i.e. when deleting a list)
type ErrNeedToBeListAdmin struct {
ListID int64
UserID int64
}
// IsErrNeedToBeListAdmin checks if an error is a ErrListDoesNotExist.
func IsErrNeedToBeListAdmin(err error) bool {
_, ok := err.(ErrNeedToBeListAdmin)
return ok
}
func (err ErrNeedToBeListAdmin) Error() string {
return fmt.Sprintf("You need to be list owner to do that [ListID: %d, UserID: %d]", err.ListID, err.UserID)
}
// ErrNeedToBeListWriter represents an error, where the user is not the owner of that list (used i.e. when deleting a list)
type ErrNeedToBeListWriter struct {
ListID int64
UserID int64
}
// IsErrNeedToBeListWriter checks if an error is a ErrListDoesNotExist.
func IsErrNeedToBeListWriter(err error) bool {
_, ok := err.(ErrNeedToBeListWriter)
return ok
}
func (err ErrNeedToBeListWriter) Error() string {
return fmt.Sprintf("You need to have write acces to the list to do that [ListID: %d, UserID: %d]", err.ListID, err.UserID)
}
// ErrNeedToHaveListReadAccess represents an error, where the user dont has read access to that List
type ErrNeedToHaveListReadAccess struct {
ListID int64
UserID int64
}
// IsErrNeedToHaveListReadAccess checks if an error is a ErrListDoesNotExist.
func IsErrNeedToHaveListReadAccess(err error) bool {
_, ok := err.(ErrNeedToHaveListReadAccess)
return ok
}
func (err ErrNeedToHaveListReadAccess) Error() string {
return fmt.Sprintf("You need to be List owner to do that [ListID: %d, UserID: %d]", err.ListID, err.UserID)
}
// ErrListTitleCannotBeEmpty represents a "ErrListTitleCannotBeEmpty" kind of error. Used if the list does not exist.
type ErrListTitleCannotBeEmpty struct{}
// IsErrListTitleCannotBeEmpty checks if an error is a ErrListTitleCannotBeEmpty.
func IsErrListTitleCannotBeEmpty(err error) bool {
_, ok := err.(ErrListTitleCannotBeEmpty)
return ok
}
func (err ErrListTitleCannotBeEmpty) Error() string {
return fmt.Sprintf("List task text cannot be empty.")
}
// ================
// List task errors
// ================
// ErrListTaskCannotBeEmpty represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
type ErrListTaskCannotBeEmpty struct{}
// IsErrListTaskCannotBeEmpty checks if an error is a ErrListDoesNotExist.
func IsErrListTaskCannotBeEmpty(err error) bool {
_, ok := err.(ErrListTaskCannotBeEmpty)
return ok
}
func (err ErrListTaskCannotBeEmpty) Error() string {
return fmt.Sprintf("List task text cannot be empty.")
}
// ErrListTaskDoesNotExist represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
type ErrListTaskDoesNotExist struct {
ID int64
}
// IsErrListTaskDoesNotExist checks if an error is a ErrListDoesNotExist.
func IsErrListTaskDoesNotExist(err error) bool {
_, ok := err.(ErrListTaskDoesNotExist)
return ok
}
func (err ErrListTaskDoesNotExist) Error() string {
return fmt.Sprintf("List task does not exist. [ID: %d]", err.ID)
}
// ErrNeedToBeTaskOwner represents an error, where the user is not the owner of that task (used i.e. when deleting a list)
type ErrNeedToBeTaskOwner struct {
TaskID int64
UserID int64
}
// IsErrNeedToBeTaskOwner checks if an error is a ErrNeedToBeTaskOwner.
func IsErrNeedToBeTaskOwner(err error) bool {
_, ok := err.(ErrNeedToBeTaskOwner)
return ok
}
func (err ErrNeedToBeTaskOwner) Error() string {
return fmt.Sprintf("You need to be task owner to do that [TaskID: %d, UserID: %d]", err.TaskID, err.UserID)
}
// =================
// Namespace errors
// =================
// ErrNamespaceDoesNotExist represents a "ErrNamespaceDoesNotExist" kind of error. Used if the namespace does not exist.
type ErrNamespaceDoesNotExist struct {
ID int64
}
// IsErrNamespaceDoesNotExist checks if an error is a ErrNamespaceDoesNotExist.
func IsErrNamespaceDoesNotExist(err error) bool {
_, ok := err.(ErrNamespaceDoesNotExist)
return ok
}
func (err ErrNamespaceDoesNotExist) Error() string {
return fmt.Sprintf("Namespace does not exist [ID: %d]", err.ID)
}
// ErrNeedToBeNamespaceOwner represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
type ErrNeedToBeNamespaceOwner struct {
NamespaceID int64
UserID int64
}
// IsErrNeedToBeNamespaceOwner checks if an error is a ErrNamespaceDoesNotExist.
func IsErrNeedToBeNamespaceOwner(err error) bool {
_, ok := err.(ErrNeedToBeNamespaceOwner)
return ok
}
func (err ErrNeedToBeNamespaceOwner) Error() string {
return fmt.Sprintf("You need to be namespace owner to do that [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrUserDoesNotHaveAccessToNamespace represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
type ErrUserDoesNotHaveAccessToNamespace struct {
NamespaceID int64
UserID int64
}
// IsErrUserDoesNotHaveAccessToNamespace checks if an error is a ErrNamespaceDoesNotExist.
func IsErrUserDoesNotHaveAccessToNamespace(err error) bool {
_, ok := err.(ErrUserDoesNotHaveAccessToNamespace)
return ok
}
func (err ErrUserDoesNotHaveAccessToNamespace) Error() string {
return fmt.Sprintf("You need to have access to this namespace to do that [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrUserNeedsToBeNamespaceAdmin represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
type ErrUserNeedsToBeNamespaceAdmin struct {
NamespaceID int64
UserID int64
}
// IsErrUserNeedsToBeNamespaceAdmin checks if an error is a ErrNamespaceDoesNotExist.
func IsErrUserNeedsToBeNamespaceAdmin(err error) bool {
_, ok := err.(ErrUserNeedsToBeNamespaceAdmin)
return ok
}
func (err ErrUserNeedsToBeNamespaceAdmin) Error() string {
return fmt.Sprintf("You need to be namespace admin to do that [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrUserDoesNotHaveWriteAccessToNamespace represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
type ErrUserDoesNotHaveWriteAccessToNamespace struct {
NamespaceID int64
UserID int64
}
// IsErrUserDoesNotHaveWriteAccessToNamespace checks if an error is a ErrNamespaceDoesNotExist.
func IsErrUserDoesNotHaveWriteAccessToNamespace(err error) bool {
_, ok := err.(ErrUserDoesNotHaveWriteAccessToNamespace)
return ok
}
func (err ErrUserDoesNotHaveWriteAccessToNamespace) Error() string {
return fmt.Sprintf("You need to have write access to this namespace to do that [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrNamespaceNameCannotBeEmpty represents an error, where a namespace name is empty.
type ErrNamespaceNameCannotBeEmpty struct {
NamespaceID int64
UserID int64
}
// IsErrNamespaceNameCannotBeEmpty checks if an error is a ErrNamespaceDoesNotExist.
func IsErrNamespaceNameCannotBeEmpty(err error) bool {
_, ok := err.(ErrNamespaceNameCannotBeEmpty)
return ok
}
func (err ErrNamespaceNameCannotBeEmpty) Error() string {
return fmt.Sprintf("Namespace name cannot be emtpy [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrNamespaceOwnerCannotBeEmpty represents an error, where a namespace owner is empty.
type ErrNamespaceOwnerCannotBeEmpty struct {
NamespaceID int64
UserID int64
}
// IsErrNamespaceOwnerCannotBeEmpty checks if an error is a ErrNamespaceDoesNotExist.
func IsErrNamespaceOwnerCannotBeEmpty(err error) bool {
_, ok := err.(ErrNamespaceOwnerCannotBeEmpty)
return ok
}
func (err ErrNamespaceOwnerCannotBeEmpty) Error() string {
return fmt.Sprintf("Namespace owner cannot be emtpy [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrNeedToBeNamespaceAdmin represents an error, where the user is not the admin of that namespace (used i.e. when deleting a namespace)
type ErrNeedToBeNamespaceAdmin struct {
NamespaceID int64
UserID int64
}
// IsErrNeedToBeNamespaceAdmin checks if an error is a ErrNamespaceDoesNotExist.
func IsErrNeedToBeNamespaceAdmin(err error) bool {
_, ok := err.(ErrNeedToBeNamespaceAdmin)
return ok
}
func (err ErrNeedToBeNamespaceAdmin) Error() string {
return fmt.Sprintf("You need to be namespace owner to do that [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrNeedToHaveNamespaceReadAccess represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
type ErrNeedToHaveNamespaceReadAccess struct {
NamespaceID int64
UserID int64
}
// IsErrNeedToHaveNamespaceReadAccess checks if an error is a ErrNamespaceDoesNotExist.
func IsErrNeedToHaveNamespaceReadAccess(err error) bool {
_, ok := err.(ErrNeedToHaveNamespaceReadAccess)
return ok
}
func (err ErrNeedToHaveNamespaceReadAccess) Error() string {
return fmt.Sprintf("You need to be namespace owner to do that [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrTeamDoesNotHaveAccessToNamespace represents an error, where the Team is not the owner of that namespace (used i.e. when deleting a namespace)
type ErrTeamDoesNotHaveAccessToNamespace struct {
NamespaceID int64
TeamID int64
}
// IsErrTeamDoesNotHaveAccessToNamespace checks if an error is a ErrNamespaceDoesNotExist.
func IsErrTeamDoesNotHaveAccessToNamespace(err error) bool {
_, ok := err.(ErrTeamDoesNotHaveAccessToNamespace)
return ok
}
func (err ErrTeamDoesNotHaveAccessToNamespace) Error() string {
return fmt.Sprintf("You need to have access to this namespace to do that [NamespaceID: %d, TeamID: %d]", err.NamespaceID, err.TeamID)
}
// ErrUserAlreadyHasNamespaceAccess represents an error where a user already has access to a namespace
type ErrUserAlreadyHasNamespaceAccess struct {
UserID int64
NamespaceID int64
}
// IsErrUserAlreadyHasNamespaceAccess checks if an error is ErrUserAlreadyHasNamespaceAccess.
func IsErrUserAlreadyHasNamespaceAccess(err error) bool {
_, ok := err.(ErrUserAlreadyHasNamespaceAccess)
return ok
}
func (err ErrUserAlreadyHasNamespaceAccess) Error() string {
return fmt.Sprintf("This user already has access to that namespace. [User ID: %d, Namespace ID: %d]", err.UserID, err.NamespaceID)
}
// ============
// Team errors
// ============
// ErrTeamNameCannotBeEmpty represents an error where a team name is empty.
type ErrTeamNameCannotBeEmpty struct {
TeamID int64
}
// IsErrTeamNameCannotBeEmpty checks if an error is a ErrNamespaceDoesNotExist.
func IsErrTeamNameCannotBeEmpty(err error) bool {
_, ok := err.(ErrTeamNameCannotBeEmpty)
return ok
}
func (err ErrTeamNameCannotBeEmpty) Error() string {
return fmt.Sprintf("Team name cannot be empty [Team ID: %d]", err.TeamID)
}
// ErrTeamDoesNotExist represents an error where a team does not exist
type ErrTeamDoesNotExist struct {
TeamID int64
}
// IsErrTeamDoesNotExist checks if an error is ErrTeamDoesNotExist.
func IsErrTeamDoesNotExist(err error) bool {
_, ok := err.(ErrTeamDoesNotExist)
return ok
}
func (err ErrTeamDoesNotExist) Error() string {
return fmt.Sprintf("Team does not exist [Team ID: %d]", err.TeamID)
}
// ErrInvalidTeamRight represents an error where a team right is invalid
type ErrInvalidTeamRight struct {
Right TeamRight
}
// IsErrInvalidTeamRight checks if an error is ErrInvalidTeamRight.
func IsErrInvalidTeamRight(err error) bool {
_, ok := err.(ErrInvalidTeamRight)
return ok
}
func (err ErrInvalidTeamRight) Error() string {
return fmt.Sprintf("The right is invalid [Right: %d]", err.Right)
}
// ErrTeamAlreadyHasAccess represents an error where a team already has access to a list/namespace
type ErrTeamAlreadyHasAccess struct {
TeamID int64
ID int64
}
// IsErrTeamAlreadyHasAccess checks if an error is ErrTeamAlreadyHasAccess.
func IsErrTeamAlreadyHasAccess(err error) bool {
_, ok := err.(ErrTeamAlreadyHasAccess)
return ok
}
func (err ErrTeamAlreadyHasAccess) Error() string {
return fmt.Sprintf("This team already has access. [Team ID: %d, ID: %d]", err.TeamID, err.ID)
}
// ErrUserIsMemberOfTeam represents an error where a user is already member of a team.
type ErrUserIsMemberOfTeam struct {
TeamID int64
UserID int64
}
// IsErrUserIsMemberOfTeam checks if an error is ErrUserIsMemberOfTeam.
func IsErrUserIsMemberOfTeam(err error) bool {
_, ok := err.(ErrUserIsMemberOfTeam)
return ok
}
func (err ErrUserIsMemberOfTeam) Error() string {
return fmt.Sprintf("This user is already a member of that team. [Team ID: %d, User ID: %d]", err.TeamID, err.UserID)
}
// ErrCannotDeleteLastTeamMember represents an error where a user wants to delete the last member of a team (probably himself)
type ErrCannotDeleteLastTeamMember struct {
TeamID int64
UserID int64
}
// IsErrCannotDeleteLastTeamMember checks if an error is ErrCannotDeleteLastTeamMember.
func IsErrCannotDeleteLastTeamMember(err error) bool {
_, ok := err.(ErrCannotDeleteLastTeamMember)
return ok
}
func (err ErrCannotDeleteLastTeamMember) Error() string {
return fmt.Sprintf("This user is already a member of that team. [Team ID: %d, User ID: %d]", err.TeamID, err.UserID)
}
// ErrTeamDoesNotHaveAccessToList represents an error, where the Team is not the owner of that List (used i.e. when deleting a List)
type ErrTeamDoesNotHaveAccessToList struct {
ListID int64
TeamID int64
}
// IsErrTeamDoesNotHaveAccessToList checks if an error is a ErrListDoesNotExist.
func IsErrTeamDoesNotHaveAccessToList(err error) bool {
_, ok := err.(ErrTeamDoesNotHaveAccessToList)
return ok
}
func (err ErrTeamDoesNotHaveAccessToList) Error() string {
return fmt.Sprintf("You need to have access to this List to do that [ListID: %d, TeamID: %d]", err.ListID, err.TeamID)
}
// ====================
// User <-> List errors
// ====================
// ErrInvalidUserRight represents an error where a user right is invalid
type ErrInvalidUserRight struct {
Right UserRight
}
// IsErrInvalidUserRight checks if an error is ErrInvalidUserRight.
func IsErrInvalidUserRight(err error) bool {
_, ok := err.(ErrInvalidUserRight)
return ok
}
func (err ErrInvalidUserRight) Error() string {
return fmt.Sprintf("The right is invalid [Right: %d]", err.Right)
}
// ErrUserAlreadyHasAccess represents an error where a user already has access to a list/namespace
type ErrUserAlreadyHasAccess struct {
UserID int64
ListID int64
}
// IsErrUserAlreadyHasAccess checks if an error is ErrUserAlreadyHasAccess.
func IsErrUserAlreadyHasAccess(err error) bool {
_, ok := err.(ErrUserAlreadyHasAccess)
return ok
}
func (err ErrUserAlreadyHasAccess) Error() string {
return fmt.Sprintf("This user already has access to that list. [User ID: %d, List ID: %d]", err.UserID, err.ListID)
}
// ErrUserDoesNotHaveAccessToList represents an error, where the user is not the owner of that List (used i.e. when deleting a List)
type ErrUserDoesNotHaveAccessToList struct {
ListID int64
UserID int64
}
// IsErrUserDoesNotHaveAccessToList checks if an error is a ErrListDoesNotExist.
func IsErrUserDoesNotHaveAccessToList(err error) bool {
_, ok := err.(ErrUserDoesNotHaveAccessToList)
return ok
}
func (err ErrUserDoesNotHaveAccessToList) Error() string {
return fmt.Sprintf("You need to have access to this List to do that [ListID: %d, UserID: %d]", err.ListID, err.UserID)
}

View File

@@ -1,90 +0,0 @@
package models
// List represents a list of tasks
type List struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"list"`
Title string `xorm:"varchar(250)" json:"title"`
Description string `xorm:"varchar(1000)" json:"description"`
OwnerID int64 `xorm:"int(11)" json:"-"`
NamespaceID int64 `xorm:"int(11)" json:"-" param:"namespace"`
Owner User `xorm:"-" json:"owner"`
Tasks []*ListTask `xorm:"-" json:"tasks"`
Created int64 `xorm:"created" json:"created"`
Updated int64 `xorm:"updated" json:"updated"`
CRUDable `xorm:"-" json:"-"`
Rights `xorm:"-" json:"-"`
}
// AfterLoad loads the owner and list tasks
func (l *List) AfterLoad() {
// Get the owner
l.Owner, _ = GetUserByID(l.OwnerID)
// Get the list tasks
l.Tasks, _ = GetTasksByListID(l.ID)
}
// GetListByID returns a list by its ID
func GetListByID(id int64) (list List, err error) {
if id < 1 {
return list, ErrListDoesNotExist{ID: id}
}
exists, err := x.ID(id).Get(&list)
if err != nil {
return list, err
}
if !exists {
return list, ErrListDoesNotExist{ID: id}
}
return list, nil
}
// GetListsByNamespaceID gets all lists in a namespace
func GetListsByNamespaceID(nID int64) (lists []*List, err error) {
err = x.Where("namespace_id = ?", nID).Find(&lists)
return lists, err
}
// ReadAll gets all lists a user has access to
func (l *List) ReadAll(user *User) (interface{}, error) {
lists := []List{}
fullUser, err := GetUserByID(user.ID)
if err != nil {
return lists, err
}
// Gets all Lists where the user is either owner or in a team which has access to the list
// Or in a team which has namespace read access
err = x.Select("l.*").
Table("list").
Alias("l").
Join("INNER", []string{"namespaces", "n"}, "l.namespace_id = n.id").
Join("LEFT", []string{"team_namespaces", "tn"}, "tn.namespace_id = n.id").
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id").
Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id").
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id").
Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id").
Where("tm.user_id = ?", fullUser.ID).
Or("tm2.user_id = ?", fullUser.ID).
Or("l.owner_id = ?", fullUser.ID).
Or("ul.user_id = ?", fullUser.ID).
Or("un.user_id = ?", fullUser.ID).
GroupBy("l.id").
Find(&lists)
return lists, err
}
// ReadOne gets one list by its ID
func (l *List) ReadOne() (err error) {
*l, err = GetListByID(l.ID)
return
}

View File

@@ -1,48 +0,0 @@
package models
// UserRight defines the rights users can have for lists/namespaces
type UserRight int
// define unknown user right
const (
UserRightUnknown = -1
)
// Enumerate all the user rights
const (
// Can read lists in a User
UserRightRead UserRight = iota
// Can write tasks in a User like lists and todo tasks. Cannot create new lists.
UserRightWrite
// Can manage a list/namespace, can do everything
UserRightAdmin
)
func (r UserRight) isValid() error {
if r != UserRightAdmin && r != UserRightRead && r != UserRightWrite {
return ErrInvalidUserRight{r}
}
return nil
}
// CanCreate checks if the user can create a new user <-> list relation
func (lu *ListUser) CanCreate(doer *User) bool {
// Get the list and check if the user has write access on it
l, _ := GetListByID(lu.ListID)
return l.CanWrite(doer)
}
// CanDelete checks if the user can delete a user <-> list relation
func (lu *ListUser) CanDelete(doer *User) bool {
// Get the list and check if the user has write access on it
l, _ := GetListByID(lu.ListID)
return l.CanWrite(doer)
}
// CanUpdate checks if the user can update a user <-> list relation
func (lu *ListUser) CanUpdate(doer *User) bool {
// Get the list and check if the user has write access on it
l, _ := GetListByID(lu.ListID)
return l.CanWrite(doer)
}

View File

@@ -1,19 +0,0 @@
package models
// CanCreate checks if the user can create a team <-> list relation
func (tl *TeamList) CanCreate(user *User) bool {
l, _ := GetListByID(tl.ListID)
return l.IsAdmin(user)
}
// CanDelete checks if the user can delete a team <-> list relation
func (tl *TeamList) CanDelete(user *User) bool {
l, _ := GetListByID(tl.ListID)
return l.IsAdmin(user)
}
// CanUpdate checks if the user can update a team <-> list relation
func (tl *TeamList) CanUpdate(user *User) bool {
l, _ := GetListByID(tl.ListID)
return l.IsAdmin(user)
}

View File

@@ -1,20 +0,0 @@
package models
// CanCreate checks if the user can add a new tem member
func (tm *TeamMember) CanCreate(user *User) bool {
return tm.IsAdmin(user)
}
// CanDelete checks if the user can delete a new team member
func (tm *TeamMember) CanDelete(user *User) bool {
return tm.IsAdmin(user)
}
// IsAdmin checks if the user is team admin
func (tm *TeamMember) IsAdmin(user *User) bool {
// A user can add a member to a team if he is admin of that team
exists, _ := x.Where("user_id = ? AND team_id = ? AND admin = ?", user.ID, tm.TeamID, true).
Get(&TeamMember{})
return exists
}

View File

@@ -1,19 +0,0 @@
package models
// CanCreate checks if one can create a new team <-> namespace relation
func (tn *TeamNamespace) CanCreate(user *User) bool {
n, _ := GetNamespaceByID(tn.NamespaceID)
return n.IsAdmin(user)
}
// CanDelete checks if a user can remove a team from a namespace. Only namespace admins can do that.
func (tn *TeamNamespace) CanDelete(user *User) bool {
n, _ := GetNamespaceByID(tn.NamespaceID)
return n.IsAdmin(user)
}
// CanUpdate checks if a user can update a team from a namespace. Only namespace admins can do that.
func (tn *TeamNamespace) CanUpdate(user *User) bool {
n, _ := GetNamespaceByID(tn.NamespaceID)
return n.IsAdmin(user)
}

View File

@@ -1,69 +0,0 @@
package models
// TeamRight defines the rights teams can have for lists/namespaces
type TeamRight int
// define unknown team right
const (
TeamRightUnknown = -1
)
// Enumerate all the team rights
const (
// Can read lists in a Team
TeamRightRead TeamRight = iota
// Can write tasks in a Team like lists and todo tasks. Cannot create new lists.
TeamRightWrite
// Can manage a list/namespace, can do everything
TeamRightAdmin
)
func (r TeamRight) isValid() error {
if r != TeamRightAdmin && r != TeamRightRead && r != TeamRightWrite {
return ErrInvalidTeamRight{r}
}
return nil
}
// CanCreate checks if the user can create a new team
func (t *Team) CanCreate(user *User) bool {
// This is currently a dummy function, later on we could imagine global limits etc.
return true
}
// CanUpdate checks if the user can update a team
func (t *Team) CanUpdate(user *User) bool {
// Check if the current user is in the team and has admin rights in it
exists, _ := x.Where("team_id = ?", t.ID).
And("user_id = ?", user.ID).
And("admin = ?", true).
Get(&TeamMember{})
return exists
}
// CanDelete checks if a user can delete a team
func (t *Team) CanDelete(user *User) bool {
//t.ID = id
return t.IsAdmin(user)
}
// IsAdmin returns true when the user is admin of a team
func (t *Team) IsAdmin(user *User) bool {
exists, _ := x.Where("team_id = ?", t.ID).
And("user_id = ?", user.ID).
And("admin = ?", true).
Get(&TeamMember{})
return exists
}
// CanRead returns true if the user has read access to the team
func (t *Team) CanRead(user *User) bool {
// Check if the user is in the team
exists, _ := x.Where("team_id = ?", t.ID).
And("user_id = ?", user.ID).
Get(&TeamMember{})
return exists
}

View File

@@ -1,9 +1,11 @@
package models
package config
import (
"crypto/rand"
"fmt"
"github.com/spf13/viper"
"os"
"path/filepath"
"strings"
)
@@ -17,8 +19,16 @@ func InitConfig() (err error) {
return err
}
// Service
viper.SetDefault("service.JWTSecret", random)
viper.SetDefault("service.interface", ":3456")
viper.SetDefault("service.frontendurl", "")
ex, err := os.Executable()
if err != nil {
panic(err)
}
exPath := filepath.Dir(ex)
viper.SetDefault("service.rootpath", exPath)
// Database
viper.SetDefault("database.type", "sqlite")
viper.SetDefault("database.host", "localhost")
@@ -27,12 +37,22 @@ func InitConfig() (err error) {
viper.SetDefault("database.database", "vikunja")
viper.SetDefault("database.path", "./vikunja.db")
viper.SetDefault("database.showqueries", false)
viper.SetDefault("database.openconnections", 100)
// Cacher
viper.SetDefault("cache.enabled", false)
viper.SetDefault("cache.type", "memory")
viper.SetDefault("cache.maxelementsize", 1000)
viper.SetDefault("cache.redishost", "localhost:6379")
viper.SetDefault("cache.redispassword", "")
// Mailer
viper.SetDefault("mailer.host", "")
viper.SetDefault("mailer.port", "587")
viper.SetDefault("mailer.user", "user")
viper.SetDefault("mailer.password", "")
viper.SetDefault("mailer.skiptlsverify", false)
viper.SetDefault("mailer.fromemail", "mail@vikunja")
viper.SetDefault("mailer.queuelength", 100)
viper.SetDefault("mailer.queuetimeout", 30)
// Init checking for environment variables
viper.SetEnvPrefix("vikunja")

View File

@@ -1,4 +1,4 @@
package models
package log
import (
"github.com/op/go-logging"
@@ -9,7 +9,7 @@ import (
var Log = logging.MustGetLogger("vikunja")
var format = logging.MustStringFormatter(
`%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`,
`%{color}%{time:2006-01-02 15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`,
)
// InitLogger initializes the global log handler

63
pkg/mail/mail.go Normal file
View File

@@ -0,0 +1,63 @@
package mail
import (
"code.vikunja.io/api/pkg/log"
"crypto/tls"
"github.com/spf13/viper"
"gopkg.in/gomail.v2"
"time"
)
// Queue is the mail queue
var Queue chan *gomail.Message
// StartMailDaemon starts the mail daemon
func StartMailDaemon() {
Queue = make(chan *gomail.Message, viper.GetInt("mailer.queuelength"))
if viper.GetString("mailer.host") == "" {
log.Log.Warning("Mailer seems to be not configured! Please see the config docs for more details.")
return
}
go func() {
d := gomail.NewDialer(viper.GetString("mailer.host"), viper.GetInt("mailer.port"), viper.GetString("mailer.username"), viper.GetString("mailer.password"))
d.TLSConfig = &tls.Config{InsecureSkipVerify: viper.GetBool("mailer.skiptlsverify")}
var s gomail.SendCloser
var err error
open := false
for {
select {
case m, ok := <-Queue:
if !ok {
return
}
if !open {
if s, err = d.Dial(); err != nil {
log.Log.Error("Error during connect to smtp server: %s", err)
}
open = true
}
if err := gomail.Send(s, m); err != nil {
log.Log.Error("Error when sending mail: %s", err)
}
// Close the connection to the SMTP server if no email was sent in
// the last 30 seconds.
case <-time.After(viper.GetDuration("mailer.queuetimeout") * time.Second):
if open {
if err := s.Close(); err != nil {
log.Log.Error("Error closing the mail server connection: %s\n", err)
}
log.Log.Infof("Closed connection to mailserver")
open = false
}
}
}
}()
}
// StopMailDaemon closes the mail queue channel, aka stops the daemon
func StopMailDaemon() {
close(Queue)
}

100
pkg/mail/send_mail.go Normal file
View File

@@ -0,0 +1,100 @@
package mail
import (
"bytes"
"code.vikunja.io/api/pkg/utils"
"github.com/labstack/gommon/log"
"github.com/spf13/viper"
"gopkg.in/gomail.v2"
"text/template"
)
// Opts holds infos for a mail
type Opts struct {
To string
Subject string
Message string
HTMLMessage string
ContentType ContentType
Boundary string
Headers []*header
}
// ContentType represents mail content types
type ContentType int
// Enumerate all the team rights
const (
ContentTypePlain ContentType = iota
ContentTypeHTML
ContentTypeMultipart
)
type header struct {
Field string
Content string
}
// SendMail puts a mail in the queue
func SendMail(opts *Opts) {
m := gomail.NewMessage()
m.SetHeader("From", viper.GetString("mailer.fromemail"))
m.SetHeader("To", opts.To)
m.SetHeader("Subject", opts.Subject)
for _, h := range opts.Headers {
m.SetHeader(h.Field, h.Content)
}
switch opts.ContentType {
case ContentTypePlain:
m.SetBody("text/plain", opts.Message)
case ContentTypeHTML:
m.SetBody("text/html", opts.Message)
case ContentTypeMultipart:
m.SetBody("text/plain", opts.Message)
m.AddAlternative("text/html", opts.HTMLMessage)
}
Queue <- m
}
// Template holds a pointer about a template
type Template struct {
Templates *template.Template
}
// SendMailWithTemplate parses a template and sends it via mail
func SendMailWithTemplate(to, subject, tpl string, data map[string]interface{}) {
var htmlContent bytes.Buffer
var plainContent bytes.Buffer
t := &Template{
Templates: template.Must(template.ParseGlob(viper.GetString("service.rootpath") + "/templates/mail/*.tmpl")),
}
boundary := "np" + utils.MakeRandomString(13)
data["Boundary"] = boundary
data["FrontendURL"] = viper.GetString("service.frontendurl")
if err := t.Templates.ExecuteTemplate(&htmlContent, tpl+".html.tmpl", data); err != nil {
log.Error(3, "Template: %v", err)
return
}
if err := t.Templates.ExecuteTemplate(&plainContent, tpl+".plain.tmpl", data); err != nil {
log.Error(3, "Template: %v", err)
return
}
opts := &Opts{
To: to,
Subject: subject,
Message: plainContent.String(),
HTMLMessage: htmlContent.String(),
ContentType: ContentTypeMultipart,
Boundary: boundary,
}
SendMail(opts)
}

779
pkg/models/error.go Normal file
View File

@@ -0,0 +1,779 @@
package models
import (
"fmt"
"net/http"
)
// HTTPErrorProcessor is executed when the defined error is thrown, it will make sure the user sees an appropriate error message and http status code
type HTTPErrorProcessor interface {
HTTPError() HTTPError
}
// HTTPError holds informations about an http error
type HTTPError struct {
HTTPCode int `json:"-"`
Code int `json:"code"`
Message string `json:"message"`
}
// =====================
// User Operation Errors
// =====================
// ErrUsernameExists represents a "UsernameAlreadyExists" kind of error.
type ErrUsernameExists struct {
UserID int64
Username string
}
// IsErrUsernameExists checks if an error is a ErrUsernameExists.
func IsErrUsernameExists(err error) bool {
_, ok := err.(ErrUsernameExists)
return ok
}
func (err ErrUsernameExists) Error() string {
return fmt.Sprintf("User with that username already exists [user id: %d, username: %s]", err.UserID, err.Username)
}
// ErrorCodeUsernameExists holds the unique world-error code of this error
const ErrorCodeUsernameExists = 1001
// HTTPError holds the http error description
func (err ErrUsernameExists) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUsernameExists, Message: "A user with this username already exists."}
}
// ErrUserEmailExists represents a "UserEmailExists" kind of error.
type ErrUserEmailExists struct {
UserID int64
Email string
}
// IsErrUserEmailExists checks if an error is a ErrUserEmailExists.
func IsErrUserEmailExists(err error) bool {
_, ok := err.(ErrUserEmailExists)
return ok
}
func (err ErrUserEmailExists) Error() string {
return fmt.Sprintf("User with that email already exists [user id: %d, email: %s]", err.UserID, err.Email)
}
// ErrorCodeUserEmailExists holds the unique world-error code of this error
const ErrorCodeUserEmailExists = 1002
// HTTPError holds the http error description
func (err ErrUserEmailExists) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUserEmailExists, Message: "A user with this email address already exists."}
}
// ErrNoUsernamePassword represents a "NoUsernamePassword" kind of error.
type ErrNoUsernamePassword struct{}
// IsErrNoUsernamePassword checks if an error is a ErrNoUsernamePassword.
func IsErrNoUsernamePassword(err error) bool {
_, ok := err.(ErrNoUsernamePassword)
return ok
}
func (err ErrNoUsernamePassword) Error() string {
return fmt.Sprintf("No username and password provided")
}
// ErrCodeNoUsernamePassword holds the unique world-error code of this error
const ErrCodeNoUsernamePassword = 1004
// HTTPError holds the http error description
func (err ErrNoUsernamePassword) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeNoUsernamePassword, Message: "Please specify a username and a password."}
}
// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error.
type ErrUserDoesNotExist struct {
UserID int64
}
// IsErrUserDoesNotExist checks if an error is a ErrUserDoesNotExist.
func IsErrUserDoesNotExist(err error) bool {
_, ok := err.(ErrUserDoesNotExist)
return ok
}
func (err ErrUserDoesNotExist) Error() string {
return fmt.Sprintf("User does not exist [user id: %d]", err.UserID)
}
// ErrCodeUserDoesNotExist holds the unique world-error code of this error
const ErrCodeUserDoesNotExist = 1005
// HTTPError holds the http error description
func (err ErrUserDoesNotExist) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeUserDoesNotExist, Message: "The user does not exist."}
}
// ErrCouldNotGetUserID represents a "ErrCouldNotGetUserID" kind of error.
type ErrCouldNotGetUserID struct{}
// IsErrCouldNotGetUserID checks if an error is a ErrCouldNotGetUserID.
func IsErrCouldNotGetUserID(err error) bool {
_, ok := err.(ErrCouldNotGetUserID)
return ok
}
func (err ErrCouldNotGetUserID) Error() string {
return fmt.Sprintf("Could not get user ID")
}
// ErrCodeCouldNotGetUserID holds the unique world-error code of this error
const ErrCodeCouldNotGetUserID = 1006
// HTTPError holds the http error description
func (err ErrCouldNotGetUserID) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCouldNotGetUserID, Message: "Could not get user id."}
}
// ErrNoPasswordResetToken represents an error where no password reset token exists for that user
type ErrNoPasswordResetToken struct {
UserID int64
}
func (err ErrNoPasswordResetToken) Error() string {
return fmt.Sprintf("No token to reset a password [UserID: %d]", err.UserID)
}
// ErrCodeNoPasswordResetToken holds the unique world-error code of this error
const ErrCodeNoPasswordResetToken = 1008
// HTTPError holds the http error description
func (err ErrNoPasswordResetToken) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeNoPasswordResetToken, Message: "No token to reset a user's password provided."}
}
// ErrInvalidPasswordResetToken is an error where the password reset token is invalid
type ErrInvalidPasswordResetToken struct {
Token string
}
func (err ErrInvalidPasswordResetToken) Error() string {
return fmt.Sprintf("Invalid token to reset a password [Token: %s]", err.Token)
}
// ErrCodeInvalidPasswordResetToken holds the unique world-error code of this error
const ErrCodeInvalidPasswordResetToken = 1009
// HTTPError holds the http error description
func (err ErrInvalidPasswordResetToken) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidPasswordResetToken, Message: "Invalid token to reset a user's password."}
}
// IsErrInvalidPasswordResetToken checks if an error is a ErrInvalidPasswordResetToken.
func IsErrInvalidPasswordResetToken(err error) bool {
_, ok := err.(ErrInvalidPasswordResetToken)
return ok
}
// ErrInvalidEmailConfirmToken is an error where the email confirm token is invalid
type ErrInvalidEmailConfirmToken struct {
Token string
}
func (err ErrInvalidEmailConfirmToken) Error() string {
return fmt.Sprintf("Invalid email confirm token [Token: %s]", err.Token)
}
// ErrCodeInvalidEmailConfirmToken holds the unique world-error code of this error
const ErrCodeInvalidEmailConfirmToken = 1010
// HTTPError holds the http error description
func (err ErrInvalidEmailConfirmToken) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidEmailConfirmToken, Message: "Invalid email confirm token."}
}
// IsErrInvalidEmailConfirmToken checks if an error is a ErrInvalidEmailConfirmToken.
func IsErrInvalidEmailConfirmToken(err error) bool {
_, ok := err.(ErrInvalidEmailConfirmToken)
return ok
}
// ErrWrongUsernameOrPassword is an error where the email was not confirmed
type ErrWrongUsernameOrPassword struct {
}
func (err ErrWrongUsernameOrPassword) Error() string {
return fmt.Sprintf("Wrong username or password")
}
// ErrCodeWrongUsernameOrPassword holds the unique world-error code of this error
const ErrCodeWrongUsernameOrPassword = 1011
// HTTPError holds the http error description
func (err ErrWrongUsernameOrPassword) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeWrongUsernameOrPassword, Message: "Wrong username or password."}
}
// IsErrWrongUsernameOrPassword checks if an error is a IsErrEmailNotConfirmed.
func IsErrWrongUsernameOrPassword(err error) bool {
_, ok := err.(ErrWrongUsernameOrPassword)
return ok
}
// ErrEmailNotConfirmed is an error where the email was not confirmed
type ErrEmailNotConfirmed struct {
UserID int64
}
func (err ErrEmailNotConfirmed) Error() string {
return fmt.Sprintf("Email is not confirmed [UserID: %d]", err.UserID)
}
// ErrCodeEmailNotConfirmed holds the unique world-error code of this error
const ErrCodeEmailNotConfirmed = 1012
// HTTPError holds the http error description
func (err ErrEmailNotConfirmed) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeEmailNotConfirmed, Message: "Please confirm your email address."}
}
// IsErrEmailNotConfirmed checks if an error is a IsErrEmailNotConfirmed.
func IsErrEmailNotConfirmed(err error) bool {
_, ok := err.(ErrEmailNotConfirmed)
return ok
}
// ===================
// Empty things errors
// ===================
// ErrIDCannotBeZero represents a "IDCannotBeZero" kind of error. Used if an ID (of something, not defined) is 0 where it should not.
type ErrIDCannotBeZero struct{}
// IsErrIDCannotBeZero checks if an error is a ErrIDCannotBeZero.
func IsErrIDCannotBeZero(err error) bool {
_, ok := err.(ErrIDCannotBeZero)
return ok
}
func (err ErrIDCannotBeZero) Error() string {
return fmt.Sprintf("ID cannot be empty or 0")
}
// ErrCodeIDCannotBeZero holds the unique world-error code of this error
const ErrCodeIDCannotBeZero = 2001
// HTTPError holds the http error description
func (err ErrIDCannotBeZero) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeIDCannotBeZero, Message: "The ID cannot be empty or 0."}
}
// ===========
// List errors
// ===========
// ErrListDoesNotExist represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
type ErrListDoesNotExist struct {
ID int64
}
// IsErrListDoesNotExist checks if an error is a ErrListDoesNotExist.
func IsErrListDoesNotExist(err error) bool {
_, ok := err.(ErrListDoesNotExist)
return ok
}
func (err ErrListDoesNotExist) Error() string {
return fmt.Sprintf("List does not exist [ID: %d]", err.ID)
}
// ErrCodeListDoesNotExist holds the unique world-error code of this error
const ErrCodeListDoesNotExist = 3001
// HTTPError holds the http error description
func (err ErrListDoesNotExist) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListDoesNotExist, Message: "This list does not exist."}
}
// ErrNeedToHaveListReadAccess represents an error, where the user dont has read access to that List
type ErrNeedToHaveListReadAccess struct {
ListID int64
UserID int64
}
// IsErrNeedToHaveListReadAccess checks if an error is a ErrListDoesNotExist.
func IsErrNeedToHaveListReadAccess(err error) bool {
_, ok := err.(ErrNeedToHaveListReadAccess)
return ok
}
func (err ErrNeedToHaveListReadAccess) Error() string {
return fmt.Sprintf("User needs to have read access to that list [ListID: %d, UserID: %d]", err.ListID, err.UserID)
}
// ErrCodeNeedToHaveListReadAccess holds the unique world-error code of this error
const ErrCodeNeedToHaveListReadAccess = 3004
// HTTPError holds the http error description
func (err ErrNeedToHaveListReadAccess) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveListReadAccess, Message: "You need to have read access to this list."}
}
// ErrListTitleCannotBeEmpty represents a "ErrListTitleCannotBeEmpty" kind of error. Used if the list does not exist.
type ErrListTitleCannotBeEmpty struct{}
// IsErrListTitleCannotBeEmpty checks if an error is a ErrListTitleCannotBeEmpty.
func IsErrListTitleCannotBeEmpty(err error) bool {
_, ok := err.(ErrListTitleCannotBeEmpty)
return ok
}
func (err ErrListTitleCannotBeEmpty) Error() string {
return fmt.Sprintf("List title cannot be empty.")
}
// ErrCodeListTitleCannotBeEmpty holds the unique world-error code of this error
const ErrCodeListTitleCannotBeEmpty = 3005
// HTTPError holds the http error description
func (err ErrListTitleCannotBeEmpty) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeListTitleCannotBeEmpty, Message: "You must provide at least a list title."}
}
// ================
// List task errors
// ================
// ErrListTaskCannotBeEmpty represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
type ErrListTaskCannotBeEmpty struct{}
// IsErrListTaskCannotBeEmpty checks if an error is a ErrListDoesNotExist.
func IsErrListTaskCannotBeEmpty(err error) bool {
_, ok := err.(ErrListTaskCannotBeEmpty)
return ok
}
func (err ErrListTaskCannotBeEmpty) Error() string {
return fmt.Sprintf("List task text cannot be empty.")
}
// ErrCodeListTaskCannotBeEmpty holds the unique world-error code of this error
const ErrCodeListTaskCannotBeEmpty = 4001
// HTTPError holds the http error description
func (err ErrListTaskCannotBeEmpty) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeListTaskCannotBeEmpty, Message: "You must provide at least a list task text."}
}
// ErrListTaskDoesNotExist represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
type ErrListTaskDoesNotExist struct {
ID int64
}
// IsErrListTaskDoesNotExist checks if an error is a ErrListDoesNotExist.
func IsErrListTaskDoesNotExist(err error) bool {
_, ok := err.(ErrListTaskDoesNotExist)
return ok
}
func (err ErrListTaskDoesNotExist) Error() string {
return fmt.Sprintf("List task does not exist. [ID: %d]", err.ID)
}
// ErrCodeListTaskDoesNotExist holds the unique world-error code of this error
const ErrCodeListTaskDoesNotExist = 4002
// HTTPError holds the http error description
func (err ErrListTaskDoesNotExist) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListTaskDoesNotExist, Message: "This list task does not exist"}
}
// =================
// Namespace errors
// =================
// ErrNamespaceDoesNotExist represents a "ErrNamespaceDoesNotExist" kind of error. Used if the namespace does not exist.
type ErrNamespaceDoesNotExist struct {
ID int64
}
// IsErrNamespaceDoesNotExist checks if an error is a ErrNamespaceDoesNotExist.
func IsErrNamespaceDoesNotExist(err error) bool {
_, ok := err.(ErrNamespaceDoesNotExist)
return ok
}
func (err ErrNamespaceDoesNotExist) Error() string {
return fmt.Sprintf("Namespace does not exist [ID: %d]", err.ID)
}
// ErrCodeNamespaceDoesNotExist holds the unique world-error code of this error
const ErrCodeNamespaceDoesNotExist = 5001
// HTTPError holds the http error description
func (err ErrNamespaceDoesNotExist) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeNamespaceDoesNotExist, Message: "Namespace not found."}
}
// ErrUserDoesNotHaveAccessToNamespace represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
type ErrUserDoesNotHaveAccessToNamespace struct {
NamespaceID int64
UserID int64
}
// IsErrUserDoesNotHaveAccessToNamespace checks if an error is a ErrNamespaceDoesNotExist.
func IsErrUserDoesNotHaveAccessToNamespace(err error) bool {
_, ok := err.(ErrUserDoesNotHaveAccessToNamespace)
return ok
}
func (err ErrUserDoesNotHaveAccessToNamespace) Error() string {
return fmt.Sprintf("User does not have access to the namespace [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrCodeUserDoesNotHaveAccessToNamespace holds the unique world-error code of this error
const ErrCodeUserDoesNotHaveAccessToNamespace = 5003
// HTTPError holds the http error description
func (err ErrUserDoesNotHaveAccessToNamespace) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToNamespace, Message: "This user does not have access to the namespace."}
}
// ErrNamespaceNameCannotBeEmpty represents an error, where a namespace name is empty.
type ErrNamespaceNameCannotBeEmpty struct {
NamespaceID int64
UserID int64
}
// IsErrNamespaceNameCannotBeEmpty checks if an error is a ErrNamespaceDoesNotExist.
func IsErrNamespaceNameCannotBeEmpty(err error) bool {
_, ok := err.(ErrNamespaceNameCannotBeEmpty)
return ok
}
func (err ErrNamespaceNameCannotBeEmpty) Error() string {
return fmt.Sprintf("Namespace name cannot be empty [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrCodeNamespaceNameCannotBeEmpty holds the unique world-error code of this error
const ErrCodeNamespaceNameCannotBeEmpty = 5006
// HTTPError holds the http error description
func (err ErrNamespaceNameCannotBeEmpty) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeNamespaceNameCannotBeEmpty, Message: "The namespace name cannot be empty."}
}
// ErrNeedToHaveNamespaceReadAccess represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
type ErrNeedToHaveNamespaceReadAccess struct {
NamespaceID int64
UserID int64
}
// IsErrNeedToHaveNamespaceReadAccess checks if an error is a ErrNamespaceDoesNotExist.
func IsErrNeedToHaveNamespaceReadAccess(err error) bool {
_, ok := err.(ErrNeedToHaveNamespaceReadAccess)
return ok
}
func (err ErrNeedToHaveNamespaceReadAccess) Error() string {
return fmt.Sprintf("User does not have access to that namespace [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
}
// ErrCodeNeedToHaveNamespaceReadAccess holds the unique world-error code of this error
const ErrCodeNeedToHaveNamespaceReadAccess = 5009
// HTTPError holds the http error description
func (err ErrNeedToHaveNamespaceReadAccess) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveNamespaceReadAccess, Message: "You need to have namespace read access to do this."}
}
// ErrTeamDoesNotHaveAccessToNamespace represents an error, where the Team is not the owner of that namespace (used i.e. when deleting a namespace)
type ErrTeamDoesNotHaveAccessToNamespace struct {
NamespaceID int64
TeamID int64
}
// IsErrTeamDoesNotHaveAccessToNamespace checks if an error is a ErrNamespaceDoesNotExist.
func IsErrTeamDoesNotHaveAccessToNamespace(err error) bool {
_, ok := err.(ErrTeamDoesNotHaveAccessToNamespace)
return ok
}
func (err ErrTeamDoesNotHaveAccessToNamespace) Error() string {
return fmt.Sprintf("Team does not have access to that namespace [NamespaceID: %d, TeamID: %d]", err.NamespaceID, err.TeamID)
}
// ErrCodeTeamDoesNotHaveAccessToNamespace holds the unique world-error code of this error
const ErrCodeTeamDoesNotHaveAccessToNamespace = 5010
// HTTPError holds the http error description
func (err ErrTeamDoesNotHaveAccessToNamespace) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToNamespace, Message: "You need to have access to this namespace to do this."}
}
// ErrUserAlreadyHasNamespaceAccess represents an error where a user already has access to a namespace
type ErrUserAlreadyHasNamespaceAccess struct {
UserID int64
NamespaceID int64
}
// IsErrUserAlreadyHasNamespaceAccess checks if an error is ErrUserAlreadyHasNamespaceAccess.
func IsErrUserAlreadyHasNamespaceAccess(err error) bool {
_, ok := err.(ErrUserAlreadyHasNamespaceAccess)
return ok
}
func (err ErrUserAlreadyHasNamespaceAccess) Error() string {
return fmt.Sprintf("User already has access to that namespace. [User ID: %d, Namespace ID: %d]", err.UserID, err.NamespaceID)
}
// ErrCodeUserAlreadyHasNamespaceAccess holds the unique world-error code of this error
const ErrCodeUserAlreadyHasNamespaceAccess = 5011
// HTTPError holds the http error description
func (err ErrUserAlreadyHasNamespaceAccess) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasNamespaceAccess, Message: "This user already has access to this namespace."}
}
// ============
// Team errors
// ============
// ErrTeamNameCannotBeEmpty represents an error where a team name is empty.
type ErrTeamNameCannotBeEmpty struct {
TeamID int64
}
// IsErrTeamNameCannotBeEmpty checks if an error is a ErrNamespaceDoesNotExist.
func IsErrTeamNameCannotBeEmpty(err error) bool {
_, ok := err.(ErrTeamNameCannotBeEmpty)
return ok
}
func (err ErrTeamNameCannotBeEmpty) Error() string {
return fmt.Sprintf("Team name cannot be empty [Team ID: %d]", err.TeamID)
}
// ErrCodeTeamNameCannotBeEmpty holds the unique world-error code of this error
const ErrCodeTeamNameCannotBeEmpty = 6001
// HTTPError holds the http error description
func (err ErrTeamNameCannotBeEmpty) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeTeamNameCannotBeEmpty, Message: "The team name cannot be empty"}
}
// ErrTeamDoesNotExist represents an error where a team does not exist
type ErrTeamDoesNotExist struct {
TeamID int64
}
// IsErrTeamDoesNotExist checks if an error is ErrTeamDoesNotExist.
func IsErrTeamDoesNotExist(err error) bool {
_, ok := err.(ErrTeamDoesNotExist)
return ok
}
func (err ErrTeamDoesNotExist) Error() string {
return fmt.Sprintf("Team does not exist [Team ID: %d]", err.TeamID)
}
// ErrCodeTeamDoesNotExist holds the unique world-error code of this error
const ErrCodeTeamDoesNotExist = 6002
// HTTPError holds the http error description
func (err ErrTeamDoesNotExist) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "This team does not exist."}
}
// ErrInvalidTeamRight represents an error where a team right is invalid
type ErrInvalidTeamRight struct {
Right TeamRight
}
// IsErrInvalidTeamRight checks if an error is ErrInvalidTeamRight.
func IsErrInvalidTeamRight(err error) bool {
_, ok := err.(ErrInvalidTeamRight)
return ok
}
func (err ErrInvalidTeamRight) Error() string {
return fmt.Sprintf("Team right invalid [Right: %d]", err.Right)
}
// ErrCodeInvalidTeamRight holds the unique world-error code of this error
const ErrCodeInvalidTeamRight = 6003
// HTTPError holds the http error description
func (err ErrInvalidTeamRight) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeInvalidTeamRight, Message: "The team right is invalid."}
}
// ErrTeamAlreadyHasAccess represents an error where a team already has access to a list/namespace
type ErrTeamAlreadyHasAccess struct {
TeamID int64
ID int64
}
// IsErrTeamAlreadyHasAccess checks if an error is ErrTeamAlreadyHasAccess.
func IsErrTeamAlreadyHasAccess(err error) bool {
_, ok := err.(ErrTeamAlreadyHasAccess)
return ok
}
func (err ErrTeamAlreadyHasAccess) Error() string {
return fmt.Sprintf("Team already has access. [Team ID: %d, ID: %d]", err.TeamID, err.ID)
}
// ErrCodeTeamAlreadyHasAccess holds the unique world-error code of this error
const ErrCodeTeamAlreadyHasAccess = 6004
// HTTPError holds the http error description
func (err ErrTeamAlreadyHasAccess) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeTeamAlreadyHasAccess, Message: "This team already has access."}
}
// ErrUserIsMemberOfTeam represents an error where a user is already member of a team.
type ErrUserIsMemberOfTeam struct {
TeamID int64
UserID int64
}
// IsErrUserIsMemberOfTeam checks if an error is ErrUserIsMemberOfTeam.
func IsErrUserIsMemberOfTeam(err error) bool {
_, ok := err.(ErrUserIsMemberOfTeam)
return ok
}
func (err ErrUserIsMemberOfTeam) Error() string {
return fmt.Sprintf("User is already a member of that team. [Team ID: %d, User ID: %d]", err.TeamID, err.UserID)
}
// ErrCodeUserIsMemberOfTeam holds the unique world-error code of this error
const ErrCodeUserIsMemberOfTeam = 6005
// HTTPError holds the http error description
func (err ErrUserIsMemberOfTeam) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserIsMemberOfTeam, Message: "This user is already a member of that team."}
}
// ErrCannotDeleteLastTeamMember represents an error where a user wants to delete the last member of a team (probably himself)
type ErrCannotDeleteLastTeamMember struct {
TeamID int64
UserID int64
}
// IsErrCannotDeleteLastTeamMember checks if an error is ErrCannotDeleteLastTeamMember.
func IsErrCannotDeleteLastTeamMember(err error) bool {
_, ok := err.(ErrCannotDeleteLastTeamMember)
return ok
}
func (err ErrCannotDeleteLastTeamMember) Error() string {
return fmt.Sprintf("Cannot delete last team member. [Team ID: %d, User ID: %d]", err.TeamID, err.UserID)
}
// ErrCodeCannotDeleteLastTeamMember holds the unique world-error code of this error
const ErrCodeCannotDeleteLastTeamMember = 6006
// HTTPError holds the http error description
func (err ErrCannotDeleteLastTeamMember) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCannotDeleteLastTeamMember, Message: "You cannot delete the last member of a team."}
}
// ErrTeamDoesNotHaveAccessToList represents an error, where the Team is not the owner of that List (used i.e. when deleting a List)
type ErrTeamDoesNotHaveAccessToList struct {
ListID int64
TeamID int64
}
// IsErrTeamDoesNotHaveAccessToList checks if an error is a ErrListDoesNotExist.
func IsErrTeamDoesNotHaveAccessToList(err error) bool {
_, ok := err.(ErrTeamDoesNotHaveAccessToList)
return ok
}
func (err ErrTeamDoesNotHaveAccessToList) Error() string {
return fmt.Sprintf("Team does not have access to the list [ListID: %d, TeamID: %d]", err.ListID, err.TeamID)
}
// ErrCodeTeamDoesNotHaveAccessToList holds the unique world-error code of this error
const ErrCodeTeamDoesNotHaveAccessToList = 6007
// HTTPError holds the http error description
func (err ErrTeamDoesNotHaveAccessToList) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToList, Message: "This team does not have access to the list."}
}
// ====================
// User <-> List errors
// ====================
// ErrInvalidUserRight represents an error where a user right is invalid
type ErrInvalidUserRight struct {
Right UserRight
}
// IsErrInvalidUserRight checks if an error is ErrInvalidUserRight.
func IsErrInvalidUserRight(err error) bool {
_, ok := err.(ErrInvalidUserRight)
return ok
}
func (err ErrInvalidUserRight) Error() string {
return fmt.Sprintf("User right is invalid [Right: %d]", err.Right)
}
// ErrCodeInvalidUserRight holds the unique world-error code of this error
const ErrCodeInvalidUserRight = 7001
// HTTPError holds the http error description
func (err ErrInvalidUserRight) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeInvalidUserRight, Message: "The user right is invalid."}
}
// ErrUserAlreadyHasAccess represents an error where a user already has access to a list/namespace
type ErrUserAlreadyHasAccess struct {
UserID int64
ListID int64
}
// IsErrUserAlreadyHasAccess checks if an error is ErrUserAlreadyHasAccess.
func IsErrUserAlreadyHasAccess(err error) bool {
_, ok := err.(ErrUserAlreadyHasAccess)
return ok
}
func (err ErrUserAlreadyHasAccess) Error() string {
return fmt.Sprintf("User already has access to that list. [User ID: %d, List ID: %d]", err.UserID, err.ListID)
}
// ErrCodeUserAlreadyHasAccess holds the unique world-error code of this error
const ErrCodeUserAlreadyHasAccess = 7002
// HTTPError holds the http error description
func (err ErrUserAlreadyHasAccess) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasAccess, Message: "This user already has access to this list."}
}
// ErrUserDoesNotHaveAccessToList represents an error, where the user is not the owner of that List (used i.e. when deleting a List)
type ErrUserDoesNotHaveAccessToList struct {
ListID int64
UserID int64
}
// IsErrUserDoesNotHaveAccessToList checks if an error is a ErrListDoesNotExist.
func IsErrUserDoesNotHaveAccessToList(err error) bool {
_, ok := err.(ErrUserDoesNotHaveAccessToList)
return ok
}
func (err ErrUserDoesNotHaveAccessToList) Error() string {
return fmt.Sprintf("User does not have access to the list [ListID: %d, UserID: %d]", err.ListID, err.UserID)
}
// ErrCodeUserDoesNotHaveAccessToList holds the unique world-error code of this error
const ErrCodeUserDoesNotHaveAccessToList = 7003
// HTTPError holds the http error description
func (err ErrUserDoesNotHaveAccessToList) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToList, Message: "This user does not have access to the list."}
}

143
pkg/models/list.go Normal file
View File

@@ -0,0 +1,143 @@
package models
// List represents a list of tasks
type List struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"list"`
Title string `xorm:"varchar(250)" json:"title"`
Description string `xorm:"varchar(1000)" json:"description"`
OwnerID int64 `xorm:"int(11) INDEX" json:"-"`
NamespaceID int64 `xorm:"int(11) INDEX" json:"-" param:"namespace"`
Owner User `xorm:"-" json:"owner"`
Tasks []*ListTask `xorm:"-" json:"tasks"`
Created int64 `xorm:"created" json:"created"`
Updated int64 `xorm:"updated" json:"updated"`
CRUDable `xorm:"-" json:"-"`
Rights `xorm:"-" json:"-"`
}
// GetListsByNamespaceID gets all lists in a namespace
func GetListsByNamespaceID(nID int64) (lists []*List, err error) {
err = x.Where("namespace_id = ?", nID).Find(&lists)
return lists, err
}
// ReadAll gets all lists a user has access to
func (l *List) ReadAll(u *User) (interface{}, error) {
lists := []*List{}
fullUser, err := GetUserByID(u.ID)
if err != nil {
return lists, err
}
// Gets all Lists where the user is either owner or in a team which has access to the list
// Or in a team which has namespace read access
err = x.Select("l.*").
Table("list").
Alias("l").
Join("INNER", []string{"namespaces", "n"}, "l.namespace_id = n.id").
Join("LEFT", []string{"team_namespaces", "tn"}, "tn.namespace_id = n.id").
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id").
Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id").
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id").
Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id").
Where("tm.user_id = ?", fullUser.ID).
Or("tm2.user_id = ?", fullUser.ID).
Or("l.owner_id = ?", fullUser.ID).
Or("ul.user_id = ?", fullUser.ID).
Or("un.user_id = ?", fullUser.ID).
GroupBy("l.id").
Find(&lists)
// Add more list details
AddListDetails(lists)
return lists, err
}
// ReadOne gets one list by its ID
func (l *List) ReadOne() (err error) {
err = l.GetSimpleByID()
if err != nil {
return err
}
// Get list tasks
l.Tasks, err = GetTasksByListID(l.ID)
if err != nil {
return err
}
// Get list owner
l.Owner, err = GetUserByID(l.OwnerID)
return
}
// GetSimpleByID gets a list with only the basic items, aka no tasks or user objects. Returns an error if the list does not exist.
func (l *List) GetSimpleByID() (err error) {
if l.ID < 1 {
return ErrListDoesNotExist{ID: l.ID}
}
// We need to re-init our list object, because otherwise xorm creates a "where for every item in that list object,
// leading to not finding anything if the id is good, but for example the title is different.
id := l.ID
*l = List{}
exists, err := x.Where("id = ?", id).Get(l)
if err != nil {
return
}
if !exists {
return ErrListDoesNotExist{ID: l.ID}
}
return
}
// AddListDetails adds owner user objects and list tasks to all lists in the slice
func AddListDetails(lists []*List) (err error) {
var listIDs []int64
var ownerIDs []int64
for _, l := range lists {
listIDs = append(listIDs, l.ID)
ownerIDs = append(ownerIDs, l.OwnerID)
}
// Get all tasks
ts := []*ListTask{}
err = x.In("list_id", listIDs).Find(&ts)
if err != nil {
return
}
// Get all list owners
owners := []*User{}
err = x.In("id", ownerIDs).Find(&owners)
if err != nil {
return
}
// Build it all into the lists slice
for in, list := range lists {
// Owner
for _, owner := range owners {
if list.OwnerID == owner.ID {
lists[in].Owner = *owner
break
}
}
// Tasks
for _, task := range ts {
if task.ListID == list.ID {
lists[in].Tasks = append(lists[in].Tasks, task)
}
}
}
return
}

View File

@@ -26,8 +26,7 @@ func CreateOrUpdateList(list *List) (err error) {
return
}
*list, err = GetListByID(list.ID)
err = list.ReadOne()
return
}
@@ -35,8 +34,7 @@ func CreateOrUpdateList(list *List) (err error) {
// Update implements the update method of CRUDable
func (l *List) Update() (err error) {
// Check if it exists
_, err = GetListByID(l.ID)
if err != nil {
if err = l.GetSimpleByID(); err != nil {
return
}
@@ -46,13 +44,13 @@ func (l *List) Update() (err error) {
// Create implements the create method of CRUDable
func (l *List) Create(doer *User) (err error) {
// Check rights
user, err := GetUserByID(doer.ID)
u, err := GetUserByID(doer.ID)
if err != nil {
return
}
l.OwnerID = user.ID
l.Owner.ID = user.ID
l.OwnerID = u.ID
l.Owner.ID = u.ID
l.ID = 0 // Otherwise only the first time a new list would be created
return CreateOrUpdateList(l)

View File

@@ -3,8 +3,7 @@ package models
// Delete implements the delete method of CRUDable
func (l *List) Delete() (err error) {
// Check if the list exists
_, err = GetListByID(l.ID)
if err != nil {
if err = l.GetSimpleByID(); err != nil {
return
}

View File

@@ -16,11 +16,11 @@ func TestList_ReadAll(t *testing.T) {
assert.Equal(t, len(lists), 2)
// Get all lists our user has access to
user, err := GetUserByID(1)
u, err := GetUserByID(1)
assert.NoError(t, err)
lists2 := List{}
lists3, err := lists2.ReadAll(&user)
lists3, err := lists2.ReadAll(&u)
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice)
s := reflect.ValueOf(lists3)

View File

@@ -1,18 +1,22 @@
package models
import (
"code.vikunja.io/api/pkg/log"
)
// IsAdmin returns whether the user has admin rights on the list or not
func (l *List) IsAdmin(user *User) bool {
func (l *List) IsAdmin(u *User) bool {
// Owners are always admins
if l.Owner.ID == user.ID {
if l.Owner.ID == u.ID {
return true
}
// Check individual rights
if l.checkListUserRight(user, UserRightAdmin) {
if l.checkListUserRight(u, UserRightAdmin) {
return true
}
return l.checkListTeamRight(user, TeamRightAdmin)
return l.checkListTeamRight(u, TeamRightAdmin)
}
// CanWrite return whether the user can write on that list or not
@@ -47,14 +51,20 @@ func (l *List) CanRead(user *User) bool {
// CanDelete checks if the user can delete a list
func (l *List) CanDelete(doer *User) bool {
list, _ := GetListByID(l.ID)
return list.IsAdmin(doer)
if err := l.GetSimpleByID(); err != nil {
log.Log.Error("Error occurred during CanDelete for List: %s", err)
return false
}
return l.IsAdmin(doer)
}
// CanUpdate checks if the user can update a list
func (l *List) CanUpdate(doer *User) bool {
list, _ := GetListByID(l.ID)
return list.CanWrite(doer)
if err := l.GetSimpleByID(); err != nil {
log.Log.Error("Error occurred during CanUpdate for List: %s", err)
return false
}
return l.CanWrite(doer)
}
// CanCreate checks if the user can update a list
@@ -72,10 +82,11 @@ func (l *List) checkListTeamRight(user *User, r TeamRight) bool {
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id").
Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id").
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
Where("((tm.user_id = ? AND tn.right = ?) OR (tm2.user_id = ? AND tl.rights = ?)) AND l.id = ?",
Where("((tm.user_id = ? AND tn.right = ?) OR (tm2.user_id = ? AND tl.right = ?)) AND l.id = ?",
user.ID, r, user.ID, r, l.ID).
Exist(&List{})
if err != nil {
log.Log.Error("Error occurred during checkListTeamRight for List: %s", err)
return false
}
@@ -96,6 +107,7 @@ func (l *List) checkListUserRight(user *User, r UserRight) bool {
user.ID, r, user.ID, r, user.ID, l.ID).
Exist(&List{})
if err != nil {
log.Log.Error("Error occurred during checkListUserRight for List: %s", err)
return false
}

View File

@@ -5,11 +5,11 @@ type ListTask struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"listtask"`
Text string `xorm:"varchar(250)" json:"text"`
Description string `xorm:"varchar(250)" json:"description"`
Done bool `json:"done"`
DueDateUnix int64 `xorm:"int(11)" json:"dueDate"`
ReminderUnix int64 `xorm:"int(11)" json:"reminderDate"`
Done bool `xorm:"INDEX" json:"done"`
DueDateUnix int64 `xorm:"int(11) INDEX" json:"dueDate"`
ReminderUnix int64 `xorm:"int(11) INDEX" json:"reminderDate"`
CreatedByID int64 `xorm:"int(11)" json:"-"` // ID of the user who put that task on the list
ListID int64 `xorm:"int(11)" json:"listID" param:"list"`
ListID int64 `xorm:"int(11) INDEX" json:"listID" param:"list"`
Created int64 `xorm:"created" json:"created"`
Updated int64 `xorm:"updated" json:"updated"`
@@ -59,9 +59,9 @@ func GetTasksByListID(listID int64) (tasks []*ListTask, err error) {
}
for in, task := range tasks {
for _, user := range users {
if task.CreatedByID == user.ID {
tasks[in].CreatedBy = user
for _, u := range users {
if task.CreatedByID == u.ID {
tasks[in].CreatedBy = u
break
}
}
@@ -88,11 +88,11 @@ func GetListTaskByID(listTaskID int64) (listTask ListTask, err error) {
return ListTask{}, ErrListTaskDoesNotExist{listTaskID}
}
user, err := GetUserByID(listTask.CreatedByID)
u, err := GetUserByID(listTask.CreatedByID)
if err != nil {
return
}
listTask.CreatedBy = user
listTask.CreatedBy = u
return
}

View File

@@ -14,19 +14,19 @@ func (i *ListTask) Create(doer *User) (err error) {
}
// Check if the list exists
_, err = GetListByID(i.ListID)
if err != nil {
l := &List{ID: i.ListID}
if err = l.GetSimpleByID(); err != nil {
return
}
user, err := GetUserByID(doer.ID)
u, err := GetUserByID(doer.ID)
if err != nil {
return err
}
i.CreatedByID = user.ID
i.CreatedBy = user
_, err = x.Insert(i)
i.CreatedByID = u.ID
i.CreatedBy = u
_, err = x.Cols("text", "description", "done", "due_date_unix", "reminder_unix", "created_by_id", "list_id", "created", "updated").Insert(i)
return err
}
@@ -39,8 +39,8 @@ func (i *ListTask) Update() (err error) {
}
// For whatever reason, xorm dont detect if done is updated, so we need to update this every time by hand
// Which is why we merge the actual task struct with the one we got from the user.
// The user struct ovverrides values in the actual one.
// Which is why we merge the actual task struct with the one we got from the
// The user struct overrides values in the actual one.
if err := mergo.Merge(&ot, i, mergo.WithOverride); err != nil {
return err
}

View File

@@ -1,28 +1,43 @@
package models
import (
"code.vikunja.io/api/pkg/log"
)
// CanDelete checks if the user can delete an task
func (i *ListTask) CanDelete(doer *User) bool {
// Get the task
lI, _ := GetListTaskByID(i.ID)
lI, err := GetListTaskByID(i.ID)
if err != nil {
log.Log.Error("Error occurred during CanDelete for ListTask: %s", err)
return false
}
// A user can delete an task if he has write acces to its list
list, _ := GetListByID(lI.ListID)
return list.CanWrite(doer)
l := &List{ID: lI.ListID}
l.ReadOne()
return l.CanWrite(doer)
}
// CanUpdate determines if a user has the right to update a list task
func (i *ListTask) CanUpdate(doer *User) bool {
// Get the task
lI, _ := GetListTaskByID(i.ID)
lI, err := GetListTaskByID(i.ID)
if err != nil {
log.Log.Error("Error occurred during CanDelete for ListTask: %s", err)
return false
}
// A user can update an task if he has write acces to its list
list, _ := GetListByID(lI.ListID)
return list.CanWrite(doer)
l := &List{ID: lI.ListID}
l.ReadOne()
return l.CanWrite(doer)
}
// CanCreate determines if a user has the right to create a list task
func (i *ListTask) CanCreate(doer *User) bool {
// A user can create an task if he has write acces to its list
list, _ := GetListByID(i.ListID)
return list.CanWrite(doer)
l := &List{ID: i.ListID}
l.ReadOne()
return l.CanWrite(doer)
}

View File

@@ -3,9 +3,9 @@ package models
// ListUser represents a list <-> user relation
type ListUser struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"namespace"`
UserID int64 `xorm:"int(11) not null" json:"user_id" param:"user"`
ListID int64 `xorm:"int(11) not null" json:"list_id" param:"list"`
Right UserRight `xorm:"int(11)" json:"right"`
UserID int64 `xorm:"int(11) not null INDEX" json:"user_id" param:"user"`
ListID int64 `xorm:"int(11) not null INDEX" json:"list_id" param:"list"`
Right UserRight `xorm:"int(11) INDEX" json:"right"`
Created int64 `xorm:"created" json:"created"`
Updated int64 `xorm:"updated" json:"updated"`

View File

@@ -1,7 +1,7 @@
package models
// Create creates a new list <-> user relation
func (ul *ListUser) Create(user *User) (err error) {
func (ul *ListUser) Create(u *User) (err error) {
// Check if the right is valid
if err := ul.Right.isValid(); err != nil {
@@ -9,8 +9,8 @@ func (ul *ListUser) Create(user *User) (err error) {
}
// Check if the list exists
l, err := GetListByID(ul.ListID)
if err != nil {
l := &List{ID: ul.ListID}
if err = l.GetSimpleByID(); err != nil {
return
}

View File

@@ -1,19 +1,19 @@
package models
// ReadAll gets all users who have access to a list
func (ul *ListUser) ReadAll(user *User) (interface{}, error) {
func (ul *ListUser) ReadAll(u *User) (interface{}, error) {
// Check if the user has access to the list
l, err := GetListByID(ul.ListID)
if err != nil {
l := &List{ID: ul.ListID}
if err := l.GetSimpleByID(); err != nil {
return nil, err
}
if !l.CanRead(user) {
if !l.CanRead(u) {
return nil, ErrNeedToHaveListReadAccess{}
}
// Get all users
all := []*userWithRight{}
err = x.
err := x.
Join("INNER", "users_list", "user_id = users.id").
Where("users_list.list_id = ?", ul.ListID).
Find(&all)

View File

@@ -0,0 +1,38 @@
package models
import (
"code.vikunja.io/api/pkg/log"
)
// CanCreate checks if the user can create a new user <-> list relation
func (lu *ListUser) CanCreate(doer *User) bool {
// Get the list and check if the user has write access on it
l := List{ID: lu.ListID}
if err := l.GetSimpleByID(); err != nil {
log.Log.Error("Error occurred during CanCreate for ListUser: %s", err)
return false
}
return l.CanWrite(doer)
}
// CanDelete checks if the user can delete a user <-> list relation
func (lu *ListUser) CanDelete(doer *User) bool {
// Get the list and check if the user has write access on it
l := List{ID: lu.ListID}
if err := l.GetSimpleByID(); err != nil {
log.Log.Error("Error occurred during CanDelete for ListUser: %s", err)
return false
}
return l.CanWrite(doer)
}
// CanUpdate checks if the user can update a user <-> list relation
func (lu *ListUser) CanUpdate(doer *User) bool {
// Get the list and check if the user has write access on it
l := List{ID: lu.ListID}
if err := l.GetSimpleByID(); err != nil {
log.Log.Error("Error occurred during CanUpdate for ListUser: %s", err)
return false
}
return l.CanWrite(doer)
}

View File

@@ -27,7 +27,9 @@ func getEngine() (*xorm.Engine, error) {
viper.GetString("database.password"),
viper.GetString("database.host"),
viper.GetString("database.database"))
return xorm.NewEngine("mysql", connStr)
e, err := xorm.NewEngine("mysql", connStr)
e.SetMaxOpenConns(viper.GetInt("database.openconnections"))
return e, err
}
// Otherwise use sqlite

View File

@@ -5,7 +5,7 @@ type Namespace struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"namespace"`
Name string `xorm:"varchar(250)" json:"name"`
Description string `xorm:"varchar(1000)" json:"description"`
OwnerID int64 `xorm:"int(11) not null" json:"-"`
OwnerID int64 `xorm:"int(11) not null INDEX" json:"-"`
Owner User `xorm:"-" json:"owner"`
@@ -21,11 +21,6 @@ func (Namespace) TableName() string {
return "namespaces"
}
// AfterLoad gets the owner
func (n *Namespace) AfterLoad() {
n.Owner, _ = GetUserByID(n.OwnerID)
}
// GetNamespaceByID returns a namespace object by its ID
func GetNamespaceByID(id int64) (namespace Namespace, err error) {
if id < 1 {
@@ -53,18 +48,7 @@ func GetNamespaceByID(id int64) (namespace Namespace, err error) {
// ReadOne gets one namespace
func (n *Namespace) ReadOne() (err error) {
getN := Namespace{}
exists, err := x.ID(n.ID).Get(&getN)
if err != nil {
return
}
if !exists {
return ErrNamespaceDoesNotExist{ID: n.ID}
}
*n = getN
*n, err = GetNamespaceByID(n.ID)
return
}
@@ -124,6 +108,9 @@ func (n *Namespace) ReadAll(doer *User) (interface{}, error) {
return all, err
}
// More details for the lists
AddListDetails(lists)
// Put objects in our namespace list
for i, n := range all {

View File

@@ -21,8 +21,8 @@ func (n *Namespace) Delete() (err error) {
// We need to do that for here because we need the list ids to delete two times:
// 1) to delete the lists itself
// 2) to delete the list tasks
for _, list := range lists {
listIDs = append(listIDs, list.ID)
for _, l := range lists {
listIDs = append(listIDs, l.ID)
}
// Delete tasks

View File

@@ -1,98 +1,110 @@
package models
import (
"code.vikunja.io/api/pkg/log"
)
// IsAdmin returns true or false if the user is admin on that namespace or not
func (n *Namespace) IsAdmin(user *User) bool {
func (n *Namespace) IsAdmin(u *User) bool {
// Owners always have admin rights
if user.ID == n.Owner.ID {
if u.ID == n.Owner.ID {
return true
}
// Check user rights
if n.checkUserRights(user, UserRightAdmin) {
if n.checkUserRights(u, UserRightAdmin) {
return true
}
// Check if that user is in a team which has admin rights to that namespace
return n.checkTeamRights(user, TeamRightAdmin)
return n.checkTeamRights(u, TeamRightAdmin)
}
// CanWrite checks if a user has write access to a namespace
func (n *Namespace) CanWrite(user *User) bool {
func (n *Namespace) CanWrite(u *User) bool {
// Admins always have write access
if n.IsAdmin(user) {
if n.IsAdmin(u) {
return true
}
// Check user rights
if n.checkUserRights(user, UserRightWrite) {
if n.checkUserRights(u, UserRightWrite) {
return true
}
// Check if that user is in a team which has write rights to that namespace
return n.checkTeamRights(user, TeamRightWrite)
return n.checkTeamRights(u, TeamRightWrite)
}
// CanRead checks if a user has read access to that namespace
func (n *Namespace) CanRead(user *User) bool {
func (n *Namespace) CanRead(u *User) bool {
// Admins always have read access
if n.IsAdmin(user) {
if n.IsAdmin(u) {
return true
}
// Check user rights
if n.checkUserRights(user, UserRightRead) {
if n.checkUserRights(u, UserRightRead) {
return true
}
// Check if the user is in a team which has access to the namespace
return n.checkTeamRights(user, TeamRightRead)
return n.checkTeamRights(u, TeamRightRead)
}
// CanUpdate checks if the user can update the namespace
func (n *Namespace) CanUpdate(user *User) bool {
nn, _ := GetNamespaceByID(n.ID)
return nn.IsAdmin(user)
func (n *Namespace) CanUpdate(u *User) bool {
nn, err := GetNamespaceByID(n.ID)
if err != nil {
log.Log.Error("Error occurred during CanUpdate for Namespace: %s", err)
return false
}
return nn.IsAdmin(u)
}
// CanDelete checks if the user can delete a namespace
func (n *Namespace) CanDelete(user *User) bool {
nn, _ := GetNamespaceByID(n.ID)
return nn.IsAdmin(user)
func (n *Namespace) CanDelete(u *User) bool {
nn, err := GetNamespaceByID(n.ID)
if err != nil {
log.Log.Error("Error occurred during CanDelete for Namespace: %s", err)
return false
}
return nn.IsAdmin(u)
}
// CanCreate checks if the user can create a new namespace
func (n *Namespace) CanCreate(user *User) bool {
func (n *Namespace) CanCreate(u *User) bool {
// This is currently a dummy function, later on we could imagine global limits etc.
return true
}
func (n *Namespace) checkTeamRights(user *User, r TeamRight) bool {
func (n *Namespace) checkTeamRights(u *User, r TeamRight) bool {
exists, err := x.Select("namespaces.*").
Table("namespaces").
Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id").
Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id").
Where("namespaces.id = ? "+
"AND ((team_members.user_id = ? AND team_namespaces.right = ?) "+
"OR namespaces.owner_id = ? ", n.ID, user.ID, r, user.ID).
"AND (team_members.user_id = ? AND team_namespaces.right = ?) "+
"OR namespaces.owner_id = ? ", n.ID, u.ID, r, u.ID).
Get(&Namespace{})
if err != nil {
log.Log.Error("Error occurred during checkTeamRights for Namespace: %s, TeamRight: %d", err, r)
return false
}
return exists
}
func (n *Namespace) checkUserRights(user *User, r UserRight) bool {
func (n *Namespace) checkUserRights(u *User, r UserRight) bool {
exists, err := x.Select("namespaces.*").
Table("namespaces").
Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id").
Where("namespaces.id = ? AND ("+
"namespaces.owner_id = ? "+
"OR (users_namespace.user_id = ? AND users_namespace.right = ?))", n.ID, user.ID, user.ID, r).
"OR (users_namespace.user_id = ? AND users_namespace.right = ?))", n.ID, u.ID, u.ID, r).
Get(&Namespace{})
if err != nil {
log.Log.Error("Error occurred during checkUserRights for Namespace: %s, UserRight: %d", err, r)
return false
}

View File

@@ -52,7 +52,7 @@ func TestNamespace_Create(t *testing.T) {
assert.NoError(t, err)
// Try updating one with a nonexistant owner
dummynamespace.OwnerID = 94829838572
dummynamespace.Owner.ID = 94829838572
err = dummynamespace.Update()
assert.Error(t, err)
assert.True(t, IsErrUserDoesNotExist(err))

View File

@@ -14,9 +14,7 @@ func (n *Namespace) Update() (err error) {
}
// Check if the (new) owner exists
if n.OwnerID == 0 {
n.OwnerID = n.Owner.ID
}
n.OwnerID = n.Owner.ID
if currentNamespace.OwnerID != n.OwnerID {
n.Owner, err = GetUserByID(n.OwnerID)
if err != nil {

View File

@@ -3,9 +3,9 @@ package models
// NamespaceUser represents a namespace <-> user relation
type NamespaceUser struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"namespace"`
UserID int64 `xorm:"int(11) not null" json:"user_id" param:"user"`
NamespaceID int64 `xorm:"int(11) not null" json:"namespace_id" param:"namespace"`
Right UserRight `xorm:"int(11)" json:"right"`
UserID int64 `xorm:"int(11) not null INDEX" json:"user_id" param:"user"`
NamespaceID int64 `xorm:"int(11) not null INDEX" json:"namespace_id" param:"namespace"`
Right UserRight `xorm:"int(11) INDEX" json:"right"`
Created int64 `xorm:"created" json:"created"`
Updated int64 `xorm:"updated" json:"updated"`

View File

@@ -1,7 +1,7 @@
package models
// Create creates a new namespace <-> user relation
func (un *NamespaceUser) Create(user *User) (err error) {
func (un *NamespaceUser) Create(u *User) (err error) {
// Reset the id
un.ID = 0

View File

@@ -1,13 +1,13 @@
package models
// ReadAll gets all users who have access to a namespace
func (un *NamespaceUser) ReadAll(user *User) (interface{}, error) {
func (un *NamespaceUser) ReadAll(u *User) (interface{}, error) {
// Check if the user has access to the namespace
l, err := GetNamespaceByID(un.NamespaceID)
if err != nil {
return nil, err
}
if !l.CanRead(user) {
if !l.CanRead(u) {
return nil, ErrNeedToHaveNamespaceReadAccess{}
}

View File

@@ -1,22 +1,38 @@
package models
import (
"code.vikunja.io/api/pkg/log"
)
// CanCreate checks if the user can create a new user <-> namespace relation
func (nu *NamespaceUser) CanCreate(doer *User) bool {
// Get the namespace and check if the user has write access on it
n, _ := GetNamespaceByID(nu.NamespaceID)
n, err := GetNamespaceByID(nu.NamespaceID)
if err != nil {
log.Log.Error("Error occurred during CanCreate for NamespaceUser: %s", err)
return false
}
return n.CanWrite(doer)
}
// CanDelete checks if the user can delete a user <-> namespace relation
func (nu *NamespaceUser) CanDelete(doer *User) bool {
// Get the namespace and check if the user has write access on it
n, _ := GetNamespaceByID(nu.NamespaceID)
n, err := GetNamespaceByID(nu.NamespaceID)
if err != nil {
log.Log.Error("Error occurred during CanDelete for NamespaceUser: %s", err)
return false
}
return n.CanWrite(doer)
}
// CanUpdate checks if the user can update a user <-> namespace relation
func (nu *NamespaceUser) CanUpdate(doer *User) bool {
// Get the namespace and check if the user has write access on it
n, _ := GetNamespaceByID(nu.NamespaceID)
n, err := GetNamespaceByID(nu.NamespaceID)
if err != nil {
log.Log.Error("Error occurred during CanUpdate for NamespaceUser: %s", err)
return false
}
return n.CanWrite(doer)
}

View File

@@ -3,9 +3,9 @@ package models
// TeamList defines the relation between a team and a list
type TeamList struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
TeamID int64 `xorm:"int(11) not null" json:"team_id" param:"team"`
ListID int64 `xorm:"int(11) not null" json:"list_id" param:"list"`
Right TeamRight `xorm:"int(11)" json:"right"`
TeamID int64 `xorm:"int(11) not null INDEX" json:"team_id" param:"team"`
ListID int64 `xorm:"int(11) not null INDEX" json:"list_id" param:"list"`
Right TeamRight `xorm:"int(11) INDEX" json:"right"`
Created int64 `xorm:"created" json:"created"`
Updated int64 `xorm:"updated" json:"updated"`

View File

@@ -15,9 +15,9 @@ func (tl *TeamList) Create(doer *User) (err error) {
}
// Check if the list exists
_, err = GetListByID(tl.ListID)
if err != nil {
return
l := &List{ID: tl.ListID}
if err := l.GetSimpleByID(); err != nil {
return err
}
// Check if the team is already on the list

View File

@@ -1,19 +1,19 @@
package models
// ReadAll implements the method to read all teams of a list
func (tl *TeamList) ReadAll(user *User) (interface{}, error) {
func (tl *TeamList) ReadAll(u *User) (interface{}, error) {
// Check if the user can read the namespace
l, err := GetListByID(tl.ListID)
if err != nil {
l := &List{ID: tl.ListID}
if err := l.GetSimpleByID(); err != nil {
return nil, err
}
if !l.CanRead(user) {
return nil, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: user.ID}
if !l.CanRead(u) {
return nil, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: u.ID}
}
// Get the teams
all := []*teamWithRight{}
err = x.
err := x.
Table("teams").
Join("INNER", "team_list", "team_id = teams.id").
Where("team_list.list_id = ?", tl.ListID).

View File

@@ -0,0 +1,35 @@
package models
import (
"code.vikunja.io/api/pkg/log"
)
// CanCreate checks if the user can create a team <-> list relation
func (tl *TeamList) CanCreate(u *User) bool {
l := List{ID: tl.ListID}
if err := l.GetSimpleByID(); err != nil {
log.Log.Error("Error occurred during CanCreate for TeamList: %s", err)
return false
}
return l.IsAdmin(u)
}
// CanDelete checks if the user can delete a team <-> list relation
func (tl *TeamList) CanDelete(user *User) bool {
l := List{ID: tl.ListID}
if err := l.GetSimpleByID(); err != nil {
log.Log.Error("Error occurred during CanDelete for TeamList: %s", err)
return false
}
return l.IsAdmin(user)
}
// CanUpdate checks if the user can update a team <-> list relation
func (tl *TeamList) CanUpdate(user *User) bool {
l := List{ID: tl.ListID}
if err := l.GetSimpleByID(); err != nil {
log.Log.Error("Error occurred during CanUpdate for TeamList: %s", err)
return false
}
return l.IsAdmin(user)
}

View File

@@ -15,67 +15,67 @@ func TestTeamList(t *testing.T) {
}
// Dummyuser
user, err := GetUserByID(1)
u, err := GetUserByID(1)
assert.NoError(t, err)
// Check normal creation
assert.True(t, tl.CanCreate(&user))
err = tl.Create(&user)
assert.True(t, tl.CanCreate(&u))
err = tl.Create(&u)
assert.NoError(t, err)
// Check again
err = tl.Create(&user)
err = tl.Create(&u)
assert.Error(t, err)
assert.True(t, IsErrTeamAlreadyHasAccess(err))
// Check with wrong rights
tl2 := tl
tl2.Right = TeamRightUnknown
err = tl2.Create(&user)
err = tl2.Create(&u)
assert.Error(t, err)
assert.True(t, IsErrInvalidTeamRight(err))
// Check with inexistant team
tl3 := tl
tl3.TeamID = 3253
err = tl3.Create(&user)
err = tl3.Create(&u)
assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err))
// Check with inexistant list
tl4 := tl
tl4.ListID = 3252
err = tl4.Create(&user)
err = tl4.Create(&u)
assert.Error(t, err)
assert.True(t, IsErrListDoesNotExist(err))
// Test Read all
teams, err := tl.ReadAll(&user)
teams, err := tl.ReadAll(&u)
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
s := reflect.ValueOf(teams)
assert.Equal(t, s.Len(), 1)
// Test Read all for nonexistant list
_, err = tl4.ReadAll(&user)
_, err = tl4.ReadAll(&u)
assert.Error(t, err)
assert.True(t, IsErrListDoesNotExist(err))
// Test Read all for a list where the user is owner of the namespace this list belongs to
tl5 := tl
tl5.ListID = 2
_, err = tl5.ReadAll(&user)
_, err = tl5.ReadAll(&u)
assert.NoError(t, err)
// Test read all for a list where the user not has access
tl6 := tl
tl6.ListID = 3
_, err = tl6.ReadAll(&user)
_, err = tl6.ReadAll(&u)
assert.Error(t, err)
assert.True(t, IsErrNeedToHaveListReadAccess(err))
// Delete
assert.True(t, tl.CanDelete(&user))
assert.True(t, tl.CanDelete(&u))
err = tl.Delete()
assert.NoError(t, err)

View File

@@ -0,0 +1,27 @@
package models
import (
"code.vikunja.io/api/pkg/log"
)
// CanCreate checks if the user can add a new tem member
func (tm *TeamMember) CanCreate(u *User) bool {
return tm.IsAdmin(u)
}
// CanDelete checks if the user can delete a new team member
func (tm *TeamMember) CanDelete(u *User) bool {
return tm.IsAdmin(u)
}
// IsAdmin checks if the user is team admin
func (tm *TeamMember) IsAdmin(u *User) bool {
// A user can add a member to a team if he is admin of that team
exists, err := x.Where("user_id = ? AND team_id = ? AND admin = ?", u.ID, tm.TeamID, true).
Get(&TeamMember{})
if err != nil {
log.Log.Error("Error occurred during IsAdmin for TeamMember: %s", err)
return false
}
return exists
}

View File

@@ -3,9 +3,9 @@ package models
// TeamNamespace defines the relationship between a Team and a Namespace
type TeamNamespace struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
TeamID int64 `xorm:"int(11) not null" json:"team_id" param:"team"`
NamespaceID int64 `xorm:"int(11) not null" json:"namespace_id" param:"namespace"`
Right TeamRight `xorm:"int(11)" json:"right"`
TeamID int64 `xorm:"int(11) not null INDEX" json:"team_id" param:"team"`
NamespaceID int64 `xorm:"int(11) not null INDEX" json:"namespace_id" param:"namespace"`
Right TeamRight `xorm:"int(11) INDEX" json:"right"`
Created int64 `xorm:"created" json:"created"`
Updated int64 `xorm:"updated" json:"updated"`

View File

@@ -0,0 +1,35 @@
package models
import (
"code.vikunja.io/api/pkg/log"
)
// CanCreate checks if one can create a new team <-> namespace relation
func (tn *TeamNamespace) CanCreate(user *User) bool {
n, err := GetNamespaceByID(tn.NamespaceID)
if err != nil {
log.Log.Error("Error occurred during CanCreate for TeamNamespace: %s", err)
return false
}
return n.IsAdmin(user)
}
// CanDelete checks if a user can remove a team from a namespace. Only namespace admins can do that.
func (tn *TeamNamespace) CanDelete(user *User) bool {
n, err := GetNamespaceByID(tn.NamespaceID)
if err != nil {
log.Log.Error("Error occurred during CanDelete for TeamNamespace: %s", err)
return false
}
return n.IsAdmin(user)
}
// CanUpdate checks if a user can update a team from a Only namespace admins can do that.
func (tn *TeamNamespace) CanUpdate(user *User) bool {
n, err := GetNamespaceByID(tn.NamespaceID)
if err != nil {
log.Log.Error("Error occurred during CanUpdate for TeamNamespace: %s", err)
return false
}
return n.IsAdmin(user)
}

27
pkg/models/team_right.go Normal file
View File

@@ -0,0 +1,27 @@
package models
// TeamRight defines the rights teams can have for lists/namespaces
type TeamRight int
// define unknown team right
const (
TeamRightUnknown = -1
)
// Enumerate all the team rights
const (
// Can read lists in a Team
TeamRightRead TeamRight = iota
// Can write tasks in a Team like lists and todo tasks. Cannot create new lists.
TeamRightWrite
// Can manage a list/namespace, can do everything
TeamRightAdmin
)
func (r TeamRight) isValid() error {
if r != TeamRightAdmin && r != TeamRightRead && r != TeamRightWrite {
return ErrInvalidTeamRight{r}
}
return nil
}

View File

@@ -5,7 +5,7 @@ type Team struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"team"`
Name string `xorm:"varchar(250) not null" json:"name"`
Description string `xorm:"varchar(250)" json:"description"`
CreatedByID int64 `xorm:"int(11) not null" json:"-"`
CreatedByID int64 `xorm:"int(11) not null INDEX" json:"-"`
CreatedBy User `xorm:"-" json:"created_by"`
Members []*TeamUser `xorm:"-" json:"members"`
@@ -38,9 +38,9 @@ func (t *Team) AfterLoad() {
// TeamMember defines the relationship between a user and a team
type TeamMember struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
TeamID int64 `xorm:"int(11) not null" json:"team_id" param:"team"`
UserID int64 `xorm:"int(11) not null" json:"user_id" param:"user"`
Admin bool `xorm:"tinyint(1)" json:"admin"`
TeamID int64 `xorm:"int(11) not null INDEX" json:"team_id" param:"team"`
UserID int64 `xorm:"int(11) not null INDEX" json:"user_id" param:"user"`
Admin bool `xorm:"tinyint(1) INDEX" json:"admin"`
Created int64 `xorm:"created" json:"created"`
Updated int64 `xorm:"updated" json:"updated"`

View File

@@ -0,0 +1,58 @@
package models
import (
"code.vikunja.io/api/pkg/log"
)
// CanCreate checks if the user can create a new team
func (t *Team) CanCreate(u *User) bool {
// This is currently a dummy function, later on we could imagine global limits etc.
return true
}
// CanUpdate checks if the user can update a team
func (t *Team) CanUpdate(u *User) bool {
// Check if the current user is in the team and has admin rights in it
exists, err := x.Where("team_id = ?", t.ID).
And("user_id = ?", u.ID).
And("admin = ?", true).
Get(&TeamMember{})
if err != nil {
log.Log.Error("Error occurred during CanUpdate for Team: %s", err)
return false
}
return exists
}
// CanDelete checks if a user can delete a team
func (t *Team) CanDelete(u *User) bool {
return t.IsAdmin(u)
}
// IsAdmin returns true when the user is admin of a team
func (t *Team) IsAdmin(u *User) bool {
exists, err := x.Where("team_id = ?", t.ID).
And("user_id = ?", u.ID).
And("admin = ?", true).
Get(&TeamMember{})
if err != nil {
log.Log.Error("Error occurred during CanUpdate for Team: %s", err)
return false
}
return exists
}
// CanRead returns true if the user has read access to the team
func (t *Team) CanRead(user *User) bool {
// Check if the user is in the team
exists, err := x.Where("team_id = ?", t.ID).
And("user_id = ?", user.ID).
Get(&TeamMember{})
if err != nil {
log.Log.Error("Error occurred during CanUpdate for Team: %s", err)
return false
}
return exists
}

View File

@@ -1,6 +1,7 @@
package models
import (
"code.vikunja.io/api/pkg/mail"
"fmt"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
@@ -10,6 +11,10 @@ import (
"testing"
)
// IsTesting is set to true when we're running tests.
// We don't have a good solution to test email sending yet, so we disable email sending when testing
var IsTesting bool
// MainTest creates the test engine
func MainTest(m *testing.M, pathToRoot string) {
var err error
@@ -19,6 +24,11 @@ func MainTest(m *testing.M, pathToRoot string) {
os.Exit(1)
}
IsTesting = true
// Start the pseudo mail queue
mail.StartMailDaemon()
// Create test database
PrepareTestDatabase()
@@ -39,7 +49,7 @@ func createTestEngine(fixturesDir string) error {
return fmt.Errorf("sync database struct error: %v", err)
}
// Show SQL-Queries if nessecary
// Show SQL-Queries if necessary
if os.Getenv("UNIT_TESTS_VERBOSE") == "1" {
x.ShowSQL(true)
}

View File

@@ -18,8 +18,13 @@ type User struct {
Username string `xorm:"varchar(250) not null unique" json:"username"`
Password string `xorm:"varchar(250) not null" json:"-"`
Email string `xorm:"varchar(250)" json:"email"`
Created int64 `xorm:"created" json:"-"`
Updated int64 `xorm:"updated" json:"-"`
IsActive bool `json:"-"`
PasswordResetToken string `xorm:"varchar(450)" json:"-"`
EmailConfirmToken string `xorm:"varchar(450)" json:"-"`
Created int64 `xorm:"created" json:"-"`
Updated int64 `xorm:"updated" json:"-"`
}
// TableName returns the table name for users
@@ -61,7 +66,7 @@ func GetUser(user User) (userOut User, err error) {
exists, err := x.Get(&userOut)
if !exists {
return User{}, ErrUserDoesNotExist{}
return User{}, ErrUserDoesNotExist{UserID: user.ID}
}
return userOut, err
@@ -69,6 +74,10 @@ func GetUser(user User) (userOut User, err error) {
// CheckUserCredentials checks user credentials
func CheckUserCredentials(u *UserLogin) (User, error) {
// Check if we have any credentials
if u.Password == "" || u.Username == "" {
return User{}, ErrNoUsernamePassword{}
}
// Check if the user exists
user, err := GetUser(User{Username: u.Username})
@@ -76,10 +85,17 @@ func CheckUserCredentials(u *UserLogin) (User, error) {
return User{}, err
}
// User is invalid if it needs to verify its email address
if !user.IsActive {
return User{}, ErrEmailNotConfirmed{UserID: user.ID}
}
// Check the users password
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(u.Password))
if err != nil {
if err == bcrypt.ErrMismatchedHashAndPassword {
return User{}, ErrWrongUsernameOrPassword{}
}
return User{}, err
}

View File

@@ -1,6 +1,8 @@
package models
import (
"code.vikunja.io/api/pkg/mail"
"code.vikunja.io/api/pkg/utils"
"golang.org/x/crypto/bcrypt"
)
@@ -48,6 +50,12 @@ func CreateUser(user User) (newUser User, err error) {
return User{}, err
}
// Generate a confirm token
newUser.EmailConfirmToken = utils.MakeRandomString(400)
// The new user should not be activated until it confirms his mail address
newUser.IsActive = false
// Insert it
_, err = x.Insert(newUser)
if err != nil {
@@ -67,6 +75,18 @@ func CreateUser(user User) (newUser User, err error) {
return User{}, err
}
// Dont send a mail if we're testing
if IsTesting {
return newUserOut, err
}
// Send the user a mail with a link to confirm the mail
data := map[string]interface{}{
"User": newUserOut,
}
mail.SendMailWithTemplate(user.Email, newUserOut.Username+" + Vikunja = <3", "confirm-email", data)
return newUserOut, err
}
@@ -109,10 +129,10 @@ func UpdateUser(user User) (updatedUser User, err error) {
}
// UpdateUserPassword updates the password of a user
func UpdateUserPassword(userID int64, newPassword string, doer *User) (err error) {
func UpdateUserPassword(user *User, newPassword string) (err error) {
// Get all user details
user, err := GetUserByID(userID)
theUser, err := GetUserByID(user.ID)
if err != nil {
return err
}
@@ -122,10 +142,10 @@ func UpdateUserPassword(userID int64, newPassword string, doer *User) (err error
if err != nil {
return err
}
user.Password = hashed
theUser.Password = hashed
// Update it
_, err = x.Id(user.ID).Update(user)
_, err = x.Id(user.ID).Update(theUser)
if err != nil {
return err
}

View File

@@ -7,18 +7,8 @@ func DeleteUserByID(id int64, doer *User) error {
return ErrIDCannotBeZero{}
}
// Check if there is > 1 user
total, err := x.Count(User{})
if err != nil {
return err
}
if total < 2 {
return ErrCannotDeleteLastUser{}
}
// Delete the user
_, err = x.Id(id).Delete(&User{})
_, err := x.Id(id).Delete(&User{})
if err != nil {
return err

View File

@@ -0,0 +1,31 @@
package models
// EmailConfirm holds the token to confirm a mail address
type EmailConfirm struct {
Token string `json:"token"`
}
// UserEmailConfirm handles the confirmation of an email address
func UserEmailConfirm(c *EmailConfirm) (err error) {
// Check if we have an email confirm token
if c.Token == "" {
return ErrInvalidEmailConfirmToken{}
}
// Check if the token is valid
user := User{}
has, err := x.Where("email_confirm_token = ?", c.Token).Get(&user)
if err != nil {
return
}
if !has {
return ErrInvalidEmailConfirmToken{Token: c.Token}
}
user.IsActive = true
user.EmailConfirmToken = ""
_, err = x.Where("id = ?", user.ID).Cols("is_active", "email_confirm_token").Update(&user)
return
}

Some files were not shown because too many files have changed in this diff Show More