diff --git a/docs/content/doc/development/structure.md b/docs/content/doc/development/structure.md index 8e819b6c..29dc1eba 100644 --- a/docs/content/doc/development/structure.md +++ b/docs/content/doc/development/structure.md @@ -16,7 +16,12 @@ In general, this api repo has the following structure: * `docs` * `pkg` * `caldav` + * `cmd` * `config` + * `db` + * `fixtures` + * `files` + * `integration` * `log` * `mail` * `metrics` @@ -29,8 +34,11 @@ In general, this api repo has the following structure: * `red` * `routes` * `api/v1` + * `static` * `swagger` + * `user` * `utils` + * `version` * `REST-Tests` * `templates` * `vendor` @@ -70,6 +78,21 @@ how to interpret which env variables for config etc. If you want to add a new config parameter, you should add default value in this package. +### db + +This package contains the db connection handling and db fixtures for testing. +Each other package gets its db connection object from this package. + +### files + +This package is responsible for all file-related things. +This means it handles saving and retrieving files from the db and the underlying file system. + +### integration + +All integration tests live here. +See [integration tests]({{< ref "test.md" >}}#integration-tests) for more details. + ### log Similar to `config`, this will set up the logging, based on differen logging backends. @@ -127,11 +150,19 @@ To add a new route, see [adding a new route]({{< ref "../practical-instructions/ This is where all http-handler functions for the api are stored. Every handler function which does not use the standard web handler should live here. +### static + +All static files generated by `make generate` live here. + ### swagger This is where the [generated]({{< ref "make.md#generate-swagger-definitions-from-code-comments">}} [api docs]({{< ref "../usage/api.md">}}) live. You usually don't need to touch this package. +### user + +All user-related things like registration etc. live in this package. + ### utils A small package, containing some helper functions: @@ -141,6 +172,12 @@ A small package, containing some helper functions: See their function definitions for instructions on how to use them. +### version + +The single purpouse of this package is to hold the current vikunja version which gets overridden through build flags +each time `make release` or `make build` is run. +It is a seperate package to avoid import cycles with other packages. + ## REST-Tests Holds all kinds of test files to directly test the api from inside of [jetbrains ide's](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html). diff --git a/docs/content/doc/development/test.md b/docs/content/doc/development/test.md index 1fdcfe89..55ddc883 100644 --- a/docs/content/doc/development/test.md +++ b/docs/content/doc/development/test.md @@ -42,3 +42,29 @@ The integration tests use the same config and fixtures as the unit tests and the see at the beginning of this document. To run integration tests, use `make integration-test`. + +# Initializing db fixtures when writing tests + +All db fixtures for all tests live in the `pkg/db/fixtures/` folder as yaml files. +Each file has the same name as the table the fixtures are for. +You should put new fixtures in this folder. + +When initializing db fixtures, you are responsible for defining which tables your package needs in your test init function. +Usually, this is done as follows (this code snippet is taken from the `user` package): + +```go +err = db.InitTestFixtures("users") +if err != nil { + log.Fatal(err) +} +``` + +In your actual tests, you then load the fixtures into the in-memory db like so: + +```go +db.LoadAndAssertFixtures(t) +``` + +This will load all fixtures you defined in your test init method. +You should always use this method to load fixtures, the only exception is when your package tests require extra test +fixtures other than db fixtures (like files). diff --git a/go.mod b/go.mod index 2b304391..b1a8ffc5 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,6 @@ require ( github.com/onsi/gomega v1.4.3 // indirect github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pelletier/go-toml v1.4.0 // indirect - github.com/pkg/errors v0.8.1 // indirect github.com/prometheus/client_golang v0.9.2 github.com/samedi/caldav-go v3.0.0+incompatible github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b diff --git a/pkg/caldav/caldav.go b/pkg/caldav/caldav.go index d988407e..c6cc1f2f 100644 --- a/pkg/caldav/caldav.go +++ b/pkg/caldav/caldav.go @@ -17,7 +17,7 @@ package caldav import ( - "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "fmt" "strconv" @@ -49,7 +49,7 @@ type Todo struct { Summary string Description string CompletedUnix int64 - Organizer *models.User + Organizer *user.User Priority int64 // 0-9, 1 is highest RelatedToUID string diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 62d70f8a..d2c1b385 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -25,6 +25,7 @@ import ( "code.vikunja.io/api/pkg/models" migrator "code.vikunja.io/api/pkg/modules/migration" "code.vikunja.io/api/pkg/red" + "code.vikunja.io/api/pkg/user" "fmt" "github.com/spf13/cobra" "os" @@ -76,6 +77,10 @@ func initialize() { if err != nil { log.Fatal(err.Error()) } + err = user.InitDB() + if err != nil { + log.Fatal(err.Error()) + } err = files.SetEngine() if err != nil { log.Fatal(err.Error()) diff --git a/pkg/db/db.go b/pkg/db/db.go index b65dc680..4d9a46ef 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -23,7 +23,6 @@ import ( "fmt" "github.com/go-xorm/core" "github.com/go-xorm/xorm" - "os" "strconv" "time" @@ -85,33 +84,6 @@ func CreateDBEngine() (engine *xorm.Engine, err error) { return } -// CreateTestEngine creates an instance of the db engine which lives in memory -func CreateTestEngine() (engine *xorm.Engine, err error) { - - if x != nil { - return x, nil - } - - if os.Getenv("VIKUNJA_TESTS_USE_CONFIG") == "1" { - config.InitConfig() - engine, err = CreateDBEngine() - if err != nil { - return nil, err - } - } else { - engine, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") - if err != nil { - return nil, err - } - } - - engine.SetMapper(core.GonicMapper{}) - engine.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1") - engine.SetLogger(xorm.NewSimpleLogger(log.GetLogWriter("database"))) - x = engine - return -} - // RegisterTableStructsForCache registers tables in gob encoding for redis cache func RegisterTableStructsForCache(val interface{}) { gob.Register(val) diff --git a/pkg/files/fixtures/files.yml b/pkg/db/fixtures/files.yml similarity index 100% rename from pkg/files/fixtures/files.yml rename to pkg/db/fixtures/files.yml diff --git a/pkg/models/fixtures/label_task.yml b/pkg/db/fixtures/label_task.yml similarity index 100% rename from pkg/models/fixtures/label_task.yml rename to pkg/db/fixtures/label_task.yml diff --git a/pkg/models/fixtures/labels.yml b/pkg/db/fixtures/labels.yml similarity index 100% rename from pkg/models/fixtures/labels.yml rename to pkg/db/fixtures/labels.yml diff --git a/pkg/models/fixtures/link_sharing.yml b/pkg/db/fixtures/link_sharing.yml similarity index 100% rename from pkg/models/fixtures/link_sharing.yml rename to pkg/db/fixtures/link_sharing.yml diff --git a/pkg/models/fixtures/list.yml b/pkg/db/fixtures/list.yml similarity index 100% rename from pkg/models/fixtures/list.yml rename to pkg/db/fixtures/list.yml diff --git a/pkg/models/fixtures/namespaces.yml b/pkg/db/fixtures/namespaces.yml similarity index 100% rename from pkg/models/fixtures/namespaces.yml rename to pkg/db/fixtures/namespaces.yml diff --git a/pkg/models/fixtures/task_assignees.yml b/pkg/db/fixtures/task_assignees.yml similarity index 100% rename from pkg/models/fixtures/task_assignees.yml rename to pkg/db/fixtures/task_assignees.yml diff --git a/pkg/models/fixtures/task_attachments.yml b/pkg/db/fixtures/task_attachments.yml similarity index 100% rename from pkg/models/fixtures/task_attachments.yml rename to pkg/db/fixtures/task_attachments.yml diff --git a/pkg/models/fixtures/task_relations.yml b/pkg/db/fixtures/task_relations.yml similarity index 100% rename from pkg/models/fixtures/task_relations.yml rename to pkg/db/fixtures/task_relations.yml diff --git a/pkg/models/fixtures/task_reminders.yml b/pkg/db/fixtures/task_reminders.yml similarity index 100% rename from pkg/models/fixtures/task_reminders.yml rename to pkg/db/fixtures/task_reminders.yml diff --git a/pkg/models/fixtures/tasks.yml b/pkg/db/fixtures/tasks.yml similarity index 100% rename from pkg/models/fixtures/tasks.yml rename to pkg/db/fixtures/tasks.yml diff --git a/pkg/models/fixtures/team_list.yml b/pkg/db/fixtures/team_list.yml similarity index 100% rename from pkg/models/fixtures/team_list.yml rename to pkg/db/fixtures/team_list.yml diff --git a/pkg/models/fixtures/team_members.yml b/pkg/db/fixtures/team_members.yml similarity index 100% rename from pkg/models/fixtures/team_members.yml rename to pkg/db/fixtures/team_members.yml diff --git a/pkg/models/fixtures/team_namespaces.yml b/pkg/db/fixtures/team_namespaces.yml similarity index 100% rename from pkg/models/fixtures/team_namespaces.yml rename to pkg/db/fixtures/team_namespaces.yml diff --git a/pkg/models/fixtures/teams.yml b/pkg/db/fixtures/teams.yml similarity index 100% rename from pkg/models/fixtures/teams.yml rename to pkg/db/fixtures/teams.yml diff --git a/pkg/models/fixtures/users.yml b/pkg/db/fixtures/users.yml similarity index 100% rename from pkg/models/fixtures/users.yml rename to pkg/db/fixtures/users.yml diff --git a/pkg/models/fixtures/users_list.yml b/pkg/db/fixtures/users_list.yml similarity index 100% rename from pkg/models/fixtures/users_list.yml rename to pkg/db/fixtures/users_list.yml diff --git a/pkg/models/fixtures/users_namespace.yml b/pkg/db/fixtures/users_namespace.yml similarity index 100% rename from pkg/models/fixtures/users_namespace.yml rename to pkg/db/fixtures/users_namespace.yml diff --git a/pkg/db/test.go b/pkg/db/test.go new file mode 100644 index 00000000..a7131084 --- /dev/null +++ b/pkg/db/test.go @@ -0,0 +1,69 @@ +// Copyright2018-2020 Vikunja and contriubtors. All rights reserved. +// +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vikunja is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vikunja. If not, see . + +package db + +import ( + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/log" + "github.com/go-xorm/core" + "github.com/go-xorm/xorm" + "os" +) + +// CreateTestEngine creates an instance of the db engine which lives in memory +func CreateTestEngine() (engine *xorm.Engine, err error) { + + if x != nil { + return x, nil + } + + if os.Getenv("VIKUNJA_TESTS_USE_CONFIG") == "1" { + config.InitConfig() + engine, err = CreateDBEngine() + if err != nil { + return nil, err + } + } else { + engine, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") + if err != nil { + return nil, err + } + } + + engine.SetMapper(core.GonicMapper{}) + engine.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1") + engine.SetLogger(xorm.NewSimpleLogger(log.GetLogWriter("database"))) + x = engine + return +} + +// InitTestFixtures populates the db with all fixtures from the fixtures folder +func InitTestFixtures(tablenames ...string) (err error) { + // Create all fixtures + config.InitDefaultConfig() + // We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly + config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH")) + + // Sync fixtures + err = InitFixtures(tablenames...) + if err != nil { + log.Fatal(err) + } + + return nil +} diff --git a/pkg/db/test_fixtures.go b/pkg/db/test_fixtures.go index 3ad9708b..16d75a2b 100644 --- a/pkg/db/test_fixtures.go +++ b/pkg/db/test_fixtures.go @@ -18,15 +18,36 @@ package db import ( + "code.vikunja.io/api/pkg/config" + "github.com/stretchr/testify/assert" "gopkg.in/testfixtures.v2" + "path/filepath" + "testing" ) var fixtures *testfixtures.Context // InitFixtures initialize test fixtures for a test database -func InitFixtures(helper testfixtures.Helper, dir string) (err error) { +func InitFixtures(tablenames ...string) (err error) { + + var helper testfixtures.Helper = &testfixtures.SQLite{} + if config.DatabaseType.GetString() == "mysql" { + helper = &testfixtures.MySQL{} + } + dir := filepath.Join(config.ServiceRootpath.GetString(), "pkg", "db", "fixtures") + testfixtures.SkipDatabaseNameCheck(true) - fixtures, err = testfixtures.NewFolder(x.DB().DB, helper, dir) + + // If fixture table names are specified, load them + // Otherwise, load all fixtures + if len(tablenames) > 0 { + for i, name := range tablenames { + tablenames[i] = filepath.Join(dir, name+".yml") + } + fixtures, err = testfixtures.NewFiles(x.DB().DB, helper, tablenames...) + } else { + fixtures, err = testfixtures.NewFolder(x.DB().DB, helper, dir) + } return err } @@ -34,3 +55,9 @@ func InitFixtures(helper testfixtures.Helper, dir string) (err error) { func LoadFixtures() error { return fixtures.Load() } + +// LoadAndAssertFixtures loads all fixtures defined before and asserts they are correctly loaded +func LoadAndAssertFixtures(t *testing.T) { + err := LoadFixtures() + assert.NoError(t, err) +} diff --git a/pkg/files/filehandling.go b/pkg/files/filehandling.go index 53c12e9e..3540b26e 100644 --- a/pkg/files/filehandling.go +++ b/pkg/files/filehandling.go @@ -22,9 +22,7 @@ import ( "code.vikunja.io/api/pkg/log" "github.com/spf13/afero" "github.com/stretchr/testify/assert" - "gopkg.in/testfixtures.v2" "os" - "path/filepath" "testing" ) @@ -45,10 +43,9 @@ func InitTestFileHandler() { } func initFixtures(t *testing.T) { - // Init db fixtures - err := db.LoadFixtures() - assert.NoError(t, err) - + // DB fixtures + db.LoadAndAssertFixtures(t) + // File fixtures InitTestFileFixtures(t) } @@ -73,17 +70,7 @@ func InitTests() { log.Fatal(err) } - config.InitDefaultConfig() - // We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly - config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH")) - - // Sync fixtures - var fixturesHelper testfixtures.Helper = &testfixtures.SQLite{} - if config.DatabaseType.GetString() == "mysql" { - fixturesHelper = &testfixtures.MySQL{} - } - fixturesDir := filepath.Join(config.ServiceRootpath.GetString(), "pkg", "files", "fixtures") - err = db.InitFixtures(fixturesHelper, fixturesDir) + err = db.InitTestFixtures("files") if err != nil { log.Fatal(err) } diff --git a/pkg/integrations/integrations.go b/pkg/integrations/integrations.go index 40bf5027..008271e3 100644 --- a/pkg/integrations/integrations.go +++ b/pkg/integrations/integrations.go @@ -23,6 +23,7 @@ import ( "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/routes" v1 "code.vikunja.io/api/pkg/routes/api/v1" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "code.vikunja.io/web/handler" "github.com/dgrijalva/jwt-go" @@ -38,34 +39,34 @@ import ( // These are the test users, the same way they are in the test database var ( - testuser1 = models.User{ + testuser1 = user.User{ ID: 1, Username: "user1", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", Email: "user1@example.com", IsActive: true, } - testuser2 = models.User{ + testuser2 = user.User{ ID: 2, Username: "user2", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", Email: "user2@example.com", } - testuser3 = models.User{ + testuser3 = user.User{ ID: 3, Username: "user3", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", Email: "user3@example.com", PasswordResetToken: "passwordresettesttoken", } - testuser4 = models.User{ + testuser4 = user.User{ ID: 4, Username: "user4", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", Email: "user4@example.com", EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael", } - testuser5 = models.User{ + testuser5 = user.User{ ID: 4, Username: "user5", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -81,7 +82,8 @@ func setupTestEnv() (e *echo.Echo, err error) { config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH")) // Some tests use the file engine, so we'll need to initialize that files.InitTests() - models.SetupTests(config.ServiceRootpath.GetString()) + user.InitTests() + models.SetupTests() err = db.LoadFixtures() if err != nil { @@ -114,7 +116,7 @@ func newTestRequest(t *testing.T, method string, handler func(ctx echo.Context) return } -func addUserTokenToContext(t *testing.T, user *models.User, c echo.Context) { +func addUserTokenToContext(t *testing.T, user *user.User, c echo.Context) { // Get the token as a string token, err := v1.NewUserJWTAuthtoken(user) assert.NoError(t, err) @@ -152,7 +154,7 @@ func testRequestSetup(t *testing.T, method string, payload string, queryParams u return } -func newTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *models.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) { +func newTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) { rec, c := testRequestSetup(t, method, payload, queryParams, urlParams) addUserTokenToContext(t, user, c) err = handler(c) @@ -185,7 +187,7 @@ func assertHandlerErrorCode(t *testing.T, err error, expectedErrorCode int) { } type webHandlerTest struct { - user *models.User + user *user.User linkShare *models.LinkSharing strFunc func() handler.CObject t *testing.T diff --git a/pkg/integrations/login_test.go b/pkg/integrations/login_test.go index 546e0270..e6ad2181 100644 --- a/pkg/integrations/login_test.go +++ b/pkg/integrations/login_test.go @@ -17,8 +17,8 @@ package integrations import ( - "code.vikunja.io/api/pkg/models" apiv1 "code.vikunja.io/api/pkg/routes/api/v1" + "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "net/http" "testing" @@ -36,7 +36,7 @@ func TestLogin(t *testing.T) { t.Run("Empty payload", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{}`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword) + assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword) }) t.Run("Not existing user", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{ @@ -44,7 +44,7 @@ func TestLogin(t *testing.T) { "password": "1234" }`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeWrongUsernameOrPassword) + assertHandlerErrorCode(t, err, user.ErrCodeWrongUsernameOrPassword) }) t.Run("Wrong password", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{ @@ -52,7 +52,7 @@ func TestLogin(t *testing.T) { "password": "wrong" }`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeWrongUsernameOrPassword) + assertHandlerErrorCode(t, err, user.ErrCodeWrongUsernameOrPassword) }) t.Run("user with unconfirmed email", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{ @@ -60,6 +60,6 @@ func TestLogin(t *testing.T) { "password": "1234" }`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeEmailNotConfirmed) + assertHandlerErrorCode(t, err, user.ErrCodeEmailNotConfirmed) }) } diff --git a/pkg/integrations/register_test.go b/pkg/integrations/register_test.go index 34b28de0..3ce59a08 100644 --- a/pkg/integrations/register_test.go +++ b/pkg/integrations/register_test.go @@ -17,8 +17,8 @@ package integrations import ( - "code.vikunja.io/api/pkg/models" apiv1 "code.vikunja.io/api/pkg/routes/api/v1" + "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "net/http" "testing" @@ -37,7 +37,7 @@ func TestRegister(t *testing.T) { t.Run("Empty payload", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{}`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword) + assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword) }) t.Run("Empty username", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{ @@ -46,7 +46,7 @@ func TestRegister(t *testing.T) { "email": "email@example.com" }`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword) + assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword) }) t.Run("Empty password", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{ @@ -55,7 +55,7 @@ func TestRegister(t *testing.T) { "email": "email@example.com" }`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword) + assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword) }) t.Run("Empty email", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{ @@ -64,7 +64,7 @@ func TestRegister(t *testing.T) { "email": "" }`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword) + assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword) }) t.Run("Already existing username", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{ @@ -73,7 +73,7 @@ func TestRegister(t *testing.T) { "email": "email@example.com" }`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrorCodeUsernameExists) + assertHandlerErrorCode(t, err, user.ErrorCodeUsernameExists) }) t.Run("Already existing email", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{ @@ -82,6 +82,6 @@ func TestRegister(t *testing.T) { "email": "user1@example.com" }`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrorCodeUserEmailExists) + assertHandlerErrorCode(t, err, user.ErrorCodeUserEmailExists) }) } diff --git a/pkg/integrations/user_change_password_test.go b/pkg/integrations/user_change_password_test.go index 5d639e29..e9afdc5e 100644 --- a/pkg/integrations/user_change_password_test.go +++ b/pkg/integrations/user_change_password_test.go @@ -17,8 +17,8 @@ package integrations import ( - "code.vikunja.io/api/pkg/models" apiv1 "code.vikunja.io/api/pkg/routes/api/v1" + "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "net/http" "testing" @@ -39,7 +39,7 @@ func TestUserChangePassword(t *testing.T) { "old_password": "invalid" }`, nil, nil) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeWrongUsernameOrPassword) + assertHandlerErrorCode(t, err, user.ErrCodeWrongUsernameOrPassword) }) t.Run("Empty old password", func(t *testing.T) { _, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserChangePassword, &testuser1, `{ @@ -47,7 +47,7 @@ func TestUserChangePassword(t *testing.T) { "old_password": "" }`, nil, nil) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeEmptyOldPassword) + assertHandlerErrorCode(t, err, user.ErrCodeEmptyOldPassword) }) t.Run("Empty new password", func(t *testing.T) { _, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserChangePassword, &testuser1, `{ @@ -55,6 +55,6 @@ func TestUserChangePassword(t *testing.T) { "old_password": "1234" }`, nil, nil) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeEmptyNewPassword) + assertHandlerErrorCode(t, err, user.ErrCodeEmptyNewPassword) }) } diff --git a/pkg/integrations/user_confirm_email_test.go b/pkg/integrations/user_confirm_email_test.go index 9e439af2..35bd769f 100644 --- a/pkg/integrations/user_confirm_email_test.go +++ b/pkg/integrations/user_confirm_email_test.go @@ -17,8 +17,8 @@ package integrations import ( - "code.vikunja.io/api/pkg/models" apiv1 "code.vikunja.io/api/pkg/routes/api/v1" + "code.vikunja.io/api/pkg/user" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "net/http" @@ -35,16 +35,16 @@ func TestUserConfirmEmail(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{}`) assert.Error(t, err) assert.Equal(t, http.StatusPreconditionFailed, err.(*echo.HTTPError).Code) - assertHandlerErrorCode(t, err, models.ErrCodeInvalidEmailConfirmToken) + assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken) }) t.Run("Empty token", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{"token": ""}`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeInvalidEmailConfirmToken) + assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken) }) t.Run("Invalid token", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{"token": "invalidToken"}`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeInvalidEmailConfirmToken) + assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken) }) } diff --git a/pkg/integrations/user_password_request_token_test.go b/pkg/integrations/user_password_request_token_test.go index e171d7b8..12f1f72c 100644 --- a/pkg/integrations/user_password_request_token_test.go +++ b/pkg/integrations/user_password_request_token_test.go @@ -17,8 +17,8 @@ package integrations import ( - "code.vikunja.io/api/pkg/models" apiv1 "code.vikunja.io/api/pkg/routes/api/v1" + "code.vikunja.io/api/pkg/user" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "net/http" @@ -34,7 +34,7 @@ func TestUserRequestResetPasswordToken(t *testing.T) { t.Run("Empty payload", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{}`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword) + assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword) }) t.Run("Invalid email address", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1example.com"}`) @@ -44,6 +44,6 @@ func TestUserRequestResetPasswordToken(t *testing.T) { t.Run("No user with that email address", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1000@example.com"}`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeUserDoesNotExist) + assertHandlerErrorCode(t, err, user.ErrCodeUserDoesNotExist) }) } diff --git a/pkg/integrations/user_password_reset_test.go b/pkg/integrations/user_password_reset_test.go index 50e09c89..28867b21 100644 --- a/pkg/integrations/user_password_reset_test.go +++ b/pkg/integrations/user_password_reset_test.go @@ -17,8 +17,8 @@ package integrations import ( - "code.vikunja.io/api/pkg/models" apiv1 "code.vikunja.io/api/pkg/routes/api/v1" + "code.vikunja.io/api/pkg/user" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "net/http" @@ -45,7 +45,7 @@ func TestUserPasswordReset(t *testing.T) { "token": "passwordresettesttoken" }`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword) + assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword) }) t.Run("Invalid password reset token", func(t *testing.T) { _, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{ @@ -53,6 +53,6 @@ func TestUserPasswordReset(t *testing.T) { "token": "invalidtoken" }`) assert.Error(t, err) - assertHandlerErrorCode(t, err, models.ErrCodeInvalidPasswordResetToken) + assertHandlerErrorCode(t, err, user.ErrCodeInvalidPasswordResetToken) }) } diff --git a/pkg/migration/migration.go b/pkg/migration/migration.go index 0d7bbfeb..b55390a8 100644 --- a/pkg/migration/migration.go +++ b/pkg/migration/migration.go @@ -22,6 +22,8 @@ import ( "code.vikunja.io/api/pkg/files" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/migration" + "code.vikunja.io/api/pkg/user" "github.com/go-xorm/xorm" "github.com/olekukonko/tablewriter" "os" @@ -138,5 +140,7 @@ func initSchema(tx *xorm.Engine) error { schemeBeans := []interface{}{} schemeBeans = append(schemeBeans, models.GetTables()...) schemeBeans = append(schemeBeans, files.GetTables()...) + schemeBeans = append(schemeBeans, migration.GetTables()...) + schemeBeans = append(schemeBeans, user.GetTables()...) return tx.Sync2(schemeBeans...) } diff --git a/pkg/models/bulk_task_test.go b/pkg/models/bulk_task_test.go index 289ba09c..39aa9f84 100644 --- a/pkg/models/bulk_task_test.go +++ b/pkg/models/bulk_task_test.go @@ -1,6 +1,8 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "testing" ) @@ -9,7 +11,7 @@ func TestBulkTask_Update(t *testing.T) { IDs []int64 Tasks []*Task Task Task - User *User + User *user.User } tests := []struct { name string @@ -24,7 +26,7 @@ func TestBulkTask_Update(t *testing.T) { Task: Task{ Text: "bulkupdated", }, - User: &User{ID: 1}, + User: &user.User{ID: 1}, }, }, { @@ -34,7 +36,7 @@ func TestBulkTask_Update(t *testing.T) { Task: Task{ Text: "bulkupdated", }, - User: &User{ID: 1}, + User: &user.User{ID: 1}, }, wantForbidden: true, }, @@ -45,13 +47,15 @@ func TestBulkTask_Update(t *testing.T) { Task: Task{ Text: "bulkupdated", }, - User: &User{ID: 1}, + User: &user.User{ID: 1}, }, wantForbidden: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + bt := &BulkTask{ IDs: tt.fields.IDs, Tasks: tt.fields.Tasks, diff --git a/pkg/models/error.go b/pkg/models/error.go index 0aa69d39..110a1198 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -46,273 +46,6 @@ func (err ErrGenericForbidden) HTTPError() web.HTTPError { return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrorCodeGenericForbidden, Message: "You're not allowed to do this."} } -// ===================== -// User Operation Errors -// ===================== - -// ErrUsernameExists represents a "UsernameAlreadyExists" kind of error. -type ErrUsernameExists struct { - UserID int64 - Username string -} - -// IsErrUsernameExists checks if an error is a ErrUsernameExists. -func IsErrUsernameExists(err error) bool { - _, ok := err.(ErrUsernameExists) - return ok -} - -func (err ErrUsernameExists) Error() string { - return fmt.Sprintf("User with that username already exists [user id: %d, username: %s]", err.UserID, err.Username) -} - -// ErrorCodeUsernameExists holds the unique world-error code of this error -const ErrorCodeUsernameExists = 1001 - -// HTTPError holds the http error description -func (err ErrUsernameExists) HTTPError() web.HTTPError { - return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUsernameExists, Message: "A user with this username already exists."} -} - -// ErrUserEmailExists represents a "UserEmailExists" kind of error. -type ErrUserEmailExists struct { - UserID int64 - Email string -} - -// IsErrUserEmailExists checks if an error is a ErrUserEmailExists. -func IsErrUserEmailExists(err error) bool { - _, ok := err.(ErrUserEmailExists) - return ok -} - -func (err ErrUserEmailExists) Error() string { - return fmt.Sprintf("User with that email already exists [user id: %d, email: %s]", err.UserID, err.Email) -} - -// ErrorCodeUserEmailExists holds the unique world-error code of this error -const ErrorCodeUserEmailExists = 1002 - -// HTTPError holds the http error description -func (err ErrUserEmailExists) HTTPError() web.HTTPError { - return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUserEmailExists, Message: "A user with this email address already exists."} -} - -// ErrNoUsernamePassword represents a "NoUsernamePassword" kind of error. -type ErrNoUsernamePassword struct{} - -// IsErrNoUsernamePassword checks if an error is a ErrNoUsernamePassword. -func IsErrNoUsernamePassword(err error) bool { - _, ok := err.(ErrNoUsernamePassword) - return ok -} - -func (err ErrNoUsernamePassword) Error() string { - return fmt.Sprintf("No username and password provided") -} - -// ErrCodeNoUsernamePassword holds the unique world-error code of this error -const ErrCodeNoUsernamePassword = 1004 - -// HTTPError holds the http error description -func (err ErrNoUsernamePassword) HTTPError() web.HTTPError { - return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeNoUsernamePassword, Message: "Please specify a username and a password."} -} - -// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error. -type ErrUserDoesNotExist struct { - UserID int64 -} - -// IsErrUserDoesNotExist checks if an error is a ErrUserDoesNotExist. -func IsErrUserDoesNotExist(err error) bool { - _, ok := err.(ErrUserDoesNotExist) - return ok -} - -func (err ErrUserDoesNotExist) Error() string { - return fmt.Sprintf("User does not exist [user id: %d]", err.UserID) -} - -// ErrCodeUserDoesNotExist holds the unique world-error code of this error -const ErrCodeUserDoesNotExist = 1005 - -// HTTPError holds the http error description -func (err ErrUserDoesNotExist) HTTPError() web.HTTPError { - return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeUserDoesNotExist, Message: "The user does not exist."} -} - -// ErrCouldNotGetUserID represents a "ErrCouldNotGetUserID" kind of error. -type ErrCouldNotGetUserID struct{} - -// IsErrCouldNotGetUserID checks if an error is a ErrCouldNotGetUserID. -func IsErrCouldNotGetUserID(err error) bool { - _, ok := err.(ErrCouldNotGetUserID) - return ok -} - -func (err ErrCouldNotGetUserID) Error() string { - return fmt.Sprintf("Could not get user ID") -} - -// ErrCodeCouldNotGetUserID holds the unique world-error code of this error -const ErrCodeCouldNotGetUserID = 1006 - -// HTTPError holds the http error description -func (err ErrCouldNotGetUserID) HTTPError() web.HTTPError { - return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCouldNotGetUserID, Message: "Could not get user id."} -} - -// ErrNoPasswordResetToken represents an error where no password reset token exists for that user -type ErrNoPasswordResetToken struct { - UserID int64 -} - -func (err ErrNoPasswordResetToken) Error() string { - return fmt.Sprintf("No token to reset a password [UserID: %d]", err.UserID) -} - -// ErrCodeNoPasswordResetToken holds the unique world-error code of this error -const ErrCodeNoPasswordResetToken = 1008 - -// HTTPError holds the http error description -func (err ErrNoPasswordResetToken) HTTPError() web.HTTPError { - return web.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() web.HTTPError { - return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidPasswordResetToken, Message: "Invalid token to reset a user's password."} -} - -// IsErrInvalidPasswordResetToken checks if an error is a ErrInvalidPasswordResetToken. -func IsErrInvalidPasswordResetToken(err error) bool { - _, ok := err.(ErrInvalidPasswordResetToken) - return ok -} - -// ErrInvalidEmailConfirmToken is an error where the email confirm token is invalid -type ErrInvalidEmailConfirmToken struct { - Token string -} - -func (err ErrInvalidEmailConfirmToken) Error() string { - return fmt.Sprintf("Invalid email confirm token [Token: %s]", err.Token) -} - -// ErrCodeInvalidEmailConfirmToken holds the unique world-error code of this error -const ErrCodeInvalidEmailConfirmToken = 1010 - -// HTTPError holds the http error description -func (err ErrInvalidEmailConfirmToken) HTTPError() web.HTTPError { - return web.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() web.HTTPError { - return web.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() web.HTTPError { - return web.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 -} - -// ErrEmptyNewPassword represents a "EmptyNewPassword" kind of error. -type ErrEmptyNewPassword struct{} - -// IsErrEmptyNewPassword checks if an error is a ErrEmptyNewPassword. -func IsErrEmptyNewPassword(err error) bool { - _, ok := err.(ErrEmptyNewPassword) - return ok -} - -func (err ErrEmptyNewPassword) Error() string { - return fmt.Sprintf("New password is empty") -} - -// ErrCodeEmptyNewPassword holds the unique world-error code of this error -const ErrCodeEmptyNewPassword = 1013 - -// HTTPError holds the http error description -func (err ErrEmptyNewPassword) HTTPError() web.HTTPError { - return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeEmptyNewPassword, Message: "Please specify new password."} -} - -// ErrEmptyOldPassword represents a "EmptyOldPassword" kind of error. -type ErrEmptyOldPassword struct{} - -// IsErrEmptyOldPassword checks if an error is a ErrEmptyOldPassword. -func IsErrEmptyOldPassword(err error) bool { - _, ok := err.(ErrEmptyOldPassword) - return ok -} - -func (err ErrEmptyOldPassword) Error() string { - return fmt.Sprintf("Old password is empty") -} - -// ErrCodeEmptyOldPassword holds the unique world-error code of this error -const ErrCodeEmptyOldPassword = 1014 - -// HTTPError holds the http error description -func (err ErrEmptyOldPassword) HTTPError() web.HTTPError { - return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeEmptyOldPassword, Message: "Please specify old password."} -} - // =================== // Empty things errors // =================== diff --git a/pkg/models/fixtures/files.yml b/pkg/models/fixtures/files.yml deleted file mode 120000 index aa889441..00000000 --- a/pkg/models/fixtures/files.yml +++ /dev/null @@ -1 +0,0 @@ -../../files/fixtures/files.yml \ No newline at end of file diff --git a/pkg/models/label.go b/pkg/models/label.go index 9f226baf..8e525cbc 100644 --- a/pkg/models/label.go +++ b/pkg/models/label.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "time" ) @@ -34,7 +35,7 @@ type Label struct { CreatedByID int64 `xorm:"int(11) not null" json:"-"` // The user who created this label - CreatedBy *User `xorm:"-" json:"created_by"` + CreatedBy *user.User `xorm:"-" json:"created_by"` // A unix timestamp when this label was created. You cannot change this value. Created int64 `xorm:"created not null" json:"created"` @@ -63,7 +64,7 @@ func (Label) TableName() string { // @Failure 500 {object} models.Message "Internal error" // @Router /labels [put] func (l *Label) Create(a web.Auth) (err error) { - u, err := getUserWithError(a) + u, err := user.GetFromAuth(a) if err != nil { return } @@ -136,7 +137,7 @@ func (l *Label) ReadAll(a web.Auth, search string, page int, perPage int) (ls in return nil, 0, 0, ErrGenericForbidden{} } - u := &User{ID: a.GetID()} + u := &user.User{ID: a.GetID()} // Get all tasks taskIDs, err := getUserTaskIDs(u) @@ -175,7 +176,7 @@ func (l *Label) ReadOne() (err error) { } *l = *label - user, err := GetUserByID(l.CreatedByID) + user, err := user.GetUserByID(l.CreatedByID) if err != nil { return err } @@ -198,7 +199,7 @@ func getLabelByIDSimple(labelID int64) (*Label, error) { } // Helper method to get all task ids a user has -func getUserTaskIDs(u *User) (taskIDs []int64, err error) { +func getUserTaskIDs(u *user.User) (taskIDs []int64, err error) { // Get all lists lists, _, _, err := getRawListsForUser("", u, -1, 0) diff --git a/pkg/models/label_rights.go b/pkg/models/label_rights.go index c7c6fe6f..9c884def 100644 --- a/pkg/models/label_rights.go +++ b/pkg/models/label_rights.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/go-xorm/builder" ) @@ -65,7 +66,7 @@ func (l *Label) hasAccessToLabel(a web.Auth) (bool, error) { // TODO: add an extra check for link share handling // Get all tasks - taskIDs, err := getUserTaskIDs(&User{ID: a.GetID()}) + taskIDs, err := getUserTaskIDs(&user.User{ID: a.GetID()}) if err != nil { return false, err } diff --git a/pkg/models/label_task.go b/pkg/models/label_task.go index bb2f57f2..6c7faa47 100644 --- a/pkg/models/label_task.go +++ b/pkg/models/label_task.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/go-xorm/builder" ) @@ -120,7 +121,7 @@ func (lt *LabelTask) ReadAll(a web.Auth, search string, page int, perPage int) ( } return getLabelsByTaskIDs(&LabelByTaskIDsOptions{ - User: &User{ID: a.GetID()}, + User: &user.User{ID: a.GetID()}, Search: search, Page: page, TaskIDs: []int64{lt.TaskID}, @@ -135,7 +136,7 @@ type labelWithTaskID struct { // LabelByTaskIDsOptions is a struct to not clutter the function with too many optional parameters. type LabelByTaskIDsOptions struct { - User *User + User *user.User Search string Page int PerPage int @@ -185,7 +186,7 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, res for _, l := range labels { userids = append(userids, l.CreatedByID) } - users := make(map[int64]*User) + users := make(map[int64]*user.User) err = x.In("id", userids).Find(&users) if err != nil { return nil, 0, 0, err @@ -290,7 +291,7 @@ func (t *Task) updateTaskLabels(creator web.Auth, labels []*Label) (err error) { return err } if !hasAccessToLabel { - user, _ := creator.(*User) + user, _ := creator.(*user.User) return ErrUserHasNoAccessToLabel{LabelID: l.ID, UserID: user.ID} } diff --git a/pkg/models/label_task_test.go b/pkg/models/label_task_test.go index 5d619857..cd497e85 100644 --- a/pkg/models/label_task_test.go +++ b/pkg/models/label_task_test.go @@ -1,6 +1,8 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "gopkg.in/d4l3k/messagediff.v1" "reflect" "runtime" @@ -37,7 +39,7 @@ func TestLabelTask_ReadAll(t *testing.T) { TaskID: 1, }, args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, wantLabels: []*labelWithTaskID{ { @@ -46,7 +48,7 @@ func TestLabelTask_ReadAll(t *testing.T) { ID: 4, Title: "Label #4 - visible via other task", CreatedByID: 2, - CreatedBy: &User{ + CreatedBy: &user.User{ ID: 2, Username: "user2", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -62,7 +64,7 @@ func TestLabelTask_ReadAll(t *testing.T) { TaskID: 14, }, args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, wantErr: true, errType: IsErrNoRightToSeeTask, @@ -73,7 +75,7 @@ func TestLabelTask_ReadAll(t *testing.T) { TaskID: 9999, }, args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, wantErr: true, errType: IsErrTaskDoesNotExist, @@ -81,6 +83,8 @@ func TestLabelTask_ReadAll(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + l := &LabelTask{ ID: tt.fields.ID, TaskID: tt.fields.TaskID, @@ -131,17 +135,17 @@ func TestLabelTask_Create(t *testing.T) { LabelID: 1, }, args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, }, { name: "already existing", fields: fields{ TaskID: 1, - LabelID: 1, + LabelID: 4, }, args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, wantErr: true, errType: IsErrLabelIsAlreadyOnTask, @@ -153,7 +157,7 @@ func TestLabelTask_Create(t *testing.T) { LabelID: 9999, }, args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, wantForbidden: true, }, @@ -164,7 +168,7 @@ func TestLabelTask_Create(t *testing.T) { LabelID: 1, }, args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, wantForbidden: true, wantErr: true, @@ -173,6 +177,8 @@ func TestLabelTask_Create(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + l := &LabelTask{ ID: tt.fields.ID, TaskID: tt.fields.TaskID, @@ -217,9 +223,9 @@ func TestLabelTask_Delete(t *testing.T) { name: "normal", fields: fields{ TaskID: 1, - LabelID: 1, + LabelID: 4, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, }, { name: "delete nonexistant", @@ -227,7 +233,7 @@ func TestLabelTask_Delete(t *testing.T) { TaskID: 1, LabelID: 1, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, wantForbidden: true, }, { @@ -236,7 +242,7 @@ func TestLabelTask_Delete(t *testing.T) { TaskID: 1, LabelID: 9999, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, wantForbidden: true, }, { @@ -245,7 +251,7 @@ func TestLabelTask_Delete(t *testing.T) { TaskID: 9999, LabelID: 1, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, wantForbidden: true, }, { @@ -254,12 +260,14 @@ func TestLabelTask_Delete(t *testing.T) { TaskID: 14, LabelID: 1, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, wantForbidden: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + l := &LabelTask{ ID: tt.fields.ID, TaskID: tt.fields.TaskID, diff --git a/pkg/models/label_test.go b/pkg/models/label_test.go index 96c2b61e..92b7537f 100644 --- a/pkg/models/label_test.go +++ b/pkg/models/label_test.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "gopkg.in/d4l3k/messagediff.v1" "reflect" "runtime" @@ -32,7 +33,7 @@ func TestLabel_ReadAll(t *testing.T) { Description string HexColor string CreatedByID int64 - CreatedBy *User + CreatedBy *user.User Created int64 Updated int64 CRUDable web.CRUDable @@ -43,7 +44,7 @@ func TestLabel_ReadAll(t *testing.T) { a web.Auth page int } - user1 := &User{ + user1 := &user.User{ ID: 1, Username: "user1", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -60,7 +61,7 @@ func TestLabel_ReadAll(t *testing.T) { { name: "normal", args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, wantLs: []*labelWithTaskID{ { @@ -85,7 +86,7 @@ func TestLabel_ReadAll(t *testing.T) { ID: 4, Title: "Label #4 - visible via other task", CreatedByID: 2, - CreatedBy: &User{ + CreatedBy: &user.User{ ID: 2, Username: "user2", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -98,7 +99,7 @@ func TestLabel_ReadAll(t *testing.T) { { name: "invalid user", args: args{ - a: &User{ID: -1}, + a: &user.User{ID: -1}, }, wantErr: true, }, @@ -136,13 +137,13 @@ func TestLabel_ReadOne(t *testing.T) { Description string HexColor string CreatedByID int64 - CreatedBy *User + CreatedBy *user.User Created int64 Updated int64 CRUDable web.CRUDable Rights web.Rights } - user1 := &User{ + user1 := &user.User{ ID: 1, Username: "user1", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -169,7 +170,7 @@ func TestLabel_ReadOne(t *testing.T) { CreatedByID: 1, CreatedBy: user1, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, }, { name: "Get nonexistant label", @@ -179,7 +180,7 @@ func TestLabel_ReadOne(t *testing.T) { wantErr: true, errType: IsErrLabelDoesNotExist, wantForbidden: true, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, }, { name: "no rights", @@ -187,7 +188,7 @@ func TestLabel_ReadOne(t *testing.T) { ID: 3, }, wantForbidden: true, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, }, { name: "Get label #4 - other user", @@ -198,14 +199,14 @@ func TestLabel_ReadOne(t *testing.T) { ID: 4, Title: "Label #4 - visible via other task", CreatedByID: 2, - CreatedBy: &User{ + CreatedBy: &user.User{ ID: 2, Username: "user2", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", AvatarURL: "ab53a2911ddf9b4817ac01ddcd3d975f", }, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, }, } for _, tt := range tests { @@ -248,7 +249,7 @@ func TestLabel_Create(t *testing.T) { Description string HexColor string CreatedByID int64 - CreatedBy *User + CreatedBy *user.User Created int64 Updated int64 CRUDable web.CRUDable @@ -272,7 +273,7 @@ func TestLabel_Create(t *testing.T) { HexColor: "ffccff", }, args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, }, } @@ -308,7 +309,7 @@ func TestLabel_Update(t *testing.T) { Description string HexColor string CreatedByID int64 - CreatedBy *User + CreatedBy *user.User Created int64 Updated int64 CRUDable web.CRUDable @@ -327,7 +328,7 @@ func TestLabel_Update(t *testing.T) { ID: 1, Title: "new and better", }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, }, { name: "nonexisting", @@ -335,7 +336,7 @@ func TestLabel_Update(t *testing.T) { ID: 99999, Title: "new and better", }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, wantForbidden: true, wantErr: true, }, @@ -345,7 +346,7 @@ func TestLabel_Update(t *testing.T) { ID: 3, Title: "new and better", }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, wantForbidden: true, }, { @@ -354,7 +355,7 @@ func TestLabel_Update(t *testing.T) { ID: 4, Title: "new and better", }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, wantForbidden: true, }, } @@ -390,7 +391,7 @@ func TestLabel_Delete(t *testing.T) { Description string HexColor string CreatedByID int64 - CreatedBy *User + CreatedBy *user.User Created int64 Updated int64 CRUDable web.CRUDable @@ -409,14 +410,14 @@ func TestLabel_Delete(t *testing.T) { fields: fields{ ID: 1, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, }, { name: "nonexisting", fields: fields{ ID: 99999, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, wantForbidden: true, // When the label does not exist, it is forbidden. We should fix this, but for everything. }, { @@ -424,7 +425,7 @@ func TestLabel_Delete(t *testing.T) { fields: fields{ ID: 3, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, wantForbidden: true, }, { @@ -432,7 +433,7 @@ func TestLabel_Delete(t *testing.T) { fields: fields{ ID: 4, }, - auth: &User{ID: 1}, + auth: &user.User{ID: 1}, wantForbidden: true, }, } diff --git a/pkg/models/link_sharing.go b/pkg/models/link_sharing.go index 9012eee8..115e3405 100644 --- a/pkg/models/link_sharing.go +++ b/pkg/models/link_sharing.go @@ -18,6 +18,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "code.vikunja.io/web" "github.com/dgrijalva/jwt-go" @@ -48,8 +49,8 @@ type LinkSharing struct { SharingType SharingType `xorm:"int(11) INDEX not null default 0" json:"sharing_type" valid:"length(0|2)" maximum:"2" default:"0"` // The user who shared this list - SharedBy *User `xorm:"-" json:"shared_by"` - SharedByID int64 `xorm:"int(11) INDEX not null" json:"-"` + SharedBy *user.User `xorm:"-" json:"shared_by"` + SharedByID int64 `xorm:"int(11) INDEX not null" json:"-"` // A unix timestamp when this list was shared. You cannot change this value. Created int64 `xorm:"created not null" json:"created"` @@ -100,7 +101,7 @@ func (share *LinkSharing) Create(a web.Auth) (err error) { share.SharedByID = a.GetID() share.Hash = utils.MakeRandomString(40) _, err = x.Insert(share) - share.SharedBy, _ = a.(*User) + share.SharedBy, _ = a.(*user.User) return } @@ -168,7 +169,7 @@ func (share *LinkSharing) ReadAll(a web.Auth, search string, page int, perPage i userIDs = append(userIDs, s.SharedByID) } - users := make(map[int64]*User) + users := make(map[int64]*user.User) err = x.In("id", userIDs).Find(&users) if err != nil { return nil, 0, 0, err diff --git a/pkg/models/list.go b/pkg/models/list.go index 0f3a4726..b18da771 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" ) @@ -36,7 +37,7 @@ type List struct { NamespaceID int64 `xorm:"int(11) INDEX not null" json:"-" param:"namespace"` // The user who created this list. - Owner *User `xorm:"-" json:"owner" valid:"-"` + Owner *user.User `xorm:"-" json:"owner" valid:"-"` // An array of tasks which belong to the list. // Deprecated: you should use the dedicated task list endpoint because it has support for pagination and filtering Tasks []*Task `xorm:"-" json:"-"` @@ -51,7 +52,7 @@ type List struct { } // GetListsByNamespaceID gets all lists in a namespace -func GetListsByNamespaceID(nID int64, doer *User) (lists []*List, err error) { +func GetListsByNamespaceID(nID int64, doer *user.User) (lists []*List, err error) { if nID == -1 { err = x.Select("l.*"). Table("list"). @@ -103,7 +104,7 @@ func (l *List) ReadAll(a web.Auth, search string, page int, perPage int) (result return lists, 0, 0, err } - lists, resultCount, totalItems, err := getRawListsForUser(search, &User{ID: a.GetID()}, page, perPage) + lists, resultCount, totalItems, err := getRawListsForUser(search, &user.User{ID: a.GetID()}, page, perPage) if err != nil { return nil, 0, 0, err } @@ -127,7 +128,7 @@ func (l *List) ReadAll(a web.Auth, search string, page int, perPage int) (result // @Router /lists/{id} [get] func (l *List) ReadOne() (err error) { // Get list owner - l.Owner, err = GetUserByID(l.OwnerID) + l.Owner, err = user.GetUserByID(l.OwnerID) return } @@ -176,8 +177,8 @@ func GetListSimplByTaskID(taskID int64) (l *List, err error) { } // Gets the lists only, without any tasks or so -func getRawListsForUser(search string, u *User, page int, perPage int) (lists []*List, resultCount int, totalItems int64, err error) { - fullUser, err := GetUserByID(u.ID) +func getRawListsForUser(search string, u *user.User, page int, perPage int) (lists []*List, resultCount int, totalItems int64, err error) { + fullUser, err := user.GetUserByID(u.ID) if err != nil { return nil, 0, 0, err } @@ -237,7 +238,7 @@ func AddListDetails(lists []*List) (err error) { } // Get all list owners - owners := []*User{} + owners := []*user.User{} err = x.In("id", ownerIDs).Find(&owners) if err != nil { return @@ -348,7 +349,7 @@ func updateListByTaskID(taskID int64) (err error) { // @Failure 500 {object} models.Message "Internal error" // @Router /namespaces/{namespaceID}/lists [put] func (l *List) Create(a web.Auth) (err error) { - doer, err := getUserWithError(a) + doer, err := user.GetFromAuth(a) if err != nil { return err } diff --git a/pkg/models/list_rights.go b/pkg/models/list_rights.go index 0a5eef8d..d3f96c8c 100644 --- a/pkg/models/list_rights.go +++ b/pkg/models/list_rights.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/go-xorm/builder" ) @@ -39,7 +40,7 @@ func (l *List) CanWrite(a web.Auth) (bool, error) { } // Check if the user is either owner or can write to the list - if originalList.isOwner(&User{ID: a.GetID()}) { + if originalList.isOwner(&user.User{ID: a.GetID()}) { return true, nil } @@ -60,7 +61,7 @@ func (l *List) CanRead(a web.Auth) (bool, error) { (shareAuth.Right == RightRead || shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), nil } - if l.isOwner(&User{ID: a.GetID()}) { + if l.isOwner(&user.User{ID: a.GetID()}) { return true, nil } return l.checkRight(a, RightRead, RightWrite, RightAdmin) @@ -100,14 +101,14 @@ func (l *List) IsAdmin(a web.Auth) (bool, error) { // Check all the things // Check if the user is either owner or can write to the list // Owners are always admins - if originalList.isOwner(&User{ID: a.GetID()}) { + if originalList.isOwner(&user.User{ID: a.GetID()}) { return true, nil } return originalList.checkRight(a, RightAdmin) } // Little helper function to check if a user is list owner -func (l *List) isOwner(u *User) bool { +func (l *List) isOwner(u *user.User) bool { return l.OwnerID == u.ID } diff --git a/pkg/models/list_team_test.go b/pkg/models/list_team_test.go index b6e9c68b..984c7069 100644 --- a/pkg/models/list_team_test.go +++ b/pkg/models/list_team_test.go @@ -17,6 +17,8 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/stretchr/testify/assert" "reflect" @@ -25,6 +27,8 @@ import ( ) func TestTeamList(t *testing.T) { + db.LoadAndAssertFixtures(t) + // Dummy relation tl := TeamList{ TeamID: 1, @@ -33,7 +37,7 @@ func TestTeamList(t *testing.T) { } // Dummyuser - u, err := GetUserByID(1) + u, err := user.GetUserByID(1) assert.NoError(t, err) // Check normal creation @@ -164,6 +168,8 @@ func TestTeamList_Update(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + tl := &TeamList{ ID: tt.fields.ID, TeamID: tt.fields.TeamID, diff --git a/pkg/models/list_test.go b/pkg/models/list_test.go index 7431b5ed..0c88428f 100644 --- a/pkg/models/list_test.go +++ b/pkg/models/list_test.go @@ -17,13 +17,15 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "reflect" "testing" ) func TestList_CreateOrUpdate(t *testing.T) { - user := &User{ + usr := &user.User{ ID: 1, Username: "user1", Email: "user1@example.com", @@ -31,41 +33,41 @@ func TestList_CreateOrUpdate(t *testing.T) { t.Run("create", func(t *testing.T) { t.Run("normal", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) list := List{ Title: "test", Description: "Lorem Ipsum", NamespaceID: 1, } - err := list.Create(user) + err := list.Create(usr) assert.NoError(t, err) }) t.Run("nonexistant namespace", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) list := List{ Title: "test", Description: "Lorem Ipsum", NamespaceID: 999999, } - err := list.Create(user) + err := list.Create(usr) assert.Error(t, err) assert.True(t, IsErrNamespaceDoesNotExist(err)) }) t.Run("nonexistant owner", func(t *testing.T) { - initFixtures(t) - user := &User{ID: 9482385} + db.LoadAndAssertFixtures(t) + usr := &user.User{ID: 9482385} list := List{ Title: "test", Description: "Lorem Ipsum", NamespaceID: 1, } - err := list.Create(user) + err := list.Create(usr) assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) + assert.True(t, user.IsErrUserDoesNotExist(err)) }) t.Run("existing identifier", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) list := List{ Title: "test", Description: "Lorem Ipsum", @@ -73,7 +75,7 @@ func TestList_CreateOrUpdate(t *testing.T) { NamespaceID: 1, } - err := list.Create(user) + err := list.Create(usr) assert.Error(t, err) assert.True(t, IsErrListIdentifierIsNotUnique(err)) }) @@ -81,7 +83,7 @@ func TestList_CreateOrUpdate(t *testing.T) { t.Run("update", func(t *testing.T) { t.Run("normal", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) list := List{ ID: 1, Title: "test", @@ -94,7 +96,7 @@ func TestList_CreateOrUpdate(t *testing.T) { }) t.Run("nonexistant", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) list := List{ ID: 99999999, Title: "test", @@ -105,7 +107,7 @@ func TestList_CreateOrUpdate(t *testing.T) { }) t.Run("existing identifier", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) list := List{ Title: "test", Description: "Lorem Ipsum", @@ -113,7 +115,7 @@ func TestList_CreateOrUpdate(t *testing.T) { NamespaceID: 1, } - err := list.Create(user) + err := list.Create(usr) assert.Error(t, err) assert.True(t, IsErrListIdentifierIsNotUnique(err)) }) @@ -121,7 +123,7 @@ func TestList_CreateOrUpdate(t *testing.T) { } func TestList_Delete(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) list := List{ ID: 1, } @@ -131,14 +133,16 @@ func TestList_Delete(t *testing.T) { func TestList_ReadAll(t *testing.T) { t.Run("all in namespace", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) // Get all lists for our namespace - lists, err := GetListsByNamespaceID(1, &User{}) + lists, err := GetListsByNamespaceID(1, &user.User{}) assert.NoError(t, err) assert.Equal(t, len(lists), 2) }) t.Run("all lists for user", func(t *testing.T) { - u := &User{ID: 1} + db.LoadAndAssertFixtures(t) + + u := &user.User{ID: 1} list := List{} lists3, _, _, err := list.ReadAll(u, "", 1, 50) @@ -148,10 +152,12 @@ func TestList_ReadAll(t *testing.T) { assert.Equal(t, 16, s.Len()) }) t.Run("lists for nonexistant user", func(t *testing.T) { - user := &User{ID: 999999} + db.LoadAndAssertFixtures(t) + + usr := &user.User{ID: 999999} list := List{} - _, _, _, err := list.ReadAll(user, "", 1, 50) + _, _, _, err := list.ReadAll(usr, "", 1, 50) assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) + assert.True(t, user.IsErrUserDoesNotExist(err)) }) } diff --git a/pkg/models/list_users.go b/pkg/models/list_users.go index f2b5a747..20564ef9 100644 --- a/pkg/models/list_users.go +++ b/pkg/models/list_users.go @@ -16,7 +16,10 @@ package models -import "code.vikunja.io/web" +import ( + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/web" +) // ListUser represents a list <-> user relation type ListUser struct { @@ -47,8 +50,8 @@ func (ListUser) TableName() string { // UserWithRight represents a user in combination with the right it can have on a list/namespace type UserWithRight struct { - User `xorm:"extends"` - Right Right `json:"right"` + user.User `xorm:"extends"` + Right Right `json:"right"` } // Create creates a new list <-> user relation @@ -80,7 +83,7 @@ func (lu *ListUser) Create(a web.Auth) (err error) { } // Check if the user exists - user, err := GetUserByUsername(lu.Username) + user, err := user.GetUserByUsername(lu.Username) if err != nil { return err } @@ -126,7 +129,7 @@ func (lu *ListUser) Create(a web.Auth) (err error) { func (lu *ListUser) Delete() (err error) { // Check if the user exists - user, err := GetUserByUsername(lu.Username) + user, err := user.GetUserByUsername(lu.Username) if err != nil { return } @@ -227,7 +230,7 @@ func (lu *ListUser) Update() (err error) { } // Check if the user exists - user, err := GetUserByUsername(lu.Username) + user, err := user.GetUserByUsername(lu.Username) if err != nil { return err } diff --git a/pkg/models/list_users_rights_test.go b/pkg/models/list_users_rights_test.go index 1fac2635..1ba105fe 100644 --- a/pkg/models/list_users_rights_test.go +++ b/pkg/models/list_users_rights_test.go @@ -17,6 +17,8 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "testing" "code.vikunja.io/web" @@ -48,7 +50,7 @@ func TestListUser_CanDoSomething(t *testing.T) { ListID: 3, }, args: args{ - a: &User{ID: 3}, + a: &user.User{ID: 3}, }, want: map[string]bool{"CanCreate": true, "CanDelete": true, "CanUpdate": true}, }, @@ -58,7 +60,7 @@ func TestListUser_CanDoSomething(t *testing.T) { ListID: 300, }, args: args{ - a: &User{ID: 3}, + a: &user.User{ID: 3}, }, want: map[string]bool{"CanCreate": false, "CanDelete": false, "CanUpdate": false}, }, @@ -68,13 +70,15 @@ func TestListUser_CanDoSomething(t *testing.T) { ListID: 3, }, args: args{ - a: &User{ID: 4}, + a: &user.User{ID: 4}, }, want: map[string]bool{"CanCreate": false, "CanDelete": false, "CanUpdate": false}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + lu := &ListUser{ ID: tt.fields.ID, UserID: tt.fields.UserID, diff --git a/pkg/models/list_users_test.go b/pkg/models/list_users_test.go index 07daafaf..ab3c61db 100644 --- a/pkg/models/list_users_test.go +++ b/pkg/models/list_users_test.go @@ -17,6 +17,8 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "gopkg.in/d4l3k/messagediff.v1" "reflect" "runtime" @@ -58,7 +60,7 @@ func TestListUser_Create(t *testing.T) { name: "ListUsers Create for duplicate", fields: fields{ Username: "user1", - ListID: 2, + ListID: 3, }, wantErr: true, errType: IsErrUserAlreadyHasAccess, @@ -89,7 +91,7 @@ func TestListUser_Create(t *testing.T) { ListID: 2, }, wantErr: true, - errType: IsErrUserDoesNotExist, + errType: user.IsErrUserDoesNotExist, }, { name: "ListUsers Create with the owner as shared user", @@ -103,6 +105,8 @@ func TestListUser_Create(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + ul := &ListUser{ ID: tt.fields.ID, UserID: tt.fields.UserID, @@ -155,11 +159,11 @@ func TestListUser_ReadAll(t *testing.T) { ListID: 3, }, args: args{ - a: &User{ID: 3}, + a: &user.User{ID: 3}, }, want: []*UserWithRight{ { - User: User{ + User: user.User{ ID: 1, Username: "user1", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -169,7 +173,7 @@ func TestListUser_ReadAll(t *testing.T) { Right: RightRead, }, { - User: User{ + User: user.User{ ID: 2, Username: "user2", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -185,7 +189,7 @@ func TestListUser_ReadAll(t *testing.T) { ListID: 3, }, args: args{ - a: &User{ID: 4}, + a: &user.User{ID: 4}, }, wantErr: true, errType: IsErrNeedToHaveListReadAccess, @@ -193,6 +197,8 @@ func TestListUser_ReadAll(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + ul := &ListUser{ ID: tt.fields.ID, UserID: tt.fields.UserID, @@ -271,6 +277,8 @@ func TestListUser_Update(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + lu := &ListUser{ ID: tt.fields.ID, Username: tt.fields.Username, @@ -316,7 +324,7 @@ func TestListUser_Delete(t *testing.T) { ListID: 2, }, wantErr: true, - errType: IsErrUserDoesNotExist, + errType: user.IsErrUserDoesNotExist, }, { name: "Try deleting a user which does not has access but exists", @@ -337,6 +345,8 @@ func TestListUser_Delete(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + lu := &ListUser{ ID: tt.fields.ID, Username: tt.fields.Username, diff --git a/pkg/models/main_test.go b/pkg/models/main_test.go index 7c1911aa..c3c912f6 100644 --- a/pkg/models/main_test.go +++ b/pkg/models/main_test.go @@ -19,6 +19,7 @@ package models import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/user" "os" "testing" ) @@ -33,7 +34,9 @@ func TestMain(m *testing.M) { // Some tests use the file engine, so we'll need to initialize that files.InitTests() - SetupTests(config.ServiceRootpath.GetString()) + user.InitTests() + + SetupTests() os.Exit(m.Run()) } diff --git a/pkg/models/models.go b/pkg/models/models.go index 33275901..4248f6a4 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -20,6 +20,7 @@ import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/user" _ "github.com/go-sql-driver/mysql" // Because. "github.com/go-xorm/xorm" @@ -33,7 +34,7 @@ var ( // GetTables returns all structs which are also a table. func GetTables() []interface{} { return []interface{}{ - &User{}, + &user.User{}, &List{}, &Task{}, &Team{}, diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index 932d0a70..7b68de05 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/imdario/mergo" "time" @@ -34,7 +35,7 @@ type Namespace struct { OwnerID int64 `xorm:"int(11) not null INDEX" json:"-"` // The user who owns this namespace - Owner *User `xorm:"-" json:"owner" valid:"-"` + Owner *user.User `xorm:"-" json:"owner" valid:"-"` // A unix timestamp when this namespace was created. You cannot change this value. Created int64 `xorm:"created not null" json:"created"` @@ -97,7 +98,7 @@ func GetNamespaceByID(id int64) (namespace Namespace, err error) { } // Get the namespace Owner - namespace.Owner, err = GetUserByID(namespace.OwnerID) + namespace.Owner, err = user.GetUserByID(namespace.OwnerID) return } @@ -115,7 +116,7 @@ func GetNamespaceByID(id int64) (namespace Namespace, err error) { // @Router /namespaces/{id} [get] func (n *Namespace) ReadOne() (err error) { // Get the namespace Owner - n.Owner, err = GetUserByID(n.OwnerID) + n.Owner, err = user.GetUserByID(n.OwnerID) return } @@ -143,7 +144,7 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r return nil, 0, 0, ErrGenericForbidden{} } - doer, err := getUserWithError(a) + doer, err := user.GetFromAuth(a) if err != nil { return nil, 0, 0, err } @@ -176,7 +177,7 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r } // Get all users - users := []*User{} + users := []*user.User{} err = x.Select("users.*"). Table("namespaces"). Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id"). @@ -301,7 +302,7 @@ func (n *Namespace) Create(a web.Auth) (err error) { n.ID = 0 // This would otherwise prevent the creation of new lists after one was created // Check if the User exists - n.Owner, err = GetUserByID(a.GetID()) + n.Owner, err = user.GetUserByID(a.GetID()) if err != nil { return } @@ -343,7 +344,7 @@ func (n *Namespace) Delete() (err error) { } // Delete all lists with their tasks - lists, err := GetListsByNamespaceID(n.ID, &User{}) + lists, err := GetListsByNamespaceID(n.ID, &user.User{}) if err != nil { return } @@ -401,7 +402,7 @@ func (n *Namespace) Update() (err error) { // Check if the (new) owner exists n.OwnerID = n.Owner.ID if currentNamespace.OwnerID != n.OwnerID { - n.Owner, err = GetUserByID(n.OwnerID) + n.Owner, err = user.GetUserByID(n.OwnerID) if err != nil { return } diff --git a/pkg/models/namespace_team_rights_test.go b/pkg/models/namespace_team_rights_test.go index 09b3d783..1634445a 100644 --- a/pkg/models/namespace_team_rights_test.go +++ b/pkg/models/namespace_team_rights_test.go @@ -17,6 +17,8 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "testing" "code.vikunja.io/web" @@ -48,7 +50,7 @@ func TestTeamNamespace_CanDoSomething(t *testing.T) { NamespaceID: 3, }, args: args{ - a: &User{ID: 3}, + a: &user.User{ID: 3}, }, want: map[string]bool{"CanCreate": true, "CanDelete": true, "CanUpdate": true}, }, @@ -58,7 +60,7 @@ func TestTeamNamespace_CanDoSomething(t *testing.T) { NamespaceID: 300, }, args: args{ - a: &User{ID: 3}, + a: &user.User{ID: 3}, }, want: map[string]bool{"CanCreate": false, "CanDelete": false, "CanUpdate": false}, }, @@ -68,13 +70,15 @@ func TestTeamNamespace_CanDoSomething(t *testing.T) { NamespaceID: 3, }, args: args{ - a: &User{ID: 4}, + a: &user.User{ID: 4}, }, want: map[string]bool{"CanCreate": false, "CanDelete": false, "CanUpdate": false}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + tn := &TeamNamespace{ ID: tt.fields.ID, TeamID: tt.fields.TeamID, diff --git a/pkg/models/namespace_team_test.go b/pkg/models/namespace_team_test.go index ef07cafc..a97ab9f1 100644 --- a/pkg/models/namespace_team_test.go +++ b/pkg/models/namespace_team_test.go @@ -17,6 +17,8 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/stretchr/testify/assert" "reflect" @@ -25,6 +27,8 @@ import ( ) func TestTeamNamespace(t *testing.T) { + db.LoadAndAssertFixtures(t) + // Dummy team <-> namespace relation tn := TeamNamespace{ TeamID: 1, @@ -32,7 +36,7 @@ func TestTeamNamespace(t *testing.T) { Right: RightAdmin, } - dummyuser, err := GetUserByID(1) + dummyuser, err := user.GetUserByID(1) assert.NoError(t, err) // Test normal creation @@ -80,7 +84,7 @@ func TestTeamNamespace(t *testing.T) { assert.True(t, IsErrNamespaceDoesNotExist(err)) // Check with no right to read the namespace - nouser := &User{ID: 393} + nouser := &user.User{ID: 393} _, _, _, err = tn.ReadAll(nouser, "", 1, 50) assert.Error(t, err) assert.True(t, IsErrNeedToHaveNamespaceReadAccess(err)) @@ -156,6 +160,8 @@ func TestTeamNamespace_Update(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + tl := &TeamNamespace{ ID: tt.fields.ID, TeamID: tt.fields.TeamID, diff --git a/pkg/models/namespace_test.go b/pkg/models/namespace_test.go index 81b4ef99..d510a768 100644 --- a/pkg/models/namespace_test.go +++ b/pkg/models/namespace_test.go @@ -17,12 +17,16 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "reflect" "testing" ) func TestNamespace_Create(t *testing.T) { + db.LoadAndAssertFixtures(t) + // Create test database //assert.NoError(t, LoadFixtures()) @@ -33,7 +37,7 @@ func TestNamespace_Create(t *testing.T) { } // Doer - doer, err := GetUserByID(1) + doer, err := user.GetUserByID(1) assert.NoError(t, err) // Try creating it @@ -57,11 +61,11 @@ func TestNamespace_Create(t *testing.T) { assert.True(t, IsErrNamespaceNameCannotBeEmpty(err)) // Try inserting one with a nonexistant user - nUser := &User{ID: 9482385} + nUser := &user.User{ID: 9482385} dnsp2 := dummynamespace err = dnsp2.Create(nUser) assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) + assert.True(t, user.IsErrUserDoesNotExist(err)) // Update it allowed, err = dummynamespace.CanUpdate(doer) @@ -85,7 +89,7 @@ func TestNamespace_Create(t *testing.T) { dummynamespace.Owner.ID = 94829838572 err = dummynamespace.Update() assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) + assert.True(t, user.IsErrUserDoesNotExist(err)) // Try updating without a name dummynamespace.Name = "" diff --git a/pkg/models/namespace_users.go b/pkg/models/namespace_users.go index b311b51f..b6256316 100644 --- a/pkg/models/namespace_users.go +++ b/pkg/models/namespace_users.go @@ -16,7 +16,10 @@ package models -import "code.vikunja.io/web" +import ( + user2 "code.vikunja.io/api/pkg/user" + "code.vikunja.io/web" +) // NamespaceUser represents a namespace <-> user relation type NamespaceUser struct { @@ -75,7 +78,7 @@ func (nu *NamespaceUser) Create(a web.Auth) (err error) { } // Check if the user exists - user, err := GetUserByUsername(nu.Username) + user, err := user2.GetUserByUsername(nu.Username) if err != nil { return err } @@ -117,7 +120,7 @@ func (nu *NamespaceUser) Create(a web.Auth) (err error) { func (nu *NamespaceUser) Delete() (err error) { // Check if the user exists - user, err := GetUserByUsername(nu.Username) + user, err := user2.GetUserByUsername(nu.Username) if err != nil { return } @@ -213,7 +216,7 @@ func (nu *NamespaceUser) Update() (err error) { } // Check if the user exists - user, err := GetUserByUsername(nu.Username) + user, err := user2.GetUserByUsername(nu.Username) if err != nil { return err } diff --git a/pkg/models/namespace_users_rights_test.go b/pkg/models/namespace_users_rights_test.go index 20de120f..b48586c3 100644 --- a/pkg/models/namespace_users_rights_test.go +++ b/pkg/models/namespace_users_rights_test.go @@ -17,6 +17,8 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "testing" "code.vikunja.io/web" @@ -48,7 +50,7 @@ func TestNamespaceUser_CanDoSomething(t *testing.T) { NamespaceID: 3, }, args: args{ - a: &User{ID: 3}, + a: &user.User{ID: 3}, }, want: map[string]bool{"CanCreate": true, "CanDelete": true, "CanUpdate": true}, }, @@ -58,7 +60,7 @@ func TestNamespaceUser_CanDoSomething(t *testing.T) { NamespaceID: 300, }, args: args{ - a: &User{ID: 3}, + a: &user.User{ID: 3}, }, want: map[string]bool{"CanCreate": false, "CanDelete": false, "CanUpdate": false}, }, @@ -68,13 +70,15 @@ func TestNamespaceUser_CanDoSomething(t *testing.T) { NamespaceID: 3, }, args: args{ - a: &User{ID: 4}, + a: &user.User{ID: 4}, }, want: map[string]bool{"CanCreate": false, "CanDelete": false, "CanUpdate": false}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + nu := &NamespaceUser{ ID: tt.fields.ID, UserID: tt.fields.UserID, diff --git a/pkg/models/namespace_users_test.go b/pkg/models/namespace_users_test.go index 4bcb49d9..0fc7fd8f 100644 --- a/pkg/models/namespace_users_test.go +++ b/pkg/models/namespace_users_test.go @@ -17,6 +17,8 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "gopkg.in/d4l3k/messagediff.v1" "reflect" @@ -56,7 +58,7 @@ func TestNamespaceUser_Create(t *testing.T) { name: "NamespaceUsers Create for duplicate", fields: fields{ Username: "user1", - NamespaceID: 2, + NamespaceID: 3, }, wantErr: true, errType: IsErrUserAlreadyHasNamespaceAccess, @@ -87,7 +89,7 @@ func TestNamespaceUser_Create(t *testing.T) { NamespaceID: 2, }, wantErr: true, - errType: IsErrUserDoesNotExist, + errType: user.IsErrUserDoesNotExist, }, { name: "NamespaceUsers Create with the owner as shared user", @@ -101,6 +103,8 @@ func TestNamespaceUser_Create(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + un := &NamespaceUser{ ID: tt.fields.ID, Username: tt.fields.Username, @@ -152,11 +156,11 @@ func TestNamespaceUser_ReadAll(t *testing.T) { NamespaceID: 3, }, args: args{ - a: &User{ID: 3}, + a: &user.User{ID: 3}, }, want: []*UserWithRight{ { - User: User{ + User: user.User{ ID: 1, Username: "user1", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -166,7 +170,7 @@ func TestNamespaceUser_ReadAll(t *testing.T) { Right: RightRead, }, { - User: User{ + User: user.User{ ID: 2, Username: "user2", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -182,7 +186,7 @@ func TestNamespaceUser_ReadAll(t *testing.T) { NamespaceID: 3, }, args: args{ - a: &User{ID: 4}, + a: &user.User{ID: 4}, }, wantErr: true, errType: IsErrNeedToHaveNamespaceReadAccess, @@ -190,6 +194,8 @@ func TestNamespaceUser_ReadAll(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + un := &NamespaceUser{ ID: tt.fields.ID, UserID: tt.fields.UserID, @@ -269,6 +275,8 @@ func TestNamespaceUser_Update(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + nu := &NamespaceUser{ ID: tt.fields.ID, Username: tt.fields.Username, @@ -314,7 +322,7 @@ func TestNamespaceUser_Delete(t *testing.T) { NamespaceID: 2, }, wantErr: true, - errType: IsErrUserDoesNotExist, + errType: user.IsErrUserDoesNotExist, }, { name: "Try deleting a user which does not has access but exists", @@ -335,6 +343,8 @@ func TestNamespaceUser_Delete(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + nu := &NamespaceUser{ ID: tt.fields.ID, Username: tt.fields.Username, diff --git a/pkg/models/task_assignees.go b/pkg/models/task_assignees.go index b588a649..2a59569a 100644 --- a/pkg/models/task_assignees.go +++ b/pkg/models/task_assignees.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" ) @@ -38,8 +39,8 @@ func (TaskAssginee) TableName() string { // TaskAssigneeWithUser is a helper type to deal with user joins type TaskAssigneeWithUser struct { - TaskID int64 - User `xorm:"extends"` + TaskID int64 + user.User `xorm:"extends"` } func getRawTaskAssigneesForTasks(taskIDs []int64) (taskAssignees []*TaskAssigneeWithUser, err error) { @@ -53,7 +54,7 @@ func getRawTaskAssigneesForTasks(taskIDs []int64) (taskAssignees []*TaskAssignee } // Create or update a bunch of task assignees -func (t *Task) updateTaskAssignees(assignees []*User) (err error) { +func (t *Task) updateTaskAssignees(assignees []*user.User) (err error) { // Load the current assignees currentAssignees, err := getRawTaskAssigneesForTasks([]int64{t.ID}) @@ -61,7 +62,7 @@ func (t *Task) updateTaskAssignees(assignees []*User) (err error) { return err } - t.Assignees = make([]*User, 0, len(currentAssignees)) + t.Assignees = make([]*user.User, 0, len(currentAssignees)) for _, assignee := range currentAssignees { t.Assignees = append(t.Assignees, &assignee.User) } @@ -80,7 +81,7 @@ func (t *Task) updateTaskAssignees(assignees []*User) (err error) { } // Make a hashmap of the new assignees for easier comparison - newAssignees := make(map[int64]*User, len(assignees)) + newAssignees := make(map[int64]*user.User, len(assignees)) for _, newAssignee := range assignees { newAssignees[newAssignee.ID] = newAssignee } @@ -88,7 +89,7 @@ func (t *Task) updateTaskAssignees(assignees []*User) (err error) { // Get old assignees to delete var found bool var assigneesToDelete []int64 - oldAssignees := make(map[int64]*User, len(t.Assignees)) + oldAssignees := make(map[int64]*user.User, len(t.Assignees)) for _, oldAssignee := range t.Assignees { found = false if newAssignees[oldAssignee.ID] != nil { @@ -142,7 +143,7 @@ func (t *Task) updateTaskAssignees(assignees []*User) (err error) { } // Small helper functions to set the new assignees in various places -func (t *Task) setTaskAssignees(assignees []*User) { +func (t *Task) setTaskAssignees(assignees []*user.User) { if len(assignees) == 0 { t.Assignees = nil return @@ -200,7 +201,7 @@ func (la *TaskAssginee) Create(a web.Auth) (err error) { func (t *Task) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) { // Check if the user exists and has access to the list - newAssignee, err := GetUserByID(newAssigneeID) + newAssignee, err := user.GetUserByID(newAssigneeID) if err != nil { return err } @@ -252,7 +253,7 @@ func (la *TaskAssginee) ReadAll(a web.Auth, search string, page int, perPage int return nil, 0, 0, ErrGenericForbidden{} } - var taskAssignees []*User + var taskAssignees []*user.User err = x.Table("task_assignees"). Select("users.*"). Join("INNER", "users", "task_assignees.user_id = users.id"). @@ -267,15 +268,15 @@ func (la *TaskAssginee) ReadAll(a web.Auth, search string, page int, perPage int Select("users.*"). Join("INNER", "users", "task_assignees.user_id = users.id"). Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%"). - Count(&User{}) + Count(&user.User{}) return taskAssignees, len(taskAssignees), numberOfTotalItems, err } // BulkAssignees is a helper struct used to update multiple assignees at once. type BulkAssignees struct { // A list with all assignees - Assignees []*User `json:"assignees"` - TaskID int64 `json:"-" param:"listtask"` + Assignees []*user.User `json:"assignees"` + TaskID int64 `json:"-" param:"listtask"` web.CRUDable `json:"-"` web.Rights `json:"-"` diff --git a/pkg/models/task_attachment.go b/pkg/models/task_attachment.go index b62f8952..0ff9e9bf 100644 --- a/pkg/models/task_attachment.go +++ b/pkg/models/task_attachment.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "io" "time" @@ -29,8 +30,8 @@ type TaskAttachment struct { TaskID int64 `xorm:"int(11) not null" json:"task_id" param:"task"` FileID int64 `xorm:"int(11) not null" json:"-"` - CreatedByID int64 `xorm:"int(11) not null" json:"-"` - CreatedBy *User `xorm:"-" json:"created_by"` + CreatedByID int64 `xorm:"int(11) not null" json:"-"` + CreatedBy *user.User `xorm:"-" json:"created_by"` File *files.File `xorm:"-" json:"file"` @@ -132,7 +133,7 @@ func (ta *TaskAttachment) ReadAll(a web.Auth, search string, page int, perPage i return nil, 0, 0, err } - us := make(map[int64]*User) + us := make(map[int64]*user.User) err = x.In("id", userIDs).Find(&us) if err != nil { return nil, 0, 0, err diff --git a/pkg/models/task_attachment_test.go b/pkg/models/task_attachment_test.go index cdb863e9..292405ca 100644 --- a/pkg/models/task_attachment_test.go +++ b/pkg/models/task_attachment_test.go @@ -20,6 +20,7 @@ package models import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "io" "os" @@ -95,7 +96,7 @@ func TestTaskAttachment_NewAttachment(t *testing.T) { tf := &testfile{ content: []byte("testingstuff"), } - testuser := &User{ID: 1} + testuser := &user.User{ID: 1} err := ta.NewAttachment(tf, "testfile", 100, testuser) assert.NoError(t, err) @@ -119,7 +120,7 @@ func TestTaskAttachment_NewAttachment(t *testing.T) { func TestTaskAttachment_ReadAll(t *testing.T) { files.InitTestFileFixtures(t) ta := &TaskAttachment{TaskID: 1} - as, _, _, err := ta.ReadAll(&User{ID: 1}, "", 0, 50) + as, _, _, err := ta.ReadAll(&user.User{ID: 1}, "", 0, 50) attachments, _ := as.([]*TaskAttachment) assert.NoError(t, err) assert.Len(t, attachments, 3) @@ -152,7 +153,7 @@ func TestTaskAttachment_Delete(t *testing.T) { } func TestTaskAttachment_Rights(t *testing.T) { - u := &User{ID: 1} + u := &user.User{ID: 1} t.Run("Can Read", func(t *testing.T) { t.Run("Allowed", func(t *testing.T) { ta := &TaskAttachment{TaskID: 1} diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index 73840c76..de19f646 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -18,6 +18,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "time" ) @@ -108,7 +109,7 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i // If the list ID is not set, we get all tasks for the user. // This allows to use this function in Task.ReadAll with a possibility to deprecate the latter at some point. if tf.ListID == 0 { - tf.Lists, _, _, err = getRawListsForUser("", &User{ID: a.GetID()}, -1, 0) + tf.Lists, _, _, err = getRawListsForUser("", &user.User{ID: a.GetID()}, -1, 0) if err != nil { return nil, 0, 0, err } diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 26807a6c..d3b440b6 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -19,30 +19,28 @@ package models import ( "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" - "github.com/stretchr/testify/assert" "gopkg.in/d4l3k/messagediff.v1" "testing" ) func TestTaskCollection_ReadAll(t *testing.T) { - assert.NoError(t, db.LoadFixtures()) - // Dummy users - user1 := &User{ + user1 := &user.User{ ID: 1, Username: "user1", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", // hash for "" } - user2 := &User{ + user2 := &user.User{ ID: 2, Username: "user2", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", AvatarURL: "ab53a2911ddf9b4817ac01ddcd3d975f", // hash for "" } - user6 := &User{ + user6 := &user.User{ ID: 6, Username: "user6", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -463,7 +461,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedByID: 1, CreatedBy: user1, ListID: 1, - Assignees: []*User{ + Assignees: []*user.User{ user1, user2, }, @@ -538,7 +536,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { fields: fields{}, args: args{ search: "", - a: &User{ID: 1}, + a: &user.User{ID: 1}, page: 0, }, want: []*Task{ @@ -585,7 +583,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { }, args: args{ search: "", - a: &User{ID: 1}, + a: &user.User{ID: 1}, page: 0, }, want: []*Task{ @@ -631,7 +629,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { }, args: args{ search: "", - a: &User{ID: 1}, + a: &user.User{ID: 1}, page: 0, }, want: []*Task{ @@ -648,7 +646,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { }, args: args{ search: "", - a: &User{ID: 1}, + a: &user.User{ID: 1}, page: 0, }, want: []*Task{ @@ -664,7 +662,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { }, args: args{ search: "", - a: &User{ID: 1}, + a: &user.User{ID: 1}, page: 0, }, want: []*Task{ @@ -677,6 +675,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + lt := &TaskCollection{ ListID: tt.fields.ListID, StartDateSortUnix: tt.fields.StartDateSortUnix, diff --git a/pkg/models/task_relation.go b/pkg/models/task_relation.go index c11d9911..75b8b4fe 100644 --- a/pkg/models/task_relation.go +++ b/pkg/models/task_relation.go @@ -18,6 +18,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" ) @@ -83,7 +84,7 @@ type TaskRelation struct { CreatedByID int64 `xorm:"int(11) not null" json:"-"` // The user who created this relation - CreatedBy *User `xorm:"-" json:"created_by"` + CreatedBy *user.User `xorm:"-" json:"created_by"` // A unix timestamp when this label was created. You cannot change this value. Created int64 `xorm:"created not null" json:"created"` diff --git a/pkg/models/task_relation_test.go b/pkg/models/task_relation_test.go index 52f6c4f4..b804f0e3 100644 --- a/pkg/models/task_relation_test.go +++ b/pkg/models/task_relation_test.go @@ -18,45 +18,55 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "testing" ) func TestTaskRelation_Create(t *testing.T) { t.Run("Normal", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 1, OtherTaskID: 2, RelationKind: RelationKindSubtask, } - err := rel.Create(&User{ID: 1}) + err := rel.Create(&user.User{ID: 1}) assert.NoError(t, err) }) t.Run("Two Tasks In Different Lists", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 1, OtherTaskID: 13, RelationKind: RelationKindSubtask, } - err := rel.Create(&User{ID: 1}) + err := rel.Create(&user.User{ID: 1}) assert.NoError(t, err) }) t.Run("Already Existing", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 1, OtherTaskID: 29, RelationKind: RelationKindSubtask, } - err := rel.Create(&User{ID: 1}) + err := rel.Create(&user.User{ID: 1}) assert.Error(t, err) assert.True(t, IsErrRelationAlreadyExists(err)) }) t.Run("Same Task", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 1, OtherTaskID: 1, } - err := rel.Create(&User{ID: 1}) + err := rel.Create(&user.User{ID: 1}) assert.Error(t, err) assert.True(t, IsErrRelationTasksCannotBeTheSame(err)) }) @@ -64,6 +74,8 @@ func TestTaskRelation_Create(t *testing.T) { func TestTaskRelation_Delete(t *testing.T) { t.Run("Normal", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 1, OtherTaskID: 29, @@ -73,6 +85,8 @@ func TestTaskRelation_Delete(t *testing.T) { assert.NoError(t, err) }) t.Run("Not existing", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 9999, OtherTaskID: 3, @@ -86,73 +100,87 @@ func TestTaskRelation_Delete(t *testing.T) { func TestTaskRelation_CanCreate(t *testing.T) { t.Run("Normal", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 1, OtherTaskID: 2, RelationKind: RelationKindSubtask, } - can, err := rel.CanCreate(&User{ID: 1}) + can, err := rel.CanCreate(&user.User{ID: 1}) assert.NoError(t, err) assert.True(t, can) }) t.Run("Two tasks on different lists", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 1, OtherTaskID: 13, RelationKind: RelationKindSubtask, } - can, err := rel.CanCreate(&User{ID: 1}) + can, err := rel.CanCreate(&user.User{ID: 1}) assert.NoError(t, err) assert.True(t, can) }) t.Run("No update rights on base task", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 14, OtherTaskID: 1, RelationKind: RelationKindSubtask, } - can, err := rel.CanCreate(&User{ID: 1}) + can, err := rel.CanCreate(&user.User{ID: 1}) assert.NoError(t, err) assert.False(t, can) }) t.Run("No update rights on base task, but read rights", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 15, OtherTaskID: 1, RelationKind: RelationKindSubtask, } - can, err := rel.CanCreate(&User{ID: 1}) + can, err := rel.CanCreate(&user.User{ID: 1}) assert.NoError(t, err) assert.False(t, can) }) t.Run("No read rights on other task", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 1, OtherTaskID: 14, RelationKind: RelationKindSubtask, } - can, err := rel.CanCreate(&User{ID: 1}) + can, err := rel.CanCreate(&user.User{ID: 1}) assert.NoError(t, err) assert.False(t, can) }) t.Run("Nonexisting base task", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 999999, OtherTaskID: 1, RelationKind: RelationKindSubtask, } - can, err := rel.CanCreate(&User{ID: 1}) + can, err := rel.CanCreate(&user.User{ID: 1}) assert.Error(t, err) assert.True(t, IsErrTaskDoesNotExist(err)) assert.False(t, can) }) t.Run("Nonexisting other task", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + rel := TaskRelation{ TaskID: 1, OtherTaskID: 999999, RelationKind: RelationKindSubtask, } - can, err := rel.CanCreate(&User{ID: 1}) + can, err := rel.CanCreate(&user.User{ID: 1}) assert.Error(t, err) assert.True(t, IsErrTaskDoesNotExist(err)) assert.False(t, can) diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index ee99e6ef..a6469c81 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -19,6 +19,7 @@ package models import ( "code.vikunja.io/api/pkg/files" "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "code.vikunja.io/web" "github.com/imdario/mergo" @@ -55,7 +56,7 @@ type Task struct { // When this task ends. EndDateUnix int64 `xorm:"int(11) INDEX null" json:"endDate" query:"-"` // An array of users who are assigned to this task - Assignees []*User `xorm:"-" json:"assignees"` + Assignees []*user.User `xorm:"-" json:"assignees"` // An array of labels which are associated with this task. Labels []*Label `xorm:"-" json:"labels"` // The task color in hex @@ -87,7 +88,7 @@ type Task struct { Updated int64 `xorm:"updated not null" json:"updated"` // The user who initially created the task. - CreatedBy *User `xorm:"-" json:"createdBy" valid:"-"` + CreatedBy *user.User `xorm:"-" json:"createdBy" valid:"-"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` @@ -365,7 +366,7 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (tasks []*Task, err error) { // Get all users of a task // aka the ones who created a task - users := make(map[int64]*User) + users := make(map[int64]*user.User) err = x.In("id", userIDs).Find(&users) if err != nil { return @@ -487,7 +488,7 @@ func (t *Task) Create(a web.Auth) (err error) { return } - u, err := GetUserByID(a.GetID()) + u, err := user.GetUserByID(a.GetID()) if err != nil { return err } diff --git a/pkg/models/tasks_test.go b/pkg/models/tasks_test.go index 2d40bf9e..af6ba540 100644 --- a/pkg/models/tasks_test.go +++ b/pkg/models/tasks_test.go @@ -17,12 +17,14 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "testing" ) func TestTask_Create(t *testing.T) { - user := &User{ + usr := &user.User{ ID: 1, Username: "user1", Email: "user1@example.com", @@ -31,13 +33,13 @@ func TestTask_Create(t *testing.T) { // We only test creating a task here, the rights are all well tested in the integration tests. t.Run("normal", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) task := &Task{ Text: "Lorem", Description: "Lorem Ipsum Dolor", ListID: 1, } - err := task.Create(user) + err := task.Create(usr) assert.NoError(t, err) // Assert getting a uid assert.NotEmpty(t, task.UID) @@ -47,30 +49,30 @@ func TestTask_Create(t *testing.T) { }) t.Run("empty text", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) task := &Task{ Text: "", Description: "Lorem Ipsum Dolor", ListID: 1, } - err := task.Create(user) + err := task.Create(usr) assert.Error(t, err) assert.True(t, IsErrTaskCannotBeEmpty(err)) }) t.Run("nonexistant list", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) task := &Task{ Text: "Test", Description: "Lorem Ipsum Dolor", ListID: 9999999, } - err := task.Create(user) + err := task.Create(usr) assert.Error(t, err) assert.True(t, IsErrListDoesNotExist(err)) }) t.Run("noneixtant user", func(t *testing.T) { - initFixtures(t) - nUser := &User{ID: 99999999} + db.LoadAndAssertFixtures(t) + nUser := &user.User{ID: 99999999} task := &Task{ Text: "Test", Description: "Lorem Ipsum Dolor", @@ -78,13 +80,13 @@ func TestTask_Create(t *testing.T) { } err := task.Create(nUser) assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) + assert.True(t, user.IsErrUserDoesNotExist(err)) }) } func TestTask_Update(t *testing.T) { t.Run("normal", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) task := &Task{ ID: 1, Text: "test10000", @@ -95,7 +97,7 @@ func TestTask_Update(t *testing.T) { assert.NoError(t, err) }) t.Run("nonexistant task", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) task := &Task{ ID: 9999999, Text: "test10000", @@ -110,7 +112,7 @@ func TestTask_Update(t *testing.T) { func TestTask_Delete(t *testing.T) { t.Run("normal", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) task := &Task{ ID: 1, } @@ -121,12 +123,14 @@ func TestTask_Delete(t *testing.T) { func TestUpdateDone(t *testing.T) { t.Run("marking a task as done", func(t *testing.T) { + db.LoadAndAssertFixtures(t) oldTask := &Task{Done: false} newTask := &Task{Done: true} updateDone(oldTask, newTask) assert.NotEqual(t, int64(0), oldTask.DoneAtUnix) }) t.Run("unmarking a task as done", func(t *testing.T) { + db.LoadAndAssertFixtures(t) oldTask := &Task{Done: true} newTask := &Task{Done: false} updateDone(oldTask, newTask) @@ -136,14 +140,14 @@ func TestUpdateDone(t *testing.T) { func TestTask_ReadOne(t *testing.T) { t.Run("default", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) task := &Task{ID: 1} err := task.ReadOne() assert.NoError(t, err) assert.Equal(t, "task #1", task.Text) }) t.Run("nonexisting", func(t *testing.T) { - initFixtures(t) + db.LoadAndAssertFixtures(t) task := &Task{ID: 99999} err := task.ReadOne() assert.Error(t, err) diff --git a/pkg/models/team_members.go b/pkg/models/team_members.go index 39acedc9..9fb2f81f 100644 --- a/pkg/models/team_members.go +++ b/pkg/models/team_members.go @@ -16,7 +16,10 @@ package models -import "code.vikunja.io/web" +import ( + user2 "code.vikunja.io/api/pkg/user" + "code.vikunja.io/web" +) // Create implements the create method to assign a user to a team // @Summary Add a user to a team @@ -41,7 +44,7 @@ func (tm *TeamMember) Create(a web.Auth) (err error) { } // Check if the user exists - user, err := GetUserByUsername(tm.Username) + user, err := user2.GetUserByUsername(tm.Username) if err != nil { return } @@ -84,7 +87,7 @@ func (tm *TeamMember) Delete() (err error) { } // Find the numeric user id - user, err := GetUserByUsername(tm.Username) + user, err := user2.GetUserByUsername(tm.Username) if err != nil { return } diff --git a/pkg/models/team_members_test.go b/pkg/models/team_members_test.go index 3d43ff39..4f9a4a30 100644 --- a/pkg/models/team_members_test.go +++ b/pkg/models/team_members_test.go @@ -17,11 +17,14 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "testing" ) func TestTeamMember_Create(t *testing.T) { + db.LoadAndAssertFixtures(t) // Dummy team member dummyteammember := TeamMember{ @@ -30,7 +33,7 @@ func TestTeamMember_Create(t *testing.T) { } // Doer - doer, err := GetUserByID(1) + doer, err := user.GetUserByID(1) assert.NoError(t, err) // Insert a new team member @@ -71,7 +74,7 @@ func TestTeamMember_Create(t *testing.T) { dummyteammember.Username = "user9484" err = dummyteammember.Create(doer) assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) + assert.True(t, user.IsErrUserDoesNotExist(err)) // Try adding a user to a team which does not exist tm = TeamMember{TeamID: 94824, Username: "user1"} diff --git a/pkg/models/teams.go b/pkg/models/teams.go index db8a6d4d..b77047f7 100644 --- a/pkg/models/teams.go +++ b/pkg/models/teams.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" ) @@ -32,7 +33,7 @@ type Team struct { CreatedByID int64 `xorm:"int(11) not null INDEX" json:"-"` // The user who created this team. - CreatedBy *User `xorm:"-" json:"createdBy"` + CreatedBy *user.User `xorm:"-" json:"createdBy"` // An array of all members in this team. Members []*TeamUser `xorm:"-" json:"members"` @@ -53,7 +54,7 @@ func (Team) TableName() string { // AfterLoad gets the created by user object func (t *Team) AfterLoad() { // Get the owner - t.CreatedBy, _ = GetUserByID(t.CreatedByID) + t.CreatedBy, _ = user.GetUserByID(t.CreatedByID) // Get all members x.Select("*"). @@ -90,7 +91,7 @@ func (TeamMember) TableName() string { // TeamUser is the team member type type TeamUser struct { - User `xorm:"extends"` + user.User `xorm:"extends"` // Whether or not the member is an admin of the team. See the docs for more about what a team admin can do Admin bool `json:"admin"` } @@ -181,7 +182,7 @@ func (t *Team) ReadAll(a web.Auth, search string, page int, perPage int) (result // @Failure 500 {object} models.Message "Internal error" // @Router /teams [put] func (t *Team) Create(a web.Auth) (err error) { - doer, err := getUserWithError(a) + doer, err := user.GetFromAuth(a) if err != nil { return err } diff --git a/pkg/models/teams_rights_test.go b/pkg/models/teams_rights_test.go index 45ac3446..3c8c27dc 100644 --- a/pkg/models/teams_rights_test.go +++ b/pkg/models/teams_rights_test.go @@ -17,6 +17,8 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "testing" "code.vikunja.io/web" @@ -28,7 +30,7 @@ func TestTeam_CanDoSomething(t *testing.T) { Name string Description string CreatedByID int64 - CreatedBy *User + CreatedBy *user.User Members []*TeamUser Created int64 Updated int64 @@ -50,7 +52,7 @@ func TestTeam_CanDoSomething(t *testing.T) { ID: 1, }, args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, want: map[string]bool{"CanCreate": true, "IsAdmin": true, "CanRead": true, "CanDelete": true, "CanUpdate": true}, }, @@ -60,7 +62,7 @@ func TestTeam_CanDoSomething(t *testing.T) { ID: 300, }, args: args{ - a: &User{ID: 1}, + a: &user.User{ID: 1}, }, want: map[string]bool{"CanCreate": true, "IsAdmin": false, "CanRead": false, "CanDelete": false, "CanUpdate": false}, }, @@ -70,13 +72,15 @@ func TestTeam_CanDoSomething(t *testing.T) { ID: 1, }, args: args{ - a: &User{ID: 4}, + a: &user.User{ID: 4}, }, want: map[string]bool{"CanCreate": true, "IsAdmin": false, "CanRead": false, "CanDelete": false, "CanUpdate": false}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + tm := &Team{ ID: tt.fields.ID, Name: tt.fields.Name, diff --git a/pkg/models/teams_test.go b/pkg/models/teams_test.go index 01d7360b..e08f89a5 100644 --- a/pkg/models/teams_test.go +++ b/pkg/models/teams_test.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "reflect" "testing" @@ -30,7 +31,7 @@ func TestTeam_Create(t *testing.T) { } // Doer - doer, err := GetUserByID(1) + doer, err := user.GetUserByID(1) assert.NoError(t, err) // Insert it diff --git a/pkg/models/unit_tests.go b/pkg/models/unit_tests.go index 84fa0979..57d00869 100644 --- a/pkg/models/unit_tests.go +++ b/pkg/models/unit_tests.go @@ -17,78 +17,49 @@ package models import ( - "code.vikunja.io/api/pkg/config" _ "code.vikunja.io/api/pkg/config" // To trigger its init() which initializes the config "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/mail" - "fmt" - "github.com/go-xorm/xorm" - "github.com/stretchr/testify/assert" - "gopkg.in/testfixtures.v2" - "os" - "path/filepath" - "testing" ) // SetupTests takes care of seting up the db, fixtures etc. // This is an extra function to be able to call the fixtures setup from the integration tests. -func SetupTests(pathToRoot string) { +func SetupTests() { var err error - fixturesDir := filepath.Join(pathToRoot, "pkg", "models", "fixtures") - if err = createTestEngine(fixturesDir); err != nil { - log.Fatalf("Error creating test engine: %v\n", err) + x, err = db.CreateTestEngine() + if err != nil { + log.Fatal(err) + } + + err = x.Sync2(GetTables()...) + if err != nil { + log.Fatal(err) + } + + err = db.InitTestFixtures( + "files", + "label_task", + "labels", + "link_sharing", + "list", + "namespaces", + "task_assignees", + "task_attachments", + "task_relations", + "task_reminders", + "tasks", + "team_list", + "team_members", + "team_namespaces", + "teams", + "users", + "users_list", + "users_namespace") + if err != nil { + log.Fatal(err) } // Start the pseudo mail queue mail.StartMailDaemon() - - // Create test database - if err = db.LoadFixtures(); err != nil { - log.Fatalf("Error preparing test database: %v", err.Error()) - } -} - -func createTestEngine(fixturesDir string) error { - var err error - var fixturesHelper testfixtures.Helper = &testfixtures.SQLite{} - // If set, use the config we provided instead of normal - if os.Getenv("VIKUNJA_TESTS_USE_CONFIG") == "1" { - x, err = db.CreateTestEngine() - if err != nil { - return fmt.Errorf("error getting test engine: %v", err) - } - - err = initSchema(x) - if err != nil { - return err - } - - if config.DatabaseType.GetString() == "mysql" { - fixturesHelper = &testfixtures.MySQL{} - } - } else { - x, err = db.CreateTestEngine() - if err != nil { - return fmt.Errorf("error getting test engine: %v", err) - } - - // Sync dat shit - err = initSchema(x) - if err != nil { - return fmt.Errorf("sync database struct error: %v", err) - } - } - - return db.InitFixtures(fixturesHelper, fixturesDir) -} - -func initSchema(tx *xorm.Engine) error { - return tx.Sync2(GetTables()...) -} - -func initFixtures(t *testing.T) { - // Init db fixtures - err := db.LoadFixtures() - assert.NoError(t, err) } diff --git a/pkg/models/users_list.go b/pkg/models/user_list.go similarity index 79% rename from pkg/models/users_list.go rename to pkg/models/user_list.go index c0aefe12..d23b993c 100644 --- a/pkg/models/users_list.go +++ b/pkg/models/user_list.go @@ -1,40 +1,26 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018-2020 Vikunja and contributors. All rights reserved. +// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved. // -// This program is free software: you can redistribute it and/or modify +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// This program is distributed in the hope that it will be useful, +// Vikunja is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with this program. If not, see . +// along with Vikunja. If not, see . package models -import "github.com/go-xorm/builder" - -// ListUsers returns a list with all users, filtered by an optional searchstring -func ListUsers(searchterm string) (users []User, err error) { - - if searchterm == "" { - err = x.Find(&users) - } else { - err = x. - Where("username LIKE ?", "%"+searchterm+"%"). - Find(&users) - } - - if err != nil { - return []User{}, err - } - - return users, nil -} +import ( + "code.vikunja.io/api/pkg/user" + "github.com/go-xorm/builder" +) // ListUIDs hold all kinds of user IDs from accounts who have somehow access to a list type ListUIDs struct { @@ -47,7 +33,7 @@ type ListUIDs struct { } // ListUsersFromList returns a list with all users who have access to a list, regardless of the method which gave them access -func ListUsersFromList(l *List, search string) (users []*User, err error) { +func ListUsersFromList(l *List, search string) (users []*user.User, err error) { userids := []*ListUIDs{} diff --git a/pkg/models/user_test.go b/pkg/models/user_test.go deleted file mode 100644 index f400875c..00000000 --- a/pkg/models/user_test.go +++ /dev/null @@ -1,178 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018-2020 Vikunja and contributors. All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package models - -import ( - "code.vikunja.io/api/pkg/utils" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestCreateUser(t *testing.T) { - // Create test database - //assert.NoError(t, LoadFixtures()) - - // Get our doer - doer, err := GetUserByID(1) - assert.NoError(t, err) - - // Our dummy user for testing - dummyuser := &User{ - Username: "testuu", - Password: "1234", - Email: "noone@example.com", - } - - // Create a new user - createdUser, err := CreateUser(dummyuser) - assert.NoError(t, err) - - // Create a second new user - _, 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 - _, err = CreateUser(dummyuser) - assert.Error(t, err) - - // Check if it fails to create a user with just the same username - _, err = CreateUser(&User{Username: dummyuser.Username, Password: "12345", Email: "email@example.com"}) - assert.Error(t, err) - assert.True(t, IsErrUsernameExists(err)) - - // Check if it fails to create one with the same email - _, err = CreateUser(&User{Username: "noone", Password: "1234", Email: dummyuser.Email}) - assert.Error(t, err) - assert.True(t, IsErrUserEmailExists(err)) - - // Check if it fails to create a user without password and username - _, err = CreateUser(&User{}) - assert.Error(t, err) - assert.True(t, IsErrNoUsernamePassword(err)) - - // Check if he exists - theuser, err := GetUser(createdUser) - assert.NoError(t, err) - - // Get by his ID - _, err = GetUserByID(theuser.ID) - assert.NoError(t, err) - - // Passing 0 as ID should return an error - _, err = GetUserByID(0) - assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) - - // Check the user credentials with an unverified email - _, err = CheckUserCredentials(&UserLogin{"user5", "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"}) - assert.Error(t, err) - assert.True(t, IsErrWrongUsernameOrPassword(err)) - - // Update the user - uuser, err := UpdateUser(&User{ID: theuser.ID, Password: "444444"}) - assert.NoError(t, err) - assert.Equal(t, theuser.Password, uuser.Password) // Password should not change - assert.Equal(t, theuser.Username, uuser.Username) // Username should not change either - - // Try updating one which does not exist - _, err = UpdateUser(&User{ID: 99999, Username: "dg"}) - assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) - - // Update a users password - newpassword := "55555" - err = UpdateUserPassword(theuser, newpassword) - assert.NoError(t, err) - - // Check if it was changed - _, err = CheckUserCredentials(&UserLogin{theuser.Username, newpassword}) - assert.NoError(t, err) - - // Check if the searchterm works - all, err := ListUsers("test") - assert.NoError(t, err) - assert.True(t, len(all) > 0) - - all, err = ListUsers("") - assert.NoError(t, err) - assert.True(t, len(all) > 0) - - // Try updating the password of a nonexistent user (should fail) - err = UpdateUserPassword(&User{ID: 9999}, newpassword) - assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) - - // Delete it - err = DeleteUserByID(theuser.ID, doer) - assert.NoError(t, err) - - // Try deleting one with ID = 0 - err = DeleteUserByID(0, doer) - assert.Error(t, err) - assert.True(t, IsErrIDCannotBeZero(err)) -} - -func TestUserPasswordReset(t *testing.T) { - // Request a new token - tr := &PasswordTokenRequest{ - Email: "user1@example.com", - } - 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)) -} diff --git a/pkg/models/users_list_test.go b/pkg/models/users_list_test.go index 2512088d..3d2cf900 100644 --- a/pkg/models/users_list_test.go +++ b/pkg/models/users_list_test.go @@ -1,38 +1,51 @@ +// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved. +// +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vikunja is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vikunja. If not, see . + package models import ( "code.vikunja.io/api/pkg/db" - "github.com/stretchr/testify/assert" + "code.vikunja.io/api/pkg/user" "gopkg.in/d4l3k/messagediff.v1" "testing" ) func TestListUsersFromList(t *testing.T) { - - err := db.LoadFixtures() - assert.NoError(t, err) - - testuser1 := &User{ + testuser1 := &user.User{ ID: 1, Username: "user1", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", } - testuser2 := &User{ + testuser2 := &user.User{ ID: 2, Username: "user2", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", AvatarURL: "ab53a2911ddf9b4817ac01ddcd3d975f", } - testuser3 := &User{ + testuser3 := &user.User{ ID: 3, Username: "user3", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", AvatarURL: "97d6d9441ff85fdc730e02a6068d267b", PasswordResetToken: "passwordresettesttoken", } - testuser4 := &User{ + testuser4 := &user.User{ ID: 4, Username: "user4", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -40,7 +53,7 @@ func TestListUsersFromList(t *testing.T) { AvatarURL: "7e65550957227bd38fe2d7fbc6fd2f7b", EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael", } - testuser5 := &User{ + testuser5 := &user.User{ ID: 5, Username: "user5", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -48,56 +61,56 @@ func TestListUsersFromList(t *testing.T) { AvatarURL: "cfa35b8cd2ec278026357769582fa563", EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael", } - testuser6 := &User{ + testuser6 := &user.User{ ID: 6, Username: "user6", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, AvatarURL: "3efbe51f864c6666bc27caf4c6ff90ed", } - testuser7 := &User{ + testuser7 := &user.User{ ID: 7, Username: "user7", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, AvatarURL: "e80a711d4de44c30054806ebbd488464", } - testuser8 := &User{ + testuser8 := &user.User{ ID: 8, Username: "user8", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, AvatarURL: "2b9b320416cd31020bb6844c3fadefd1", } - testuser9 := &User{ + testuser9 := &user.User{ ID: 9, Username: "user9", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, AvatarURL: "f784fdb21d26dd2c64f5135f35ec401f", } - testuser10 := &User{ + testuser10 := &user.User{ ID: 10, Username: "user10", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, AvatarURL: "fce8ff4ff56d75ad587d1bbaa5ef0563", } - testuser11 := &User{ + testuser11 := &user.User{ ID: 11, Username: "user11", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, AvatarURL: "ad6d67d0c4495e186010732a7d360028", } - testuser12 := &User{ + testuser12 := &user.User{ ID: 12, Username: "user12", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, AvatarURL: "ef1debc1364806281c42eeedfdeb943b", } - testuser13 := &User{ + testuser13 := &user.User{ ID: 13, Username: "user13", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", @@ -112,19 +125,19 @@ func TestListUsersFromList(t *testing.T) { tests := []struct { name string args args - wantUsers []*User + wantUsers []*user.User wantErr bool }{ { name: "Check owner only", args: args{l: &List{ID: 18, OwnerID: 7}}, - wantUsers: []*User{testuser7}, + wantUsers: []*user.User{testuser7}, }, { // This list has another different user shared for each possible method name: "Check with owner and other users", args: args{l: &List{ID: 19, OwnerID: 7}}, - wantUsers: []*User{ + wantUsers: []*user.User{ testuser1, // Shared Via Team readonly testuser2, // Shared Via Team write testuser3, // Shared Via Team admin @@ -147,6 +160,8 @@ func TestListUsersFromList(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + gotUsers, err := ListUsersFromList(tt.args.l, tt.args.search) if (err != nil) != tt.wantErr { t.Errorf("ListUsersFromList() error = %v, wantErr %v", err, tt.wantErr) diff --git a/pkg/modules/migration/create_from_structure.go b/pkg/modules/migration/create_from_structure.go index 07164e64..7796715f 100644 --- a/pkg/modules/migration/create_from_structure.go +++ b/pkg/modules/migration/create_from_structure.go @@ -19,12 +19,13 @@ package migration import ( "bytes" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" "io/ioutil" ) // InsertFromStructure takes a fully nested Vikunja data structure and a user and then creates everything for this user // (Namespaces, tasks, etc. Even attachments and relations.) -func InsertFromStructure(str []*models.NamespaceWithLists, user *models.User) (err error) { +func InsertFromStructure(str []*models.NamespaceWithLists, user *user.User) (err error) { // Create all namespaces for _, n := range str { diff --git a/pkg/modules/migration/handler/handler.go b/pkg/modules/migration/handler/handler.go index 805e9374..a8678024 100644 --- a/pkg/modules/migration/handler/handler.go +++ b/pkg/modules/migration/handler/handler.go @@ -19,6 +19,7 @@ package handler import ( "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/migration" + user2 "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" @@ -53,7 +54,7 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error { ms := mw.MigrationStruct() // Get the user from context - user, err := models.GetCurrentUser(c) + user, err := user2.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } @@ -82,7 +83,7 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error { func (mw *MigrationWeb) Status(c echo.Context) error { ms := mw.MigrationStruct() - user, err := models.GetCurrentUser(c) + user, err := user2.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/modules/migration/migration_status.go b/pkg/modules/migration/migration_status.go index 4ec8bec8..e5209340 100644 --- a/pkg/modules/migration/migration_status.go +++ b/pkg/modules/migration/migration_status.go @@ -16,7 +16,9 @@ package migration -import "code.vikunja.io/api/pkg/models" +import ( + "code.vikunja.io/api/pkg/user" +) // Status represents this migration status type Status struct { @@ -32,7 +34,7 @@ func (s *Status) TableName() string { } // SetMigrationStatus sets the migration status for a user -func SetMigrationStatus(m Migrator, u *models.User) (err error) { +func SetMigrationStatus(m Migrator, u *user.User) (err error) { status := &Status{ UserID: u.ID, MigratorName: m.Name(), @@ -42,7 +44,7 @@ func SetMigrationStatus(m Migrator, u *models.User) (err error) { } // GetMigrationStatus returns the migration status for a migration and a user -func GetMigrationStatus(m Migrator, u *models.User) (status *Status, err error) { +func GetMigrationStatus(m Migrator, u *user.User) (status *Status, err error) { status = &Status{} _, err = x.Where("user_id = ? and migrator_name = ?", u.ID, m.Name()).Desc("id").Get(status) return diff --git a/pkg/modules/migration/migrator.go b/pkg/modules/migration/migrator.go index 8ea27391..059a2be5 100644 --- a/pkg/modules/migration/migrator.go +++ b/pkg/modules/migration/migrator.go @@ -17,13 +17,15 @@ package migration -import "code.vikunja.io/api/pkg/models" +import ( + "code.vikunja.io/api/pkg/user" +) // Migrator is the basic migrator interface which is shared among all migrators type Migrator interface { // Migrate is the interface used to migrate a user's tasks from another platform to vikunja. // The user object is the user who's tasks will be migrated. - Migrate(user *models.User) error + Migrate(user *user.User) error // AuthURL returns a url for clients to authenticate against. // The use case for this are Oauth flows, where the server token should remain hidden and not // known to the frontend. diff --git a/pkg/modules/migration/wunderlist/wunderlist.go b/pkg/modules/migration/wunderlist/wunderlist.go index 3930146b..1d9c6bee 100644 --- a/pkg/modules/migration/wunderlist/wunderlist.go +++ b/pkg/modules/migration/wunderlist/wunderlist.go @@ -23,6 +23,7 @@ import ( "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/migration" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "encoding/json" "fmt" @@ -341,7 +342,7 @@ func makeAuthGetRequest(token *wunderlistAuthToken, urlPart string, v interface{ // @Success 200 {object} models.Message "A message telling you everything was migrated successfully." // @Failure 500 {object} models.Message "Internal server error" // @Router /migration/wunderlist/migrate [post] -func (w *Migration) Migrate(user *models.User) (err error) { +func (w *Migration) Migrate(user *user.User) (err error) { log.Debugf("[Wunderlist migration] Starting wunderlist migration for user %d", user.ID) diff --git a/pkg/routes/api/v1/auth.go b/pkg/routes/api/v1/auth.go index ee7688e1..17553179 100644 --- a/pkg/routes/api/v1/auth.go +++ b/pkg/routes/api/v1/auth.go @@ -19,6 +19,7 @@ package v1 import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/dgrijalva/jwt-go" "github.com/labstack/echo/v4" @@ -34,7 +35,7 @@ const ( ) // NewUserJWTAuthtoken generates and signes a new jwt token for a user. This is a global function to be able to call it from integration tests. -func NewUserJWTAuthtoken(user *models.User) (token string, err error) { +func NewUserJWTAuthtoken(user *user.User) (token string, err error) { t := jwt.New(jwt.SigningMethodHS256) // Set claims @@ -78,7 +79,7 @@ func GetAuthFromClaims(c echo.Context) (a web.Auth, err error) { return models.GetLinkShareFromClaims(claims) } if typ == AuthTypeUser { - return models.GetUserFromClaims(claims) + return user.GetUserFromClaims(claims) } return nil, echo.NewHTTPError(http.StatusBadRequest, models.Message{Message: "Invalid JWT token."}) } diff --git a/pkg/routes/api/v1/list_by_namespace.go b/pkg/routes/api/v1/list_by_namespace.go index 6287c153..9bc5b54c 100644 --- a/pkg/routes/api/v1/list_by_namespace.go +++ b/pkg/routes/api/v1/list_by_namespace.go @@ -18,6 +18,7 @@ package v1 import ( "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" @@ -46,7 +47,7 @@ func GetListsByNamespaceID(c echo.Context) error { } // Get the lists - doer, err := models.GetCurrentUser(c) + doer, err := user.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } @@ -73,7 +74,7 @@ func getNamespace(c echo.Context) (namespace *models.Namespace, err error) { } // Check if the user has acces to that namespace - user, err := models.GetCurrentUser(c) + user, err := user.GetCurrentUser(c) if err != nil { return } diff --git a/pkg/routes/api/v1/login.go b/pkg/routes/api/v1/login.go index 2eeea4fe..e92bad3b 100644 --- a/pkg/routes/api/v1/login.go +++ b/pkg/routes/api/v1/login.go @@ -18,6 +18,7 @@ package v1 import ( "code.vikunja.io/api/pkg/models" + user2 "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/dgrijalva/jwt-go" "github.com/labstack/echo/v4" @@ -41,13 +42,13 @@ type Token struct { // @Failure 403 {object} models.Message "Invalid username or password." // @Router /login [post] func Login(c echo.Context) error { - u := models.UserLogin{} + u := user2.Login{} if err := c.Bind(&u); err != nil { return c.JSON(http.StatusBadRequest, models.Message{"Please provide a username and password."}) } // Check user - user, err := models.CheckUserCredentials(&u) + user, err := user2.CheckUserCredentials(&u) if err != nil { return handler.HandleHTTPError(err, c) } @@ -80,7 +81,7 @@ func RenewToken(c echo.Context) error { return echo.ErrBadRequest } - user, err := models.GetUserFromClaims(claims) + user, err := user2.GetUserFromClaims(claims) if err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/routes/api/v1/task_attachment.go b/pkg/routes/api/v1/task_attachment.go index 4d189ed0..d1d60355 100644 --- a/pkg/routes/api/v1/task_attachment.go +++ b/pkg/routes/api/v1/task_attachment.go @@ -18,6 +18,7 @@ package v1 import ( "code.vikunja.io/api/pkg/models" + user2 "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" @@ -45,7 +46,7 @@ func UploadTaskAttachment(c echo.Context) error { } // Rights check - user, err := models.GetCurrentUser(c) + user, err := user2.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } @@ -114,7 +115,7 @@ func GetTaskAttachment(c echo.Context) error { } // Rights check - user, err := models.GetCurrentUser(c) + user, err := user2.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/routes/api/v1/user_add_update.go b/pkg/routes/api/v1/user_add_update.go index 08aa4390..42e5a025 100644 --- a/pkg/routes/api/v1/user_add_update.go +++ b/pkg/routes/api/v1/user_add_update.go @@ -19,6 +19,7 @@ package v1 import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" @@ -40,13 +41,20 @@ func RegisterUser(c echo.Context) error { return echo.ErrNotFound } // Check for Request Content - var datUser *models.APIUserPassword + var datUser *user.APIUserPassword if err := c.Bind(&datUser); err != nil { return c.JSON(http.StatusBadRequest, models.Message{"No or invalid user model provided."}) } // Insert the user - newUser, err := models.CreateUser(datUser.APIFormat()) + newUser, err := user.CreateUser(datUser.APIFormat()) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + // Add its namespace + newN := &models.Namespace{Name: newUser.Username, Description: newUser.Username + "'s namespace.", Owner: newUser} + err = newN.Create(newUser) if err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/routes/api/v1/user_confirm_email.go b/pkg/routes/api/v1/user_confirm_email.go index f5021ad0..66b4e2dd 100644 --- a/pkg/routes/api/v1/user_confirm_email.go +++ b/pkg/routes/api/v1/user_confirm_email.go @@ -18,6 +18,7 @@ package v1 import ( "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" @@ -36,12 +37,12 @@ import ( // @Router /user/confirm [post] func UserConfirmEmail(c echo.Context) error { // Check for Request Content - var emailConfirm models.EmailConfirm + var emailConfirm user.EmailConfirm if err := c.Bind(&emailConfirm); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "No token provided.") } - err := models.UserEmailConfirm(&emailConfirm) + err := user.ConfirmEmail(&emailConfirm) if err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/routes/api/v1/user_delete.go b/pkg/routes/api/v1/user_delete.go deleted file mode 100644 index 8940b22b..00000000 --- a/pkg/routes/api/v1/user_delete.go +++ /dev/null @@ -1,65 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018-2020 Vikunja and contributors. All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package v1 - -import ( - "code.vikunja.io/api/pkg/models" - "code.vikunja.io/web/handler" - "github.com/labstack/echo/v4" - "net/http" - "strconv" -) - -// UserDelete is the handler to delete a user -func UserDelete(c echo.Context) error { - - // TODO: only allow users to allow itself - - id := c.Param("id") - - // Make int - userID, err := strconv.ParseInt(id, 10, 64) - - if err != nil { - return c.JSON(http.StatusBadRequest, models.Message{"User ID is invalid."}) - } - - // Check if the user exists - _, err = models.GetUserByID(userID) - - if err != nil { - if models.IsErrUserDoesNotExist(err) { - return c.JSON(http.StatusNotFound, models.Message{"The user does not exist."}) - } - return c.JSON(http.StatusInternalServerError, models.Message{"Could not get user."}) - } - - // Get the doer options - doer, err := models.GetCurrentUser(c) - if err != nil { - return err - } - - // Delete it - err = models.DeleteUserByID(userID, doer) - - if err != nil { - return handler.HandleHTTPError(err, c) - } - - return c.JSON(http.StatusOK, models.Message{"success"}) -} diff --git a/pkg/routes/api/v1/user_list.go b/pkg/routes/api/v1/user_list.go index 8ae33399..abfca4c4 100644 --- a/pkg/routes/api/v1/user_list.go +++ b/pkg/routes/api/v1/user_list.go @@ -18,6 +18,7 @@ package v1 import ( "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" @@ -38,7 +39,7 @@ import ( // @Router /users [get] func UserList(c echo.Context) error { s := c.QueryParam("s") - users, err := models.ListUsers(s) + users, err := user.ListUsers(s) if err != nil { return handler.HandleHTTPError(err, c) } @@ -72,7 +73,7 @@ func ListUsersForList(c echo.Context) error { } list := models.List{ID: listID} - currentUser, err := models.GetCurrentUser(c) + currentUser, err := user.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/routes/api/v1/user_password_reset.go b/pkg/routes/api/v1/user_password_reset.go index 62512c79..4b2bb6d8 100644 --- a/pkg/routes/api/v1/user_password_reset.go +++ b/pkg/routes/api/v1/user_password_reset.go @@ -18,6 +18,7 @@ package v1 import ( "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" @@ -36,12 +37,12 @@ import ( // @Router /user/password/reset [post] func UserResetPassword(c echo.Context) error { // Check for Request Content - var pwReset models.PasswordReset + var pwReset user.PasswordReset if err := c.Bind(&pwReset); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "No password provided.") } - err := models.UserPasswordReset(&pwReset) + err := user.ResetPassword(&pwReset) if err != nil { return handler.HandleHTTPError(err, c) } @@ -62,7 +63,7 @@ func UserResetPassword(c echo.Context) error { // @Router /user/password/token [post] func UserRequestResetPasswordToken(c echo.Context) error { // Check for Request Content - var pwTokenReset models.PasswordTokenRequest + var pwTokenReset user.PasswordTokenRequest if err := c.Bind(&pwTokenReset); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "No username provided.") } @@ -71,7 +72,7 @@ func UserRequestResetPasswordToken(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, err) } - err := models.RequestUserPasswordResetToken(&pwTokenReset) + err := user.RequestUserPasswordResetToken(&pwTokenReset) if err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/routes/api/v1/user_show.go b/pkg/routes/api/v1/user_show.go index 5cae05c5..390a5879 100644 --- a/pkg/routes/api/v1/user_show.go +++ b/pkg/routes/api/v1/user_show.go @@ -17,7 +17,7 @@ package v1 import ( - "code.vikunja.io/api/pkg/models" + user2 "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" @@ -35,12 +35,12 @@ import ( // @Failure 500 {object} models.Message "Internal server error." // @Router /user [get] func UserShow(c echo.Context) error { - userInfos, err := models.GetCurrentUser(c) + userInfos, err := user2.GetCurrentUser(c) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Error getting current user.") } - user, err := models.GetUserByID(userInfos.ID) + user, err := user2.GetUserByID(userInfos.ID) if err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/routes/api/v1/user_update_password.go b/pkg/routes/api/v1/user_update_password.go index 74c251e5..4c775eb0 100644 --- a/pkg/routes/api/v1/user_update_password.go +++ b/pkg/routes/api/v1/user_update_password.go @@ -18,6 +18,7 @@ package v1 import ( "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" @@ -44,7 +45,7 @@ type UserPassword struct { // @Router /user/password [post] func UserChangePassword(c echo.Context) error { // Check if the user is itself - doer, err := models.GetCurrentUser(c) + doer, err := user.GetCurrentUser(c) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Error getting current user.") } @@ -56,16 +57,16 @@ func UserChangePassword(c echo.Context) error { } if newPW.OldPassword == "" { - return handler.HandleHTTPError(models.ErrEmptyOldPassword{}, c) + return handler.HandleHTTPError(user.ErrEmptyOldPassword{}, c) } // Check the current password - if _, err = models.CheckUserCredentials(&models.UserLogin{Username: doer.Username, Password: newPW.OldPassword}); err != nil { + if _, err = user.CheckUserCredentials(&user.Login{Username: doer.Username, Password: newPW.OldPassword}); err != nil { return handler.HandleHTTPError(err, c) } // Update the password - if err = models.UpdateUserPassword(doer, newPW.NewPassword); err != nil { + if err = user.UpdateUserPassword(doer, newPW.NewPassword); err != nil { return handler.HandleHTTPError(err, c) } diff --git a/pkg/routes/caldav/handler.go b/pkg/routes/caldav/handler.go index afab3527..52d36160 100644 --- a/pkg/routes/caldav/handler.go +++ b/pkg/routes/caldav/handler.go @@ -20,6 +20,7 @@ import ( "bytes" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "fmt" "github.com/labstack/echo/v4" @@ -31,10 +32,10 @@ import ( "strings" ) -func getBasicAuthUserFromContext(c echo.Context) (user models.User, err error) { - u, is := c.Get("userBasicAuth").(models.User) +func getBasicAuthUserFromContext(c echo.Context) (user.User, error) { + u, is := c.Get("userBasicAuth").(user.User) if !is { - return models.User{}, fmt.Errorf("user is not user element, is %s", reflect.TypeOf(c.Get("userBasicAuth"))) + return user.User{}, fmt.Errorf("user is not user element, is %s", reflect.TypeOf(c.Get("userBasicAuth"))) } return u, nil } diff --git a/pkg/routes/caldav/listStorageProvider.go b/pkg/routes/caldav/listStorageProvider.go index 88419892..1d9f8663 100644 --- a/pkg/routes/caldav/listStorageProvider.go +++ b/pkg/routes/caldav/listStorageProvider.go @@ -19,6 +19,7 @@ package caldav import ( "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" + user2 "code.vikunja.io/api/pkg/user" "github.com/samedi/caldav-go/data" "github.com/samedi/caldav-go/errs" "strconv" @@ -39,7 +40,7 @@ type VikunjaCaldavListStorage struct { // Used when handling a single task, like updating task *models.Task // The current user - user *models.User + user *user2.User isPrincipal bool isEntry bool // Entry level handling should only return a link to the principal url } diff --git a/pkg/routes/metrics.go b/pkg/routes/metrics.go index b960f602..1f811abf 100644 --- a/pkg/routes/metrics.go +++ b/pkg/routes/metrics.go @@ -23,6 +23,7 @@ import ( "code.vikunja.io/api/pkg/metrics" "code.vikunja.io/api/pkg/models" v1 "code.vikunja.io/api/pkg/routes/api/v1" + "code.vikunja.io/api/pkg/user" "github.com/labstack/echo/v4" "github.com/prometheus/client_golang/prometheus/promhttp" "time" @@ -51,7 +52,7 @@ func setupMetrics(a *echo.Group) { }, { metrics.UserCountKey, - models.User{}, + user.User{}, }, { metrics.NamespaceCountKey, diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index fdb6705d..6facec41 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -53,6 +53,7 @@ import ( apiv1 "code.vikunja.io/api/pkg/routes/api/v1" "code.vikunja.io/api/pkg/routes/caldav" _ "code.vikunja.io/api/pkg/swagger" // To generate swagger docs + "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "code.vikunja.io/web/handler" "github.com/asaskevich/govalidator" @@ -407,11 +408,11 @@ func registerCalDavRoutes(c *echo.Group) { } func caldavBasicAuth(username, password string, c echo.Context) (bool, error) { - creds := &models.UserLogin{ + creds := &user.Login{ Username: username, Password: password, } - u, err := models.CheckUserCredentials(creds) + u, err := user.CheckUserCredentials(creds) if err != nil { log.Errorf("Error during basic auth for caldav: %v", err) return false, nil diff --git a/pkg/user/db.go b/pkg/user/db.go new file mode 100644 index 00000000..a1f3b731 --- /dev/null +++ b/pkg/user/db.go @@ -0,0 +1,50 @@ +// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved. +// +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vikunja is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vikunja. If not, see . + +package user + +import ( + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/log" + "github.com/go-xorm/xorm" +) + +var x *xorm.Engine + +// InitDB sets up the database connection to use in this module +func InitDB() (err error) { + x, err = db.CreateDBEngine() + if err != nil { + log.Criticalf("Could not connect to db: %v", err.Error()) + return + } + + // Cache + if config.CacheEnabled.GetBool() && config.CacheType.GetString() == "redis" { + db.RegisterTableStructsForCache(GetTables()) + } + + return nil +} + +// GetTables returns all structs which are also a table. +func GetTables() []interface{} { + return []interface{}{ + &User{}, + } +} diff --git a/pkg/user/error.go b/pkg/user/error.go new file mode 100644 index 00000000..1a26a5b7 --- /dev/null +++ b/pkg/user/error.go @@ -0,0 +1,291 @@ +// Copyright2018-2020 Vikunja and contriubtors. All rights reserved. +// +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vikunja is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vikunja. If not, see . + +package user + +import ( + "code.vikunja.io/web" + "fmt" + "net/http" +) + +// ===================== +// User Operation Errors +// ===================== + +// ErrUsernameExists represents a "UsernameAlreadyExists" kind of error. +type ErrUsernameExists struct { + UserID int64 + Username string +} + +// IsErrUsernameExists checks if an error is a ErrUsernameExists. +func IsErrUsernameExists(err error) bool { + _, ok := err.(ErrUsernameExists) + return ok +} + +func (err ErrUsernameExists) Error() string { + return fmt.Sprintf("User with that username already exists [user id: %d, username: %s]", err.UserID, err.Username) +} + +// ErrorCodeUsernameExists holds the unique world-error code of this error +const ErrorCodeUsernameExists = 1001 + +// HTTPError holds the http error description +func (err ErrUsernameExists) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUsernameExists, Message: "A user with this username already exists."} +} + +// ErrUserEmailExists represents a "UserEmailExists" kind of error. +type ErrUserEmailExists struct { + UserID int64 + Email string +} + +// IsErrUserEmailExists checks if an error is a ErrUserEmailExists. +func IsErrUserEmailExists(err error) bool { + _, ok := err.(ErrUserEmailExists) + return ok +} + +func (err ErrUserEmailExists) Error() string { + return fmt.Sprintf("User with that email already exists [user id: %d, email: %s]", err.UserID, err.Email) +} + +// ErrorCodeUserEmailExists holds the unique world-error code of this error +const ErrorCodeUserEmailExists = 1002 + +// HTTPError holds the http error description +func (err ErrUserEmailExists) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUserEmailExists, Message: "A user with this email address already exists."} +} + +// ErrNoUsernamePassword represents a "NoUsernamePassword" kind of error. +type ErrNoUsernamePassword struct{} + +// IsErrNoUsernamePassword checks if an error is a ErrNoUsernamePassword. +func IsErrNoUsernamePassword(err error) bool { + _, ok := err.(ErrNoUsernamePassword) + return ok +} + +func (err ErrNoUsernamePassword) Error() string { + return fmt.Sprintf("No username and password provided") +} + +// ErrCodeNoUsernamePassword holds the unique world-error code of this error +const ErrCodeNoUsernamePassword = 1004 + +// HTTPError holds the http error description +func (err ErrNoUsernamePassword) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeNoUsernamePassword, Message: "Please specify a username and a password."} +} + +// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error. +type ErrUserDoesNotExist struct { + UserID int64 +} + +// IsErrUserDoesNotExist checks if an error is a ErrUserDoesNotExist. +func IsErrUserDoesNotExist(err error) bool { + _, ok := err.(ErrUserDoesNotExist) + return ok +} + +func (err ErrUserDoesNotExist) Error() string { + return fmt.Sprintf("User does not exist [user id: %d]", err.UserID) +} + +// ErrCodeUserDoesNotExist holds the unique world-error code of this error +const ErrCodeUserDoesNotExist = 1005 + +// HTTPError holds the http error description +func (err ErrUserDoesNotExist) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeUserDoesNotExist, Message: "The user does not exist."} +} + +// ErrCouldNotGetUserID represents a "ErrCouldNotGetUserID" kind of error. +type ErrCouldNotGetUserID struct{} + +// IsErrCouldNotGetUserID checks if an error is a ErrCouldNotGetUserID. +func IsErrCouldNotGetUserID(err error) bool { + _, ok := err.(ErrCouldNotGetUserID) + return ok +} + +func (err ErrCouldNotGetUserID) Error() string { + return fmt.Sprintf("Could not get user ID") +} + +// ErrCodeCouldNotGetUserID holds the unique world-error code of this error +const ErrCodeCouldNotGetUserID = 1006 + +// HTTPError holds the http error description +func (err ErrCouldNotGetUserID) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCouldNotGetUserID, Message: "Could not get user id."} +} + +// ErrNoPasswordResetToken represents an error where no password reset token exists for that user +type ErrNoPasswordResetToken struct { + UserID int64 +} + +func (err ErrNoPasswordResetToken) Error() string { + return fmt.Sprintf("No token to reset a password [UserID: %d]", err.UserID) +} + +// ErrCodeNoPasswordResetToken holds the unique world-error code of this error +const ErrCodeNoPasswordResetToken = 1008 + +// HTTPError holds the http error description +func (err ErrNoPasswordResetToken) HTTPError() web.HTTPError { + return web.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() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidPasswordResetToken, Message: "Invalid token to reset a user's password."} +} + +// IsErrInvalidPasswordResetToken checks if an error is a ErrInvalidPasswordResetToken. +func IsErrInvalidPasswordResetToken(err error) bool { + _, ok := err.(ErrInvalidPasswordResetToken) + return ok +} + +// ErrInvalidEmailConfirmToken is an error where the email confirm token is invalid +type ErrInvalidEmailConfirmToken struct { + Token string +} + +func (err ErrInvalidEmailConfirmToken) Error() string { + return fmt.Sprintf("Invalid email confirm token [Token: %s]", err.Token) +} + +// ErrCodeInvalidEmailConfirmToken holds the unique world-error code of this error +const ErrCodeInvalidEmailConfirmToken = 1010 + +// HTTPError holds the http error description +func (err ErrInvalidEmailConfirmToken) HTTPError() web.HTTPError { + return web.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() web.HTTPError { + return web.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() web.HTTPError { + return web.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 +} + +// ErrEmptyNewPassword represents a "EmptyNewPassword" kind of error. +type ErrEmptyNewPassword struct{} + +// IsErrEmptyNewPassword checks if an error is a ErrEmptyNewPassword. +func IsErrEmptyNewPassword(err error) bool { + _, ok := err.(ErrEmptyNewPassword) + return ok +} + +func (err ErrEmptyNewPassword) Error() string { + return fmt.Sprintf("New password is empty") +} + +// ErrCodeEmptyNewPassword holds the unique world-error code of this error +const ErrCodeEmptyNewPassword = 1013 + +// HTTPError holds the http error description +func (err ErrEmptyNewPassword) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeEmptyNewPassword, Message: "Please specify new password."} +} + +// ErrEmptyOldPassword represents a "EmptyOldPassword" kind of error. +type ErrEmptyOldPassword struct{} + +// IsErrEmptyOldPassword checks if an error is a ErrEmptyOldPassword. +func IsErrEmptyOldPassword(err error) bool { + _, ok := err.(ErrEmptyOldPassword) + return ok +} + +func (err ErrEmptyOldPassword) Error() string { + return fmt.Sprintf("Old password is empty") +} + +// ErrCodeEmptyOldPassword holds the unique world-error code of this error +const ErrCodeEmptyOldPassword = 1014 + +// HTTPError holds the http error description +func (err ErrEmptyOldPassword) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeEmptyOldPassword, Message: "Please specify old password."} +} diff --git a/pkg/user/main_test.go b/pkg/user/main_test.go new file mode 100644 index 00000000..57a0e921 --- /dev/null +++ b/pkg/user/main_test.go @@ -0,0 +1,29 @@ +// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved. +// +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vikunja is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vikunja. If not, see . + +package user + +import ( + "os" + "testing" +) + +// TestMain is the main test function used to bootstrap the test env +func TestMain(m *testing.M) { + InitTests() + os.Exit(m.Run()) +} diff --git a/pkg/user/test.go b/pkg/user/test.go new file mode 100644 index 00000000..f2a61cd2 --- /dev/null +++ b/pkg/user/test.go @@ -0,0 +1,42 @@ +// Copyright2018-2020 Vikunja and contriubtors. All rights reserved. +// +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vikunja is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vikunja. If not, see . + +package user + +import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/log" +) + +// InitTests handles the actual bootstrapping of the test env +func InitTests() { + var err error + x, err = db.CreateTestEngine() + if err != nil { + log.Fatal(err) + } + + err = x.Sync2(GetTables()...) + if err != nil { + log.Fatal(err) + } + + err = db.InitTestFixtures("users") + if err != nil { + log.Fatal(err) + } +} diff --git a/pkg/models/user.go b/pkg/user/user.go similarity index 89% rename from pkg/models/user.go rename to pkg/user/user.go index e2c2e810..42cfc33d 100644 --- a/pkg/models/user.go +++ b/pkg/user/user.go @@ -1,20 +1,21 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018-2020 Vikunja and contributors. All rights reserved. +// Copyright2018-2020 Vikunja and contriubtors. All rights reserved. // -// This program is free software: you can redistribute it and/or modify +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// This program is distributed in the hope that it will be useful, +// Vikunja is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with this program. If not, see . +// along with Vikunja. If not, see . -package models +package user import ( "code.vikunja.io/api/pkg/config" @@ -29,8 +30,8 @@ import ( "reflect" ) -// UserLogin Object to recive user credentials in JSON format -type UserLogin struct { +// Login Object to recive user credentials in JSON format +type Login struct { // The username used to log in. Username string `json:"username"` // The password for the user. @@ -76,7 +77,9 @@ func (User) TableName() string { return "users" } -func getUserWithError(a web.Auth) (*User, error) { +// GetFromAuth returns a user object from a web.Auth object and returns an error if the underlying type +// is not a user object +func GetFromAuth(a web.Auth) (*User, error) { u, is := a.(*User) if !is { return &User{}, fmt.Errorf("user is not user element, is %s", reflect.TypeOf(a)) @@ -153,7 +156,7 @@ func getUser(user *User, withEmail bool) (userOut *User, err error) { } // CheckUserCredentials checks user credentials -func CheckUserCredentials(u *UserLogin) (*User, error) { +func CheckUserCredentials(u *Login) (*User, error) { // Check if we have any credentials if u.Password == "" || u.Username == "" { return &User{}, ErrNoUsernamePassword{} @@ -273,13 +276,6 @@ func CreateUser(user *User) (newUser *User, err error) { return &User{}, err } - // Create the user's namespace - newN := &Namespace{Name: newUserOut.Username, Description: newUserOut.Username + "'s namespace.", Owner: newUserOut} - err = newN.Create(newUserOut) - if err != nil { - return &User{}, err - } - // Dont send a mail if we're testing if !config.MailerEnabled.GetBool() { return newUserOut, err @@ -361,23 +357,3 @@ func UpdateUserPassword(user *User, newPassword string) (err error) { return err } - -// DeleteUserByID deletes a user by its ID -func DeleteUserByID(id int64, doer *User) error { - // Check if the id is 0 - if id == 0 { - return ErrIDCannotBeZero{} - } - - // Delete the user - _, err := x.Id(id).Delete(&User{}) - - if err != nil { - return err - } - - // Update the metrics - metrics.UpdateCount(-1, metrics.ActiveUsersKey) - - return err -} diff --git a/pkg/models/user_email_confirm.go b/pkg/user/user_email_confirm.go similarity index 69% rename from pkg/models/user_email_confirm.go rename to pkg/user/user_email_confirm.go index 216d89b7..68378c2b 100644 --- a/pkg/models/user_email_confirm.go +++ b/pkg/user/user_email_confirm.go @@ -1,20 +1,21 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018-2020 Vikunja and contributors. All rights reserved. +// Copyright2018-2020 Vikunja and contriubtors. All rights reserved. // -// This program is free software: you can redistribute it and/or modify +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// This program is distributed in the hope that it will be useful, +// Vikunja is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with this program. If not, see . +// along with Vikunja. If not, see . -package models +package user // EmailConfirm holds the token to confirm a mail address type EmailConfirm struct { @@ -22,8 +23,8 @@ type EmailConfirm struct { Token string `json:"token"` } -// UserEmailConfirm handles the confirmation of an email address -func UserEmailConfirm(c *EmailConfirm) (err error) { +// ConfirmEmail handles the confirmation of an email address +func ConfirmEmail(c *EmailConfirm) (err error) { // Check if we have an email confirm token if c.Token == "" { diff --git a/pkg/models/user_email_confirm_test.go b/pkg/user/user_email_confirm_test.go similarity index 69% rename from pkg/models/user_email_confirm_test.go rename to pkg/user/user_email_confirm_test.go index e2401805..bfb38138 100644 --- a/pkg/models/user_email_confirm_test.go +++ b/pkg/user/user_email_confirm_test.go @@ -1,22 +1,26 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018-2020 Vikunja and contributors. All rights reserved. +// Copyright2018-2020 Vikunja and contriubtors. All rights reserved. // -// This program is free software: you can redistribute it and/or modify +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// This program is distributed in the hope that it will be useful, +// Vikunja is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with this program. If not, see . +// along with Vikunja. If not, see . -package models +package user -import "testing" +import ( + "code.vikunja.io/api/pkg/db" + "testing" +) func TestUserEmailConfirm(t *testing.T) { type args struct { @@ -59,8 +63,9 @@ func TestUserEmailConfirm(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := UserEmailConfirm(tt.args.c); (err != nil) != tt.wantErr { - t.Errorf("UserEmailConfirm() error = %v, wantErr %v", err, tt.wantErr) + db.LoadAndAssertFixtures(t) + if err := ConfirmEmail(tt.args.c); (err != nil) != tt.wantErr { + t.Errorf("ConfirmEmail() error = %v, wantErr %v", err, tt.wantErr) } }) } diff --git a/pkg/models/user_password_reset.go b/pkg/user/user_password_reset.go similarity index 85% rename from pkg/models/user_password_reset.go rename to pkg/user/user_password_reset.go index f2d273bc..24d47d2b 100644 --- a/pkg/models/user_password_reset.go +++ b/pkg/user/user_password_reset.go @@ -1,20 +1,21 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018-2020 Vikunja and contributors. All rights reserved. +// Copyright2018-2020 Vikunja and contriubtors. All rights reserved. // -// This program is free software: you can redistribute it and/or modify +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// This program is distributed in the hope that it will be useful, +// Vikunja is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with this program. If not, see . +// along with Vikunja. If not, see . -package models +package user import ( "code.vikunja.io/api/pkg/config" @@ -30,8 +31,8 @@ type PasswordReset struct { NewPassword string `json:"new_password"` } -// UserPasswordReset resets a users password -func UserPasswordReset(reset *PasswordReset) (err error) { +// ResetPassword resets a users password +func ResetPassword(reset *PasswordReset) (err error) { // Check if the password is not empty if reset.NewPassword == "" { diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go new file mode 100644 index 00000000..4ff75759 --- /dev/null +++ b/pkg/user/user_test.go @@ -0,0 +1,301 @@ +// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved. +// +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vikunja is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vikunja. If not, see . + +package user + +import ( + "code.vikunja.io/api/pkg/db" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCreateUser(t *testing.T) { + // Our dummy user for testing + dummyuser := &User{ + Username: "testuser", + Password: "1234", + Email: "noone@example.com", + } + + t.Run("normal", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + createdUser, err := CreateUser(dummyuser) + assert.NoError(t, err) + assert.NotZero(t, createdUser.Created) + }) + t.Run("already existing", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CreateUser(&User{ + Username: "user1", + Password: "12345", + Email: "email@example.com", + }) + assert.Error(t, err) + assert.True(t, IsErrUsernameExists(err)) + }) + t.Run("same email", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CreateUser(&User{ + Username: "testuser", + Password: "12345", + Email: "user1@example.com", + }) + assert.Error(t, err) + assert.True(t, IsErrUserEmailExists(err)) + }) + t.Run("no username", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CreateUser(&User{ + Username: "", + Password: "12345", + Email: "user1@example.com", + }) + assert.Error(t, err) + assert.True(t, IsErrNoUsernamePassword(err)) + }) + t.Run("no password", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CreateUser(&User{ + Username: "testuser", + Password: "", + Email: "user1@example.com", + }) + assert.Error(t, err) + assert.True(t, IsErrNoUsernamePassword(err)) + }) + t.Run("no email", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CreateUser(&User{ + Username: "testuser", + Password: "12345", + Email: "", + }) + assert.Error(t, err) + assert.True(t, IsErrNoUsernamePassword(err)) + }) +} + +func TestGetUser(t *testing.T) { + t.Run("by name", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + theuser, err := GetUser(&User{ + Username: "user1", + }) + assert.NoError(t, err) + assert.Equal(t, theuser.ID, int64(1)) + assert.Empty(t, theuser.Email) + }) + t.Run("by email", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + theuser, err := GetUser(&User{ + Email: "user1@example.com", + }) + assert.NoError(t, err) + assert.Equal(t, theuser.ID, int64(1)) + assert.Empty(t, theuser.Email) + }) + t.Run("by id", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + theuser, err := GetUserByID(1) + assert.NoError(t, err) + assert.Equal(t, theuser.ID, int64(1)) + assert.Equal(t, theuser.Username, "user1") + assert.Empty(t, theuser.Email) + }) + t.Run("invalid id", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := GetUserByID(99999) + assert.Error(t, err) + assert.True(t, IsErrUserDoesNotExist(err)) + }) + t.Run("nonexistant", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := GetUserByID(0) + assert.Error(t, err) + assert.True(t, IsErrUserDoesNotExist(err)) + }) + t.Run("empty name", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := GetUserByUsername("") + assert.Error(t, err) + assert.True(t, IsErrUserDoesNotExist(err)) + }) + t.Run("with email", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + theuser, err := GetUserWithEmail(&User{ID: 1}) + assert.NoError(t, err) + assert.Equal(t, theuser.ID, int64(1)) + assert.Equal(t, theuser.Username, "user1") + assert.NotEmpty(t, theuser.Email) + }) +} + +func TestCheckUserCredentials(t *testing.T) { + t.Run("normal", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CheckUserCredentials(&Login{"user1", "1234"}) + assert.NoError(t, err) + }) + t.Run("unverified email", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CheckUserCredentials(&Login{"user5", "1234"}) + assert.Error(t, err) + assert.True(t, IsErrEmailNotConfirmed(err)) + }) + t.Run("wrong password", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CheckUserCredentials(&Login{"user1", "12345"}) + assert.Error(t, err) + assert.True(t, IsErrWrongUsernameOrPassword(err)) + }) + t.Run("nonexistant user", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CheckUserCredentials(&Login{"dfstestuu", "1234"}) + assert.Error(t, err) + assert.True(t, IsErrWrongUsernameOrPassword(err)) + }) + t.Run("empty password", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CheckUserCredentials(&Login{"user1", ""}) + assert.Error(t, err) + assert.True(t, IsErrNoUsernamePassword(err)) + }) + t.Run("empty username", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := CheckUserCredentials(&Login{"", "1234"}) + assert.Error(t, err) + assert.True(t, IsErrNoUsernamePassword(err)) + }) +} + +func TestUpdateUser(t *testing.T) { + t.Run("normal", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + uuser, err := UpdateUser(&User{ + ID: 1, + Password: "LoremIpsum", + Email: "testing@example.com", + }) + assert.NoError(t, err) + assert.Equal(t, "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", uuser.Password) // Password should not change + assert.Equal(t, "user1", uuser.Username) // Username should not change either + }) + t.Run("change username", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + uuser, err := UpdateUser(&User{ + ID: 1, + Username: "changedname", + }) + assert.NoError(t, err) + assert.Equal(t, "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", uuser.Password) // Password should not change + assert.Equal(t, "changedname", uuser.Username) + }) + t.Run("nonexistant", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + _, err := UpdateUser(&User{ + ID: 99999, + }) + assert.Error(t, err) + assert.True(t, IsErrUserDoesNotExist(err)) + }) +} + +func TestUpdateUserPassword(t *testing.T) { + + t.Run("normal", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + err := UpdateUserPassword(&User{ + ID: 1, + }, "12345", + ) + assert.NoError(t, err) + }) + t.Run("nonexistant user", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + err := UpdateUserPassword(&User{ + ID: 9999, + }, "12345") + assert.Error(t, err) + assert.True(t, IsErrUserDoesNotExist(err)) + }) + t.Run("empty password", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + err := UpdateUserPassword(&User{ + ID: 1, + }, "", + ) + assert.Error(t, err) + assert.True(t, IsErrEmptyNewPassword(err)) + }) +} + +func TestListUsers(t *testing.T) { + t.Run("normal", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + all, err := ListUsers("user1") + assert.NoError(t, err) + assert.True(t, len(all) > 0) + assert.Equal(t, all[0].Username, "user1") + }) + t.Run("all users", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + all, err := ListUsers("") + assert.NoError(t, err) + assert.Len(t, all, 13) + }) +} + +func TestUserPasswordReset(t *testing.T) { + t.Run("normal", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + reset := &PasswordReset{ + Token: "passwordresettesttoken", + NewPassword: "12345", + } + err := ResetPassword(reset) + assert.NoError(t, err) + }) + t.Run("without password", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + reset := &PasswordReset{ + Token: "passwordresettesttoken", + } + err := ResetPassword(reset) + assert.Error(t, err) + assert.True(t, IsErrNoUsernamePassword(err)) + }) + t.Run("empty token", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + reset := &PasswordReset{ + Token: "somethingsomething", + NewPassword: "12345", + } + err := ResetPassword(reset) + assert.Error(t, err) + assert.True(t, IsErrInvalidPasswordResetToken(err)) + }) + t.Run("wrong token", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + reset := &PasswordReset{ + Token: "somethingsomething", + NewPassword: "12345", + } + err := ResetPassword(reset) + assert.Error(t, err) + assert.True(t, IsErrInvalidPasswordResetToken(err)) + }) +} diff --git a/pkg/user/users_list.go b/pkg/user/users_list.go new file mode 100644 index 00000000..dd838aab --- /dev/null +++ b/pkg/user/users_list.go @@ -0,0 +1,36 @@ +// Copyright2018-2020 Vikunja and contriubtors. All rights reserved. +// +// This file is part of Vikunja. +// +// Vikunja is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vikunja is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vikunja. If not, see . + +package user + +// ListUsers returns a list with all users, filtered by an optional searchstring +func ListUsers(searchterm string) (users []User, err error) { + + if searchterm == "" { + err = x.Find(&users) + } else { + err = x. + Where("username LIKE ?", "%"+searchterm+"%"). + Find(&users) + } + + if err != nil { + return []User{}, err + } + + return users, nil +}