Compare commits

..

38 Commits
v0.1 ... v0.2

Author SHA1 Message Date
kolaente
206705d2e9 updated makefile to use new golint url 2018-10-15 12:54:39 +02:00
kolaente
522e647db0 Added config for database max connections 2018-10-11 18:39:53 +02:00
kolaente
bbd90eb976 Fixed not setting http code on error 2018-10-11 18:18:44 +02:00
kolaente
d8f6a628db Replaced CObject with a function returning an object 2018-10-11 17:53:59 +02:00
kolaente
a612037ee1 added go mod 2018-10-11 13:28:41 +02:00
kolaente
224cebfd10 Fixed tests failing 2018-10-06 22:26:17 +02:00
kolaente
f4ac036f27 removed unused errors 2018-10-06 18:55:14 +02:00
kolaente
c626b05af6 Improved log error messages 2018-10-06 18:44:04 +02:00
kolaente
56dc781594 added docs for error codes 2018-10-06 18:32:33 +02:00
kolaente
1ddb819c31 updated docs 2018-10-06 15:47:26 +02:00
kolaente
75b611de48 updated docs 2018-10-06 15:41:57 +02:00
konrad
c5100aad42 updated todo 2018-10-06 15:08:38 +02:00
konrad
f969593c0a Improved error handling 2018-10-06 15:04:14 +02:00
konrad
a4137b3d6f Added more checks to not leave an error unpassed when checking for rights 2018-10-06 13:25:37 +02:00
konrad
06638fccc8 Fixed lint & misspell 2018-10-06 13:07:42 +02:00
konrad
a108ed689d Added extra function to get a list without tasks or user objects 2018-10-06 13:05:29 +02:00
kolaente
53a7f2e6a7 cleanup 2018-10-05 19:17:39 +02:00
kolaente
3819927dd2 updated todo 2018-10-05 19:16:31 +02:00
kolaente
cca677878d Optimized used sql queries when getting lists or namespaces with lists 2018-10-05 19:16:14 +02:00
kolaente
a1a77a9b75 improved logging format 2018-10-05 18:49:56 +02:00
kolaente
6ea4f0598c fmt 2018-10-05 18:46:05 +02:00
kolaente
358b569bf1 Addded indecies everywhere 2018-10-05 18:46:01 +02:00
kolaente
1139eee2ad Improved update password method to ask the current password 2018-10-04 07:53:45 +02:00
konrad
a6d49a5e70 Fixed lint 2018-10-03 19:32:05 +02:00
kolaente
cd4fca19fd updated swagger spec 2018-10-03 19:30:43 +02:00
konrad
85c9fba808 Added the ability to update a users password 2018-10-03 19:28:17 +02:00
konrad
17368dea9a Cleanup 2018-10-03 19:00:09 +02:00
konrad
43c67d8c29 added go report badge 2018-09-23 21:16:56 +02:00
konrad
e724aeb206 Added new checks (#2)
* added gocyclo-check
* Added new tests to drone
* Added new checks to makefile
2018-09-23 19:15:14 +00:00
konrad
b912ff4176 updated drone config 2018-09-22 23:52:19 +02:00
konrad
849b64fc0c updated drone 2018-09-22 22:55:06 +02:00
konrad
ce661f1c6e updated drone 2018-09-22 22:38:25 +02:00
konrad
06a4ede4a8 fmt 2018-09-22 11:06:39 +02:00
kolaente
6519c62ade fixed a bug where xorm treid to insert something with an id wich already exist 2018-09-21 08:41:28 +02:00
kolaente
bfc783d1d0 updated swagger docs 2018-09-20 22:06:19 +02:00
kolaente
e7d43567aa updated readme 2018-09-20 21:59:43 +02:00
kolaente
6c35db57c7 updated todo 2018-09-20 19:44:43 +02:00
kolaente
64d290bcae Added endpoint to search for users 2018-09-20 19:42:01 +02:00
61 changed files with 1047 additions and 572 deletions

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,10 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone.kolaente.de/vikunja/api)
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.1-brightgreen.svg)](https://storage.kolaente.de/minio/vikunja/)
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/swagger)
[![Go Report Card](https://goreportcard.com/badge/git.kolaente.de/vikunja/api)](https://goreportcard.com/report/git.kolaente.de/vikunja/api
)
## Features
* Create TODO lists with tasks

View File

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

View File

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

24
REST-Tests/users.http Normal file
View 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"
}
###

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,8 +26,7 @@ func CreateOrUpdateList(list *List) (err error) {
return
}
*list, err = GetListByID(list.ID)
err = list.ReadOne()
return
}
@@ -35,8 +34,7 @@ func CreateOrUpdateList(list *List) (err error) {
// Update implements the update method of CRUDable
func (l *List) Update() (err error) {
// Check if it exists
_, err = GetListByID(l.ID)
if err != nil {
if err = l.GetSimpleByID(); err != nil {
return
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,8 +9,8 @@ func (ul *ListUser) Create(user *User) (err error) {
}
// Check if the list exists
l, err := GetListByID(ul.ListID)
if err != nil {
l := &List{ID: ul.ListID}
if err = l.GetSimpleByID(); err != nil {
return
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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": {

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -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(&currentUser) {
if !currentStruct.CanCreate(&currentUser) {
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(&currentUser)
err = currentStruct.Create(&currentUser)
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)
}

View File

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

View File

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

View File

@@ -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(&currentUser)
lists, err := currentStruct.ReadAll(&currentUser)
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)

View File

@@ -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(&currentUser) {
if !currentStruct.CanRead(&currentUser) {
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)
}

View File

@@ -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(&currentUser) {
if !currentStruct.CanUpdate(&currentUser) {
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)
}

View File

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