From 7e9446ea07babbb24a5852d843dc2bdba73fa2bb Mon Sep 17 00:00:00 2001 From: konrad Date: Sun, 26 Jan 2020 17:08:06 +0000 Subject: [PATCH] Refactor User and DB handling (#123) fix copyright date Add more user tests More user tests More user tests Start refactoring user tests Docs Fix lint Fix db fixtures init in tests Fix models test Fix loading fixtures Fix ineffasign Fix lint Fix integration tests Fix init of test engine creation Fix user related tests Better handling of creating test enging Moved all fixtures to db package Moved all fixtures to db package Moved user related stuff to seperate package Co-authored-by: kolaente Reviewed-on: https://kolaente.dev/vikunja/api/pulls/123 --- docs/content/doc/development/structure.md | 37 +++ docs/content/doc/development/test.md | 26 ++ go.mod | 1 - pkg/caldav/caldav.go | 4 +- pkg/cmd/cmd.go | 5 + pkg/db/db.go | 28 -- pkg/{files => db}/fixtures/files.yml | 0 pkg/{models => db}/fixtures/label_task.yml | 0 pkg/{models => db}/fixtures/labels.yml | 0 pkg/{models => db}/fixtures/link_sharing.yml | 0 pkg/{models => db}/fixtures/list.yml | 0 pkg/{models => db}/fixtures/namespaces.yml | 0 .../fixtures/task_assignees.yml | 0 .../fixtures/task_attachments.yml | 0 .../fixtures/task_relations.yml | 0 .../fixtures/task_reminders.yml | 0 pkg/{models => db}/fixtures/tasks.yml | 0 pkg/{models => db}/fixtures/team_list.yml | 0 pkg/{models => db}/fixtures/team_members.yml | 0 .../fixtures/team_namespaces.yml | 0 pkg/{models => db}/fixtures/teams.yml | 0 pkg/{models => db}/fixtures/users.yml | 0 pkg/{models => db}/fixtures/users_list.yml | 0 .../fixtures/users_namespace.yml | 0 pkg/db/test.go | 69 ++++ pkg/db/test_fixtures.go | 31 +- pkg/files/filehandling.go | 21 +- pkg/integrations/integrations.go | 20 +- pkg/integrations/login_test.go | 10 +- pkg/integrations/register_test.go | 14 +- pkg/integrations/user_change_password_test.go | 8 +- pkg/integrations/user_confirm_email_test.go | 8 +- .../user_password_request_token_test.go | 6 +- pkg/integrations/user_password_reset_test.go | 6 +- pkg/migration/migration.go | 4 + pkg/models/bulk_task_test.go | 12 +- pkg/models/error.go | 267 ---------------- pkg/models/fixtures/files.yml | 1 - pkg/models/label.go | 11 +- pkg/models/label_rights.go | 3 +- pkg/models/label_task.go | 9 +- pkg/models/label_task_test.go | 38 ++- pkg/models/label_test.go | 49 +-- pkg/models/link_sharing.go | 9 +- pkg/models/list.go | 17 +- pkg/models/list_rights.go | 9 +- pkg/models/list_team_test.go | 8 +- pkg/models/list_test.go | 50 +-- pkg/models/list_users.go | 15 +- pkg/models/list_users_rights_test.go | 10 +- pkg/models/list_users_test.go | 24 +- pkg/models/main_test.go | 5 +- pkg/models/models.go | 3 +- pkg/models/namespace.go | 17 +- pkg/models/namespace_team_rights_test.go | 10 +- pkg/models/namespace_team_test.go | 10 +- pkg/models/namespace_test.go | 12 +- pkg/models/namespace_users.go | 11 +- pkg/models/namespace_users_rights_test.go | 10 +- pkg/models/namespace_users_test.go | 24 +- pkg/models/task_assignees.go | 25 +- pkg/models/task_attachment.go | 7 +- pkg/models/task_attachment_test.go | 7 +- pkg/models/task_collection.go | 3 +- pkg/models/task_collection_test.go | 24 +- pkg/models/task_relation.go | 3 +- pkg/models/task_relation_test.go | 50 ++- pkg/models/tasks.go | 9 +- pkg/models/tasks_test.go | 34 +- pkg/models/team_members.go | 9 +- pkg/models/team_members_test.go | 7 +- pkg/models/teams.go | 9 +- pkg/models/teams_rights_test.go | 12 +- pkg/models/teams_test.go | 3 +- pkg/models/unit_tests.go | 93 ++---- pkg/models/{users_list.go => user_list.go} | 36 +-- pkg/models/user_test.go | 178 ----------- pkg/models/users_list_test.go | 57 ++-- .../migration/create_from_structure.go | 3 +- pkg/modules/migration/handler/handler.go | 5 +- pkg/modules/migration/migration_status.go | 8 +- pkg/modules/migration/migrator.go | 6 +- .../migration/wunderlist/wunderlist.go | 3 +- pkg/routes/api/v1/auth.go | 5 +- pkg/routes/api/v1/list_by_namespace.go | 5 +- pkg/routes/api/v1/login.go | 7 +- pkg/routes/api/v1/task_attachment.go | 5 +- pkg/routes/api/v1/user_add_update.go | 12 +- pkg/routes/api/v1/user_confirm_email.go | 5 +- pkg/routes/api/v1/user_delete.go | 65 ---- pkg/routes/api/v1/user_list.go | 5 +- pkg/routes/api/v1/user_password_reset.go | 9 +- pkg/routes/api/v1/user_show.go | 6 +- pkg/routes/api/v1/user_update_password.go | 9 +- pkg/routes/caldav/handler.go | 7 +- pkg/routes/caldav/listStorageProvider.go | 3 +- pkg/routes/metrics.go | 3 +- pkg/routes/routes.go | 5 +- pkg/user/db.go | 50 +++ pkg/user/error.go | 291 +++++++++++++++++ pkg/user/main_test.go | 29 ++ pkg/user/test.go | 42 +++ pkg/{models => user}/user.go | 50 +-- pkg/{models => user}/user_email_confirm.go | 17 +- .../user_email_confirm_test.go | 23 +- pkg/{models => user}/user_password_reset.go | 17 +- pkg/user/user_test.go | 301 ++++++++++++++++++ pkg/user/users_list.go | 36 +++ 108 files changed, 1506 insertions(+), 1024 deletions(-) rename pkg/{files => db}/fixtures/files.yml (100%) rename pkg/{models => db}/fixtures/label_task.yml (100%) rename pkg/{models => db}/fixtures/labels.yml (100%) rename pkg/{models => db}/fixtures/link_sharing.yml (100%) rename pkg/{models => db}/fixtures/list.yml (100%) rename pkg/{models => db}/fixtures/namespaces.yml (100%) rename pkg/{models => db}/fixtures/task_assignees.yml (100%) rename pkg/{models => db}/fixtures/task_attachments.yml (100%) rename pkg/{models => db}/fixtures/task_relations.yml (100%) rename pkg/{models => db}/fixtures/task_reminders.yml (100%) rename pkg/{models => db}/fixtures/tasks.yml (100%) rename pkg/{models => db}/fixtures/team_list.yml (100%) rename pkg/{models => db}/fixtures/team_members.yml (100%) rename pkg/{models => db}/fixtures/team_namespaces.yml (100%) rename pkg/{models => db}/fixtures/teams.yml (100%) rename pkg/{models => db}/fixtures/users.yml (100%) rename pkg/{models => db}/fixtures/users_list.yml (100%) rename pkg/{models => db}/fixtures/users_namespace.yml (100%) create mode 100644 pkg/db/test.go delete mode 120000 pkg/models/fixtures/files.yml rename pkg/models/{users_list.go => user_list.go} (79%) delete mode 100644 pkg/models/user_test.go delete mode 100644 pkg/routes/api/v1/user_delete.go create mode 100644 pkg/user/db.go create mode 100644 pkg/user/error.go create mode 100644 pkg/user/main_test.go create mode 100644 pkg/user/test.go rename pkg/{models => user}/user.go (89%) rename pkg/{models => user}/user_email_confirm.go (69%) rename pkg/{models => user}/user_email_confirm_test.go (69%) rename pkg/{models => user}/user_password_reset.go (85%) create mode 100644 pkg/user/user_test.go create mode 100644 pkg/user/users_list.go diff --git a/docs/content/doc/development/structure.md b/docs/content/doc/development/structure.md index 8e819b6c48..29dc1eba3d 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 1fdcfe8937..55ddc883f7 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 2b30439190..b1a8ffc57f 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 d988407ed3..c6cc1f2faf 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 62d70f8ad0..d2c1b38599 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 b65dc68090..4d9a46ef25 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 0000000000..a713108451 --- /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 3ad9708b4f..16d75a2b3f 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 53c12e9e8d..3540b26e50 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 40bf502791..008271e3b7 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 546e0270c1..e6ad21810b 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 34b28de030..3ce59a0836 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 5d639e29ed..e9afdc5eff 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 9e439af2d0..35bd769f50 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 e171d7b8b5..12f1f72cdb 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 50e09c8997..28867b2186 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 0d7bbfeb0d..b55390a80d 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 289ba09cf7..39aa9f846d 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 0aa69d3911..110a119808 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 aa88944131..0000000000 --- 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 9f226bafc7..8e525cbc5a 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 c7c6fe6fee..9c884defd5 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 bb2f57f20e..6c7faa4766 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 5d619857fa..cd497e8594 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 96c2b61eeb..92b7537f4e 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 9012eee8c4..115e3405c3 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 0f3a47265e..b18da771f0 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 0a5eef8dd6..d3f96c8c61 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 b6e9c68ba2..984c706941 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 7431b5ed1a..0c88428f24 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 f2b5a7472c..20564ef910 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 1fac26357e..1ba105fe77 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 07daafafc2..ab3c61db83 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 7c1911aa59..c3c912f635 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 33275901d3..4248f6a4f0 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 932d0a704d..7b68de052c 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 09b3d7830e..1634445a18 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 ef07cafc2f..a97ab9f1bd 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 81b4ef9914..d510a76863 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 b311b51fb2..b6256316b4 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 20de120f5b..b48586c3c3 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 4bcb49d9c3..0fc7fd8f6c 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 b588a6497c..2a59569aea 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 b62f895280..0ff9e9bfd1 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 cdb863e9dd..292405caf6 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 73840c7687..de19f6462b 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 26807a6ce0..d3b440b665 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 c11d991195..75b8b4fe15 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 52f6c4f4b5..b804f0e359 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 ee99e6effd..a6469c81bc 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 2d40bf9e91..af6ba54016 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 39acedc93f..9fb2f81f45 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 3d43ff39ee..4f9a4a304e 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 db8a6d4dfa..b77047f7bb 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 45ac3446db..3c8c27dc50 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 01d7360bf3..e08f89a5bd 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 84fa09790c..57d008697f 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 c0aefe1281..d23b993ca2 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 f400875c56..0000000000 --- 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 2512088d92..3d2cf900c2 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 07164e642e..7796715f31 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 805e93747c..a86780248d 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 4ec8bec883..e520934032 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 8ea27391ae..059a2be51e 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 3930146b27..1d9c6beeda 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 ee7688e1e0..17553179ee 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 6287c1537d..9bc5b54c33 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 2eeea4fe3c..e92bad3b23 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 4d189ed0b9..d1d60355be 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 08aa4390a5..42e5a0254d 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 f5021ad019..66b4e2ddda 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 8940b22b45..0000000000 --- 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 8ae333999c..abfca4c467 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 62512c7906..4b2bb6d8b8 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 5cae05c5a7..390a5879b6 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 74c251e53e..4c775eb061 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 afab352732..52d3616072 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 88419892e4..1d9f8663c6 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 b960f60253..1f811abfbc 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 fdb6705df2..6facec4199 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 0000000000..a1f3b731e8 --- /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 0000000000..1a26a5b719 --- /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 0000000000..57a0e9211b --- /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 0000000000..f2a61cd29e --- /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 e2c2e8108a..42cfc33d3a 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 216d89b713..68378c2bf7 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 e24018051f..bfb38138e6 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 f2d273bc93..24d47d2b5e 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 0000000000..4ff757594a --- /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 0000000000..dd838aab3b --- /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 +}