Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
206705d2e9 | ||
|
|
522e647db0 | ||
|
|
bbd90eb976 | ||
|
|
d8f6a628db | ||
|
|
a612037ee1 | ||
|
|
224cebfd10 | ||
|
|
f4ac036f27 | ||
|
|
c626b05af6 | ||
|
|
56dc781594 | ||
|
|
1ddb819c31 | ||
|
|
75b611de48 | ||
|
|
c5100aad42 | ||
|
|
f969593c0a | ||
|
|
a4137b3d6f | ||
|
|
06638fccc8 | ||
|
|
a108ed689d | ||
|
|
53a7f2e6a7 | ||
|
|
3819927dd2 | ||
|
|
cca677878d | ||
|
|
a1a77a9b75 | ||
|
|
6ea4f0598c | ||
|
|
358b569bf1 | ||
|
|
1139eee2ad | ||
|
|
a6d49a5e70 | ||
|
|
cd4fca19fd | ||
|
|
85c9fba808 | ||
|
|
17368dea9a | ||
|
|
43c67d8c29 | ||
|
|
e724aeb206 | ||
|
|
b912ff4176 | ||
|
|
849b64fc0c | ||
|
|
ce661f1c6e | ||
|
|
06a4ede4a8 | ||
|
|
6519c62ade | ||
|
|
bfc783d1d0 | ||
|
|
e7d43567aa | ||
|
|
6c35db57c7 | ||
|
|
64d290bcae |
14
.drone.yml
14
.drone.yml
@@ -21,6 +21,8 @@ pipeline:
|
||||
- make lint
|
||||
- make fmt-check
|
||||
- make swagger-check
|
||||
- make ineffassign-check
|
||||
- make misspell-check
|
||||
- make build
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
@@ -155,4 +157,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"}}.
|
||||
|
||||
|
||||
@@ -191,7 +191,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 +211,14 @@ 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
|
||||
* [ ] Search endpoints /users?s=name und /teams?s=name, erstmal nur mit Namen suchen.
|
||||
* [ ] Userstuff aufräumen, auf neuen Handler umziehen?
|
||||
* [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)
|
||||
* [ ] 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
|
||||
* [ ] Rausfinden warum xorm teilweise beim einfügen IDs mit einfügen will -> Das schlägt dann wegen duplicate fehl
|
||||
|
||||
### Later/Nice to have
|
||||
|
||||
|
||||
23
Makefile
23
Makefile
@@ -63,7 +63,7 @@ required-gofmt-version:
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/golang/lint/golint; \
|
||||
go get -u golang.org/x/lint/golint; \
|
||||
fi
|
||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||
|
||||
@@ -170,3 +170,24 @@ swagger-validate:
|
||||
go get -u 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 get -u 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 get -u 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 get github.com/fzipp/gocyclo; \
|
||||
fi
|
||||
for S in $(GOFILES); do gocyclo -over 14 $$S || exit 1; done;
|
||||
@@ -5,7 +5,10 @@
|
||||
[](https://drone.kolaente.de/vikunja/api)
|
||||
[](LICENSE)
|
||||
[](https://storage.kolaente.de/minio/vikunja/)
|
||||
|
||||
[](https://hub.docker.com/r/vikunja/api/)
|
||||
[](https://try.vikunja.io/api/v1/swagger)
|
||||
[](https://goreportcard.com/report/git.kolaente.de/vikunja/api
|
||||
)
|
||||
## Features
|
||||
|
||||
* Create TODO lists with tasks
|
||||
|
||||
@@ -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}}
|
||||
|
||||
###
|
||||
|
||||
@@ -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}}
|
||||
|
||||
###
|
||||
|
||||
24
REST-Tests/users.http
Normal file
24
REST-Tests/users.http
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
# 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"
|
||||
}
|
||||
|
||||
###
|
||||
@@ -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.
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -45,6 +45,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
|
||||
|
||||
34
docs/errors.md
Normal file
34
docs/errors.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 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. |
|
||||
| 1007 | 409 | Cannot delete the last user on the system. |
|
||||
| 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. |
|
||||
34
go.mod
Normal file
34
go.mod
Normal file
@@ -0,0 +1,34 @@
|
||||
module code.vikunja.io/api
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.30.0 // indirect
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 // indirect
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a
|
||||
github.com/garyburd/redigo v1.6.0 // indirect
|
||||
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b
|
||||
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/imdario/mergo v0.3.6
|
||||
github.com/joho/godotenv v1.3.0 // indirect
|
||||
github.com/labstack/echo v3.1.0+incompatible
|
||||
github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0 // indirect
|
||||
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/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/viper v1.2.0
|
||||
github.com/stretchr/testify v0.0.0-20171231124224-87b1dfb5b2fa
|
||||
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
|
||||
gopkg.in/testfixtures.v2 v2.4.5
|
||||
)
|
||||
88
go.sum
Normal file
88
go.sum
Normal file
@@ -0,0 +1,88 @@
|
||||
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/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/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/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
|
||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
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-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/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/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
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/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/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/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 v0.0.0-20171231124224-87b1dfb5b2fa h1:ws/U/9eA/uVBX3BckIHVlYLtQLuWodrnpPBuL8Q0N1E=
|
||||
github.com/stretchr/testify v0.0.0-20171231124224-87b1dfb5b2fa/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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/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=
|
||||
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/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=
|
||||
@@ -27,6 +27,7 @@ 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")
|
||||
|
||||
392
models/error.go
392
models/error.go
@@ -1,6 +1,21 @@
|
||||
package models
|
||||
|
||||
import "fmt"
|
||||
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
|
||||
@@ -19,7 +34,15 @@ func IsErrUsernameExists(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
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.
|
||||
@@ -35,22 +58,15 @@ func IsErrUserEmailExists(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
return fmt.Sprintf("User with that email already exists [user id: %d, email: %s]", err.UserID, err.Email)
|
||||
}
|
||||
|
||||
// ErrNoUsername represents a "UsernameAlreadyExists" kind of error.
|
||||
type ErrNoUsername struct {
|
||||
UserID int64
|
||||
}
|
||||
// ErrorCodeUserEmailExists holds the unique world-error code of this error
|
||||
const ErrorCodeUserEmailExists = 1002
|
||||
|
||||
// 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)
|
||||
// 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.
|
||||
@@ -63,7 +79,15 @@ func IsErrNoUsernamePassword(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrNoUsernamePassword) Error() string {
|
||||
return fmt.Sprintf("you need to specify a username and a password")
|
||||
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.
|
||||
@@ -78,7 +102,15 @@ func IsErrUserDoesNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("this user does not exist [user id: %d]", err.UserID)
|
||||
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.
|
||||
@@ -91,7 +123,15 @@ func IsErrCouldNotGetUserID(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrCouldNotGetUserID) Error() string {
|
||||
return fmt.Sprintf("could not get user ID")
|
||||
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."}
|
||||
}
|
||||
|
||||
// ErrCannotDeleteLastUser represents a "ErrCannotDeleteLastUser" kind of error.
|
||||
@@ -104,7 +144,15 @@ func IsErrCannotDeleteLastUser(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrCannotDeleteLastUser) Error() string {
|
||||
return fmt.Sprintf("cannot delete last user")
|
||||
return fmt.Sprintf("Cannot delete last user")
|
||||
}
|
||||
|
||||
// ErrCodeCannotDeleteLastUser holds the unique world-error code of this error
|
||||
const ErrCodeCannotDeleteLastUser = 1007
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrCannotDeleteLastUser) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeCannotDeleteLastUser, Message: "Cannot delete the last user on the server."}
|
||||
}
|
||||
|
||||
// ===================
|
||||
@@ -121,7 +169,15 @@ func IsErrIDCannotBeZero(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrIDCannotBeZero) Error() string {
|
||||
return fmt.Sprintf("ID cannot be 0")
|
||||
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."}
|
||||
}
|
||||
|
||||
// ===========
|
||||
@@ -143,36 +199,12 @@ 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
|
||||
}
|
||||
// ErrCodeListDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeListDoesNotExist = 3001
|
||||
|
||||
// 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)
|
||||
// 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
|
||||
@@ -188,7 +220,15 @@ func IsErrNeedToHaveListReadAccess(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
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.
|
||||
@@ -201,7 +241,15 @@ func IsErrListTitleCannotBeEmpty(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrListTitleCannotBeEmpty) Error() string {
|
||||
return fmt.Sprintf("List task text cannot be empty.")
|
||||
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."}
|
||||
}
|
||||
|
||||
// ================
|
||||
@@ -221,6 +269,14 @@ 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
|
||||
@@ -236,20 +292,12 @@ 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
|
||||
}
|
||||
// ErrCodeListTaskDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeListTaskDoesNotExist = 4002
|
||||
|
||||
// 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)
|
||||
// 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"}
|
||||
}
|
||||
|
||||
// =================
|
||||
@@ -271,20 +319,12 @@ 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
|
||||
}
|
||||
// ErrCodeNamespaceDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeNamespaceDoesNotExist = 5001
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
@@ -300,39 +340,15 @@ func IsErrUserDoesNotHaveAccessToNamespace(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
return fmt.Sprintf("User does not have access to the namespace [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
|
||||
}
|
||||
// ErrCodeUserDoesNotHaveAccessToNamespace holds the unique world-error code of this error
|
||||
const ErrCodeUserDoesNotHaveAccessToNamespace = 5003
|
||||
|
||||
// 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)
|
||||
// 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.
|
||||
@@ -348,39 +364,15 @@ func IsErrNamespaceNameCannotBeEmpty(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrNamespaceNameCannotBeEmpty) Error() string {
|
||||
return fmt.Sprintf("Namespace name cannot be emtpy [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
|
||||
return fmt.Sprintf("Namespace name cannot be empty [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
|
||||
}
|
||||
// ErrCodeNamespaceNameCannotBeEmpty holds the unique world-error code of this error
|
||||
const ErrCodeNamespaceNameCannotBeEmpty = 5006
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
@@ -396,7 +388,15 @@ func IsErrNeedToHaveNamespaceReadAccess(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
@@ -412,7 +412,15 @@ func IsErrTeamDoesNotHaveAccessToNamespace(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
@@ -428,7 +436,15 @@ func IsErrUserAlreadyHasNamespaceAccess(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
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."}
|
||||
}
|
||||
|
||||
// ============
|
||||
@@ -450,6 +466,14 @@ 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
|
||||
@@ -465,6 +489,14 @@ 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
|
||||
@@ -477,7 +509,15 @@ func IsErrInvalidTeamRight(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrInvalidTeamRight) Error() string {
|
||||
return fmt.Sprintf("The right is invalid [Right: %d]", err.Right)
|
||||
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
|
||||
@@ -493,7 +533,15 @@ func IsErrTeamAlreadyHasAccess(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrTeamAlreadyHasAccess) Error() string {
|
||||
return fmt.Sprintf("This team already has access. [Team ID: %d, ID: %d]", err.TeamID, err.ID)
|
||||
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.
|
||||
@@ -509,7 +557,15 @@ func IsErrUserIsMemberOfTeam(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
@@ -525,7 +581,15 @@ func IsErrCannotDeleteLastTeamMember(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
@@ -541,7 +605,15 @@ func IsErrTeamDoesNotHaveAccessToList(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
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."}
|
||||
}
|
||||
|
||||
// ====================
|
||||
@@ -560,7 +632,15 @@ func IsErrInvalidUserRight(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrInvalidUserRight) Error() string {
|
||||
return fmt.Sprintf("The right is invalid [Right: %d]", err.Right)
|
||||
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
|
||||
@@ -576,7 +656,15 @@ func IsErrUserAlreadyHasAccess(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
@@ -592,5 +680,13 @@ func IsErrUserDoesNotHaveAccessToList(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
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."}
|
||||
}
|
||||
|
||||
117
models/list.go
117
models/list.go
@@ -5,8 +5,8 @@ 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"`
|
||||
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"`
|
||||
@@ -18,34 +18,6 @@ type List struct {
|
||||
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)
|
||||
@@ -54,7 +26,7 @@ func GetListsByNamespaceID(nID int64) (lists []*List, err error) {
|
||||
|
||||
// ReadAll gets all lists a user has access to
|
||||
func (l *List) ReadAll(user *User) (interface{}, error) {
|
||||
lists := []List{}
|
||||
lists := []*List{}
|
||||
fullUser, err := GetUserByID(user.ID)
|
||||
if err != nil {
|
||||
return lists, err
|
||||
@@ -80,11 +52,92 @@ func (l *List) ReadAll(user *User) (interface{}, error) {
|
||||
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) {
|
||||
*l, err = GetListByID(l.ID)
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
tasks := []*ListTask{}
|
||||
err = x.In("list_id", listIDs).Find(&tasks)
|
||||
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 tasks {
|
||||
if task.ListID == list.ID {
|
||||
lists[in].Tasks = append(lists[in].Tasks, task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"`
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ 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
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func (i *ListTask) Create(doer *User) (err error) {
|
||||
|
||||
i.CreatedByID = user.ID
|
||||
i.CreatedBy = user
|
||||
_, err = x.Insert(i)
|
||||
_, err = x.Cols("text", "description", "done", "due_date_unix", "reminder_unix", "created_by_id", "list_id", "created", "updated").Insert(i)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -3,26 +3,37 @@ package models
|
||||
// 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.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)
|
||||
list := &List{ID: lI.ListID}
|
||||
list.ReadOne()
|
||||
return list.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.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)
|
||||
list := &List{ID: lI.ListID}
|
||||
list.ReadOne()
|
||||
return list.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)
|
||||
list := &List{ID: i.ListID}
|
||||
list.ReadOne()
|
||||
return list.CanWrite(doer)
|
||||
}
|
||||
|
||||
@@ -47,14 +47,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.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.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 +78,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.Error("Error occurred during checkListTeamRight for List: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -96,6 +103,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.Error("Error occurred during checkListUserRight for List: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ package models
|
||||
// ReadAll gets all users who have access to a list
|
||||
func (ul *ListUser) ReadAll(user *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) {
|
||||
@@ -13,7 +13,7 @@ func (ul *ListUser) ReadAll(user *User) (interface{}, error) {
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -29,20 +29,32 @@ func (r UserRight) isValid() error {
|
||||
// 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)
|
||||
l := List{ID: lu.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
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, _ := GetListByID(lu.ListID)
|
||||
l := List{ID: lu.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
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, _ := GetListByID(lu.ListID)
|
||||
l := List{ID: lu.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanUpdate for ListUser: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.CanWrite(doer)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -50,13 +50,21 @@ func (n *Namespace) CanRead(user *User) bool {
|
||||
|
||||
// CanUpdate checks if the user can update the namespace
|
||||
func (n *Namespace) CanUpdate(user *User) bool {
|
||||
nn, _ := GetNamespaceByID(n.ID)
|
||||
nn, err := GetNamespaceByID(n.ID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanUpdate for Namespace: %s", err)
|
||||
return false
|
||||
}
|
||||
return nn.IsAdmin(user)
|
||||
}
|
||||
|
||||
// CanDelete checks if the user can delete a namespace
|
||||
func (n *Namespace) CanDelete(user *User) bool {
|
||||
nn, _ := GetNamespaceByID(n.ID)
|
||||
nn, err := GetNamespaceByID(n.ID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanDelete for Namespace: %s", err)
|
||||
return false
|
||||
}
|
||||
return nn.IsAdmin(user)
|
||||
}
|
||||
|
||||
@@ -72,11 +80,11 @@ func (n *Namespace) checkTeamRights(user *User, r TeamRight) bool {
|
||||
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 = ?) "+
|
||||
"AND (team_members.user_id = ? AND team_namespaces.right = ?) "+
|
||||
"OR namespaces.owner_id = ? ", n.ID, user.ID, r, user.ID).
|
||||
Get(&Namespace{})
|
||||
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during checkTeamRights for Namespace: %s, TeamRight: %d", err, r)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -91,8 +99,8 @@ func (n *Namespace) checkUserRights(user *User, r UserRight) bool {
|
||||
"namespaces.owner_id = ? "+
|
||||
"OR (users_namespace.user_id = ? AND users_namespace.right = ?))", n.ID, user.ID, user.ID, r).
|
||||
Get(&Namespace{})
|
||||
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during checkUserRights for Namespace: %s, UserRight: %d", err, r)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -3,20 +3,32 @@ package models
|
||||
// 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.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.Error("Error occurred during CanCreate 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.Error("Error occurred during CanCreate for NamespaceUser: %s", err)
|
||||
return false
|
||||
}
|
||||
return n.CanWrite(doer)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,8 +3,8 @@ package models
|
||||
// ReadAll implements the method to read all teams of a list
|
||||
func (tl *TeamList) ReadAll(user *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) {
|
||||
@@ -13,7 +13,7 @@ func (tl *TeamList) ReadAll(user *User) (interface{}, error) {
|
||||
|
||||
// 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).
|
||||
|
||||
@@ -2,18 +2,30 @@ package models
|
||||
|
||||
// CanCreate checks if the user can create a team <-> list relation
|
||||
func (tl *TeamList) CanCreate(user *User) bool {
|
||||
l, _ := GetListByID(tl.ListID)
|
||||
l := List{ID: tl.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanCreate for TeamList: %s", err)
|
||||
return false
|
||||
}
|
||||
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)
|
||||
l := List{ID: tl.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
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, _ := GetListByID(tl.ListID)
|
||||
l := List{ID: tl.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanUpdate for TeamList: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.IsAdmin(user)
|
||||
}
|
||||
|
||||
@@ -12,9 +12,12 @@ func (tm *TeamMember) CanDelete(user *User) bool {
|
||||
|
||||
// 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).
|
||||
exists, err := x.Where("user_id = ? AND team_id = ? AND admin = ?", user.ID, tm.TeamID, true).
|
||||
Get(&TeamMember{})
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during IsAdmin for TeamMember: %s", err)
|
||||
return false
|
||||
}
|
||||
return exists
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -2,18 +2,30 @@ package models
|
||||
|
||||
// CanCreate checks if one can create a new team <-> namespace relation
|
||||
func (tn *TeamNamespace) CanCreate(user *User) bool {
|
||||
n, _ := GetNamespaceByID(tn.NamespaceID)
|
||||
n, err := GetNamespaceByID(tn.NamespaceID)
|
||||
if err != nil {
|
||||
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, _ := GetNamespaceByID(tn.NamespaceID)
|
||||
n, err := GetNamespaceByID(tn.NamespaceID)
|
||||
if err != nil {
|
||||
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 namespace. Only namespace admins can do that.
|
||||
func (tn *TeamNamespace) CanUpdate(user *User) bool {
|
||||
n, _ := GetNamespaceByID(tn.NamespaceID)
|
||||
n, err := GetNamespaceByID(tn.NamespaceID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanUpdate for TeamNamespace: %s", err)
|
||||
return false
|
||||
}
|
||||
return n.IsAdmin(user)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -36,34 +36,45 @@ func (t *Team) CanCreate(user *User) bool {
|
||||
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).
|
||||
exists, err := x.Where("team_id = ?", t.ID).
|
||||
And("user_id = ?", user.ID).
|
||||
And("admin = ?", true).
|
||||
Get(&TeamMember{})
|
||||
if err != nil {
|
||||
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(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).
|
||||
exists, err := x.Where("team_id = ?", t.ID).
|
||||
And("user_id = ?", user.ID).
|
||||
And("admin = ?", true).
|
||||
Get(&TeamMember{})
|
||||
if err != nil {
|
||||
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, _ := x.Where("team_id = ?", t.ID).
|
||||
exists, err := x.Where("team_id = ?", t.ID).
|
||||
And("user_id = ?", user.ID).
|
||||
Get(&TeamMember{})
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanUpdate for Team: %s", err)
|
||||
return false
|
||||
}
|
||||
return exists
|
||||
}
|
||||
|
||||
@@ -39,7 +39,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)
|
||||
}
|
||||
|
||||
@@ -109,10 +109,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 +122,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
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func TestCreateUser(t *testing.T) {
|
||||
|
||||
// Update a users password
|
||||
newpassword := "55555"
|
||||
err = UpdateUserPassword(theuser.ID, newpassword, &doer)
|
||||
err = UpdateUserPassword(&theuser, newpassword)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if it was changed
|
||||
@@ -116,7 +116,7 @@ func TestCreateUser(t *testing.T) {
|
||||
assert.True(t, len(all) > 0)
|
||||
|
||||
// Try updating the password of a nonexistent user (should fail)
|
||||
err = UpdateUserPassword(9999, newpassword, &doer)
|
||||
err = UpdateUserPassword(&User{ID: 9999}, newpassword)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrUserDoesNotExist(err))
|
||||
|
||||
|
||||
@@ -11,11 +11,6 @@ func ListUsers(searchterm string) (users []User, err error) {
|
||||
Find(&users)
|
||||
}
|
||||
|
||||
// Obfuscate the password. Selecting everything except the password didn't work.
|
||||
for i := range users {
|
||||
users[i].Password = ""
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return []User{}, err
|
||||
}
|
||||
|
||||
@@ -1542,6 +1542,103 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Shows the current user",
|
||||
"operationId": "showUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/User"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/Message"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/password": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Shows the current user",
|
||||
"operationId": "updatePassword",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Password"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/Message"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/Message"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/Message"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Lists all users",
|
||||
"operationId": "list",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A searchterm to search for a user by its username",
|
||||
"name": "s",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/User"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/Message"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
|
||||
@@ -39,7 +39,7 @@ func GetListsByNamespaceID(c echo.Context) error {
|
||||
if models.IsErrUserDoesNotHaveAccessToNamespace(err) {
|
||||
return c.JSON(http.StatusForbidden, models.Message{"You don't have access to this namespace."})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, models.Message{"An error occured."})
|
||||
return c.JSON(http.StatusInternalServerError, models.Message{"An error occurred."})
|
||||
}
|
||||
|
||||
// Get the lists
|
||||
@@ -48,7 +48,7 @@ func GetListsByNamespaceID(c echo.Context) error {
|
||||
if models.IsErrNamespaceDoesNotExist(err) {
|
||||
return c.JSON(http.StatusNotFound, models.Message{"Namespace not found."})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, models.Message{"An error occured."})
|
||||
return c.JSON(http.StatusInternalServerError, models.Message{"An error occurred."})
|
||||
}
|
||||
return c.JSON(http.StatusOK, lists)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package v1
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/models"
|
||||
"code.vikunja.io/api/routes/crud"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -77,32 +78,7 @@ func userAddOrUpdate(c echo.Context) error {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Check for user already exists
|
||||
if models.IsErrUsernameExists(err) {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{"A user with this username already exists."})
|
||||
}
|
||||
|
||||
// Check for user with that email already exists
|
||||
if models.IsErrUserEmailExists(err) {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{"A user with this email address already exists."})
|
||||
}
|
||||
|
||||
// Check for no username provided
|
||||
if models.IsErrNoUsername(err) {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{"Please specify a username."})
|
||||
}
|
||||
|
||||
// Check for no username or password provided
|
||||
if models.IsErrNoUsernamePassword(err) {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{"Please specify a username and a password."})
|
||||
}
|
||||
|
||||
// Check for user does not exist
|
||||
if models.IsErrUserDoesNotExist(err) {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{"The user does not exist."})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusInternalServerError, models.Message{"Error"})
|
||||
return crud.HandleHTTPError(err)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, newUser)
|
||||
|
||||
@@ -2,6 +2,7 @@ package v1
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/models"
|
||||
"code.vikunja.io/api/routes/crud"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -41,15 +42,7 @@ func UserDelete(c echo.Context) error {
|
||||
err = models.DeleteUserByID(userID, &doer)
|
||||
|
||||
if err != nil {
|
||||
if models.IsErrIDCannotBeZero(err) {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{"Id cannot be 0"})
|
||||
}
|
||||
|
||||
if models.IsErrCannotDeleteLastUser(err) {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{"Cannot delete last user."})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusInternalServerError, models.Message{"Could not delete user."})
|
||||
return crud.HandleHTTPError(err)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{"success"})
|
||||
|
||||
44
routes/api/v1/user_list.go
Normal file
44
routes/api/v1/user_list.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/models"
|
||||
"code.vikunja.io/api/routes/crud"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// UserList gets all information about a user
|
||||
func UserList(c echo.Context) error {
|
||||
|
||||
// swagger:operation GET /users user list
|
||||
// ---
|
||||
// summary: Lists all users
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: s
|
||||
// description: A searchterm to search for a user by its username
|
||||
// in: query
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/User"
|
||||
// "400":
|
||||
// "$ref": "#/responses/Message"
|
||||
// "500":
|
||||
// "$ref": "#/responses/Message"
|
||||
|
||||
s := c.QueryParam("s")
|
||||
users, err := models.ListUsers(s)
|
||||
if err != nil {
|
||||
return crud.HandleHTTPError(err)
|
||||
}
|
||||
|
||||
// Obfuscate the mailadresses
|
||||
for in := range users {
|
||||
users[in].Email = ""
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, users)
|
||||
}
|
||||
@@ -2,29 +2,37 @@ package v1
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/models"
|
||||
"code.vikunja.io/api/routes/crud"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// UserShow gets all information about a user
|
||||
// UserShow gets all informations about the current user
|
||||
func UserShow(c echo.Context) error {
|
||||
// swagger:operation GET /user user showUser
|
||||
// ---
|
||||
// summary: Shows the current user
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/User"
|
||||
// "400":
|
||||
// "$ref": "#/responses/Message"
|
||||
// "500":
|
||||
// "$ref": "#/responses/Message"
|
||||
|
||||
userInfos, err := models.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
if models.IsErrUserDoesNotExist(err) {
|
||||
return c.JSON(http.StatusNotFound, models.Message{"The user does not exist."})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, models.Message{"Error getting user infos."})
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Error getting current user.")
|
||||
}
|
||||
|
||||
user, err := models.GetUserByID(userInfos.ID)
|
||||
if err != nil {
|
||||
if models.IsErrUserDoesNotExist(err) {
|
||||
return c.JSON(http.StatusNotFound, models.Message{"The user does not exist."})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, models.Message{"Error getting user infos."})
|
||||
return crud.HandleHTTPError(err)
|
||||
}
|
||||
// Obfuscate his password
|
||||
user.Password = ""
|
||||
|
||||
return c.JSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
@@ -1,74 +1,63 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"code.vikunja.io/api/models"
|
||||
"code.vikunja.io/api/routes/crud"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type datPassword struct {
|
||||
Password string `json:"password"`
|
||||
// UserPassword holds a user password. Used to update it.
|
||||
type UserPassword struct {
|
||||
OldPassword string `json:"old_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
// UserChangePassword is the handler to add a user
|
||||
// UserChangePassword is the handler to change a users password
|
||||
func UserChangePassword(c echo.Context) error {
|
||||
|
||||
// Get the ID
|
||||
user := c.Param("id")
|
||||
|
||||
if user == "" {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{"User ID cannot be empty."})
|
||||
}
|
||||
|
||||
// Make int
|
||||
userID, err := strconv.ParseInt(user, 10, 64)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{"User ID is invalid."})
|
||||
}
|
||||
// swagger:operation POST /user/password user updatePassword
|
||||
// ---
|
||||
// summary: Shows the current user
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/Password"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Message"
|
||||
// "400":
|
||||
// "$ref": "#/responses/Message"
|
||||
// "404":
|
||||
// "$ref": "#/responses/Message"
|
||||
// "500":
|
||||
// "$ref": "#/responses/Message"
|
||||
|
||||
// Check if the user is itself
|
||||
userJWTinfo, err := models.GetCurrentUser(c)
|
||||
|
||||
if userJWTinfo.ID != userID {
|
||||
return echo.ErrUnauthorized
|
||||
doer, err := models.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Error getting current user.")
|
||||
}
|
||||
|
||||
// Check for Request Content
|
||||
pwFromString := c.FormValue("password")
|
||||
var datPw datPassword
|
||||
|
||||
if pwFromString == "" {
|
||||
if err := c.Bind(&datPw); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{"No password provided."})
|
||||
}
|
||||
} else {
|
||||
// Take the value directly from the input
|
||||
datPw.Password = pwFromString
|
||||
var newPW UserPassword
|
||||
if err := c.Bind(&newPW); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No password provided.")
|
||||
}
|
||||
|
||||
// Get User Infos
|
||||
_, err = models.GetUserByID(userID)
|
||||
|
||||
if err != nil {
|
||||
if models.IsErrUserDoesNotExist(err) {
|
||||
return c.JSON(http.StatusNotFound, models.Message{"The user does not exist."})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, models.Message{"Error getting user infos."})
|
||||
// Check the current password
|
||||
if _, err = models.CheckUserCredentials(&models.UserLogin{Username: doer.Username, Password: newPW.OldPassword}); err != nil {
|
||||
return crud.HandleHTTPError(err)
|
||||
}
|
||||
|
||||
// Get the doer options
|
||||
doer, err := models.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
// Update the password
|
||||
if err = models.UpdateUserPassword(&doer, newPW.NewPassword); err != nil {
|
||||
return crud.HandleHTTPError(err)
|
||||
}
|
||||
|
||||
err = models.UpdateUserPassword(userID, datPw.Password, &doer)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{"The password was updated successfully"})
|
||||
return c.JSON(http.StatusOK, models.Message{"The password was updated successfully."})
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// SetCORSHeader sets relevant CORS headers for Cross-Site-Requests to the api
|
||||
func SetCORSHeader(c echo.Context) error {
|
||||
res := c.Response()
|
||||
res.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
res.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
|
||||
res.Header().Set("Access-Control-Allow-Headers", "authorization,content-type")
|
||||
res.Header().Set("Access-Control-Expose-Headers", "authorization,content-type")
|
||||
return c.String(http.StatusOK, "")
|
||||
}
|
||||
@@ -4,17 +4,15 @@ import (
|
||||
"code.vikunja.io/api/models"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// CreateWeb is the handler to create an object
|
||||
func (c *WebHandler) CreateWeb(ctx echo.Context) error {
|
||||
// Re-initialize our model
|
||||
p := reflect.ValueOf(c.CObject).Elem()
|
||||
p.Set(reflect.Zero(p.Type()))
|
||||
// Get our model
|
||||
currentStruct := c.EmptyStruct()
|
||||
|
||||
// Get the object & bind params to struct
|
||||
if err := ParamBinder(c.CObject, ctx); err != nil {
|
||||
if err := ParamBinder(currentStruct, ctx); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided.")
|
||||
}
|
||||
|
||||
@@ -25,59 +23,16 @@ func (c *WebHandler) CreateWeb(ctx echo.Context) error {
|
||||
}
|
||||
|
||||
// Check rights
|
||||
if !c.CObject.CanCreate(¤tUser) {
|
||||
if !currentStruct.CanCreate(¤tUser) {
|
||||
models.Log.Noticef("%s [ID: %d] tried to create while not having the rights for it", currentUser.Username, currentUser.ID)
|
||||
return echo.NewHTTPError(http.StatusForbidden)
|
||||
}
|
||||
|
||||
// Create
|
||||
err = c.CObject.Create(¤tUser)
|
||||
err = currentStruct.Create(¤tUser)
|
||||
if err != nil {
|
||||
models.Log.Error(err.Error())
|
||||
|
||||
if models.IsErrListDoesNotExist(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "The list does not exist.")
|
||||
}
|
||||
if models.IsErrListTitleCannotBeEmpty(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "You must provide at least a list title.")
|
||||
}
|
||||
if models.IsErrListTaskCannotBeEmpty(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "You must provide at least a list task text.")
|
||||
}
|
||||
if models.IsErrUserDoesNotExist(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "The user does not exist.")
|
||||
}
|
||||
if models.IsErrNeedToBeListWriter(err) {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "You need to have write access on that list.")
|
||||
}
|
||||
|
||||
if models.IsErrNamespaceNameCannotBeEmpty(err) {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "The namespace name cannot be empty.")
|
||||
}
|
||||
|
||||
if models.IsErrTeamNameCannotBeEmpty(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "The team name cannot be empty.")
|
||||
}
|
||||
|
||||
if models.IsErrTeamAlreadyHasAccess(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "This team already has access.")
|
||||
}
|
||||
if models.IsErrUserIsMemberOfTeam(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "This user is already a member of that team.")
|
||||
}
|
||||
|
||||
if models.IsErrUserAlreadyHasAccess(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "This user already has access to this list.")
|
||||
}
|
||||
if models.IsErrUserAlreadyHasNamespaceAccess(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "This user already has access to this namespace.")
|
||||
}
|
||||
if models.IsErrInvalidUserRight(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "The right is invalid.")
|
||||
}
|
||||
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusCreated, c.CObject)
|
||||
return ctx.JSON(http.StatusCreated, currentStruct)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,12 @@ import (
|
||||
|
||||
// DeleteWeb is the web handler to delete something
|
||||
func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
|
||||
|
||||
// Get our model
|
||||
currentStruct := c.EmptyStruct()
|
||||
|
||||
// Bind params to struct
|
||||
if err := ParamBinder(c.CObject, ctx); err != nil {
|
||||
if err := ParamBinder(currentStruct, ctx); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid URL param.")
|
||||
}
|
||||
|
||||
@@ -18,43 +22,14 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
if !c.CObject.CanDelete(&user) {
|
||||
if !currentStruct.CanDelete(&user) {
|
||||
models.Log.Noticef("%s [ID: %d] tried to delete while not having the rights for it", user.Username, user.ID)
|
||||
return echo.NewHTTPError(http.StatusForbidden)
|
||||
}
|
||||
|
||||
err = c.CObject.Delete()
|
||||
err = currentStruct.Delete()
|
||||
if err != nil {
|
||||
models.Log.Error(err.Error())
|
||||
|
||||
if models.IsErrNeedToBeListAdmin(err) {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "You need to be the list admin to delete a list.")
|
||||
}
|
||||
|
||||
if models.IsErrListDoesNotExist(err) {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "This list does not exist.")
|
||||
}
|
||||
if models.IsErrTeamDoesNotHaveAccessToList(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "This team does not have access to the list.")
|
||||
}
|
||||
|
||||
if models.IsErrTeamDoesNotExist(err) {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "This team does not exist.")
|
||||
}
|
||||
|
||||
if models.IsErrCannotDeleteLastTeamMember(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "You cannot delete the last member of a team.")
|
||||
}
|
||||
|
||||
if models.IsErrUserDoesNotHaveAccessToList(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "This user does not have access to the list.")
|
||||
}
|
||||
|
||||
if models.IsErrUserDoesNotHaveAccessToNamespace(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "This user does not have access to the namespace.")
|
||||
}
|
||||
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusOK, models.Message{"Successfully deleted."})
|
||||
|
||||
@@ -2,13 +2,28 @@ package crud
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/models"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// WebHandler defines the webhandler object
|
||||
// This does web stuff, aka returns json etc. Uses CRUDable Methods to get the data
|
||||
type WebHandler struct {
|
||||
CObject interface {
|
||||
models.CRUDable
|
||||
models.Rights
|
||||
}
|
||||
EmptyStruct func() CObject
|
||||
}
|
||||
|
||||
// CObject is the definition of our object, holds the structs
|
||||
type CObject interface {
|
||||
models.CRUDable
|
||||
models.Rights
|
||||
}
|
||||
|
||||
// HandleHTTPError does what it says
|
||||
func HandleHTTPError(err error) *echo.HTTPError {
|
||||
if a, has := err.(models.HTTPErrorProcessor); has {
|
||||
errDetails := a.HTTPError()
|
||||
return echo.NewHTTPError(errDetails.HTTPCode, errDetails)
|
||||
}
|
||||
models.Log.Error(err.Error())
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
@@ -8,29 +8,22 @@ import (
|
||||
|
||||
// ReadAllWeb is the webhandler to get all objects of a type
|
||||
func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
|
||||
// Get our model
|
||||
currentStruct := c.EmptyStruct()
|
||||
|
||||
currentUser, err := models.GetCurrentUser(ctx)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Could not determine the current user.")
|
||||
}
|
||||
|
||||
// Get the object & bind params to struct
|
||||
if err := ParamBinder(c.CObject, ctx); err != nil {
|
||||
if err := ParamBinder(currentStruct, ctx); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided.")
|
||||
}
|
||||
|
||||
lists, err := c.CObject.ReadAll(¤tUser)
|
||||
lists, err := currentStruct.ReadAll(¤tUser)
|
||||
if err != nil {
|
||||
models.Log.Error(err.Error())
|
||||
|
||||
if models.IsErrNeedToHaveListReadAccess(err) {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "You need to have read access to this list.")
|
||||
}
|
||||
|
||||
if models.IsErrNamespaceDoesNotExist(err) {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "This namespace does not exist.")
|
||||
}
|
||||
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "An error occured.")
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusOK, lists)
|
||||
|
||||
@@ -8,30 +8,18 @@ import (
|
||||
|
||||
// ReadOneWeb is the webhandler to get one object
|
||||
func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
|
||||
// Get our model
|
||||
currentStruct := c.EmptyStruct()
|
||||
|
||||
// Get the object & bind params to struct
|
||||
if err := ParamBinder(c.CObject, ctx); err != nil {
|
||||
if err := ParamBinder(currentStruct, ctx); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided.")
|
||||
}
|
||||
|
||||
// Get our object
|
||||
err := c.CObject.ReadOne()
|
||||
err := currentStruct.ReadOne()
|
||||
if err != nil {
|
||||
models.Log.Error(err.Error())
|
||||
|
||||
if models.IsErrListDoesNotExist(err) {
|
||||
return echo.NewHTTPError(http.StatusNotFound)
|
||||
}
|
||||
|
||||
if models.IsErrNamespaceDoesNotExist(err) {
|
||||
return echo.NewHTTPError(http.StatusNotFound)
|
||||
}
|
||||
|
||||
if models.IsErrTeamDoesNotExist(err) {
|
||||
return echo.NewHTTPError(http.StatusNotFound)
|
||||
}
|
||||
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "An error occured.")
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
|
||||
// Check rights
|
||||
@@ -40,10 +28,10 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Could not determine the current user.")
|
||||
}
|
||||
if !c.CObject.CanRead(¤tUser) {
|
||||
if !currentStruct.CanRead(¤tUser) {
|
||||
models.Log.Noticef("%s [ID: %d] tried to read while not having the rights for it", currentUser.Username, currentUser.ID)
|
||||
return echo.NewHTTPError(http.StatusForbidden, "You don't have the right to see this")
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusOK, c.CObject)
|
||||
return ctx.JSON(http.StatusOK, currentStruct)
|
||||
}
|
||||
|
||||
@@ -4,17 +4,16 @@ import (
|
||||
"code.vikunja.io/api/models"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// UpdateWeb is the webhandler to update an object
|
||||
func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
|
||||
// Re-initialize our model
|
||||
p := reflect.ValueOf(c.CObject).Elem()
|
||||
p.Set(reflect.Zero(p.Type()))
|
||||
|
||||
// Get our model
|
||||
currentStruct := c.EmptyStruct()
|
||||
|
||||
// Get the object & bind params to struct
|
||||
if err := ParamBinder(c.CObject, ctx); err != nil {
|
||||
if err := ParamBinder(currentStruct, ctx); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided.")
|
||||
}
|
||||
|
||||
@@ -23,32 +22,16 @@ func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Could not determine the current user.")
|
||||
}
|
||||
if !c.CObject.CanUpdate(¤tUser) {
|
||||
if !currentStruct.CanUpdate(¤tUser) {
|
||||
models.Log.Noticef("%s [ID: %d] tried to update while not having the rights for it", currentUser.Username, currentUser.ID)
|
||||
return echo.NewHTTPError(http.StatusForbidden)
|
||||
}
|
||||
|
||||
// Do the update
|
||||
err = c.CObject.Update()
|
||||
err = currentStruct.Update()
|
||||
if err != nil {
|
||||
models.Log.Error(err.Error())
|
||||
|
||||
if models.IsErrNeedToBeListAdmin(err) {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "You need to be list admin to do that.")
|
||||
}
|
||||
|
||||
if models.IsErrNamespaceDoesNotExist(err) {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "The namespace does not exist.")
|
||||
}
|
||||
if models.IsErrNamespaceNameCannotBeEmpty(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "The namespace name cannot be empty.")
|
||||
}
|
||||
if models.IsErrNamespaceOwnerCannotBeEmpty(err) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "The namespace owner cannot be empty.")
|
||||
}
|
||||
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusOK, c.CObject)
|
||||
return ctx.JSON(http.StatusOK, currentStruct)
|
||||
}
|
||||
|
||||
@@ -71,8 +71,15 @@ func RegisterRoutes(e *echo.Echo) {
|
||||
a.Use(middleware.JWT([]byte(viper.GetString("service.JWTSecret"))))
|
||||
a.POST("/tokenTest", apiv1.CheckToken)
|
||||
|
||||
// User stuff
|
||||
a.GET("/user", apiv1.UserShow)
|
||||
a.POST("/user/password", apiv1.UserChangePassword)
|
||||
a.GET("/users", apiv1.UserList)
|
||||
|
||||
listHandler := &crud.WebHandler{
|
||||
CObject: &models.List{},
|
||||
EmptyStruct: func() crud.CObject {
|
||||
return &models.List{}
|
||||
},
|
||||
}
|
||||
a.GET("/lists", listHandler.ReadAllWeb)
|
||||
a.GET("/lists/:list", listHandler.ReadOneWeb)
|
||||
@@ -81,14 +88,18 @@ func RegisterRoutes(e *echo.Echo) {
|
||||
a.PUT("/namespaces/:namespace/lists", listHandler.CreateWeb)
|
||||
|
||||
taskHandler := &crud.WebHandler{
|
||||
CObject: &models.ListTask{},
|
||||
EmptyStruct: func() crud.CObject {
|
||||
return &models.ListTask{}
|
||||
},
|
||||
}
|
||||
a.PUT("/lists/:list", taskHandler.CreateWeb)
|
||||
a.DELETE("/tasks/:listtask", taskHandler.DeleteWeb)
|
||||
a.POST("/tasks/:listtask", taskHandler.UpdateWeb)
|
||||
|
||||
listTeamHandler := &crud.WebHandler{
|
||||
CObject: &models.TeamList{},
|
||||
EmptyStruct: func() crud.CObject {
|
||||
return &models.TeamList{}
|
||||
},
|
||||
}
|
||||
a.GET("/lists/:list/teams", listTeamHandler.ReadAllWeb)
|
||||
a.PUT("/lists/:list/teams", listTeamHandler.CreateWeb)
|
||||
@@ -96,7 +107,9 @@ func RegisterRoutes(e *echo.Echo) {
|
||||
a.POST("/lists/:list/teams/:team", listTeamHandler.UpdateWeb)
|
||||
|
||||
listUserHandler := &crud.WebHandler{
|
||||
CObject: &models.ListUser{},
|
||||
EmptyStruct: func() crud.CObject {
|
||||
return &models.ListUser{}
|
||||
},
|
||||
}
|
||||
a.GET("/lists/:list/users", listUserHandler.ReadAllWeb)
|
||||
a.PUT("/lists/:list/users", listUserHandler.CreateWeb)
|
||||
@@ -104,7 +117,9 @@ func RegisterRoutes(e *echo.Echo) {
|
||||
a.POST("/lists/:list/users/:user", listUserHandler.UpdateWeb)
|
||||
|
||||
namespaceHandler := &crud.WebHandler{
|
||||
CObject: &models.Namespace{},
|
||||
EmptyStruct: func() crud.CObject {
|
||||
return &models.Namespace{}
|
||||
},
|
||||
}
|
||||
a.GET("/namespaces", namespaceHandler.ReadAllWeb)
|
||||
a.PUT("/namespaces", namespaceHandler.CreateWeb)
|
||||
@@ -114,7 +129,9 @@ func RegisterRoutes(e *echo.Echo) {
|
||||
a.GET("/namespaces/:namespace/lists", apiv1.GetListsByNamespaceID)
|
||||
|
||||
namespaceTeamHandler := &crud.WebHandler{
|
||||
CObject: &models.TeamNamespace{},
|
||||
EmptyStruct: func() crud.CObject {
|
||||
return &models.TeamNamespace{}
|
||||
},
|
||||
}
|
||||
a.GET("/namespaces/:namespace/teams", namespaceTeamHandler.ReadAllWeb)
|
||||
a.PUT("/namespaces/:namespace/teams", namespaceTeamHandler.CreateWeb)
|
||||
@@ -122,7 +139,9 @@ func RegisterRoutes(e *echo.Echo) {
|
||||
a.POST("/namespaces/:namespace/teams/:team", namespaceTeamHandler.UpdateWeb)
|
||||
|
||||
namespaceUserHandler := &crud.WebHandler{
|
||||
CObject: &models.NamespaceUser{},
|
||||
EmptyStruct: func() crud.CObject {
|
||||
return &models.NamespaceUser{}
|
||||
},
|
||||
}
|
||||
a.GET("/namespaces/:namespace/users", namespaceUserHandler.ReadAllWeb)
|
||||
a.PUT("/namespaces/:namespace/users", namespaceUserHandler.CreateWeb)
|
||||
@@ -130,7 +149,9 @@ func RegisterRoutes(e *echo.Echo) {
|
||||
a.POST("/namespaces/:namespace/users/:user", namespaceUserHandler.UpdateWeb)
|
||||
|
||||
teamHandler := &crud.WebHandler{
|
||||
CObject: &models.Team{},
|
||||
EmptyStruct: func() crud.CObject {
|
||||
return &models.Team{}
|
||||
},
|
||||
}
|
||||
a.GET("/teams", teamHandler.ReadAllWeb)
|
||||
a.GET("/teams/:team", teamHandler.ReadOneWeb)
|
||||
@@ -139,10 +160,10 @@ func RegisterRoutes(e *echo.Echo) {
|
||||
a.DELETE("/teams/:team", teamHandler.DeleteWeb)
|
||||
|
||||
teamMemberHandler := &crud.WebHandler{
|
||||
CObject: &models.TeamMember{},
|
||||
EmptyStruct: func() crud.CObject {
|
||||
return &models.TeamMember{}
|
||||
},
|
||||
}
|
||||
a.PUT("/teams/:team/members", teamMemberHandler.CreateWeb)
|
||||
a.DELETE("/teams/:team/members/:user", teamMemberHandler.DeleteWeb)
|
||||
|
||||
a.GET("/user", apiv1.UserShow)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user