Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14207a0fa8 | ||
|
|
e906184bf7 | ||
|
|
1f41db9239 | ||
|
|
4f8a0f8739 | ||
|
|
c51ca4dd26 | ||
|
|
9e539b8c40 | ||
|
|
9a5eb577b8 | ||
|
|
4713023a97 | ||
|
|
301a4eedda | ||
|
|
3f9fad0e2a | ||
|
|
990740fc19 | ||
|
|
960d72aa1b | ||
|
|
f94bbf023c | ||
|
|
2556ef7624 | ||
|
|
321c0f2404 | ||
|
|
738c22b5f9 | ||
|
|
d0c30cb089 | ||
|
|
0cfea682ea | ||
|
|
95197ec6ed | ||
|
|
eecace511d | ||
|
|
6793e13e6d | ||
|
|
85da2d72aa |
10
.drone.yml
10
.drone.yml
@@ -10,17 +10,15 @@ clone:
|
||||
|
||||
pipeline:
|
||||
build:
|
||||
image: webhippie/golang:edge
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
group: build
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make clean
|
||||
- make lint
|
||||
- make fmt-check
|
||||
- make swagger-check
|
||||
# - make swagger-check # Inactive until go-swagger works with go modules
|
||||
- make ineffassign-check
|
||||
- make misspell-check
|
||||
- make build
|
||||
@@ -28,10 +26,8 @@ pipeline:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
test:
|
||||
image: webhippie/golang:edge
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make test
|
||||
when:
|
||||
|
||||
@@ -5,6 +5,8 @@ FROM golang:1.11-alpine3.7 AS build-env
|
||||
|
||||
ARG VIKUNJA_VERSION
|
||||
ENV TAGS "sqlite"
|
||||
ENV GO111MODULE=on
|
||||
ENV GOFLAGS=-mod=vendor
|
||||
|
||||
#Build deps
|
||||
RUN apk --no-cache add build-base git
|
||||
@@ -34,8 +36,11 @@ RUN apk --no-cache add \
|
||||
tzdata
|
||||
|
||||
COPY docker /
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/public /app/vikunja/
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/public /app/vikunja/public
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/templates /app/vikunja/templates
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja /app/vikunja/vikunja
|
||||
|
||||
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
|
||||
|
||||
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
|
||||
CMD []
|
||||
|
||||
@@ -167,6 +167,9 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
|
||||
* [ ] Attachments
|
||||
* [ ] Repeating tasks
|
||||
* [ ] Tagesübersicht ("Was ist heute/diese Woche due?") -> Machen letztenendes die Clients, wir brauchen nur nen endpoint, der alle tasks auskotzt, der Client macht dann die Sortierung.
|
||||
* [ ] Mit definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat"
|
||||
* [ ] Namespaces in Namespaces (in Namespaces in Namespaces in Namespaces...)
|
||||
* [ ] Rechtemanagement dafür wird schwierig
|
||||
|
||||
## Clients
|
||||
|
||||
@@ -212,13 +215,27 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
|
||||
* [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
|
||||
* [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] Rausfinden warum xorm teilweise beim einfügen IDs mit einfügen will -> Das schlägt dann wegen duplicate fehl
|
||||
* [x] Bei den Structs "AfterLoad" raus, das verbraucht bei Gruppenabfragen zu viele SQL-Abfragen -> Die sollen einfach die entsprechenden Read()-Methoden verwenden (Krassestes bsp. ist GET /namespaces mit so ca 50 Abfragen)
|
||||
* [ ] Search endpoints /users?s=name und /teams?s=name, erstmal nur mit Namen suchen.
|
||||
-> Search methode in den Handler einbauen und dann die Endpoints entsprechend anpassen
|
||||
-> Macht Sinn das in ne eigene Methode umzubauen weil der query dazu leicht anders ist und man das auch nicht sooo einfach integrieren kann (so nach dem Motto einfach nen Searchstring anhängen)
|
||||
* [ ] Methode einbauen, um mit einem gültigen token ein neues gültiges zu kriegen
|
||||
* [ ] Wir brauchen noch ne gute idee, wie man die listen kriegt, auf die man nur so Zugriff hat (ohne namespace)
|
||||
* [ ] Validation der ankommenden structs, am besten mit https://github.com/go-validator/validator
|
||||
* [ ] Rausfinden warum xorm teilweise beim einfügen IDs mit einfügen will -> Das schlägt dann wegen duplicate fehl
|
||||
* [ ] Pagination
|
||||
* Sollte in der Config definierbar sein, wie viel pro Seite angezeigt werden soll, die CRUD-Methoden übergeben dann ein "gibt mir die Seite sowieso" an die CRUDable-Funktionenen, die müssen das dann Auswerten. Geht leider nicht anders, wenn man erst 2342352 Einträge hohlt und die dann nachträglich auf 200 begrenzt ist das ne massive Ressourcenverschwendung.
|
||||
* [ ] Testing mit locust: https://locust.io/
|
||||
|
||||
#### Userstuff
|
||||
|
||||
* [ ] Userstuff aufräumen
|
||||
-> Soweit es geht und Sinnvoll ist auf den neuen Handler umziehen
|
||||
-> Login/Register/Password-reset geht natürlich nicht
|
||||
-> Bleibt noch Profile abrufen und Einstellungen -> Macht also keinen Sinn das auf den neuen Handler umzuziehen
|
||||
* [x] Email-Verifizierung beim Registrieren
|
||||
* [x] Password Reset -> Link via email oder so
|
||||
* [ ] Settings
|
||||
|
||||
### Later/Nice to have
|
||||
|
||||
|
||||
324
Gopkg.lock
generated
324
Gopkg.lock
generated
@@ -1,324 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "UT"
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6cb5bf34f3e3bb61e76d976f5bd29d245b567f208e1fd7cb8104b736a5eb649d"
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a539ee1a749a2b895533f979515ac7e6e0f5b650"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
||||
version = "v1.4.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0594af97b2f4cec6554086eeace6597e20a4b69466eb4ada25adf9f4300dddd2"
|
||||
name = "github.com/garyburd/redigo"
|
||||
packages = [
|
||||
"internal",
|
||||
"redis",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "a69d19351219b6dd56f274f96d85a7014a2ec34e"
|
||||
version = "v1.6.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d2b67246c3fa959edfa1be84b407cf0a0a4cabbace0843814b1729b5af808d9e"
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ee359f95877bdef36cbb602711e49b6f0becfca9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3f854136f2da9cde22fa0dd29e74fb7c920c5d79d060847fe9b96f2d3cfab746"
|
||||
name = "github.com/go-xorm/builder"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c8871c857d2555fbfbd8524f895be5386d3d8836"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ee0b218753df985dd6633b13a894a69f0c1ea385f092f4afa093d407bb165cc5"
|
||||
name = "github.com/go-xorm/core"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f43c33d9a48db006417a7ac4c16b08897e3e1458"
|
||||
version = "v0.5.8"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8e0764e74dd6cf539d3c035772d3fd0009b89fff9889a1131139be6d871c90dd"
|
||||
name = "github.com/go-xorm/xorm"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "29d4a0330a00b9be468b70e3fb0f74109348c358"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d4a957ebe4ccebc299c1f6b6a78a0713af379721980cf14990b8ab8fa93cd92d"
|
||||
name = "github.com/go-xorm/xorm-redis-cache"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "859b313566b2ef090319245fd4fe7692f25dbd79"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
"hcl/ast",
|
||||
"hcl/parser",
|
||||
"hcl/printer",
|
||||
"hcl/scanner",
|
||||
"hcl/strconv",
|
||||
"hcl/token",
|
||||
"json/parser",
|
||||
"json/scanner",
|
||||
"json/token",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8eb1de8112c9924d59bf1d3e5c26f5eaa2bfc2a5fcbb92dc1c2e4546d695f277"
|
||||
name = "github.com/imdario/mergo"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4"
|
||||
version = "v0.3.6"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:82c5850e702ccef3976cdfdf737725013587c64e02e5ecae57e86431163b27aa"
|
||||
name = "github.com/labstack/echo"
|
||||
packages = [
|
||||
".",
|
||||
"middleware",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "1049c9613cd371b7ea8f219404c9a821734781ed"
|
||||
version = "v3.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ef6158d11f87c8036114cf3d2bf5c50edb4aa0b4aab5deef26716a8bb674a9ad"
|
||||
name = "github.com/labstack/gommon"
|
||||
packages = [
|
||||
"bytes",
|
||||
"color",
|
||||
"log",
|
||||
"random",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "57409ada9da0f2afad6664c49502f8c50fbd8476"
|
||||
version = "0.2.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c568d7727aa262c32bdf8a3f7db83614f7af0ed661474b24588de635c20024c7"
|
||||
name = "github.com/magiconair/properties"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c2353362d570a7bfa228149c62842019201cfb71"
|
||||
version = "v1.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a36a1febe1240bb79a208390ad17e1080555b0031a9ed42a41eae173cca3fd74"
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ad5389df28cdac544c99bd7b9161a0b5b6ca9d1b"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a5cdd64afdee435007ee3e9f6ed4684af949d568"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3cafc6a5a1b8269605d9df4c6956d43d8011fc57f266ca6b9d04da6c09dee548"
|
||||
name = "github.com/mattn/go-sqlite3"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "25ecb14adfc7543176f7d85291ec7dba82c6f7e4"
|
||||
version = "v1.9.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:645110e089152bd0f4a011a2648fbb0e4df5977be73ca605781157ac297f50c4"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "fa473d140ef3c6adf42d6b391fe76707f1f243c8"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5b3b29ce0e569f62935d9541dff2e16cc09df981ebde48e82259076a73a3d0c7"
|
||||
name = "github.com/op/go-logging"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "b2cb9fa56473e98db8caba80237377e83fe44db5"
|
||||
version = "v1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e"
|
||||
name = "github.com/pelletier/go-toml"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = "UT"
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd"
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
"mem",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d40851caa0d747393da1ffb28f7f9d8b4eeffebd"
|
||||
version = "v1.1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f"
|
||||
name = "github.com/spf13/cast"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8965335b8c7107321228e3e3702cab9832751bac"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:68ea4e23713989dc20b1bded5d9da2c5f9be14ff9885beef481848edd18c26cb"
|
||||
name = "github.com/spf13/jwalterweatherman"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "4a4406e478ca629068e7768fc33f3f044173c0a6"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:dab83a1bbc7ad3d7a6ba1a1cc1760f25ac38cdf7d96a5cdd55cd915a4f5ceaf9"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9a97c102cda95a86cec2345a6f09f55a939babf5"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6e30a27eac59a148b3f7a32e0ba54706b31dcde5a42f63b22cb47873b62fa343"
|
||||
name = "github.com/spf13/viper"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8fb642006536c8d3760c99d4fa2389f5e2205631"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a7496c8d47f794d15a6e41f8ada426d4a41c038fac9bd75c1abc30faacf469c9"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
pruneopts = "UT"
|
||||
revision = "87b1dfb5b2fa649f52695dd9eae19abe404a4308"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c468422f334a6b46a19448ad59aaffdfc0a36b08fdcc1c749a0b29b6453d7e59"
|
||||
name = "github.com/valyala/bytebufferpool"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:268b8bce0064e8c057d7b913605459f9a26dcab864c0886a56d196540fbf003f"
|
||||
name = "github.com/valyala/fasttemplate"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:456c96b87c9481d98aac8ef9862053a8fc97b981d26f6cd8d63de783dd24acec"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"acme",
|
||||
"acme/autocert",
|
||||
"bcrypt",
|
||||
"blowfish",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "9419663f5a44be8b34ca85f08abc5fe1be11f8a3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f1275b0d1ed7d43eeb7a0464a148258f04e66a4783514d5c790d4e970cc7df6d"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
pruneopts = "UT"
|
||||
revision = "314a259e304ff91bd6985da2a7149bbf91237993"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8029e9743749d4be5bc9f7d42ea1659471767860f0cdc34d37c3111bd308a295"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"internal/gen",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"transform",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:59c645bd5db5ec4a889a1ac62ce9cb3c4483cc24c66907ea75de2516d6fe4716"
|
||||
name = "gopkg.in/testfixtures.v2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "1d98c34adfb14dbedeef37127968233b5d960f02"
|
||||
version = "v2.4.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:73e6fda93622790d2371344759df06ff5ff2fac64a6b6e8832b792e7402956e7"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
|
||||
version = "v2.0.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/dgrijalva/jwt-go",
|
||||
"github.com/go-sql-driver/mysql",
|
||||
"github.com/go-xorm/core",
|
||||
"github.com/go-xorm/xorm",
|
||||
"github.com/go-xorm/xorm-redis-cache",
|
||||
"github.com/imdario/mergo",
|
||||
"github.com/labstack/echo",
|
||||
"github.com/labstack/echo/middleware",
|
||||
"github.com/mattn/go-sqlite3",
|
||||
"github.com/op/go-logging",
|
||||
"github.com/spf13/viper",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"golang.org/x/crypto/bcrypt",
|
||||
"gopkg.in/testfixtures.v2",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
32
Makefile
32
Makefile
@@ -16,7 +16,7 @@ endif
|
||||
GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go")
|
||||
GOFMT ?= gofmt -s
|
||||
|
||||
GOFLAGS := -i -v
|
||||
GOFLAGS := -v -mod=vendor
|
||||
EXTRA_GOFLAGS ?=
|
||||
|
||||
LDFLAGS := -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')" -X "main.Tags=$(TAGS)"
|
||||
@@ -44,12 +44,14 @@ else
|
||||
endif
|
||||
endif
|
||||
|
||||
VERSION := $(shell echo $(VERSION) | sed 's/\//\-/g')
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
go clean -i ./...
|
||||
go clean ./...
|
||||
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA)
|
||||
|
||||
.PHONY: test
|
||||
@@ -58,12 +60,12 @@ test:
|
||||
go tool cover -html=cover.out -o cover.html
|
||||
|
||||
required-gofmt-version:
|
||||
@go version | grep -q '\(1.7\|1.8\|1.9\|1.10\|1.11\)' || { echo "We require go version 1.7, 1.8, 1.9 or 1.10 to format code" >&2 && exit 1; }
|
||||
@go version | grep -q '\(1.7\|1.8\|1.9\|1.10\|1.11\)' || { echo "We require go version 1.7, 1.8, 1.9, 1.10 or 1.11 to format code" >&2 && exit 1; }
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u golang.org/x/lint/golint; \
|
||||
go install $(GOFLAGS) golang.org/x/lint/golint; \
|
||||
fi
|
||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||
|
||||
@@ -101,7 +103,7 @@ release-dirs:
|
||||
.PHONY: release-windows
|
||||
release-windows:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/karalabe/xgo; \
|
||||
go install $(GOFLAGS) github.com/karalabe/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out vikunja-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
@@ -111,7 +113,7 @@ endif
|
||||
.PHONY: release-linux
|
||||
release-linux:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/karalabe/xgo; \
|
||||
go install $(GOFLAGS) github.com/karalabe/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out vikunja-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
@@ -121,7 +123,7 @@ endif
|
||||
.PHONY: release-darwin
|
||||
release-darwin:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/karalabe/xgo; \
|
||||
go install $(GOFLAGS) github.com/karalabe/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out vikunja-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
@@ -133,6 +135,8 @@ release-copy:
|
||||
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
|
||||
mkdir $(DIST)/release/public
|
||||
cp public/ $(DIST)/release/ -R
|
||||
mkdir $(DIST)/release/templates
|
||||
cp templates/ $(DIST)/templates/ -R
|
||||
|
||||
.PHONY: release-check
|
||||
release-check:
|
||||
@@ -141,7 +145,7 @@ release-check:
|
||||
|
||||
.PHONY: release-os-package
|
||||
release-os-package:
|
||||
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp $(DIST)/release/public $(file)-full/ -R; cp LICENSE $(file)-full/; )
|
||||
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp $(DIST)/release/public $(file)-full/ -R; cp $(DIST)/release/templates $(file)-full/ -R; cp LICENSE $(file)-full/; )
|
||||
rm $(DIST)/release/public -rf
|
||||
|
||||
.PHONY: release-zip
|
||||
@@ -151,7 +155,7 @@ release-zip:
|
||||
.PHONY: generate-swagger
|
||||
generate-swagger:
|
||||
@hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/go-swagger/go-swagger/cmd/swagger; \
|
||||
go install $(GOFLAGS) github.com/go-swagger/go-swagger/cmd/swagger; \
|
||||
fi
|
||||
swagger generate spec -o ./public/swagger/swagger.v1.json
|
||||
|
||||
@@ -167,27 +171,27 @@ swagger-check: generate-swagger
|
||||
.PHONY: swagger-validate
|
||||
swagger-validate:
|
||||
@hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/go-swagger/go-swagger/cmd/swagger; \
|
||||
go install $(GOFLAGS) github.com/go-swagger/go-swagger/cmd/swagger; \
|
||||
fi
|
||||
swagger validate ./public/swagger/swagger.v1.json
|
||||
|
||||
.PHONY: misspell-check
|
||||
misspell-check:
|
||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/client9/misspell/cmd/misspell; \
|
||||
go install $(GOFLAGS) github.com/client9/misspell/cmd/misspell; \
|
||||
fi
|
||||
for S in $(GOFILES); do misspell -error $$S || exit 1; done;
|
||||
|
||||
.PHONY: ineffassign-check
|
||||
ineffassign-check:
|
||||
@hash ineffassign > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/gordonklaus/ineffassign; \
|
||||
go install $(GOFLAGS) github.com/gordonklaus/ineffassign; \
|
||||
fi
|
||||
for S in $(GOFILES); do ineffassign $$S || exit 1; done;
|
||||
|
||||
.PHONY: gocyclo-check
|
||||
gocyclo-check:
|
||||
@hash gocyclo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get github.com/fzipp/gocyclo; \
|
||||
go install $(GOFLAGS) github.com/fzipp/gocyclo; \
|
||||
fi
|
||||
for S in $(GOFILES); do gocyclo -over 14 $$S || exit 1; done;
|
||||
for S in $(GOFILES); do gocyclo -over 14 $$S || exit 1; done;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
[](https://drone.kolaente.de/vikunja/api)
|
||||
[](LICENSE)
|
||||
[](https://storage.kolaente.de/minio/vikunja/)
|
||||
[](https://storage.kolaente.de/minio/vikunja/)
|
||||
[](https://hub.docker.com/r/vikunja/api/)
|
||||
[](https://try.vikunja.io/api/v1/swagger)
|
||||
[](https://goreportcard.com/report/git.kolaente.de/vikunja/api
|
||||
@@ -32,6 +32,8 @@ Try it under [try.vikunja.io](https://try.vikunja.io)!
|
||||
|
||||
## Development
|
||||
|
||||
We use go modules to vendor libraries for Vikunja, so you'll need at least go `1.11`.
|
||||
|
||||
To contribute to Vikunja, fork the project and work on the master branch.
|
||||
|
||||
Some internal packages are referenced using their respective package URL. This can become problematic. To “trick” the Go tool into thinking this is a clone from the official repository, download the source code into `$GOPATH/code.vikunja.io/api`. Fork the Vikunja repository, it should then be possible to switch the source directory on the command line.
|
||||
@@ -44,7 +46,7 @@ To be able to create pull requests, the forked repository should be added as a r
|
||||
|
||||
```bash
|
||||
git remote rename origin upstream
|
||||
git remote add origin git@git.kolaente.de:<USERNAME>/vikunja.git
|
||||
git remote add origin git@git.kolaente.de:<USERNAME>/api.git
|
||||
git fetch --all --prune
|
||||
```
|
||||
|
||||
|
||||
@@ -3,23 +3,21 @@ POST http://localhost:8080/api/v1/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"user": "user",
|
||||
"username": "user",
|
||||
"password": "1234"
|
||||
}
|
||||
|
||||
> {% client.global.set("auth_token", response.body.token); %}
|
||||
|
||||
###
|
||||
|
||||
## Register
|
||||
### Register
|
||||
|
||||
POST http://localhost:8080/api/v1/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "user3",
|
||||
"username": "user4",
|
||||
"password": "1234",
|
||||
"email": "3@knt.li"
|
||||
"email": "4@knt.li"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
@@ -21,4 +21,33 @@ Content-Type: application/json
|
||||
"new_password": "1234"
|
||||
}
|
||||
|
||||
###
|
||||
### Request a password to reset a password
|
||||
POST http://localhost:8080/api/v1/user/password/token
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
|
||||
{
|
||||
"user_name": "user"
|
||||
}
|
||||
|
||||
### Request a token to reset a password
|
||||
POST http://localhost:8080/api/v1/user/password/reset
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
|
||||
{
|
||||
"token": "eAsZzakgqARnjzXHqsHqZtSUKuiOhoJjHANhgTxUIDBSalhbtdpAdLeywGXzVDBuRQGNpHdMxoHXhLVSlzpJsFvuoJgMdkhRhkNhaQXfufuZCdtUlerZHSJQLgYMUryHIxIREcmZLtWoZVrYyARkCvkyFhcGtoCwQOEjAOEZMQQuxTVoGYfAqcfNggQnerUcXCiRIgRtkusXSnltomhaeyRwAbrckXFeXxUjslgplSGqSTOqJTYuhrSzAVTwNvuYyvuXLaZoNnJEyeVDWlRydnxfgUQjQZOKwCBRWVQPKpZhlslLUyUAMsRQkHITkruQCjDnOGCCRsSNplbNCEuDmMfpWYHSQAcQIDZtbQWkxzpfmHDMQvvKPPrxEnrTErlvTfKDKICFYPQxXNpNE",
|
||||
"new_password": "1234"
|
||||
}
|
||||
|
||||
### Confirm a users email address
|
||||
|
||||
POST http://localhost:8080/api/v1/user/confirm
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
|
||||
{
|
||||
"token": ""
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
@@ -5,6 +5,8 @@ service:
|
||||
JWTSecret: "cei6gaezoosah2bao3ieZohkae5aicah"
|
||||
# The interface on which to run the webserver
|
||||
interface: ":3456"
|
||||
# The URL of the frontend, used to send password reset emails.
|
||||
frontendurl: ""
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql and sqlite.
|
||||
@@ -33,3 +35,17 @@ cache:
|
||||
redishost: 'localhost:6379'
|
||||
# When using redis, this is the password used to authenicate against the redis server
|
||||
redispassword: ''
|
||||
|
||||
mailer:
|
||||
# SMTP Host
|
||||
host: ''
|
||||
# SMTP Host port
|
||||
port: 587
|
||||
# SMTP username
|
||||
username: 'user'
|
||||
# SMTP password
|
||||
password: ''
|
||||
# Wether to skip verification of the tls certificate on the server
|
||||
skiptlsverify: false
|
||||
# The default from address when sending emails
|
||||
fromemail: 'mail@vikunja'
|
||||
@@ -29,6 +29,10 @@ service:
|
||||
JWTSecret: "cei6gaezoosah2bao3ieZohkae5aicah"
|
||||
# The interface on which to run the webserver
|
||||
interface: ":3456"
|
||||
# The URL of the frontend, used to send password reset emails.
|
||||
frontendurl: ""
|
||||
# The base path on the file system where the binary and assets are
|
||||
rootpath: <the path of the executable>
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql and sqlite.
|
||||
@@ -60,4 +64,22 @@ cache:
|
||||
redishost: 'localhost:6379'
|
||||
# When using redis, this is the password used to authenicate against the redis server
|
||||
redispassword: ''
|
||||
|
||||
mailer:
|
||||
# SMTP Host
|
||||
host: ""
|
||||
# SMTP Host port
|
||||
port: 587
|
||||
# SMTP username
|
||||
username: "user"
|
||||
# SMTP password
|
||||
password: ""
|
||||
# Wether to skip verification of the tls certificate on the server
|
||||
skiptlsverify: false
|
||||
# The default from address when sending emails
|
||||
fromemail: "mail@vikunja"
|
||||
# The length of the mail queue.
|
||||
queuelength: 100
|
||||
# The timeout in seconds after which the current open connection to the mailserver will be closed.
|
||||
queuetimeout: 30
|
||||
```
|
||||
|
||||
@@ -9,7 +9,11 @@ This document describes the different errors Vikunja can return.
|
||||
| 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. |
|
||||
| 1008 | 412 | No password reset token provided. |
|
||||
| 1009 | 412 | Invalid password reset token. |
|
||||
| 1010 | 412 | Invalid email confirm token. |
|
||||
| 1011 | 412 | Wrong username or password. |
|
||||
| 1012 | 412 | Email address of the user not confirmed. |
|
||||
| 2001 | 400 | ID cannot be empty or 0. |
|
||||
| 3001 | 404 | The list does not exist. |
|
||||
| 3004 | 403 | The user needs to have read permissions on that list to perform that action. |
|
||||
|
||||
27
go.mod
27
go.mod
@@ -3,32 +3,53 @@ module code.vikunja.io/api
|
||||
require (
|
||||
cloud.google.com/go v0.30.0 // indirect
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/client9/misspell v0.3.4
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 // indirect
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a
|
||||
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
|
||||
github.com/garyburd/redigo v1.6.0 // indirect
|
||||
github.com/go-openapi/analysis v0.17.2 // indirect
|
||||
github.com/go-openapi/errors v0.17.2 // indirect
|
||||
github.com/go-openapi/inflect v0.17.2 // indirect
|
||||
github.com/go-openapi/loads v0.17.2 // indirect
|
||||
github.com/go-openapi/runtime v0.17.2 // indirect
|
||||
github.com/go-openapi/spec v0.17.2 // indirect
|
||||
github.com/go-openapi/strfmt v0.17.2 // indirect
|
||||
github.com/go-openapi/swag v0.17.2 // indirect
|
||||
github.com/go-openapi/validate v0.17.2 // indirect
|
||||
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b
|
||||
github.com/go-swagger/go-swagger v0.17.2
|
||||
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 // indirect
|
||||
github.com/go-xorm/core v0.5.8
|
||||
github.com/go-xorm/tests v0.5.6 // indirect
|
||||
github.com/go-xorm/xorm v0.0.0-20170930012613-29d4a0330a00
|
||||
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2
|
||||
github.com/google/go-cmp v0.2.0 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc
|
||||
github.com/gorilla/handlers v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.6
|
||||
github.com/jessevdk/go-flags v1.4.0 // indirect
|
||||
github.com/joho/godotenv v1.3.0 // indirect
|
||||
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/labstack/echo v3.1.0+incompatible
|
||||
github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0 // indirect
|
||||
github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0
|
||||
github.com/lib/pq v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd // indirect
|
||||
github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee // indirect
|
||||
github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.9.0
|
||||
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473
|
||||
github.com/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/stretchr/testify v1.2.2
|
||||
github.com/toqueteos/webbrowser v1.1.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 // indirect
|
||||
github.com/ziutek/mymysql v1.5.4 // indirect
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3
|
||||
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/testfixtures.v2 v2.4.5
|
||||
)
|
||||
|
||||
81
go.sum
81
go.sum
@@ -2,18 +2,64 @@ cloud.google.com/go v0.30.0 h1:xKvyLgk56d0nksWq49J0UyGEeUIicTl4+UBiX1NPX9g=
|
||||
cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 h1:BZGp1dbKFjqlGmxEpwkDpCWNxVwEYnUPoncIzLiHlPo=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a h1:nmYyGtn9AO7FCeZ2tHr1ZWjJAHi6SfGB3o80F8o7EbA=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 h1:roDmqJ4Qes7hrDOsWsMCce0vQHz3xiMPjJ9m4c2eeNs=
|
||||
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835/go.mod h1:BjL/N0+C+j9uNX+1xcNuM9vdSIcXCZrQZUYbXOFbgN8=
|
||||
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
|
||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/analysis v0.17.2 h1:eYp14J1o8TTSCzndHBtsNuckikV1PfZOSnx4BcBeu0c=
|
||||
github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.17.2 h1:azEQ8Fnx0jmtFF2fxsnmd6I0x6rsweUF63qqSO1NmKk=
|
||||
github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/inflect v0.17.2 h1:4Zg/XLOwxsyKGFHxCI9e4AkxUFSjLKaoY+jnStSJAfw=
|
||||
github.com/go-openapi/inflect v0.17.2/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA=
|
||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.17.2 h1:tEXYu6Xc0pevpzzQx5ghrMN9F7IVpN/+u4iD3rkYE5o=
|
||||
github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
|
||||
github.com/go-openapi/runtime v0.17.2 h1:/ZK67ikFhQAMFFH/aPu2MaGH7QjP4wHBvHYOVIzDAw0=
|
||||
github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q=
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.17.2 h1:eb2NbuCnoe8cWAxhtK6CfMWUYmiFEZJ9Hx3Z2WRwJ5M=
|
||||
github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.17.2 h1:2KDns36DMHXG9/iYkOjiX+/8fKK9GCU5ELZ+J6qcRVA=
|
||||
github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.17.2 h1:K/ycE/XTUDFltNHSO32cGRUhrVGJD64o8WgAIZNyc3k=
|
||||
github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-openapi/validate v0.17.2 h1:lwFfiS4sv5DvOrsYDsYq4N7UU8ghXiYtPJ+VcQnC3Xg=
|
||||
github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b h1:/CMGgAYard7jx9+bI7tUIqafFDR7Pv2BRu2Tb5dDaqM=
|
||||
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-swagger/go-swagger v0.17.2 h1:eizwRyO8THHMA4kXyM5Z1UTPslZGE8VsfJC0jJqsRI8=
|
||||
github.com/go-swagger/go-swagger v0.17.2/go.mod h1:fOcXeMI1KPNv3uk4u7cR4VSyq0NyrYx4SS1/ajuTWDg=
|
||||
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 h1:jUX9yw6+iKrs/WuysV2M6ap/ObK/07SE/a7I2uxitwM=
|
||||
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25/go.mod h1:M+P3wv0K2C+ynucGDEqJCeOTc+6DcAtiiqU8GrCksXY=
|
||||
github.com/go-xorm/core v0.5.8 h1:vQ0ghlVGnlnFmm4SpHY+xNnPlH810paMcw+Hwz9BCqE=
|
||||
@@ -26,12 +72,26 @@ github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2 h1:57QbyU
|
||||
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2/go.mod h1:xxK9FGkFXrau9/vGdDYSOyQfSgKXBV7iHXpQfNuv6B0=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
|
||||
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970 h1:0+1ZURVRim6FxA/jhWhJklsgoWc69q1sxlIu2Ztnhy0=
|
||||
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970/go.mod h1:iYGcTYIPUvEWhFo6aKUuLchs+AV4ssYdyuBbQJZGcBk=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labstack/echo v3.1.0+incompatible h1:kb0CCZ0boaiGsZOqR9E9+GDpQEoIaKClVqqo0+/hzbM=
|
||||
github.com/labstack/echo v3.1.0+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||
github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0 h1:kcJPx2Ug9owxOsVfuXPCludLaIudyI57YQd6ocyrO4o=
|
||||
@@ -40,6 +100,8 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd h1:eYiiP5pgdf+n78BU5JFWt7yI2bpxW31L/R5Rrk8vLgs=
|
||||
github.com/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee h1:tUyoJR5V1TdXnTh9v8c1YAHvDdut2+zkuyUX3gAY/wI=
|
||||
@@ -50,8 +112,11 @@ github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
|
||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473 h1:J1QZwDXgZ4dJD2s19iqR9+U00OWM2kDzbf1O/fmvCWg=
|
||||
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -66,8 +131,10 @@ 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/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/toqueteos/webbrowser v1.1.0 h1:Prj1okiysRgHPoe3B1bOIVxcv+UuSt525BDQmR5W0x0=
|
||||
github.com/toqueteos/webbrowser v1.1.0/go.mod h1:Hqqqmzj8AHn+VlZyVjaRWY20i25hoOZGAABCcg2el4A=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
|
||||
@@ -76,12 +143,22 @@ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 h1:9lP3x0pW80sDI6t1UMSLA4to18W7R7imwAI/sWS9S8Q=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg=
|
||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 h1:QJP9sxq2/KbTxFnGduVryxJOt6r/UVGyom3tLaqu7tc=
|
||||
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/testfixtures.v2 v2.4.5 h1:mnfYPBNoJnis+4crs6UzC4lv4GjTVoLXE9B/tW802q0=
|
||||
gopkg.in/testfixtures.v2 v2.4.5/go.mod h1:vyAq+MYCgNpR29qitQdLZhdbLFf4mR/2MFJRFoQZZ2M=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
|
||||
22
main.go
22
main.go
@@ -1,8 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/models"
|
||||
"code.vikunja.io/api/routes"
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/mail"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/routes"
|
||||
|
||||
"context"
|
||||
"github.com/spf13/viper"
|
||||
@@ -17,24 +20,27 @@ var Version = "0.1"
|
||||
func main() {
|
||||
|
||||
// Init logging
|
||||
models.InitLogger()
|
||||
log.InitLogger()
|
||||
|
||||
// Init Config
|
||||
err := models.InitConfig()
|
||||
err := config.InitConfig()
|
||||
if err != nil {
|
||||
models.Log.Error(err.Error())
|
||||
log.Log.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Set Engine
|
||||
err = models.SetEngine()
|
||||
if err != nil {
|
||||
models.Log.Error(err.Error())
|
||||
log.Log.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Start the mail daemon
|
||||
mail.StartMailDaemon()
|
||||
|
||||
// Version notification
|
||||
models.Log.Infof("Vikunja version %s", Version)
|
||||
log.Log.Infof("Vikunja version %s", Version)
|
||||
|
||||
// Start the webserver
|
||||
e := routes.NewEcho()
|
||||
@@ -53,7 +59,7 @@ func main() {
|
||||
<-quit
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
models.Log.Infof("Sutting down...")
|
||||
log.Log.Infof("Shutting down...")
|
||||
if err := e.Shutdown(ctx); err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package models
|
||||
|
||||
// TeamRight defines the rights teams can have for lists/namespaces
|
||||
type TeamRight int
|
||||
|
||||
// define unknown team right
|
||||
const (
|
||||
TeamRightUnknown = -1
|
||||
)
|
||||
|
||||
// Enumerate all the team rights
|
||||
const (
|
||||
// Can read lists in a Team
|
||||
TeamRightRead TeamRight = iota
|
||||
// Can write tasks in a Team like lists and todo tasks. Cannot create new lists.
|
||||
TeamRightWrite
|
||||
// Can manage a list/namespace, can do everything
|
||||
TeamRightAdmin
|
||||
)
|
||||
|
||||
func (r TeamRight) isValid() error {
|
||||
if r != TeamRightAdmin && r != TeamRightRead && r != TeamRightWrite {
|
||||
return ErrInvalidTeamRight{r}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanCreate checks if the user can create a new team
|
||||
func (t *Team) CanCreate(user *User) bool {
|
||||
// This is currently a dummy function, later on we could imagine global limits etc.
|
||||
return true
|
||||
}
|
||||
|
||||
// CanUpdate checks if the user can update a team
|
||||
func (t *Team) CanUpdate(user *User) bool {
|
||||
|
||||
// Check if the current user is in the team and has admin rights in it
|
||||
exists, 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 {
|
||||
return t.IsAdmin(user)
|
||||
}
|
||||
|
||||
// IsAdmin returns true when the user is admin of a team
|
||||
func (t *Team) IsAdmin(user *User) bool {
|
||||
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, 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
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package models
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -17,8 +19,16 @@ func InitConfig() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Service
|
||||
viper.SetDefault("service.JWTSecret", random)
|
||||
viper.SetDefault("service.interface", ":3456")
|
||||
viper.SetDefault("service.frontendurl", "")
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
exPath := filepath.Dir(ex)
|
||||
viper.SetDefault("service.rootpath", exPath)
|
||||
// Database
|
||||
viper.SetDefault("database.type", "sqlite")
|
||||
viper.SetDefault("database.host", "localhost")
|
||||
@@ -34,6 +44,15 @@ func InitConfig() (err error) {
|
||||
viper.SetDefault("cache.maxelementsize", 1000)
|
||||
viper.SetDefault("cache.redishost", "localhost:6379")
|
||||
viper.SetDefault("cache.redispassword", "")
|
||||
// Mailer
|
||||
viper.SetDefault("mailer.host", "")
|
||||
viper.SetDefault("mailer.port", "587")
|
||||
viper.SetDefault("mailer.user", "user")
|
||||
viper.SetDefault("mailer.password", "")
|
||||
viper.SetDefault("mailer.skiptlsverify", false)
|
||||
viper.SetDefault("mailer.fromemail", "mail@vikunja")
|
||||
viper.SetDefault("mailer.queuelength", 100)
|
||||
viper.SetDefault("mailer.queuetimeout", 30)
|
||||
|
||||
// Init checking for environment variables
|
||||
viper.SetEnvPrefix("vikunja")
|
||||
@@ -1,4 +1,4 @@
|
||||
package models
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/op/go-logging"
|
||||
63
pkg/mail/mail.go
Normal file
63
pkg/mail/mail.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"crypto/tls"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/gomail.v2"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Queue is the mail queue
|
||||
var Queue chan *gomail.Message
|
||||
|
||||
// StartMailDaemon starts the mail daemon
|
||||
func StartMailDaemon() {
|
||||
Queue = make(chan *gomail.Message, viper.GetInt("mailer.queuelength"))
|
||||
|
||||
if viper.GetString("mailer.host") == "" {
|
||||
log.Log.Warning("Mailer seems to be not configured! Please see the config docs for more details.")
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
d := gomail.NewDialer(viper.GetString("mailer.host"), viper.GetInt("mailer.port"), viper.GetString("mailer.username"), viper.GetString("mailer.password"))
|
||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: viper.GetBool("mailer.skiptlsverify")}
|
||||
|
||||
var s gomail.SendCloser
|
||||
var err error
|
||||
open := false
|
||||
for {
|
||||
select {
|
||||
case m, ok := <-Queue:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !open {
|
||||
if s, err = d.Dial(); err != nil {
|
||||
log.Log.Error("Error during connect to smtp server: %s", err)
|
||||
}
|
||||
open = true
|
||||
}
|
||||
if err := gomail.Send(s, m); err != nil {
|
||||
log.Log.Error("Error when sending mail: %s", err)
|
||||
}
|
||||
// Close the connection to the SMTP server if no email was sent in
|
||||
// the last 30 seconds.
|
||||
case <-time.After(viper.GetDuration("mailer.queuetimeout") * time.Second):
|
||||
if open {
|
||||
if err := s.Close(); err != nil {
|
||||
log.Log.Error("Error closing the mail server connection: %s\n", err)
|
||||
}
|
||||
log.Log.Infof("Closed connection to mailserver")
|
||||
open = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// StopMailDaemon closes the mail queue channel, aka stops the daemon
|
||||
func StopMailDaemon() {
|
||||
close(Queue)
|
||||
}
|
||||
100
pkg/mail/send_mail.go
Normal file
100
pkg/mail/send_mail.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"github.com/labstack/gommon/log"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/gomail.v2"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Opts holds infos for a mail
|
||||
type Opts struct {
|
||||
To string
|
||||
Subject string
|
||||
Message string
|
||||
HTMLMessage string
|
||||
ContentType ContentType
|
||||
Boundary string
|
||||
Headers []*header
|
||||
}
|
||||
|
||||
// ContentType represents mail content types
|
||||
type ContentType int
|
||||
|
||||
// Enumerate all the team rights
|
||||
const (
|
||||
ContentTypePlain ContentType = iota
|
||||
ContentTypeHTML
|
||||
ContentTypeMultipart
|
||||
)
|
||||
|
||||
type header struct {
|
||||
Field string
|
||||
Content string
|
||||
}
|
||||
|
||||
// SendMail puts a mail in the queue
|
||||
func SendMail(opts *Opts) {
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", viper.GetString("mailer.fromemail"))
|
||||
m.SetHeader("To", opts.To)
|
||||
m.SetHeader("Subject", opts.Subject)
|
||||
for _, h := range opts.Headers {
|
||||
m.SetHeader(h.Field, h.Content)
|
||||
}
|
||||
|
||||
switch opts.ContentType {
|
||||
case ContentTypePlain:
|
||||
m.SetBody("text/plain", opts.Message)
|
||||
case ContentTypeHTML:
|
||||
m.SetBody("text/html", opts.Message)
|
||||
case ContentTypeMultipart:
|
||||
m.SetBody("text/plain", opts.Message)
|
||||
m.AddAlternative("text/html", opts.HTMLMessage)
|
||||
}
|
||||
|
||||
Queue <- m
|
||||
}
|
||||
|
||||
// Template holds a pointer about a template
|
||||
type Template struct {
|
||||
Templates *template.Template
|
||||
}
|
||||
|
||||
// SendMailWithTemplate parses a template and sends it via mail
|
||||
func SendMailWithTemplate(to, subject, tpl string, data map[string]interface{}) {
|
||||
var htmlContent bytes.Buffer
|
||||
var plainContent bytes.Buffer
|
||||
|
||||
t := &Template{
|
||||
Templates: template.Must(template.ParseGlob(viper.GetString("service.rootpath") + "/templates/mail/*.tmpl")),
|
||||
}
|
||||
|
||||
boundary := "np" + utils.MakeRandomString(13)
|
||||
|
||||
data["Boundary"] = boundary
|
||||
data["FrontendURL"] = viper.GetString("service.frontendurl")
|
||||
|
||||
if err := t.Templates.ExecuteTemplate(&htmlContent, tpl+".html.tmpl", data); err != nil {
|
||||
log.Error(3, "Template: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := t.Templates.ExecuteTemplate(&plainContent, tpl+".plain.tmpl", data); err != nil {
|
||||
log.Error(3, "Template: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := &Opts{
|
||||
To: to,
|
||||
Subject: subject,
|
||||
Message: plainContent.String(),
|
||||
HTMLMessage: htmlContent.String(),
|
||||
ContentType: ContentTypeMultipart,
|
||||
Boundary: boundary,
|
||||
}
|
||||
|
||||
SendMail(opts)
|
||||
}
|
||||
@@ -134,25 +134,112 @@ 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.
|
||||
type ErrCannotDeleteLastUser struct{}
|
||||
// ErrNoPasswordResetToken represents an error where no password reset token exists for that user
|
||||
type ErrNoPasswordResetToken struct {
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrCannotDeleteLastUser checks if an error is a ErrCannotDeleteLastUser.
|
||||
func IsErrCannotDeleteLastUser(err error) bool {
|
||||
_, ok := err.(ErrCannotDeleteLastUser)
|
||||
func (err ErrNoPasswordResetToken) Error() string {
|
||||
return fmt.Sprintf("No token to reset a password [UserID: %d]", err.UserID)
|
||||
}
|
||||
|
||||
// ErrCodeNoPasswordResetToken holds the unique world-error code of this error
|
||||
const ErrCodeNoPasswordResetToken = 1008
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNoPasswordResetToken) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeNoPasswordResetToken, Message: "No token to reset a user's password provided."}
|
||||
}
|
||||
|
||||
// ErrInvalidPasswordResetToken is an error where the password reset token is invalid
|
||||
type ErrInvalidPasswordResetToken struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
func (err ErrInvalidPasswordResetToken) Error() string {
|
||||
return fmt.Sprintf("Invalid token to reset a password [Token: %s]", err.Token)
|
||||
}
|
||||
|
||||
// ErrCodeInvalidPasswordResetToken holds the unique world-error code of this error
|
||||
const ErrCodeInvalidPasswordResetToken = 1009
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidPasswordResetToken) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidPasswordResetToken, Message: "Invalid token to reset a user's password."}
|
||||
}
|
||||
|
||||
// IsErrInvalidPasswordResetToken checks if an error is a ErrInvalidPasswordResetToken.
|
||||
func IsErrInvalidPasswordResetToken(err error) bool {
|
||||
_, ok := err.(ErrInvalidPasswordResetToken)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrCannotDeleteLastUser) Error() string {
|
||||
return fmt.Sprintf("Cannot delete last user")
|
||||
// ErrInvalidEmailConfirmToken is an error where the email confirm token is invalid
|
||||
type ErrInvalidEmailConfirmToken struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// ErrCodeCannotDeleteLastUser holds the unique world-error code of this error
|
||||
const ErrCodeCannotDeleteLastUser = 1007
|
||||
func (err ErrInvalidEmailConfirmToken) Error() string {
|
||||
return fmt.Sprintf("Invalid email confirm token [Token: %s]", err.Token)
|
||||
}
|
||||
|
||||
// ErrCodeInvalidEmailConfirmToken holds the unique world-error code of this error
|
||||
const ErrCodeInvalidEmailConfirmToken = 1010
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrCannotDeleteLastUser) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeCannotDeleteLastUser, Message: "Cannot delete the last user on the server."}
|
||||
func (err ErrInvalidEmailConfirmToken) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidEmailConfirmToken, Message: "Invalid email confirm token."}
|
||||
}
|
||||
|
||||
// IsErrInvalidEmailConfirmToken checks if an error is a ErrInvalidEmailConfirmToken.
|
||||
func IsErrInvalidEmailConfirmToken(err error) bool {
|
||||
_, ok := err.(ErrInvalidEmailConfirmToken)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ErrWrongUsernameOrPassword is an error where the email was not confirmed
|
||||
type ErrWrongUsernameOrPassword struct {
|
||||
}
|
||||
|
||||
func (err ErrWrongUsernameOrPassword) Error() string {
|
||||
return fmt.Sprintf("Wrong username or password")
|
||||
}
|
||||
|
||||
// ErrCodeWrongUsernameOrPassword holds the unique world-error code of this error
|
||||
const ErrCodeWrongUsernameOrPassword = 1011
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrWrongUsernameOrPassword) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeWrongUsernameOrPassword, Message: "Wrong username or password."}
|
||||
}
|
||||
|
||||
// IsErrWrongUsernameOrPassword checks if an error is a IsErrEmailNotConfirmed.
|
||||
func IsErrWrongUsernameOrPassword(err error) bool {
|
||||
_, ok := err.(ErrWrongUsernameOrPassword)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ErrEmailNotConfirmed is an error where the email was not confirmed
|
||||
type ErrEmailNotConfirmed struct {
|
||||
UserID int64
|
||||
}
|
||||
|
||||
func (err ErrEmailNotConfirmed) Error() string {
|
||||
return fmt.Sprintf("Email is not confirmed [UserID: %d]", err.UserID)
|
||||
}
|
||||
|
||||
// ErrCodeEmailNotConfirmed holds the unique world-error code of this error
|
||||
const ErrCodeEmailNotConfirmed = 1012
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrEmailNotConfirmed) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeEmailNotConfirmed, Message: "Please confirm your email address."}
|
||||
}
|
||||
|
||||
// IsErrEmailNotConfirmed checks if an error is a IsErrEmailNotConfirmed.
|
||||
func IsErrEmailNotConfirmed(err error) bool {
|
||||
_, ok := err.(ErrEmailNotConfirmed)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ===================
|
||||
@@ -25,9 +25,9 @@ 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) {
|
||||
func (l *List) ReadAll(u *User) (interface{}, error) {
|
||||
lists := []*List{}
|
||||
fullUser, err := GetUserByID(user.ID)
|
||||
fullUser, err := GetUserByID(u.ID)
|
||||
if err != nil {
|
||||
return lists, err
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func (l *List) ReadAll(user *User) (interface{}, error) {
|
||||
Find(&lists)
|
||||
|
||||
// Add more list details
|
||||
addListDetails(lists)
|
||||
AddListDetails(lists)
|
||||
|
||||
return lists, err
|
||||
}
|
||||
@@ -98,8 +98,8 @@ func (l *List) GetSimpleByID() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Adds owner user objects and list tasks to all lists in the slice
|
||||
func addListDetails(lists []*List) (err error) {
|
||||
// AddListDetails adds owner user objects and list tasks to all lists in the slice
|
||||
func AddListDetails(lists []*List) (err error) {
|
||||
var listIDs []int64
|
||||
var ownerIDs []int64
|
||||
for _, l := range lists {
|
||||
@@ -108,8 +108,8 @@ func addListDetails(lists []*List) (err error) {
|
||||
}
|
||||
|
||||
// Get all tasks
|
||||
tasks := []*ListTask{}
|
||||
err = x.In("list_id", listIDs).Find(&tasks)
|
||||
ts := []*ListTask{}
|
||||
err = x.In("list_id", listIDs).Find(&ts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func addListDetails(lists []*List) (err error) {
|
||||
}
|
||||
|
||||
// Tasks
|
||||
for _, task := range tasks {
|
||||
for _, task := range ts {
|
||||
if task.ListID == list.ID {
|
||||
lists[in].Tasks = append(lists[in].Tasks, task)
|
||||
}
|
||||
@@ -44,13 +44,13 @@ func (l *List) Update() (err error) {
|
||||
// Create implements the create method of CRUDable
|
||||
func (l *List) Create(doer *User) (err error) {
|
||||
// Check rights
|
||||
user, err := GetUserByID(doer.ID)
|
||||
u, err := GetUserByID(doer.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
l.OwnerID = user.ID
|
||||
l.Owner.ID = user.ID
|
||||
l.OwnerID = u.ID
|
||||
l.Owner.ID = u.ID
|
||||
l.ID = 0 // Otherwise only the first time a new list would be created
|
||||
|
||||
return CreateOrUpdateList(l)
|
||||
@@ -16,11 +16,11 @@ func TestList_ReadAll(t *testing.T) {
|
||||
assert.Equal(t, len(lists), 2)
|
||||
|
||||
// Get all lists our user has access to
|
||||
user, err := GetUserByID(1)
|
||||
u, err := GetUserByID(1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
lists2 := List{}
|
||||
lists3, err := lists2.ReadAll(&user)
|
||||
lists3, err := lists2.ReadAll(&u)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice)
|
||||
s := reflect.ValueOf(lists3)
|
||||
@@ -1,18 +1,22 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
)
|
||||
|
||||
// IsAdmin returns whether the user has admin rights on the list or not
|
||||
func (l *List) IsAdmin(user *User) bool {
|
||||
func (l *List) IsAdmin(u *User) bool {
|
||||
// Owners are always admins
|
||||
if l.Owner.ID == user.ID {
|
||||
if l.Owner.ID == u.ID {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check individual rights
|
||||
if l.checkListUserRight(user, UserRightAdmin) {
|
||||
if l.checkListUserRight(u, UserRightAdmin) {
|
||||
return true
|
||||
}
|
||||
|
||||
return l.checkListTeamRight(user, TeamRightAdmin)
|
||||
return l.checkListTeamRight(u, TeamRightAdmin)
|
||||
}
|
||||
|
||||
// CanWrite return whether the user can write on that list or not
|
||||
@@ -48,7 +52,7 @@ func (l *List) CanRead(user *User) bool {
|
||||
// CanDelete checks if the user can delete a list
|
||||
func (l *List) CanDelete(doer *User) bool {
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanDelete for List: %s", err)
|
||||
log.Log.Error("Error occurred during CanDelete for List: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.IsAdmin(doer)
|
||||
@@ -57,7 +61,7 @@ func (l *List) CanDelete(doer *User) bool {
|
||||
// CanUpdate checks if the user can update a list
|
||||
func (l *List) CanUpdate(doer *User) bool {
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanUpdate for List: %s", err)
|
||||
log.Log.Error("Error occurred during CanUpdate for List: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.CanWrite(doer)
|
||||
@@ -82,7 +86,7 @@ func (l *List) checkListTeamRight(user *User, r TeamRight) bool {
|
||||
user.ID, r, user.ID, r, l.ID).
|
||||
Exist(&List{})
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during checkListTeamRight for List: %s", err)
|
||||
log.Log.Error("Error occurred during checkListTeamRight for List: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -103,7 +107,7 @@ func (l *List) checkListUserRight(user *User, r UserRight) bool {
|
||||
user.ID, r, user.ID, r, user.ID, l.ID).
|
||||
Exist(&List{})
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during checkListUserRight for List: %s", err)
|
||||
log.Log.Error("Error occurred during checkListUserRight for List: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -59,9 +59,9 @@ func GetTasksByListID(listID int64) (tasks []*ListTask, err error) {
|
||||
}
|
||||
|
||||
for in, task := range tasks {
|
||||
for _, user := range users {
|
||||
if task.CreatedByID == user.ID {
|
||||
tasks[in].CreatedBy = user
|
||||
for _, u := range users {
|
||||
if task.CreatedByID == u.ID {
|
||||
tasks[in].CreatedBy = u
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -88,11 +88,11 @@ func GetListTaskByID(listTaskID int64) (listTask ListTask, err error) {
|
||||
return ListTask{}, ErrListTaskDoesNotExist{listTaskID}
|
||||
}
|
||||
|
||||
user, err := GetUserByID(listTask.CreatedByID)
|
||||
u, err := GetUserByID(listTask.CreatedByID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
listTask.CreatedBy = user
|
||||
listTask.CreatedBy = u
|
||||
|
||||
return
|
||||
}
|
||||
@@ -19,13 +19,13 @@ func (i *ListTask) Create(doer *User) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := GetUserByID(doer.ID)
|
||||
u, err := GetUserByID(doer.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.CreatedByID = user.ID
|
||||
i.CreatedBy = user
|
||||
i.CreatedByID = u.ID
|
||||
i.CreatedBy = u
|
||||
_, err = x.Cols("text", "description", "done", "due_date_unix", "reminder_unix", "created_by_id", "list_id", "created", "updated").Insert(i)
|
||||
return err
|
||||
}
|
||||
@@ -39,8 +39,8 @@ func (i *ListTask) Update() (err error) {
|
||||
}
|
||||
|
||||
// For whatever reason, xorm dont detect if done is updated, so we need to update this every time by hand
|
||||
// Which is why we merge the actual task struct with the one we got from the user.
|
||||
// The user struct ovverrides values in the actual one.
|
||||
// Which is why we merge the actual task struct with the one we got from the
|
||||
// The user struct overrides values in the actual one.
|
||||
if err := mergo.Merge(&ot, i, mergo.WithOverride); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,18 +1,22 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
)
|
||||
|
||||
// CanDelete checks if the user can delete an task
|
||||
func (i *ListTask) CanDelete(doer *User) bool {
|
||||
// Get the task
|
||||
lI, err := GetListTaskByID(i.ID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanDelete for ListTask: %s", err)
|
||||
log.Log.Error("Error occurred during CanDelete for ListTask: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// A user can delete an task if he has write acces to its list
|
||||
list := &List{ID: lI.ListID}
|
||||
list.ReadOne()
|
||||
return list.CanWrite(doer)
|
||||
l := &List{ID: lI.ListID}
|
||||
l.ReadOne()
|
||||
return l.CanWrite(doer)
|
||||
}
|
||||
|
||||
// CanUpdate determines if a user has the right to update a list task
|
||||
@@ -20,20 +24,20 @@ func (i *ListTask) CanUpdate(doer *User) bool {
|
||||
// Get the task
|
||||
lI, err := GetListTaskByID(i.ID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanDelete for ListTask: %s", err)
|
||||
log.Log.Error("Error occurred during CanDelete for ListTask: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// A user can update an task if he has write acces to its list
|
||||
list := &List{ID: lI.ListID}
|
||||
list.ReadOne()
|
||||
return list.CanWrite(doer)
|
||||
l := &List{ID: lI.ListID}
|
||||
l.ReadOne()
|
||||
return l.CanWrite(doer)
|
||||
}
|
||||
|
||||
// CanCreate determines if a user has the right to create a list task
|
||||
func (i *ListTask) CanCreate(doer *User) bool {
|
||||
// A user can create an task if he has write acces to its list
|
||||
list := &List{ID: i.ListID}
|
||||
list.ReadOne()
|
||||
return list.CanWrite(doer)
|
||||
l := &List{ID: i.ListID}
|
||||
l.ReadOne()
|
||||
return l.CanWrite(doer)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package models
|
||||
|
||||
// Create creates a new list <-> user relation
|
||||
func (ul *ListUser) Create(user *User) (err error) {
|
||||
func (ul *ListUser) Create(u *User) (err error) {
|
||||
|
||||
// Check if the right is valid
|
||||
if err := ul.Right.isValid(); err != nil {
|
||||
@@ -1,13 +1,13 @@
|
||||
package models
|
||||
|
||||
// ReadAll gets all users who have access to a list
|
||||
func (ul *ListUser) ReadAll(user *User) (interface{}, error) {
|
||||
func (ul *ListUser) ReadAll(u *User) (interface{}, error) {
|
||||
// Check if the user has access to the list
|
||||
l := &List{ID: ul.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !l.CanRead(user) {
|
||||
if !l.CanRead(u) {
|
||||
return nil, ErrNeedToHaveListReadAccess{}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,15 @@
|
||||
package models
|
||||
|
||||
// UserRight defines the rights users can have for lists/namespaces
|
||||
type UserRight int
|
||||
|
||||
// define unknown user right
|
||||
const (
|
||||
UserRightUnknown = -1
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
)
|
||||
|
||||
// Enumerate all the user rights
|
||||
const (
|
||||
// Can read lists in a User
|
||||
UserRightRead UserRight = iota
|
||||
// Can write tasks in a User like lists and todo tasks. Cannot create new lists.
|
||||
UserRightWrite
|
||||
// Can manage a list/namespace, can do everything
|
||||
UserRightAdmin
|
||||
)
|
||||
|
||||
func (r UserRight) isValid() error {
|
||||
if r != UserRightAdmin && r != UserRightRead && r != UserRightWrite {
|
||||
return ErrInvalidUserRight{r}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanCreate checks if the user can create a new user <-> list relation
|
||||
func (lu *ListUser) CanCreate(doer *User) bool {
|
||||
// Get the list and check if the user has write access on it
|
||||
l := List{ID: lu.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanCreate for ListUser: %s", err)
|
||||
log.Log.Error("Error occurred during CanCreate for ListUser: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.CanWrite(doer)
|
||||
@@ -42,7 +20,7 @@ func (lu *ListUser) CanDelete(doer *User) bool {
|
||||
// Get the list and check if the user has write access on it
|
||||
l := List{ID: lu.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanDelete for ListUser: %s", err)
|
||||
log.Log.Error("Error occurred during CanDelete for ListUser: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.CanWrite(doer)
|
||||
@@ -53,7 +31,7 @@ func (lu *ListUser) CanUpdate(doer *User) bool {
|
||||
// Get the list and check if the user has write access on it
|
||||
l := List{ID: lu.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanUpdate for ListUser: %s", err)
|
||||
log.Log.Error("Error occurred during CanUpdate for ListUser: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.CanWrite(doer)
|
||||
@@ -109,7 +109,7 @@ func (n *Namespace) ReadAll(doer *User) (interface{}, error) {
|
||||
}
|
||||
|
||||
// More details for the lists
|
||||
addListDetails(lists)
|
||||
AddListDetails(lists)
|
||||
|
||||
// Put objects in our namespace list
|
||||
for i, n := range all {
|
||||
@@ -21,8 +21,8 @@ func (n *Namespace) Delete() (err error) {
|
||||
// We need to do that for here because we need the list ids to delete two times:
|
||||
// 1) to delete the lists itself
|
||||
// 2) to delete the list tasks
|
||||
for _, list := range lists {
|
||||
listIDs = append(listIDs, list.ID)
|
||||
for _, l := range lists {
|
||||
listIDs = append(listIDs, l.ID)
|
||||
}
|
||||
|
||||
// Delete tasks
|
||||
@@ -1,106 +1,110 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
)
|
||||
|
||||
// IsAdmin returns true or false if the user is admin on that namespace or not
|
||||
func (n *Namespace) IsAdmin(user *User) bool {
|
||||
func (n *Namespace) IsAdmin(u *User) bool {
|
||||
// Owners always have admin rights
|
||||
if user.ID == n.Owner.ID {
|
||||
if u.ID == n.Owner.ID {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check user rights
|
||||
if n.checkUserRights(user, UserRightAdmin) {
|
||||
if n.checkUserRights(u, UserRightAdmin) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if that user is in a team which has admin rights to that namespace
|
||||
return n.checkTeamRights(user, TeamRightAdmin)
|
||||
return n.checkTeamRights(u, TeamRightAdmin)
|
||||
}
|
||||
|
||||
// CanWrite checks if a user has write access to a namespace
|
||||
func (n *Namespace) CanWrite(user *User) bool {
|
||||
func (n *Namespace) CanWrite(u *User) bool {
|
||||
// Admins always have write access
|
||||
if n.IsAdmin(user) {
|
||||
if n.IsAdmin(u) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check user rights
|
||||
if n.checkUserRights(user, UserRightWrite) {
|
||||
if n.checkUserRights(u, UserRightWrite) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if that user is in a team which has write rights to that namespace
|
||||
return n.checkTeamRights(user, TeamRightWrite)
|
||||
return n.checkTeamRights(u, TeamRightWrite)
|
||||
}
|
||||
|
||||
// CanRead checks if a user has read access to that namespace
|
||||
func (n *Namespace) CanRead(user *User) bool {
|
||||
func (n *Namespace) CanRead(u *User) bool {
|
||||
// Admins always have read access
|
||||
if n.IsAdmin(user) {
|
||||
if n.IsAdmin(u) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check user rights
|
||||
if n.checkUserRights(user, UserRightRead) {
|
||||
if n.checkUserRights(u, UserRightRead) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if the user is in a team which has access to the namespace
|
||||
return n.checkTeamRights(user, TeamRightRead)
|
||||
return n.checkTeamRights(u, TeamRightRead)
|
||||
}
|
||||
|
||||
// CanUpdate checks if the user can update the namespace
|
||||
func (n *Namespace) CanUpdate(user *User) bool {
|
||||
func (n *Namespace) CanUpdate(u *User) bool {
|
||||
nn, err := GetNamespaceByID(n.ID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanUpdate for Namespace: %s", err)
|
||||
log.Log.Error("Error occurred during CanUpdate for Namespace: %s", err)
|
||||
return false
|
||||
}
|
||||
return nn.IsAdmin(user)
|
||||
return nn.IsAdmin(u)
|
||||
}
|
||||
|
||||
// CanDelete checks if the user can delete a namespace
|
||||
func (n *Namespace) CanDelete(user *User) bool {
|
||||
func (n *Namespace) CanDelete(u *User) bool {
|
||||
nn, err := GetNamespaceByID(n.ID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanDelete for Namespace: %s", err)
|
||||
log.Log.Error("Error occurred during CanDelete for Namespace: %s", err)
|
||||
return false
|
||||
}
|
||||
return nn.IsAdmin(user)
|
||||
return nn.IsAdmin(u)
|
||||
}
|
||||
|
||||
// CanCreate checks if the user can create a new namespace
|
||||
func (n *Namespace) CanCreate(user *User) bool {
|
||||
func (n *Namespace) CanCreate(u *User) bool {
|
||||
// This is currently a dummy function, later on we could imagine global limits etc.
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *Namespace) checkTeamRights(user *User, r TeamRight) bool {
|
||||
func (n *Namespace) checkTeamRights(u *User, r TeamRight) bool {
|
||||
exists, err := x.Select("namespaces.*").
|
||||
Table("namespaces").
|
||||
Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id").
|
||||
Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id").
|
||||
Where("namespaces.id = ? "+
|
||||
"AND (team_members.user_id = ? AND team_namespaces.right = ?) "+
|
||||
"OR namespaces.owner_id = ? ", n.ID, user.ID, r, user.ID).
|
||||
"OR namespaces.owner_id = ? ", n.ID, u.ID, r, u.ID).
|
||||
Get(&Namespace{})
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during checkTeamRights for Namespace: %s, TeamRight: %d", err, r)
|
||||
log.Log.Error("Error occurred during checkTeamRights for Namespace: %s, TeamRight: %d", err, r)
|
||||
return false
|
||||
}
|
||||
|
||||
return exists
|
||||
}
|
||||
|
||||
func (n *Namespace) checkUserRights(user *User, r UserRight) bool {
|
||||
func (n *Namespace) checkUserRights(u *User, r UserRight) bool {
|
||||
exists, err := x.Select("namespaces.*").
|
||||
Table("namespaces").
|
||||
Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id").
|
||||
Where("namespaces.id = ? AND ("+
|
||||
"namespaces.owner_id = ? "+
|
||||
"OR (users_namespace.user_id = ? AND users_namespace.right = ?))", n.ID, user.ID, user.ID, r).
|
||||
"OR (users_namespace.user_id = ? AND users_namespace.right = ?))", n.ID, u.ID, u.ID, r).
|
||||
Get(&Namespace{})
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during checkUserRights for Namespace: %s, UserRight: %d", err, r)
|
||||
log.Log.Error("Error occurred during checkUserRights for Namespace: %s, UserRight: %d", err, r)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package models
|
||||
|
||||
// Create creates a new namespace <-> user relation
|
||||
func (un *NamespaceUser) Create(user *User) (err error) {
|
||||
func (un *NamespaceUser) Create(u *User) (err error) {
|
||||
|
||||
// Reset the id
|
||||
un.ID = 0
|
||||
@@ -1,13 +1,13 @@
|
||||
package models
|
||||
|
||||
// ReadAll gets all users who have access to a namespace
|
||||
func (un *NamespaceUser) ReadAll(user *User) (interface{}, error) {
|
||||
func (un *NamespaceUser) ReadAll(u *User) (interface{}, error) {
|
||||
// Check if the user has access to the namespace
|
||||
l, err := GetNamespaceByID(un.NamespaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !l.CanRead(user) {
|
||||
if !l.CanRead(u) {
|
||||
return nil, ErrNeedToHaveNamespaceReadAccess{}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
)
|
||||
|
||||
// CanCreate checks if the user can create a new user <-> namespace relation
|
||||
func (nu *NamespaceUser) CanCreate(doer *User) bool {
|
||||
// Get the namespace and check if the user has write access on it
|
||||
n, err := GetNamespaceByID(nu.NamespaceID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanCreate for NamespaceUser: %s", err)
|
||||
log.Log.Error("Error occurred during CanCreate for NamespaceUser: %s", err)
|
||||
return false
|
||||
}
|
||||
return n.CanWrite(doer)
|
||||
@@ -16,7 +20,7 @@ func (nu *NamespaceUser) CanDelete(doer *User) bool {
|
||||
// Get the namespace and check if the user has write access on it
|
||||
n, err := GetNamespaceByID(nu.NamespaceID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanCreate for NamespaceUser: %s", err)
|
||||
log.Log.Error("Error occurred during CanDelete for NamespaceUser: %s", err)
|
||||
return false
|
||||
}
|
||||
return n.CanWrite(doer)
|
||||
@@ -27,7 +31,7 @@ func (nu *NamespaceUser) CanUpdate(doer *User) bool {
|
||||
// Get the namespace and check if the user has write access on it
|
||||
n, err := GetNamespaceByID(nu.NamespaceID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanCreate for NamespaceUser: %s", err)
|
||||
log.Log.Error("Error occurred during CanUpdate for NamespaceUser: %s", err)
|
||||
return false
|
||||
}
|
||||
return n.CanWrite(doer)
|
||||
@@ -1,14 +1,14 @@
|
||||
package models
|
||||
|
||||
// ReadAll implements the method to read all teams of a list
|
||||
func (tl *TeamList) ReadAll(user *User) (interface{}, error) {
|
||||
func (tl *TeamList) ReadAll(u *User) (interface{}, error) {
|
||||
// Check if the user can read the namespace
|
||||
l := &List{ID: tl.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !l.CanRead(user) {
|
||||
return nil, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: user.ID}
|
||||
if !l.CanRead(u) {
|
||||
return nil, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: u.ID}
|
||||
}
|
||||
|
||||
// Get the teams
|
||||
@@ -1,20 +1,24 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
)
|
||||
|
||||
// CanCreate checks if the user can create a team <-> list relation
|
||||
func (tl *TeamList) CanCreate(user *User) bool {
|
||||
func (tl *TeamList) CanCreate(u *User) bool {
|
||||
l := List{ID: tl.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanCreate for TeamList: %s", err)
|
||||
log.Log.Error("Error occurred during CanCreate for TeamList: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.IsAdmin(user)
|
||||
return l.IsAdmin(u)
|
||||
}
|
||||
|
||||
// CanDelete checks if the user can delete a team <-> list relation
|
||||
func (tl *TeamList) CanDelete(user *User) bool {
|
||||
l := List{ID: tl.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanDelete for TeamList: %s", err)
|
||||
log.Log.Error("Error occurred during CanDelete for TeamList: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.IsAdmin(user)
|
||||
@@ -24,7 +28,7 @@ func (tl *TeamList) CanDelete(user *User) bool {
|
||||
func (tl *TeamList) CanUpdate(user *User) bool {
|
||||
l := List{ID: tl.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
Log.Error("Error occurred during CanUpdate for TeamList: %s", err)
|
||||
log.Log.Error("Error occurred during CanUpdate for TeamList: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.IsAdmin(user)
|
||||
@@ -15,67 +15,67 @@ func TestTeamList(t *testing.T) {
|
||||
}
|
||||
|
||||
// Dummyuser
|
||||
user, err := GetUserByID(1)
|
||||
u, err := GetUserByID(1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check normal creation
|
||||
assert.True(t, tl.CanCreate(&user))
|
||||
err = tl.Create(&user)
|
||||
assert.True(t, tl.CanCreate(&u))
|
||||
err = tl.Create(&u)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check again
|
||||
err = tl.Create(&user)
|
||||
err = tl.Create(&u)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrTeamAlreadyHasAccess(err))
|
||||
|
||||
// Check with wrong rights
|
||||
tl2 := tl
|
||||
tl2.Right = TeamRightUnknown
|
||||
err = tl2.Create(&user)
|
||||
err = tl2.Create(&u)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrInvalidTeamRight(err))
|
||||
|
||||
// Check with inexistant team
|
||||
tl3 := tl
|
||||
tl3.TeamID = 3253
|
||||
err = tl3.Create(&user)
|
||||
err = tl3.Create(&u)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrTeamDoesNotExist(err))
|
||||
|
||||
// Check with inexistant list
|
||||
tl4 := tl
|
||||
tl4.ListID = 3252
|
||||
err = tl4.Create(&user)
|
||||
err = tl4.Create(&u)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrListDoesNotExist(err))
|
||||
|
||||
// Test Read all
|
||||
teams, err := tl.ReadAll(&user)
|
||||
teams, err := tl.ReadAll(&u)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
|
||||
s := reflect.ValueOf(teams)
|
||||
assert.Equal(t, s.Len(), 1)
|
||||
|
||||
// Test Read all for nonexistant list
|
||||
_, err = tl4.ReadAll(&user)
|
||||
_, err = tl4.ReadAll(&u)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrListDoesNotExist(err))
|
||||
|
||||
// Test Read all for a list where the user is owner of the namespace this list belongs to
|
||||
tl5 := tl
|
||||
tl5.ListID = 2
|
||||
_, err = tl5.ReadAll(&user)
|
||||
_, err = tl5.ReadAll(&u)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test read all for a list where the user not has access
|
||||
tl6 := tl
|
||||
tl6.ListID = 3
|
||||
_, err = tl6.ReadAll(&user)
|
||||
_, err = tl6.ReadAll(&u)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrNeedToHaveListReadAccess(err))
|
||||
|
||||
// Delete
|
||||
assert.True(t, tl.CanDelete(&user))
|
||||
assert.True(t, tl.CanDelete(&u))
|
||||
err = tl.Delete()
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
)
|
||||
|
||||
// CanCreate checks if the user can add a new tem member
|
||||
func (tm *TeamMember) CanCreate(user *User) bool {
|
||||
return tm.IsAdmin(user)
|
||||
func (tm *TeamMember) CanCreate(u *User) bool {
|
||||
return tm.IsAdmin(u)
|
||||
}
|
||||
|
||||
// CanDelete checks if the user can delete a new team member
|
||||
func (tm *TeamMember) CanDelete(user *User) bool {
|
||||
return tm.IsAdmin(user)
|
||||
func (tm *TeamMember) CanDelete(u *User) bool {
|
||||
return tm.IsAdmin(u)
|
||||
}
|
||||
|
||||
// IsAdmin checks if the user is team admin
|
||||
func (tm *TeamMember) IsAdmin(user *User) bool {
|
||||
func (tm *TeamMember) IsAdmin(u *User) bool {
|
||||
// A user can add a member to a team if he is admin of that team
|
||||
exists, err := x.Where("user_id = ? AND team_id = ? AND admin = ?", user.ID, tm.TeamID, true).
|
||||
exists, err := x.Where("user_id = ? AND team_id = ? AND admin = ?", u.ID, tm.TeamID, true).
|
||||
Get(&TeamMember{})
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during IsAdmin for TeamMember: %s", err)
|
||||
log.Log.Error("Error occurred during IsAdmin for TeamMember: %s", err)
|
||||
return false
|
||||
}
|
||||
return exists
|
||||
@@ -1,10 +1,14 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
)
|
||||
|
||||
// CanCreate checks if one can create a new team <-> namespace relation
|
||||
func (tn *TeamNamespace) CanCreate(user *User) bool {
|
||||
n, err := GetNamespaceByID(tn.NamespaceID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanCreate for TeamNamespace: %s", err)
|
||||
log.Log.Error("Error occurred during CanCreate for TeamNamespace: %s", err)
|
||||
return false
|
||||
}
|
||||
return n.IsAdmin(user)
|
||||
@@ -14,17 +18,17 @@ func (tn *TeamNamespace) CanCreate(user *User) bool {
|
||||
func (tn *TeamNamespace) CanDelete(user *User) bool {
|
||||
n, err := GetNamespaceByID(tn.NamespaceID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanDelete for TeamNamespace: %s", err)
|
||||
log.Log.Error("Error occurred during CanDelete for TeamNamespace: %s", err)
|
||||
return false
|
||||
}
|
||||
return n.IsAdmin(user)
|
||||
}
|
||||
|
||||
// CanUpdate checks if a user can update a team from a namespace. Only namespace admins can do that.
|
||||
// CanUpdate checks if a user can update a team from a Only namespace admins can do that.
|
||||
func (tn *TeamNamespace) CanUpdate(user *User) bool {
|
||||
n, err := GetNamespaceByID(tn.NamespaceID)
|
||||
if err != nil {
|
||||
Log.Error("Error occurred during CanUpdate for TeamNamespace: %s", err)
|
||||
log.Log.Error("Error occurred during CanUpdate for TeamNamespace: %s", err)
|
||||
return false
|
||||
}
|
||||
return n.IsAdmin(user)
|
||||
27
pkg/models/team_right.go
Normal file
27
pkg/models/team_right.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package models
|
||||
|
||||
// TeamRight defines the rights teams can have for lists/namespaces
|
||||
type TeamRight int
|
||||
|
||||
// define unknown team right
|
||||
const (
|
||||
TeamRightUnknown = -1
|
||||
)
|
||||
|
||||
// Enumerate all the team rights
|
||||
const (
|
||||
// Can read lists in a Team
|
||||
TeamRightRead TeamRight = iota
|
||||
// Can write tasks in a Team like lists and todo tasks. Cannot create new lists.
|
||||
TeamRightWrite
|
||||
// Can manage a list/namespace, can do everything
|
||||
TeamRightAdmin
|
||||
)
|
||||
|
||||
func (r TeamRight) isValid() error {
|
||||
if r != TeamRightAdmin && r != TeamRightRead && r != TeamRightWrite {
|
||||
return ErrInvalidTeamRight{r}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
58
pkg/models/teams_rights.go
Normal file
58
pkg/models/teams_rights.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
)
|
||||
|
||||
// CanCreate checks if the user can create a new team
|
||||
func (t *Team) CanCreate(u *User) bool {
|
||||
// This is currently a dummy function, later on we could imagine global limits etc.
|
||||
return true
|
||||
}
|
||||
|
||||
// CanUpdate checks if the user can update a team
|
||||
func (t *Team) CanUpdate(u *User) bool {
|
||||
|
||||
// Check if the current user is in the team and has admin rights in it
|
||||
exists, err := x.Where("team_id = ?", t.ID).
|
||||
And("user_id = ?", u.ID).
|
||||
And("admin = ?", true).
|
||||
Get(&TeamMember{})
|
||||
if err != nil {
|
||||
log.Log.Error("Error occurred during CanUpdate for Team: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return exists
|
||||
}
|
||||
|
||||
// CanDelete checks if a user can delete a team
|
||||
func (t *Team) CanDelete(u *User) bool {
|
||||
return t.IsAdmin(u)
|
||||
}
|
||||
|
||||
// IsAdmin returns true when the user is admin of a team
|
||||
func (t *Team) IsAdmin(u *User) bool {
|
||||
exists, err := x.Where("team_id = ?", t.ID).
|
||||
And("user_id = ?", u.ID).
|
||||
And("admin = ?", true).
|
||||
Get(&TeamMember{})
|
||||
if err != nil {
|
||||
log.Log.Error("Error occurred during CanUpdate for Team: %s", err)
|
||||
return false
|
||||
}
|
||||
return exists
|
||||
}
|
||||
|
||||
// CanRead returns true if the user has read access to the team
|
||||
func (t *Team) CanRead(user *User) bool {
|
||||
// Check if the user is in the team
|
||||
exists, err := x.Where("team_id = ?", t.ID).
|
||||
And("user_id = ?", user.ID).
|
||||
Get(&TeamMember{})
|
||||
if err != nil {
|
||||
log.Log.Error("Error occurred during CanUpdate for Team: %s", err)
|
||||
return false
|
||||
}
|
||||
return exists
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/mail"
|
||||
"fmt"
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
@@ -10,6 +11,10 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// IsTesting is set to true when we're running tests.
|
||||
// We don't have a good solution to test email sending yet, so we disable email sending when testing
|
||||
var IsTesting bool
|
||||
|
||||
// MainTest creates the test engine
|
||||
func MainTest(m *testing.M, pathToRoot string) {
|
||||
var err error
|
||||
@@ -19,6 +24,11 @@ func MainTest(m *testing.M, pathToRoot string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
IsTesting = true
|
||||
|
||||
// Start the pseudo mail queue
|
||||
mail.StartMailDaemon()
|
||||
|
||||
// Create test database
|
||||
PrepareTestDatabase()
|
||||
|
||||
@@ -18,8 +18,13 @@ type User struct {
|
||||
Username string `xorm:"varchar(250) not null unique" json:"username"`
|
||||
Password string `xorm:"varchar(250) not null" json:"-"`
|
||||
Email string `xorm:"varchar(250)" json:"email"`
|
||||
Created int64 `xorm:"created" json:"-"`
|
||||
Updated int64 `xorm:"updated" json:"-"`
|
||||
IsActive bool `json:"-"`
|
||||
|
||||
PasswordResetToken string `xorm:"varchar(450)" json:"-"`
|
||||
EmailConfirmToken string `xorm:"varchar(450)" json:"-"`
|
||||
|
||||
Created int64 `xorm:"created" json:"-"`
|
||||
Updated int64 `xorm:"updated" json:"-"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for users
|
||||
@@ -61,7 +66,7 @@ func GetUser(user User) (userOut User, err error) {
|
||||
exists, err := x.Get(&userOut)
|
||||
|
||||
if !exists {
|
||||
return User{}, ErrUserDoesNotExist{}
|
||||
return User{}, ErrUserDoesNotExist{UserID: user.ID}
|
||||
}
|
||||
|
||||
return userOut, err
|
||||
@@ -69,6 +74,10 @@ func GetUser(user User) (userOut User, err error) {
|
||||
|
||||
// CheckUserCredentials checks user credentials
|
||||
func CheckUserCredentials(u *UserLogin) (User, error) {
|
||||
// Check if we have any credentials
|
||||
if u.Password == "" || u.Username == "" {
|
||||
return User{}, ErrNoUsernamePassword{}
|
||||
}
|
||||
|
||||
// Check if the user exists
|
||||
user, err := GetUser(User{Username: u.Username})
|
||||
@@ -76,10 +85,17 @@ func CheckUserCredentials(u *UserLogin) (User, error) {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
// User is invalid if it needs to verify its email address
|
||||
if !user.IsActive {
|
||||
return User{}, ErrEmailNotConfirmed{UserID: user.ID}
|
||||
}
|
||||
|
||||
// Check the users password
|
||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(u.Password))
|
||||
|
||||
if err != nil {
|
||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
return User{}, ErrWrongUsernameOrPassword{}
|
||||
}
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/mail"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -48,6 +50,12 @@ func CreateUser(user User) (newUser User, err error) {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
// Generate a confirm token
|
||||
newUser.EmailConfirmToken = utils.MakeRandomString(400)
|
||||
|
||||
// The new user should not be activated until it confirms his mail address
|
||||
newUser.IsActive = false
|
||||
|
||||
// Insert it
|
||||
_, err = x.Insert(newUser)
|
||||
if err != nil {
|
||||
@@ -67,6 +75,18 @@ func CreateUser(user User) (newUser User, err error) {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
// Dont send a mail if we're testing
|
||||
if IsTesting {
|
||||
return newUserOut, err
|
||||
}
|
||||
|
||||
// Send the user a mail with a link to confirm the mail
|
||||
data := map[string]interface{}{
|
||||
"User": newUserOut,
|
||||
}
|
||||
|
||||
mail.SendMailWithTemplate(user.Email, newUserOut.Username+" + Vikunja = <3", "confirm-email", data)
|
||||
|
||||
return newUserOut, err
|
||||
}
|
||||
|
||||
@@ -7,18 +7,8 @@ func DeleteUserByID(id int64, doer *User) error {
|
||||
return ErrIDCannotBeZero{}
|
||||
}
|
||||
|
||||
// Check if there is > 1 user
|
||||
total, err := x.Count(User{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if total < 2 {
|
||||
return ErrCannotDeleteLastUser{}
|
||||
}
|
||||
|
||||
// Delete the user
|
||||
_, err = x.Id(id).Delete(&User{})
|
||||
_, err := x.Id(id).Delete(&User{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
31
pkg/models/user_email_confirm.go
Normal file
31
pkg/models/user_email_confirm.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package models
|
||||
|
||||
// EmailConfirm holds the token to confirm a mail address
|
||||
type EmailConfirm struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// UserEmailConfirm handles the confirmation of an email address
|
||||
func UserEmailConfirm(c *EmailConfirm) (err error) {
|
||||
|
||||
// Check if we have an email confirm token
|
||||
if c.Token == "" {
|
||||
return ErrInvalidEmailConfirmToken{}
|
||||
}
|
||||
|
||||
// Check if the token is valid
|
||||
user := User{}
|
||||
has, err := x.Where("email_confirm_token = ?", c.Token).Get(&user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !has {
|
||||
return ErrInvalidEmailConfirmToken{Token: c.Token}
|
||||
}
|
||||
|
||||
user.IsActive = true
|
||||
user.EmailConfirmToken = ""
|
||||
_, err = x.Where("id = ?", user.ID).Cols("is_active", "email_confirm_token").Update(&user)
|
||||
return
|
||||
}
|
||||
94
pkg/models/user_password_reset.go
Normal file
94
pkg/models/user_password_reset.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/mail"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
)
|
||||
|
||||
// PasswordReset holds the data to reset a password
|
||||
type PasswordReset struct {
|
||||
Token string `json:"token"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
// UserPasswordReset resets a users password
|
||||
func UserPasswordReset(reset *PasswordReset) (err error) {
|
||||
|
||||
// Check if the password is not empty
|
||||
if reset.NewPassword == "" {
|
||||
return ErrNoUsernamePassword{}
|
||||
}
|
||||
|
||||
// Check if we have a token
|
||||
var user User
|
||||
exists, err := x.Where("password_reset_token = ?", reset.Token).Get(&user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return ErrInvalidPasswordResetToken{Token: reset.Token}
|
||||
}
|
||||
|
||||
// Hash the password
|
||||
user.Password, err = hashPassword(reset.NewPassword)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Save it
|
||||
_, err = x.Where("id = ?", user.ID).Update(&user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Dont send a mail if we're testing
|
||||
if IsTesting {
|
||||
return
|
||||
}
|
||||
|
||||
// Send a mail to the user to notify it his password was changed.
|
||||
data := map[string]interface{}{
|
||||
"User": user,
|
||||
}
|
||||
|
||||
mail.SendMailWithTemplate(user.Email, "Your password on Vikunja was changed", "password-changed", data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// PasswordTokenRequest defines the request format for password reset resqest
|
||||
type PasswordTokenRequest struct {
|
||||
Username string `json:"user_name"`
|
||||
}
|
||||
|
||||
// RequestUserPasswordResetToken inserts a random token to reset a users password into the databsse
|
||||
func RequestUserPasswordResetToken(tr *PasswordTokenRequest) (err error) {
|
||||
// Check if the user exists
|
||||
user, err := GetUser(User{Username: tr.Username})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Generate a token and save it
|
||||
user.PasswordResetToken = utils.MakeRandomString(400)
|
||||
|
||||
// Save it
|
||||
_, err = x.Where("id = ?", user.ID).Update(&user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Dont send a mail if we're testing
|
||||
if IsTesting {
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"User": user,
|
||||
}
|
||||
|
||||
// Send the user a mail with the reset token
|
||||
mail.SendMailWithTemplate(user.Email, "Reset your password on Vikunja", "reset-password", data)
|
||||
return
|
||||
}
|
||||
27
pkg/models/user_right.go
Normal file
27
pkg/models/user_right.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package models
|
||||
|
||||
// UserRight defines the rights users can have for lists/namespaces
|
||||
type UserRight int
|
||||
|
||||
// define unknown user right
|
||||
const (
|
||||
UserRightUnknown = -1
|
||||
)
|
||||
|
||||
// Enumerate all the user rights
|
||||
const (
|
||||
// Can read lists in a User
|
||||
UserRightRead UserRight = iota
|
||||
// Can write tasks in a User like lists and todo tasks. Cannot create new lists.
|
||||
UserRightWrite
|
||||
// Can manage a list/namespace, can do everything
|
||||
UserRightAdmin
|
||||
)
|
||||
|
||||
func (r UserRight) isValid() error {
|
||||
if r != UserRightAdmin && r != UserRightRead && r != UserRightWrite {
|
||||
return ErrInvalidUserRight{r}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
@@ -20,24 +21,12 @@ func TestCreateUser(t *testing.T) {
|
||||
Email: "noone@example.com",
|
||||
}
|
||||
|
||||
// Delete every preexisting user to have a fresh start
|
||||
_, err = x.Where("1 = 1").Delete(&User{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
allusers, err := ListUsers("")
|
||||
assert.NoError(t, err)
|
||||
for _, user := range allusers {
|
||||
// Delete it
|
||||
err := DeleteUserByID(user.ID, &doer)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// Create a new user
|
||||
createdUser, err := CreateUser(dummyuser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a second new user
|
||||
createdUser2, err := CreateUser(User{Username: dummyuser.Username + "2", Email: dummyuser.Email + "m", Password: dummyuser.Password})
|
||||
_, err = CreateUser(User{Username: dummyuser.Username + "2", Email: dummyuser.Email + "m", Password: dummyuser.Password})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if it fails to create the same user again
|
||||
@@ -72,14 +61,22 @@ func TestCreateUser(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrUserDoesNotExist(err))
|
||||
|
||||
// Check the user credentials
|
||||
// Check the user credentials with an unverified email
|
||||
user, err := CheckUserCredentials(&UserLogin{"testuu", "1234"})
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrEmailNotConfirmed(err))
|
||||
|
||||
// Update everything and check again
|
||||
_, err = x.Cols("is_active").Where("true").Update(User{IsActive: true})
|
||||
assert.NoError(t, err)
|
||||
user, err = CheckUserCredentials(&UserLogin{"testuu", "1234"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "testuu", user.Username)
|
||||
|
||||
// Check wrong password (should also fail)
|
||||
_, err = CheckUserCredentials(&UserLogin{"testuu", "12345"})
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrWrongUsernameOrPassword(err))
|
||||
|
||||
// Check usercredentials for a nonexistent user (should fail)
|
||||
_, err = CheckUserCredentials(&UserLogin{"dfstestuu", "1234"})
|
||||
@@ -128,9 +125,38 @@ func TestCreateUser(t *testing.T) {
|
||||
err = DeleteUserByID(0, &doer)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrIDCannotBeZero(err))
|
||||
|
||||
// Try delete the last user (Should fail)
|
||||
err = DeleteUserByID(createdUser2.ID, &doer)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrCannotDeleteLastUser(err))
|
||||
}
|
||||
|
||||
func TestUserPasswordReset(t *testing.T) {
|
||||
// Request a new token
|
||||
tr := &PasswordTokenRequest{
|
||||
Username: "user1",
|
||||
}
|
||||
err := RequestUserPasswordResetToken(tr)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get the token / inside the user object
|
||||
userWithToken, err := GetUserByID(1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try resetting it
|
||||
reset := &PasswordReset{
|
||||
Token: userWithToken.PasswordResetToken,
|
||||
}
|
||||
|
||||
// Try resetting it without a password
|
||||
reset.NewPassword = ""
|
||||
err = UserPasswordReset(reset)
|
||||
assert.True(t, IsErrNoUsernamePassword(err))
|
||||
|
||||
// Reset it
|
||||
reset.NewPassword = "1234"
|
||||
err = UserPasswordReset(reset)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try resetting it with a wrong token
|
||||
reset.Token = utils.MakeRandomString(400)
|
||||
err = UserPasswordReset(reset)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrInvalidPasswordResetToken(err))
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/models"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -1,7 +1,8 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/models"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/routes/crud"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
@@ -41,7 +42,7 @@ func Login(c echo.Context) error {
|
||||
// Check user
|
||||
user, err := models.CheckUserCredentials(&u)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusUnauthorized, models.Message{"Wrong username or password."})
|
||||
return crud.HandleHTTPError(err)
|
||||
}
|
||||
|
||||
// Create token
|
||||
@@ -1,6 +1,8 @@
|
||||
package swagger
|
||||
|
||||
import "code.vikunja.io/api/models"
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
)
|
||||
|
||||
// not actually a response, just a hack to get go-swagger to include definitions
|
||||
// of the various XYZOption structs
|
||||
@@ -40,4 +42,13 @@ type swaggerParameterBodies struct {
|
||||
|
||||
// in:body
|
||||
NamespaceUser models.NamespaceUser
|
||||
|
||||
// in:body
|
||||
PasswordReset models.PasswordReset
|
||||
|
||||
// in:body
|
||||
PasswordTokenRequest models.PasswordTokenRequest
|
||||
|
||||
// in:body
|
||||
EmailConfirm models.EmailConfirm
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package swagger
|
||||
|
||||
import "code.vikunja.io/api/models"
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
)
|
||||
|
||||
// Message
|
||||
// swagger:response Message
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user