From 3a7678bfb98d4dd96c9e1042ef492851fb33afb3 Mon Sep 17 00:00:00 2001 From: renovate Date: Thu, 11 Mar 2021 14:45:51 +0000 Subject: [PATCH 01/35] Update module gabriel-vasile/mimetype to v1.2.0 (#812) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/812 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c39588bbc..f840fe031 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/disintegration/imaging v1.6.2 github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 github.com/fzipp/gocyclo v0.3.1 - github.com/gabriel-vasile/mimetype v1.1.2 + github.com/gabriel-vasile/mimetype v1.2.0 github.com/getsentry/sentry-go v0.10.0 github.com/go-errors/errors v1.1.1 // indirect github.com/go-redis/redis/v8 v8.6.0 diff --git a/go.sum b/go.sum index 6c786f1f2..9c47f938e 100644 --- a/go.sum +++ b/go.sum @@ -195,6 +195,8 @@ github.com/fzipp/gocyclo v0.3.1 h1:A9UeX3HJSXTBzvHzhqoYVuE0eAhe+aM8XBCCwsPMZOc= github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= github.com/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU= github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= +github.com/gabriel-vasile/mimetype v1.2.0 h1:A6z5J8OhjiWFV91sQ3dMI8apYu/tvP9keDaMM3Xu6p4= +github.com/gabriel-vasile/mimetype v1.2.0/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= From f78bb613e414eb716ae3b3dc09b25b9b7a711636 Mon Sep 17 00:00:00 2001 From: renovate Date: Thu, 11 Mar 2021 17:19:53 +0000 Subject: [PATCH 02/35] Update golang.org/x/oauth2 commit hash to 5366d9d (#813) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/813 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f840fe031..4af8c232a 100644 --- a/go.mod +++ b/go.mod @@ -79,7 +79,7 @@ require ( golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 golang.org/x/net v0.0.0-20201216054612-986b41b23924 // indirect - golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 + golang.org/x/oauth2 v0.0.0-20210311163135-5366d9dc1934 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d diff --git a/go.sum b/go.sum index 9c47f938e..a25510b74 100644 --- a/go.sum +++ b/go.sum @@ -992,6 +992,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 h1:alLDrZkL34Y2bnGHfvC1CYBRBXCXgx8AC2vY4MRtYX4= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210311163135-5366d9dc1934 h1:Y2nxrNrrWOZn5yjDEEVU3R7V9HGW5SWsw6B6YL/ZRFw= +golang.org/x/oauth2 v0.0.0-20210311163135-5366d9dc1934/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From cf2cfde4fcb3a3a3e759dbd68189dfd0e1afb3ff Mon Sep 17 00:00:00 2001 From: renovate Date: Fri, 12 Mar 2021 09:22:48 +0000 Subject: [PATCH 03/35] Update module src.techknowlogick.com/xgo to v1.4.0+1.16.2 (#814) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/814 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4af8c232a..bf88e48ca 100644 --- a/go.mod +++ b/go.mod @@ -92,7 +92,7 @@ require ( gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c honnef.co/go/tools v0.0.1-2020.1.5 - src.techknowlogick.com/xgo v1.3.1-0.20210218015915-6a603afeb960 + src.techknowlogick.com/xgo v1.4.1-0.20210311222705-d25c33fcd864 src.techknowlogick.com/xormigrate v1.4.0 xorm.io/builder v0.3.8 xorm.io/core v0.7.3 diff --git a/go.sum b/go.sum index a25510b74..9febfeac3 100644 --- a/go.sum +++ b/go.sum @@ -1330,6 +1330,8 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= src.techknowlogick.com/xgo v1.3.1-0.20210218015915-6a603afeb960 h1:885qVTLUDXe985P1nNf50jb5Up02igmxt7sNuE/4W/E= src.techknowlogick.com/xgo v1.3.1-0.20210218015915-6a603afeb960/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU= +src.techknowlogick.com/xgo v1.4.1-0.20210311222705-d25c33fcd864 h1:wBdOhmwnc6zZZzlGdhZLxBk2yDzKcQoqB5C9fePlORM= +src.techknowlogick.com/xgo v1.4.1-0.20210311222705-d25c33fcd864/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU= src.techknowlogick.com/xormigrate v1.4.0 h1:gAfLoDwcVfMiFhSXg5Qwm7LNnG1iUbBVDUNfHagDLQc= src.techknowlogick.com/xormigrate v1.4.0/go.mod h1:xCtbAK00lJ0v4zP2O6VBVMG3RHm7W5Yo1Dz0r9kL/ho= xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= From 156e50f3714374c6531235f76ac6b8d1b17a2c86 Mon Sep 17 00:00:00 2001 From: renovate Date: Sun, 14 Mar 2021 12:49:05 +0000 Subject: [PATCH 04/35] Update golang.org/x/oauth2 commit hash to cd4f82c (#815) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/815 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index bf88e48ca..307b344ec 100644 --- a/go.mod +++ b/go.mod @@ -79,7 +79,7 @@ require ( golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 golang.org/x/net v0.0.0-20201216054612-986b41b23924 // indirect - golang.org/x/oauth2 v0.0.0-20210311163135-5366d9dc1934 + golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d diff --git a/go.sum b/go.sum index 9febfeac3..18f1affae 100644 --- a/go.sum +++ b/go.sum @@ -994,6 +994,8 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 h1:alLDrZkL34Y2bnGHfvC1CY golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210311163135-5366d9dc1934 h1:Y2nxrNrrWOZn5yjDEEVU3R7V9HGW5SWsw6B6YL/ZRFw= golang.org/x/oauth2 v0.0.0-20210311163135-5366d9dc1934/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 h1:duBc5zuJsmJXYOVVE/6PxejI+N3AaCqKjtsoLn1Je5Q= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 67167d4abbe1550dded1719b99641a6f0c67f7b4 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 14 Mar 2021 21:12:03 +0100 Subject: [PATCH 05/35] Fix shared lists showing up twice --- pkg/models/namespace.go | 4 ++++ pkg/models/namespace_test.go | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index 4b4c4c634..25f90378a 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -550,6 +550,10 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int } for _, list := range lists { + if list.NamespaceID == SharedListsPseudoNamespace.ID { + // Shared lists are already in the namespace + continue + } namespaces[list.NamespaceID].Lists = append(namespaces[list.NamespaceID].Lists, list) } diff --git a/pkg/models/namespace_test.go b/pkg/models/namespace_test.go index 24aae54cd..363558c18 100644 --- a/pkg/models/namespace_test.go +++ b/pkg/models/namespace_test.go @@ -194,6 +194,7 @@ func TestNamespace_Delete(t *testing.T) { func TestNamespace_ReadAll(t *testing.T) { user1 := &user.User{ID: 1} + user6 := &user.User{ID: 6} user7 := &user.User{ID: 7} user11 := &user.User{ID: 11} user12 := &user.User{ID: 12} @@ -209,7 +210,7 @@ func TestNamespace_ReadAll(t *testing.T) { namespaces := nn.([]*NamespaceWithLists) assert.NotNil(t, namespaces) assert.Len(t, namespaces, 11) // Total of 11 including shared, favorites and saved filters - assert.Equal(t, int64(-3), namespaces[0].ID) // The first one should be the one with shared filters + assert.Equal(t, int64(-3), namespaces[0].ID) // The first one should be the one with saved filters assert.Equal(t, int64(-2), namespaces[1].ID) // The second one should be the one with favorites assert.Equal(t, int64(-1), namespaces[2].ID) // The third one should be the one with the shared namespaces // Ensure every list and namespace are not archived @@ -220,6 +221,28 @@ func TestNamespace_ReadAll(t *testing.T) { } } }) + t.Run("no own shared lists", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + n := &Namespace{} + nn, _, _, err := n.ReadAll(s, user6, "", 1, -1) + assert.NoError(t, err) + namespaces := nn.([]*NamespaceWithLists) + assert.NotNil(t, namespaces) + assert.Equal(t, int64(-1), namespaces[1].ID) // The third one should be the one with the shared namespaces + + sharedListOccurences := make(map[int64]int64) + for _, list := range namespaces[1].Lists { + assert.NotEqual(t, user1.ID, list.OwnerID) + sharedListOccurences[list.ID]++ + } + + for listID, occ := range sharedListOccurences { + assert.Equal(t, int64(1), occ, "shared list %d is present %d times, should be 1", listID, occ) + } + }) t.Run("namespaces only", func(t *testing.T) { db.LoadAndAssertFixtures(t) s := db.NewSession() From 5e048feedf9b0233b52f3d72ca5ce0284281562d Mon Sep 17 00:00:00 2001 From: renovate Date: Mon, 15 Mar 2021 21:17:14 +0000 Subject: [PATCH 06/35] Update golang.org/x/crypto commit hash to e6e6c4f (#816) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/816 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 3 +-- go.sum | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 307b344ec..57011666a 100644 --- a/go.mod +++ b/go.mod @@ -75,10 +75,9 @@ require ( github.com/stretchr/testify v1.7.0 github.com/swaggo/swag v1.7.0 github.com/ulule/limiter/v3 v3.8.0 - golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 + golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 - golang.org/x/net v0.0.0-20201216054612-986b41b23924 // indirect golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect diff --git a/go.sum b/go.sum index 18f1affae..1f8837426 100644 --- a/go.sum +++ b/go.sum @@ -894,6 +894,8 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rB golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -984,6 +986,8 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/Lt golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY= golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= From f8683796e81208e4b06a6e20be4c89b120387bd1 Mon Sep 17 00:00:00 2001 From: renovate Date: Wed, 17 Mar 2021 21:11:29 +0000 Subject: [PATCH 07/35] Update golang.org/x/crypto commit hash to 513c2a4 (#817) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/817 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 57011666a..222ee22da 100644 --- a/go.mod +++ b/go.mod @@ -75,7 +75,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/swaggo/swag v1.7.0 github.com/ulule/limiter/v3 v3.8.0 - golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b + golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 diff --git a/go.sum b/go.sum index 1f8837426..4f0d5b894 100644 --- a/go.sum +++ b/go.sum @@ -896,6 +896,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqt golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo= +golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 87f7cbfa73a52ae2110cd939d61df043bae7f7ed Mon Sep 17 00:00:00 2001 From: renovate Date: Wed, 17 Mar 2021 21:11:45 +0000 Subject: [PATCH 08/35] Update golang.org/x/term commit hash to de623e6 (#818) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/818 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 222ee22da..199362f9f 100644 --- a/go.mod +++ b/go.mod @@ -81,7 +81,7 @@ require ( golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d + golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 golang.org/x/text v0.3.5 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/go.sum b/go.sum index 4f0d5b894..616ed0424 100644 --- a/go.sum +++ b/go.sum @@ -1087,6 +1087,8 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs= +golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From fc4eb8ceb9cbda2b27381f3d3bc314faf5f3b038 Mon Sep 17 00:00:00 2001 From: renovate Date: Thu, 18 Mar 2021 18:25:48 +0000 Subject: [PATCH 09/35] Update module prometheus/client_golang to v1.10.0 (#819) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/819 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 3 +-- go.sum | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 199362f9f..52af10fe2 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,7 @@ require ( github.com/pelletier/go-toml v1.8.0 // indirect github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect github.com/pquerna/otp v1.3.0 - github.com/prometheus/client_golang v1.9.0 + github.com/prometheus/client_golang v1.10.0 github.com/robfig/cron/v3 v3.0.1 github.com/samedi/caldav-go v3.0.0+incompatible github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect @@ -80,7 +80,6 @@ require ( golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 golang.org/x/text v0.3.5 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index 616ed0424..326f0187d 100644 --- a/go.sum +++ b/go.sum @@ -677,6 +677,8 @@ github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNja github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU= github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg= +github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f h1:BVwpUVJDADN2ufcGik7W992pyps0wZ888b/y9GXcLTU= @@ -698,6 +700,8 @@ github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lN github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y= +github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -709,6 +713,8 @@ github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFB github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -1015,6 +1021,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2By golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1082,6 +1089,8 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From 6fa95e6492871838adcd145819e4879cc7034628 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 18 Mar 2021 21:39:38 +0100 Subject: [PATCH 10/35] Fix getting lists for shared, favorite and saved lists namespace --- pkg/models/list.go | 50 ++++++++++++++++++++++++---------- pkg/models/namespace.go | 4 +++ pkg/models/namespace_rights.go | 5 +++- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/pkg/models/list.go b/pkg/models/list.go index 118cbdc0c..3db5bcae1 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -102,20 +102,42 @@ var FavoritesPseudoList = List{ // GetListsByNamespaceID gets all lists in a namespace func GetListsByNamespaceID(s *xorm.Session, nID int64, doer *user.User) (lists []*List, err error) { - if nID == -1 { - err = s.Select("l.*"). - Table("list"). - Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id"). - Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tl.team_id"). - Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id"). - Join("LEFT", []string{"namespaces", "n"}, "l.namespace_id = n.id"). - Where("tm.user_id = ?", doer.ID). - Where("l.is_archived = false"). - Where("n.is_archived = false"). - Or("ul.user_id = ?", doer.ID). - GroupBy("l.id"). - Find(&lists) - } else { + switch nID { + case SharedListsPseudoNamespace.ID: + nnn, err := getSharedListsInNamespace(s, false, doer) + if err != nil { + return nil, err + } + if nnn != nil && nnn.Lists != nil { + lists = nnn.Lists + } + case FavoritesPseudoNamespace.ID: + namespaces := make(map[int64]*NamespaceWithLists) + _, err := getNamespacesWithLists(s, &namespaces, "", false, 0, -1, doer.ID) + if err != nil { + return nil, err + } + namespaceIDs, _ := getNamespaceOwnerIDs(namespaces) + ls, err := getListsForNamespaces(s, namespaceIDs, false) + if err != nil { + return nil, err + } + nnn, err := getFavoriteLists(s, ls, namespaceIDs, doer) + if err != nil { + return nil, err + } + if nnn != nil && nnn.Lists != nil { + lists = nnn.Lists + } + case SavedFiltersPseudoNamespace.ID: + nnn, err := getSavedFilters(s, doer) + if err != nil { + return nil, err + } + if nnn != nil && nnn.Lists != nil { + lists = nnn.Lists + } + default: err = s.Select("l.*"). Alias("l"). Join("LEFT", []string{"namespaces", "n"}, "l.namespace_id = n.id"). diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index 25f90378a..69cced677 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -113,6 +113,10 @@ func getNamespaceSimpleByID(s *xorm.Session, id int64) (namespace *Namespace, er return &FavoritesPseudoNamespace, nil } + if id == SavedFiltersPseudoNamespace.ID { + return &SavedFiltersPseudoNamespace, nil + } + namespace = &Namespace{} exists, err := s.Where("id = ?", id).Get(namespace) diff --git a/pkg/models/namespace_rights.go b/pkg/models/namespace_rights.go index d634ba25c..97cb6f045 100644 --- a/pkg/models/namespace_rights.go +++ b/pkg/models/namespace_rights.go @@ -72,7 +72,10 @@ func (n *Namespace) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bo return false, 0, err } - if a.GetID() == nn.OwnerID { + if a.GetID() == nn.OwnerID || + nn.ID == SharedListsPseudoNamespace.ID || + nn.ID == FavoritesPseudoNamespace.ID || + nn.ID == SavedFiltersPseudoNamespace.ID { return true, int(RightAdmin), nil } From 9d0dcb8d7db000db449ec483edad77ad0b38fb5d Mon Sep 17 00:00:00 2001 From: renovate Date: Sun, 21 Mar 2021 10:53:41 +0000 Subject: [PATCH 11/35] Update module spf13/afero to v1.6.0 (#820) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/820 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 52af10fe2..0c983c2a5 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( github.com/samedi/caldav-go v3.0.0+incompatible github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 - github.com/spf13/afero v1.5.1 + github.com/spf13/afero v1.6.0 github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.1.3 github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index 326f0187d..51c864d97 100644 --- a/go.sum +++ b/go.sum @@ -760,6 +760,8 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg= github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= From ee436efba306edb7f0f8c4e5f5e1c9dfff0746d2 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 21 Mar 2021 17:49:14 +0100 Subject: [PATCH 12/35] Add endpoint to remove a list background --- pkg/models/list.go | 1 + pkg/modules/background/handler/background.go | 81 +++++++++++++++----- pkg/routes/routes.go | 1 + pkg/swagger/docs.go | 50 ++++++++++++ pkg/swagger/swagger.json | 50 ++++++++++++ pkg/swagger/swagger.yaml | 32 ++++++++ 6 files changed, 195 insertions(+), 20 deletions(-) diff --git a/pkg/models/list.go b/pkg/models/list.go index 3db5bcae1..a742402fe 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -543,6 +543,7 @@ func CreateOrUpdateList(s *xorm.Session, list *List, auth web.Auth) (err error) "identifier", "hex_color", "is_favorite", + "background_file_id", } if list.Description != "" { colsToUpdate = append(colsToUpdate, "description") diff --git a/pkg/modules/background/handler/background.go b/pkg/modules/background/handler/background.go index 4548c0739..7e5f85a0c 100644 --- a/pkg/modules/background/handler/background.go +++ b/pkg/modules/background/handler/background.go @@ -199,6 +199,33 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error { return c.JSON(http.StatusOK, list) } +func checkListBackgroundRights(s *xorm.Session, c echo.Context) (list *models.List, auth web.Auth, err error) { + auth, err = auth2.GetAuthFromClaims(c) + if err != nil { + return nil, auth, echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error()) + } + + listID, err := strconv.ParseInt(c.Param("list"), 10, 64) + if err != nil { + return nil, auth, echo.NewHTTPError(http.StatusBadRequest, "Invalid list ID: "+err.Error()) + } + + // Check if a background for this list exists + Rights + list = &models.List{ID: listID} + can, _, err := list.CanRead(s, auth) + if err != nil { + _ = s.Rollback() + return nil, auth, handler.HandleHTTPError(err, c) + } + if !can { + _ = s.Rollback() + log.Infof("Tried to get list background of list %d while not having the rights for it (User: %v)", listID, auth) + return nil, auth, echo.NewHTTPError(http.StatusForbidden) + } + + return +} + // GetListBackground serves a previously set background from a list // It has no knowledge of the provider that was responsible for setting the background. // @Summary Get the list background @@ -214,31 +241,14 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error { // @Router /lists/{id}/background [get] func GetListBackground(c echo.Context) error { - auth, err := auth2.GetAuthFromClaims(c) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error()) - } - - listID, err := strconv.ParseInt(c.Param("list"), 10, 64) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid list ID: "+err.Error()) - } - s := db.NewSession() defer s.Close() - // Check if a background for this list exists + Rights - list := &models.List{ID: listID} - can, _, err := list.CanRead(s, auth) + list, _, err := checkListBackgroundRights(s, c) if err != nil { - _ = s.Rollback() - return handler.HandleHTTPError(err, c) - } - if !can { - _ = s.Rollback() - log.Infof("Tried to get list background of list %d while not having the rights for it (User: %v)", listID, auth) - return echo.NewHTTPError(http.StatusForbidden) + return err } + if list.BackgroundFileID == 0 { _ = s.Rollback() return echo.NotFoundHandler(c) @@ -266,3 +276,34 @@ func GetListBackground(c echo.Context) error { // Serve the file return c.Stream(http.StatusOK, "image/jpg", bgFile.File) } + +// RemoveListBackground removes a list background, no matter the background provider +// @Summary Remove a list background +// @Description Removes a previously set list background, regardless of the list provider used to set the background. It does not throw an error if the list does not have a background. +// @tags list +// @Produce json +// @Param id path int true "List ID" +// @Security JWTKeyAuth +// @Success 200 {object} models.List "The list" +// @Failure 403 {object} models.Message "No access to this list." +// @Failure 404 {object} models.Message "The list does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{id}/background [delete] +func RemoveListBackground(c echo.Context) error { + s := db.NewSession() + defer s.Close() + + list, auth, err := checkListBackgroundRights(s, c) + if err != nil { + return err + } + + list.BackgroundFileID = 0 + list.BackgroundInformation = nil + err = list.Update(s, auth) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, list) +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 2a3a4c459..7232fe622 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -576,6 +576,7 @@ func registerAPIRoutes(a *echo.Group) { // List Backgrounds if config.BackgroundsEnabled.GetBool() { a.GET("/lists/:list/background", backgroundHandler.GetListBackground) + a.DELETE("/lists/:list/background", backgroundHandler.RemoveListBackground) if config.BackgroundsUploadEnabled.GetBool() { uploadBackgroundProvider := &backgroundHandler.BackgroundProvider{ Provider: func() background.Provider { diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index d2900bbb9..616c9de9e 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -1063,6 +1063,56 @@ var doc = `{ } } } + }, + "delete": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Removes a previously set list background, regardless of the list provider used to set the background. It does not throw an error if the list does not have a background.", + "produces": [ + "application/json" + ], + "tags": [ + "list" + ], + "summary": "Remove a list background", + "parameters": [ + { + "type": "integer", + "description": "List ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The list", + "schema": { + "$ref": "#/definitions/models.List" + } + }, + "403": { + "description": "No access to this list.", + "schema": { + "$ref": "#/definitions/models.Message" + } + }, + "404": { + "description": "The list does not exist.", + "schema": { + "$ref": "#/definitions/models.Message" + } + }, + "500": { + "description": "Internal error", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } } }, "/lists/{id}/backgrounds/unsplash": { diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index 79e53cb4d..de2b6b423 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -1046,6 +1046,56 @@ } } } + }, + "delete": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Removes a previously set list background, regardless of the list provider used to set the background. It does not throw an error if the list does not have a background.", + "produces": [ + "application/json" + ], + "tags": [ + "list" + ], + "summary": "Remove a list background", + "parameters": [ + { + "type": "integer", + "description": "List ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The list", + "schema": { + "$ref": "#/definitions/models.List" + } + }, + "403": { + "description": "No access to this list.", + "schema": { + "$ref": "#/definitions/models.Message" + } + }, + "404": { + "description": "The list does not exist.", + "schema": { + "$ref": "#/definitions/models.Message" + } + }, + "500": { + "description": "Internal error", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } } }, "/lists/{id}/backgrounds/unsplash": { diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index c63880094..71b7d6667 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -1835,6 +1835,38 @@ paths: tags: - task /lists/{id}/background: + delete: + description: Removes a previously set list background, regardless of the list provider used to set the background. It does not throw an error if the list does not have a background. + parameters: + - description: List ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: The list + schema: + $ref: '#/definitions/models.List' + "403": + description: No access to this list. + schema: + $ref: '#/definitions/models.Message' + "404": + description: The list does not exist. + schema: + $ref: '#/definitions/models.Message' + "500": + description: Internal error + schema: + $ref: '#/definitions/models.Message' + security: + - JWTKeyAuth: [] + summary: Remove a list background + tags: + - list get: description: Get the list background of a specific list. **Returns json on error.** parameters: From 532855d85000b7174f6c8600252fe8893723d897 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 21 Mar 2021 18:48:41 +0100 Subject: [PATCH 13/35] Fix filter for task index --- pkg/models/task_collection_test.go | 13 +++++++++++++ pkg/models/tasks.go | 21 +++++++++++---------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 0e879ee4d..2f50fecf7 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -985,6 +985,19 @@ func TestTaskCollection_ReadAll(t *testing.T) { }, wantErr: false, }, + { + name: "filter by index", + fields: fields{ + FilterBy: []string{"index"}, + FilterValue: []string{"5"}, + FilterComparator: []string{"equals"}, + }, + args: defaultArgs, + want: []*Task{ + task5, + }, + wantErr: false, + }, } for _, tt := range tests { diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index b7045317c..f0d06b72a 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -178,33 +178,34 @@ func (t *Task) ReadAll(s *xorm.Session, a web.Auth, search string, page int, per } func getFilterCond(f *taskFilter, includeNulls bool) (cond builder.Cond, err error) { + field := "`" + f.field + "`" switch f.comparator { case taskFilterComparatorEquals: - cond = &builder.Eq{f.field: f.value} + cond = &builder.Eq{field: f.value} case taskFilterComparatorNotEquals: - cond = &builder.Neq{f.field: f.value} + cond = &builder.Neq{field: f.value} case taskFilterComparatorGreater: - cond = &builder.Gt{f.field: f.value} + cond = &builder.Gt{field: f.value} case taskFilterComparatorGreateEquals: - cond = &builder.Gte{f.field: f.value} + cond = &builder.Gte{field: f.value} case taskFilterComparatorLess: - cond = &builder.Lt{f.field: f.value} + cond = &builder.Lt{field: f.value} case taskFilterComparatorLessEquals: - cond = &builder.Lte{f.field: f.value} + cond = &builder.Lte{field: f.value} case taskFilterComparatorLike: val, is := f.value.(string) if !is { - return nil, ErrInvalidTaskFilterValue{Field: f.field, Value: f.value} + return nil, ErrInvalidTaskFilterValue{Field: field, Value: f.value} } - cond = &builder.Like{f.field, "%" + val + "%"} + cond = &builder.Like{field, "%" + val + "%"} case taskFilterComparatorIn: - cond = builder.In(f.field, f.value) + cond = builder.In(field, f.value) case taskFilterComparatorInvalid: // Nothing to do } if includeNulls { - cond = builder.Or(cond, &builder.IsNull{f.field}) + cond = builder.Or(cond, &builder.IsNull{field}) } return From 7b7ccddf51fee6edef977dbb8bfa618764cd1cbe Mon Sep 17 00:00:00 2001 From: renovate Date: Tue, 23 Mar 2021 19:28:10 +0000 Subject: [PATCH 14/35] Update golang.org/x/crypto commit hash to 0c34fe9 (#822) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/822 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0c983c2a5..d761cc62d 100644 --- a/go.mod +++ b/go.mod @@ -75,7 +75,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/swaggo/swag v1.7.0 github.com/ulule/limiter/v3 v3.8.0 - golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 diff --git a/go.sum b/go.sum index 51c864d97..9ba98c4bc 100644 --- a/go.sum +++ b/go.sum @@ -906,6 +906,8 @@ golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIs golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 7b29ac7128c1f15007ee8037721aff4e00425b0a Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 23 Mar 2021 22:32:23 +0100 Subject: [PATCH 15/35] Fix user uploaded avatars --- pkg/user/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/user/user.go b/pkg/user/user.go index 838097e38..bbf397f2e 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -61,7 +61,7 @@ type User struct { EmailConfirmToken string `xorm:"varchar(450) null" json:"-"` AvatarProvider string `xorm:"varchar(255) null" json:"-"` - AvatarFileID int64 `xorn:"null" json:"-"` + AvatarFileID int64 `xorm:"null" json:"-"` // Issuer and Subject contain the issuer and subject from the source the user authenticated with. Issuer string `xorm:"text null" json:"-"` From d1b87d2705603754a4afb49ae12ea1a1aeb361af Mon Sep 17 00:00:00 2001 From: konrad Date: Wed, 24 Mar 2021 20:16:35 +0000 Subject: [PATCH 16/35] Add a "done" option to kanban buckets (#821) Co-authored-by: kolaente Reviewed-on: https://kolaente.dev/vikunja/api/pulls/821 Co-authored-by: konrad Co-committed-by: konrad --- docs/content/doc/usage/errors.md | 9 ++- pkg/db/fixtures/buckets.yml | 2 + pkg/migration/20210321185225.go | 43 ++++++++++ pkg/models/error.go | 29 +++++++ pkg/models/kanban.go | 36 ++++++++- pkg/models/kanban_test.go | 15 ++++ pkg/models/tasks.go | 131 ++++++++++++++++--------------- pkg/models/tasks_test.go | 80 +++++++++++++++++++ pkg/swagger/docs.go | 4 + pkg/swagger/swagger.json | 4 + pkg/swagger/swagger.yaml | 3 + 11 files changed, 288 insertions(+), 68 deletions(-) create mode 100644 pkg/migration/20210321185225.go diff --git a/docs/content/doc/usage/errors.md b/docs/content/doc/usage/errors.md index a418cff80..9b072cd0d 100644 --- a/docs/content/doc/usage/errors.md +++ b/docs/content/doc/usage/errors.md @@ -131,10 +131,11 @@ This document describes the different errors Vikunja can return. | ErrorCode | HTTP Status Code | Description | |-----------|------------------|-------------| -| 10001 | 404 | The bucket does not exist. | -| 10002 | 400 | The bucket does not belong to that list. | -| 10003 | 412 | You cannot remove the last bucket on a list. | -| 10004 | 412 | You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold. | +| 10001 | 404 | The bucket does not exist. | +| 10002 | 400 | The bucket does not belong to that list. | +| 10003 | 412 | You cannot remove the last bucket on a list. | +| 10004 | 412 | You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold. | +| 10005 | 412 | There can be only one done bucket per list. | ## Saved Filters diff --git a/pkg/db/fixtures/buckets.yml b/pkg/db/fixtures/buckets.yml index 5c2c62d6e..7f2940260 100644 --- a/pkg/db/fixtures/buckets.yml +++ b/pkg/db/fixtures/buckets.yml @@ -16,12 +16,14 @@ title: testbucket3 list_id: 1 created_by_id: 1 + is_done_bucket: 1 created: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52 - id: 4 title: testbucket4 - other list list_id: 2 created_by_id: 1 + is_done_bucket: 1 created: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52 # The following are not or only partly owned by user 1 diff --git a/pkg/migration/20210321185225.go b/pkg/migration/20210321185225.go new file mode 100644 index 000000000..eec75a3df --- /dev/null +++ b/pkg/migration/20210321185225.go @@ -0,0 +1,43 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type buckets20210321185225 struct { + IsDoneBucket bool `xorm:"BOOL" json:"is_done_column"` +} + +func (buckets20210321185225) TableName() string { + return "buckets" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20210321185225", + Description: "Add is done bucket to buckets", + Migrate: func(tx *xorm.Engine) error { + return tx.Sync2(buckets20210321185225{}) + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/models/error.go b/pkg/models/error.go index d71f5bb78..abcbe8af8 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -1365,6 +1365,35 @@ func (err ErrBucketLimitExceeded) HTTPError() web.HTTPError { } } +// ErrOnlyOneDoneBucketPerList represents an error where a bucket is set to the done bucket but one already exists for its list. +type ErrOnlyOneDoneBucketPerList struct { + BucketID int64 + ListID int64 + DoneBucketID int64 +} + +// IsErrOnlyOneDoneBucketPerList checks if an error is ErrBucketLimitExceeded. +func IsErrOnlyOneDoneBucketPerList(err error) bool { + _, ok := err.(*ErrOnlyOneDoneBucketPerList) + return ok +} + +func (err *ErrOnlyOneDoneBucketPerList) Error() string { + return fmt.Sprintf("There can be only one done bucket per list [BucketID: %d, ListID: %d]", err.BucketID, err.ListID) +} + +// ErrCodeOnlyOneDoneBucketPerList holds the unique world-error code of this error +const ErrCodeOnlyOneDoneBucketPerList = 10005 + +// HTTPError holds the http error description +func (err *ErrOnlyOneDoneBucketPerList) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusPreconditionFailed, + Code: ErrCodeOnlyOneDoneBucketPerList, + Message: "There can be only one done bucket per list.", + } +} + // ============= // Saved Filters // ============= diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go index 1c19e76d6..94d28d545 100644 --- a/pkg/models/kanban.go +++ b/pkg/models/kanban.go @@ -38,6 +38,8 @@ type Bucket struct { // How many tasks can be at the same time on this board max Limit int64 `xorm:"default 0" json:"limit"` + // If this bucket is the "done bucket". All tasks moved into this bucket will automatically marked as done. All tasks marked as done from elsewhere will be moved into this bucket. + IsDoneBucket bool `xorm:"BOOL" json:"is_done_bucket"` // A timestamp when this bucket was created. You cannot change this value. Created time.Time `xorm:"created not null" json:"created"` @@ -81,6 +83,21 @@ func getDefaultBucket(s *xorm.Session, listID int64) (bucket *Bucket, err error) return } +func getDoneBucketForList(s *xorm.Session, listID int64) (bucket *Bucket, err error) { + bucket = &Bucket{} + exists, err := s. + Where("list_id = ? and is_done_bucket = ?", listID, true). + Get(bucket) + if err != nil { + return nil, err + } + if !exists { + bucket = nil + } + + return +} + // ReadAll returns all buckets with their tasks for a certain list // @Summary Get all kanban buckets of a list // @Description Returns all kanban buckets with belong to a list including their tasks. @@ -239,9 +256,26 @@ func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) { // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{listID}/buckets/{bucketID} [post] func (b *Bucket) Update(s *xorm.Session, a web.Auth) (err error) { + doneBucket, err := getDoneBucketForList(s, b.ListID) + if err != nil { + return err + } + + if doneBucket != nil && doneBucket.IsDoneBucket && b.IsDoneBucket { + return &ErrOnlyOneDoneBucketPerList{ + BucketID: b.ID, + ListID: b.ListID, + DoneBucketID: doneBucket.ID, + } + } + _, err = s. Where("id = ?", b.ID). - Cols("title", "limit"). + Cols( + "title", + "limit", + "is_done_bucket", + ). Update(b) return } diff --git a/pkg/models/kanban_test.go b/pkg/models/kanban_test.go index 450afbe3c..6dc6e6ba7 100644 --- a/pkg/models/kanban_test.go +++ b/pkg/models/kanban_test.go @@ -182,4 +182,19 @@ func TestBucket_Update(t *testing.T) { testAndAssertBucketUpdate(t, b, s) }) + t.Run("only one done bucket per list", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + b := &Bucket{ + ID: 1, + ListID: 1, + IsDoneBucket: true, + } + + err := b.Update(s, &user.User{ID: 1}) + assert.Error(t, err) + assert.True(t, IsErrOnlyOneDoneBucketPerList(err)) + }) } diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index f0d06b72a..8295f64b3 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -707,34 +707,19 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task) (err error) { return } -func checkBucketAndTaskBelongToSameList(s *xorm.Session, fullTask *Task, bucketID int64) (err error) { - if bucketID != 0 { - b, err := getBucketByID(s, bucketID) - if err != nil { - return err - } - if fullTask.ListID != b.ListID { - return ErrBucketDoesNotBelongToList{ - ListID: fullTask.ListID, - BucketID: fullTask.BucketID, - } +func checkBucketAndTaskBelongToSameList(fullTask *Task, bucket *Bucket) (err error) { + if fullTask.ListID != bucket.ListID { + return ErrBucketDoesNotBelongToList{ + ListID: fullTask.ListID, + BucketID: fullTask.BucketID, } } + return } // Checks if adding a new task would exceed the bucket limit func checkBucketLimit(s *xorm.Session, t *Task, bucket *Bucket) (err error) { - - // We need the bucket to check if it has more tasks than the limit allows - if bucket == nil { - bucket, err = getBucketByID(s, t.BucketID) - if err != nil { - return err - } - } - - // Check the limit if bucket.Limit > 0 { taskCount, err := s. Where("bucket_id = ?", bucket.ID). @@ -749,6 +734,56 @@ func checkBucketLimit(s *xorm.Session, t *Task, bucket *Bucket) (err error) { return nil } +// Contains all the task logic to figure out what bucket to use for this task. +func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool) (err error) { + // Make sure we have a bucket + var bucket *Bucket + if task.Done && originalTask != nil && !originalTask.Done { + bucket, err := getDoneBucketForList(s, task.ListID) + if err != nil { + return err + } + if bucket != nil { + task.BucketID = bucket.ID + } + } + + if task.BucketID == 0 || (originalTask != nil && task.ListID != 0 && originalTask.ListID != task.ListID) { + bucket, err = getDefaultBucket(s, task.ListID) + if err != nil { + return err + } + task.BucketID = bucket.ID + } + + if bucket == nil { + bucket, err = getBucketByID(s, task.BucketID) + if err != nil { + return err + } + } + + // If there is a bucket set, make sure they belong to the same list as the task + err = checkBucketAndTaskBelongToSameList(task, bucket) + if err != nil { + return + } + + // Check the bucket limit + // Only check the bucket limit if the task is being moved between buckets, allow reordering the task within a bucket + if doCheckBucketLimit { + if err := checkBucketLimit(s, task, bucket); err != nil { + return err + } + } + + if bucket.IsDoneBucket && originalTask != nil && !originalTask.Done { + task.Done = true + } + + return nil +} + // Create is the implementation to create a list task // @Summary Create a task // @Description Inserts a task into a list. @@ -799,27 +834,12 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err t.UID = utils.MakeRandomString(40) } - // If there is a bucket set, make sure they belong to the same list as the task - err = checkBucketAndTaskBelongToSameList(s, t, t.BucketID) + // Get the default bucket and move the task there + err = setTaskBucket(s, t, nil, true) if err != nil { return } - // Get the default bucket and move the task there - var bucket *Bucket - if t.BucketID == 0 { - bucket, err = getDefaultBucket(s, t.ListID) - if err != nil { - return err - } - t.BucketID = bucket.ID - } - - // Bucket Limit - if err := checkBucketLimit(s, t, bucket); err != nil { - return err - } - // Get the index for this task latestTask := &Task{} _, err = s.Where("list_id = ?", t.ListID).OrderBy("id desc").Get(latestTask) @@ -886,6 +906,10 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { return } + if t.ListID == 0 { + t.ListID = ot.ListID + } + // Get the reminders reminders, err := getRemindersForTasks(s, []int64{t.ID}) if err != nil { @@ -897,9 +921,6 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { ot.Reminders[i] = r.Reminder } - // When a repeating task is marked as done, we update all deadlines and reminders and set it as undone - updateDone(&ot, t) - // Update the assignees if err := ot.updateTaskAssignees(s, t.Assignees, a); err != nil { return err @@ -910,12 +931,6 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { return err } - // If there is a bucket set, make sure they belong to the same list as the task - err = checkBucketAndTaskBelongToSameList(s, &ot, t.BucketID) - if err != nil { - return - } - // All columns to update in a separate variable to be able to add to them colsToUpdate := []string{ "title", @@ -936,16 +951,14 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { "is_favorite", } - // Make sure we have a bucket - var bucket *Bucket - if t.BucketID == 0 || (t.ListID != 0 && ot.ListID != t.ListID) { - bucket, err = getDefaultBucket(s, t.ListID) - if err != nil { - return err - } - t.BucketID = bucket.ID + err = setTaskBucket(s, t, &ot, t.BucketID != ot.BucketID) + if err != nil { + return err } + // When a repeating task is marked as done, we update all deadlines and reminders and set it as undone + updateDone(&ot, t) + // If the task is being moved between lists, make sure to move the bucket + index as well if t.ListID != 0 && ot.ListID != t.ListID { latestTask := &Task{} @@ -958,14 +971,6 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { colsToUpdate = append(colsToUpdate, "index") } - // Check the bucket limit - // Only check the bucket limit if the task is being moved between buckets, allow reordering the task within a bucket - if t.BucketID != ot.BucketID { - if err := checkBucketLimit(s, t, bucket); err != nil { - return err - } - } - // Update the labels // // Maybe FIXME: diff --git a/pkg/models/tasks_test.go b/pkg/models/tasks_test.go index 705df4541..67a5db283 100644 --- a/pkg/models/tasks_test.go +++ b/pkg/models/tasks_test.go @@ -202,6 +202,86 @@ func TestTask_Update(t *testing.T) { err := task.Update(s, u) assert.NoError(t, err) }) + t.Run("bucket on other list", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + task := &Task{ + ID: 1, + Title: "test10000", + Description: "Lorem Ipsum Dolor", + ListID: 1, + BucketID: 4, // Bucket 4 belongs to list 2 + } + err := task.Update(s, u) + assert.Error(t, err) + assert.True(t, IsErrBucketDoesNotBelongToList(err)) + }) + t.Run("moving a task to the done bucket", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + task := &Task{ + ID: 1, + Title: "test", + ListID: 1, + BucketID: 3, // Bucket 3 is the done bucket + } + err := task.Update(s, u) + assert.NoError(t, err) + err = s.Commit() + assert.NoError(t, err) + assert.True(t, task.Done) + + db.AssertExists(t, "tasks", map[string]interface{}{ + "id": 1, + "done": true, + "title": "test", + "list_id": 1, + "bucket_id": 3, + }, false) + }) + t.Run("default bucket when moving a task between lists", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + task := &Task{ + ID: 1, + ListID: 2, + } + err := task.Update(s, u) + assert.NoError(t, err) + err = s.Commit() + assert.NoError(t, err) + + assert.Equal(t, int64(4), task.BucketID) // bucket 4 is the default bucket on list 2 + assert.True(t, task.Done) // bucket 4 is the done bucket, so the task should be marked as done as well + }) + t.Run("marking a task as done should move it to the done bucket", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + task := &Task{ + ID: 1, + Done: true, + } + err := task.Update(s, u) + assert.NoError(t, err) + err = s.Commit() + assert.NoError(t, err) + assert.True(t, task.Done) + assert.Equal(t, int64(3), task.BucketID) + + db.AssertExists(t, "tasks", map[string]interface{}{ + "id": 1, + "done": true, + "bucket_id": 3, + }, false) + }) } func TestTask_Delete(t *testing.T) { diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 616c9de9e..3dad52f1d 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -7189,6 +7189,10 @@ var doc = `{ "description": "The unique, numeric id of this bucket.", "type": "integer" }, + "is_done_bucket": { + "description": "If this bucket is the \"done bucket\". All tasks moved into this bucket will automatically marked as done. All tasks marked as done from elsewhere will be moved into this bucket.", + "type": "boolean" + }, "limit": { "description": "How many tasks can be at the same time on this board max", "type": "integer" diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index de2b6b423..218bb867b 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -7172,6 +7172,10 @@ "description": "The unique, numeric id of this bucket.", "type": "integer" }, + "is_done_bucket": { + "description": "If this bucket is the \"done bucket\". All tasks moved into this bucket will automatically marked as done. All tasks marked as done from elsewhere will be moved into this bucket.", + "type": "boolean" + }, "limit": { "description": "How many tasks can be at the same time on this board max", "type": "integer" diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index 71b7d6667..f55d98cbf 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -81,6 +81,9 @@ definitions: id: description: The unique, numeric id of this bucket. type: integer + is_done_bucket: + description: If this bucket is the "done bucket". All tasks moved into this bucket will automatically marked as done. All tasks marked as done from elsewhere will be moved into this bucket. + type: boolean limit: description: How many tasks can be at the same time on this board max type: integer From 81d021e872373362fae000136c42289dbe14babc Mon Sep 17 00:00:00 2001 From: konrad Date: Wed, 24 Mar 2021 21:46:20 +0000 Subject: [PATCH 17/35] Improve loading labels performance (#824) Co-authored-by: kolaente Reviewed-on: https://kolaente.dev/vikunja/api/pulls/824 Co-authored-by: konrad Co-committed-by: konrad --- pkg/models/label.go | 38 +------------------ pkg/models/label_rights.go | 19 +++++++--- pkg/models/label_task.go | 27 ++++++++++---- pkg/models/list.go | 76 ++++++++++++++++++-------------------- 4 files changed, 69 insertions(+), 91 deletions(-) diff --git a/pkg/models/label.go b/pkg/models/label.go index 890a7164f..8d8acef82 100644 --- a/pkg/models/label.go +++ b/pkg/models/label.go @@ -146,10 +146,7 @@ func (l *Label) ReadAll(s *xorm.Session, a web.Auth, search string, page int, pe return nil, 0, 0, ErrGenericForbidden{} } - u := &user.User{ID: a.GetID()} - - // Get all tasks - taskIDs, err := getUserTaskIDs(s, u) + u, err := user.GetUserByID(s, a.GetID()) if err != nil { return nil, 0, 0, err } @@ -157,7 +154,7 @@ func (l *Label) ReadAll(s *xorm.Session, a web.Auth, search string, page int, pe return getLabelsByTaskIDs(s, &LabelByTaskIDsOptions{ Search: search, User: u, - TaskIDs: taskIDs, + GetForUser: u.ID, Page: page, PerPage: perPage, GetUnusedLabels: true, @@ -206,34 +203,3 @@ func getLabelByIDSimple(s *xorm.Session, labelID int64) (*Label, error) { } return &label, err } - -// Helper method to get all task ids a user has -func getUserTaskIDs(s *xorm.Session, u *user.User) (taskIDs []int64, err error) { - - // Get all lists - lists, _, _, err := getRawListsForUser( - s, - &listOptions{ - user: u, - page: -1, - }, - ) - if err != nil { - return nil, err - } - - tasks, _, _, err := getRawTasksForLists(s, lists, u, &taskOptions{ - page: -1, - perPage: 0, - }) - if err != nil { - return nil, err - } - - // make a slice of task ids - for _, t := range tasks { - taskIDs = append(taskIDs, t.ID) - } - - return -} diff --git a/pkg/models/label_rights.go b/pkg/models/label_rights.go index bd8c15337..9bb2926f2 100644 --- a/pkg/models/label_rights.go +++ b/pkg/models/label_rights.go @@ -64,21 +64,28 @@ func (l *Label) isLabelOwner(s *xorm.Session, a web.Auth) (bool, error) { // Helper method to check if a user can see a specific label func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxRight int, err error) { - // TODO: add an extra check for link share handling + if _, is := a.(*LinkSharing); is { + return false, 0, nil + } - // Get all tasks - taskIDs, err := getUserTaskIDs(s, &user.User{ID: a.GetID()}) + u, err := user.GetUserByID(s, a.GetID()) if err != nil { return false, 0, err } - // Get all labels associated with these tasks + cond := builder.In("label_task.task_id", + builder. + Select("id"). + From("tasks"). + Where(builder.In("list_id", getUserListsStatement(u.ID).Select("l.id"))), + ) + ll := &LabelTask{} has, err = s.Table("labels"). Select("label_task.*"). Join("LEFT", "label_task", "label_task.label_id = labels.id"). - Where("label_task.label_id is not null OR labels.created_by_id = ?", a.GetID()). - Or(builder.In("label_task.task_id", taskIDs)). + Where("label_task.label_id is not null OR labels.created_by_id = ?", u.ID). + Or(cond). And("labels.id = ?", l.ID). Exist(ll) if err != nil { diff --git a/pkg/models/label_task.go b/pkg/models/label_task.go index 5c94a5c77..ae6672d65 100644 --- a/pkg/models/label_task.go +++ b/pkg/models/label_task.go @@ -149,6 +149,7 @@ type LabelByTaskIDsOptions struct { TaskIDs []int64 GetUnusedLabels bool GroupByLabelIDsOnly bool + GetForUser int64 } // Helper function to get all labels for a set of tasks @@ -168,22 +169,32 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab // Get all labels associated with these tasks var labels []*labelWithTaskID cond := builder.And(builder.NotNull{"label_task.label_id"}) - if len(opts.TaskIDs) > 0 { + if len(opts.TaskIDs) > 0 && opts.GetForUser == 0 { cond = builder.And(builder.In("label_task.task_id", opts.TaskIDs), cond) } + if opts.GetForUser != 0 { + cond = builder.And(builder.In("label_task.task_id", + builder. + Select("id"). + From("tasks"). + Where(builder.In("list_id", getUserListsStatement(opts.GetForUser).Select("l.id"))), + ), cond) + } if opts.GetUnusedLabels { cond = builder.Or(cond, builder.Eq{"labels.created_by_id": opts.User.ID}) } - vals := strings.Split(opts.Search, ",") ids := []int64{} - for _, val := range vals { - v, err := strconv.ParseInt(val, 10, 64) - if err != nil { - log.Debugf("Label search string part '%s' is not a number: %s", val, err) - continue + if opts.Search != "" { + vals := strings.Split(opts.Search, ",") + for _, val := range vals { + v, err := strconv.ParseInt(val, 10, 64) + if err != nil { + log.Debugf("Label search string part '%s' is not a number: %s", val, err) + continue + } + ids = append(ids, v) } - ids = append(ids, v) } if len(ids) > 0 { diff --git a/pkg/models/list.go b/pkg/models/list.go index a742402fe..5ced67d42 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -21,11 +21,10 @@ import ( "strings" "time" + "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/events" - - "code.vikunja.io/api/pkg/log" - "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "xorm.io/builder" @@ -329,6 +328,32 @@ type listOptions struct { isArchived bool } +func getUserListsStatement(userID int64) *builder.Builder { + dialect := config.DatabaseType.GetString() + if dialect == "sqlite" { + dialect = builder.SQLITE + } + + return builder.Dialect(dialect). + Select("l.*"). + From("list", "l"). + Join("INNER", "namespaces n", "l.namespace_id = n.id"). + Join("LEFT", "team_namespaces tn", "tn.namespace_id = n.id"). + Join("LEFT", "team_members tm", "tm.team_id = tn.team_id"). + Join("LEFT", "team_list tl", "l.id = tl.list_id"). + Join("LEFT", "team_members tm2", "tm2.team_id = tl.team_id"). + Join("LEFT", "users_list ul", "ul.list_id = l.id"). + Join("LEFT", "users_namespace un", "un.namespace_id = l.namespace_id"). + Where(builder.Or( + builder.Eq{"tm.user_id": userID}, + builder.Eq{"tm2.user_id": userID}, + builder.Eq{"ul.user_id": userID}, + builder.Eq{"un.user_id": userID}, + builder.Eq{"l.owner_id": userID}, + )). + GroupBy("l.id") +} + // Gets the lists only, without any tasks or so func getRawListsForUser(s *xorm.Session, opts *listOptions) (lists []*List, resultCount int, totalItems int64, err error) { fullUser, err := user.GetUserByID(s, opts.user.ID) @@ -367,54 +392,23 @@ func getRawListsForUser(s *xorm.Session, opts *listOptions) (lists []*List, resu // Gets all Lists where the user is either owner or in a team which has access to the list // Or in a team which has namespace read access - query := s.Select("l.*"). - Table("list"). - Alias("l"). - Join("INNER", []string{"namespaces", "n"}, "l.namespace_id = n.id"). - Join("LEFT", []string{"team_namespaces", "tn"}, "tn.namespace_id = n.id"). - Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id"). - Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id"). - Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id"). - Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id"). - Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id"). - Where(builder.Or( - builder.Eq{"tm.user_id": fullUser.ID}, - builder.Eq{"tm2.user_id": fullUser.ID}, - builder.Eq{"ul.user_id": fullUser.ID}, - builder.Eq{"un.user_id": fullUser.ID}, - builder.Eq{"l.owner_id": fullUser.ID}, - )). - GroupBy("l.id"). + + query := getUserListsStatement(fullUser.ID). Where(filterCond). Where(isArchivedCond) if limit > 0 { query = query.Limit(limit, start) } - err = query.Find(&lists) + err = s.SQL(query).Find(&lists) if err != nil { return nil, 0, 0, err } - totalItems, err = s. - Table("list"). - Alias("l"). - Join("INNER", []string{"namespaces", "n"}, "l.namespace_id = n.id"). - Join("LEFT", []string{"team_namespaces", "tn"}, "tn.namespace_id = n.id"). - Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id"). - Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id"). - Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id"). - Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id"). - Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id"). - Where(builder.Or( - builder.Eq{"tm.user_id": fullUser.ID}, - builder.Eq{"tm2.user_id": fullUser.ID}, - builder.Eq{"ul.user_id": fullUser.ID}, - builder.Eq{"un.user_id": fullUser.ID}, - builder.Eq{"l.owner_id": fullUser.ID}, - )). - GroupBy("l.id"). + query = getUserListsStatement(fullUser.ID). Where(filterCond). - Where(isArchivedCond). + Where(isArchivedCond) + totalItems, err = s. + SQL(query.Select("count(*)")). Count(&List{}) return lists, len(lists), totalItems, err } From be3184d49ff7a97a964ead5140ce17a10f044a8e Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 24 Mar 2021 22:47:03 +0100 Subject: [PATCH 18/35] Add test for moving a task to another list --- pkg/models/tasks_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pkg/models/tasks_test.go b/pkg/models/tasks_test.go index 67a5db283..725c2c402 100644 --- a/pkg/models/tasks_test.go +++ b/pkg/models/tasks_test.go @@ -282,6 +282,26 @@ func TestTask_Update(t *testing.T) { "bucket_id": 3, }, false) }) + t.Run("move task to another list", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + task := &Task{ + ID: 1, + ListID: 2, + } + err := task.Update(s, u) + assert.NoError(t, err) + err = s.Commit() + assert.NoError(t, err) + + db.AssertExists(t, "tasks", map[string]interface{}{ + "id": 1, + "list_id": 2, + "bucket_id": 4, + }, false) + }) } func TestTask_Delete(t *testing.T) { From 398f05e9a0c5c47c26c2b04a059ac302d2967f61 Mon Sep 17 00:00:00 2001 From: renovate Date: Thu, 25 Mar 2021 10:50:41 +0000 Subject: [PATCH 19/35] Update golang.org/x/oauth2 commit hash to 22b0ada (#823) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/823 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d761cc62d..b029ae023 100644 --- a/go.mod +++ b/go.mod @@ -78,7 +78,7 @@ require ( golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 - golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 + golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 golang.org/x/text v0.3.5 // indirect diff --git a/go.sum b/go.sum index 9ba98c4bc..bf5617bd6 100644 --- a/go.sum +++ b/go.sum @@ -1012,6 +1012,8 @@ golang.org/x/oauth2 v0.0.0-20210311163135-5366d9dc1934 h1:Y2nxrNrrWOZn5yjDEEVU3R golang.org/x/oauth2 v0.0.0-20210311163135-5366d9dc1934/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 h1:duBc5zuJsmJXYOVVE/6PxejI+N3AaCqKjtsoLn1Je5Q= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 h1:D7nTwh4J0i+5mW4Zjzn5omvlr6YBcWywE6KOcatyNxY= +golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From e2aa916bdd110af184ef4838bb47c7a955e0f5c5 Mon Sep 17 00:00:00 2001 From: renovate Date: Sun, 28 Mar 2021 15:04:37 +0000 Subject: [PATCH 20/35] Update module adlio/trello to v1.9.0 (#825) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/825 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b029ae023..45f32a549 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( code.vikunja.io/web v0.0.0-20210131201003-26386be9a9ae gitea.com/xorm/xorm-redis-cache v0.2.0 github.com/ThreeDotsLabs/watermill v1.1.1 - github.com/adlio/trello v1.8.0 + github.com/adlio/trello v1.9.0 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef github.com/beevik/etree v1.1.0 // indirect From d7f3c653f91553c43284f650b9ef79de47660c05 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 28 Mar 2021 17:24:56 +0200 Subject: [PATCH 21/35] Remove unused tools from tools.go --- go.mod | 14 +++++++------- magefile.go | 2 +- tools.go | 9 --------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 45f32a549..307b1c630 100644 --- a/go.mod +++ b/go.mod @@ -26,14 +26,14 @@ require ( github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef github.com/beevik/etree v1.1.0 // indirect github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2 - github.com/client9/misspell v0.3.4 + github.com/client9/misspell v0.3.4 // indirect github.com/coreos/go-oidc v2.2.1+incompatible github.com/cweill/gotests v1.6.0 github.com/d4l3k/messagediff v1.2.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/disintegration/imaging v1.6.2 github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 - github.com/fzipp/gocyclo v0.3.1 + github.com/fzipp/gocyclo v0.3.1 // indirect github.com/gabriel-vasile/mimetype v1.2.0 github.com/getsentry/sentry-go v0.10.0 github.com/go-errors/errors v1.1.1 // indirect @@ -42,10 +42,10 @@ require ( github.com/go-testfixtures/testfixtures/v3 v3.5.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/golang/snappy v0.0.2 // indirect - github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254 + github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254 // indirect github.com/iancoleman/strcase v0.1.3 github.com/imdario/mergo v0.3.12 - github.com/jgautheron/goconst v1.4.0 + github.com/jgautheron/goconst v1.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/labstack/echo/v4 v4.2.1 github.com/labstack/gommon v0.3.0 @@ -66,7 +66,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/samedi/caldav-go v3.0.0+incompatible github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect - github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 + github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect github.com/spf13/afero v1.6.0 github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.1.3 @@ -77,7 +77,7 @@ require ( github.com/ulule/limiter/v3 v3.8.0 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb - golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 + golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 @@ -89,7 +89,7 @@ require ( gopkg.in/ini.v1 v1.57.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c - honnef.co/go/tools v0.0.1-2020.1.5 + honnef.co/go/tools v0.0.1-2020.1.5 // indirect src.techknowlogick.com/xgo v1.4.1-0.20210311222705-d25c33fcd864 src.techknowlogick.com/xormigrate v1.4.0 xorm.io/builder v0.3.8 diff --git a/magefile.go b/magefile.go index aee52b053..b34370472 100644 --- a/magefile.go +++ b/magefile.go @@ -410,7 +410,7 @@ func (Check) GolangciFix() { runAndStreamOutput("golangci-lint", "run", "--fix") } -// Runs fmt-check, lint, got-swag, misspell-check, ineffasign-check, gocyclo-check, static-check, gosec-check, goconst-check all in parallel +// Runs golangci and the swagger test in parralel func (Check) All() { mg.Deps(initVars) mg.Deps( diff --git a/tools.go b/tools.go index a19ed27e1..77775b149 100644 --- a/tools.go +++ b/tools.go @@ -21,18 +21,9 @@ package tools // This file is needed for go mod to recognize the tools we use. import ( - _ "github.com/client9/misspell/cmd/misspell" _ "github.com/cweill/gotests" - _ "github.com/fzipp/gocyclo" - _ "github.com/gordonklaus/ineffassign" _ "github.com/swaggo/swag/cmd/swag" - _ "golang.org/x/lint/golint" _ "src.techknowlogick.com/xgo" - _ "github.com/jgautheron/goconst/cmd/goconst" - _ "honnef.co/go/tools/cmd/staticcheck" - - _ "github.com/shurcooL/vfsgen" - _ "github.com/magefile/mage" ) From 6c3488b8aae1898620736232183f26bcc4dc8130 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 28 Mar 2021 17:55:19 +0200 Subject: [PATCH 22/35] Fix event error handler retrying infinitely --- pkg/events/events.go | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/pkg/events/events.go b/pkg/events/events.go index c1ddac2eb..0aec2eed7 100644 --- a/pkg/events/events.go +++ b/pkg/events/events.go @@ -49,16 +49,6 @@ func InitEvents() (err error) { return err } - router.AddMiddleware( - middleware.Retry{ - MaxRetries: 5, - InitialInterval: time.Millisecond * 100, - Logger: logger, - Multiplier: 2, - }.Middleware, - middleware.Recoverer, - ) - metricsBuilder := metrics.NewPrometheusMetricsBuilder(vmetrics.GetRegistry(), "", "") metricsBuilder.AddPrometheusRouterMetrics(router) @@ -69,6 +59,33 @@ func InitEvents() (err error) { logger, ) + poison, err := middleware.PoisonQueue(pubsub, "poison") + if err != nil { + return err + } + router.AddNoPublisherHandler("poison.logger", "poison", pubsub, func(msg *message.Message) error { + meta := "" + for s, m := range msg.Metadata { + meta += s + "=" + m + ", " + } + log.Errorf("Error while handling message %s, %s payload=%s", msg.UUID, meta, string(msg.Payload)) + return nil + }) + + router.AddMiddleware( + poison, + middleware.Retry{ + MaxRetries: 5, + InitialInterval: time.Millisecond * 100, + MaxInterval: time.Hour, + Multiplier: 2, + MaxElapsedTime: 0, + RandomizationFactor: 1, + Logger: logger, + }.Middleware, + middleware.Recoverer, + ) + for topic, funcs := range listeners { for _, handler := range funcs { router.AddNoPublisherHandler(topic+"."+handler.Name(), topic, pubsub, handler.Handle) From bc782e68fffeacc885057b58ab26030d8caecd31 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 28 Mar 2021 18:18:58 +0200 Subject: [PATCH 23/35] Add systemd service file to linux packages --- build/after-install.sh | 2 ++ nfpm.yaml | 4 ++++ vikunja.service | 24 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 vikunja.service diff --git a/build/after-install.sh b/build/after-install.sh index 0299b08c5..4dc24b15c 100644 --- a/build/after-install.sh +++ b/build/after-install.sh @@ -1,5 +1,7 @@ #!/bin/bash +systemctl enable vikunja.service + # Fix the config to contain proper values NEW_SECRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) sed -i "s//$NEW_SECRET/g" /etc/vikunja/config.yml diff --git a/nfpm.yaml b/nfpm.yaml index ad2248a7d..ab279fdcd 100644 --- a/nfpm.yaml +++ b/nfpm.yaml @@ -8,6 +8,8 @@ homepage: "https://vikunja.io" section: "default" priority: "extra" license: "AGPLv3" +depends: + - systemd contents: - src: dst: /opt/vikunja/vikunja @@ -17,5 +19,7 @@ contents: - src: /opt/vikunja/vikunja dst: /usr/local/bin/vikunja type: "symlink" + - src: vikunja.service + dst: /usr/lib/systemd/system/vikunja.service scripts: postinstall: ./build/after-install.sh diff --git a/vikunja.service b/vikunja.service new file mode 100644 index 000000000..c9f083095 --- /dev/null +++ b/vikunja.service @@ -0,0 +1,24 @@ +[Unit] +Description=Vikunja +After=syslog.target +After=network.target +# Depending on how you configured Vikunja, you may want to uncomment these: +#Requires=mysql.service +#Requires=mariadb.service +#Requires=postgresql.service +#Requires=redis.service + +[Service] +RestartSec=2s +Type=simple +WorkingDirectory=/opt/vikunja +ExecStart=/usr/local/bin/vikunja +Restart=always +# If you want to bind Vikunja to a port below 1024 uncomment +# the two values below +### +#CapabilityBoundingSet=CAP_NET_BIND_SERVICE +#AmbientCapabilities=CAP_NET_BIND_SERVICE + +[Install] +WantedBy=multi-user.target \ No newline at end of file From 73f2d4532de569d34f87cd93dd863deee0f17a41 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 28 Mar 2021 20:17:35 +0200 Subject: [PATCH 24/35] Make sure all tables are properly pluralized --- .../{label_task.yml => label_tasks.yml} | 0 .../{link_sharing.yml => link_shares.yml} | 0 pkg/db/fixtures/{list.yml => lists.yml} | 0 .../{team_list.yml => team_lists.yml} | 0 .../{users_list.yml => users_lists.yml} | 0 ...ers_namespace.yml => users_namespaces.yml} | 0 pkg/migration/20210328191017.go | 52 +++++++++++++++++++ pkg/migration/migration.go | 23 ++++++++ pkg/models/error.go | 2 +- pkg/models/label_rights.go | 8 +-- pkg/models/label_task.go | 16 +++--- pkg/models/label_task_test.go | 10 ++-- pkg/models/link_sharing.go | 2 +- pkg/models/list.go | 39 ++++++++------ pkg/models/list_rights.go | 8 +-- pkg/models/list_team.go | 10 ++-- pkg/models/list_team_test.go | 6 +-- pkg/models/list_test.go | 8 +-- pkg/models/list_users.go | 10 ++-- pkg/models/list_users_test.go | 6 +-- pkg/models/namespace.go | 18 +++---- pkg/models/namespace_rights.go | 8 +-- pkg/models/namespace_users.go | 10 ++-- pkg/models/namespace_users_test.go | 6 +-- pkg/models/subscription.go | 6 +-- pkg/models/tasks.go | 4 +- pkg/models/unit_tests.go | 12 ++--- pkg/models/user_list.go | 8 +-- .../migration/create_from_structure_test.go | 2 +- 29 files changed, 178 insertions(+), 96 deletions(-) rename pkg/db/fixtures/{label_task.yml => label_tasks.yml} (100%) rename pkg/db/fixtures/{link_sharing.yml => link_shares.yml} (100%) rename pkg/db/fixtures/{list.yml => lists.yml} (100%) rename pkg/db/fixtures/{team_list.yml => team_lists.yml} (100%) rename pkg/db/fixtures/{users_list.yml => users_lists.yml} (100%) rename pkg/db/fixtures/{users_namespace.yml => users_namespaces.yml} (100%) create mode 100644 pkg/migration/20210328191017.go diff --git a/pkg/db/fixtures/label_task.yml b/pkg/db/fixtures/label_tasks.yml similarity index 100% rename from pkg/db/fixtures/label_task.yml rename to pkg/db/fixtures/label_tasks.yml diff --git a/pkg/db/fixtures/link_sharing.yml b/pkg/db/fixtures/link_shares.yml similarity index 100% rename from pkg/db/fixtures/link_sharing.yml rename to pkg/db/fixtures/link_shares.yml diff --git a/pkg/db/fixtures/list.yml b/pkg/db/fixtures/lists.yml similarity index 100% rename from pkg/db/fixtures/list.yml rename to pkg/db/fixtures/lists.yml diff --git a/pkg/db/fixtures/team_list.yml b/pkg/db/fixtures/team_lists.yml similarity index 100% rename from pkg/db/fixtures/team_list.yml rename to pkg/db/fixtures/team_lists.yml diff --git a/pkg/db/fixtures/users_list.yml b/pkg/db/fixtures/users_lists.yml similarity index 100% rename from pkg/db/fixtures/users_list.yml rename to pkg/db/fixtures/users_lists.yml diff --git a/pkg/db/fixtures/users_namespace.yml b/pkg/db/fixtures/users_namespaces.yml similarity index 100% rename from pkg/db/fixtures/users_namespace.yml rename to pkg/db/fixtures/users_namespaces.yml diff --git a/pkg/migration/20210328191017.go b/pkg/migration/20210328191017.go new file mode 100644 index 000000000..c8e60e5bb --- /dev/null +++ b/pkg/migration/20210328191017.go @@ -0,0 +1,52 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20210328191017", + Description: "Make sure all tables are correctly pluralized", + Migrate: func(tx *xorm.Engine) error { + // old name => new name + tables := map[string]string{ + "label_task": "label_tasks", + "link_sharing": "link_shares", + "list": "lists", + "team_list": "team_lists", + "users_list": "users_lists", + "users_namespace": "users_namespaces", + } + + for oldName, newName := range tables { + err := renameTable(tx, oldName, newName) + if err != nil { + return err + } + } + + return nil + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/migration/migration.go b/pkg/migration/migration.go index 7f77f4536..612042e7c 100644 --- a/pkg/migration/migration.go +++ b/pkg/migration/migration.go @@ -158,6 +158,29 @@ func modifyColumn(x *xorm.Engine, tableName, col, newDefinition string) error { return nil } +func renameTable(x *xorm.Engine, oldName, newName string) error { + switch config.DatabaseType.GetString() { + case "sqlite": + _, err := x.Exec("ALTER TABLE `" + oldName + "` RENAME TO `" + newName + "`") + if err != nil { + return err + } + case "mysql": + _, err := x.Exec("RENAME TABLE `" + oldName + "` TO `" + newName + "`") + if err != nil { + return err + } + case "postgres": + _, err := x.Exec("ALTER TABLE `" + oldName + "` RENAME TO `" + newName + "`") + if err != nil { + return err + } + default: + log.Fatal("Unknown db.") + } + return nil +} + func initSchema(tx *xorm.Engine) error { schemeBeans := []interface{}{} schemeBeans = append(schemeBeans, models.GetTables()...) diff --git a/pkg/models/error.go b/pkg/models/error.go index abcbe8af8..81e481536 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -252,7 +252,7 @@ const ErrCodeListIsArchived = 3008 // HTTPError holds the http error description func (err ErrListIsArchived) HTTPError() web.HTTPError { - return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeListIsArchived, Message: "This lists is archived. Editing or creating new tasks is not possible."} + return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeListIsArchived, Message: "This list is archived. Editing or creating new tasks is not possible."} } // ================ diff --git a/pkg/models/label_rights.go b/pkg/models/label_rights.go index 9bb2926f2..bb3136097 100644 --- a/pkg/models/label_rights.go +++ b/pkg/models/label_rights.go @@ -73,7 +73,7 @@ func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxRigh return false, 0, err } - cond := builder.In("label_task.task_id", + cond := builder.In("label_tasks.task_id", builder. Select("id"). From("tasks"). @@ -82,9 +82,9 @@ func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxRigh ll := &LabelTask{} has, err = s.Table("labels"). - Select("label_task.*"). - Join("LEFT", "label_task", "label_task.label_id = labels.id"). - Where("label_task.label_id is not null OR labels.created_by_id = ?", u.ID). + Select("label_tasks.*"). + Join("LEFT", "label_tasks", "label_tasks.label_id = labels.id"). + Where("label_tasks.label_id is not null OR labels.created_by_id = ?", u.ID). Or(cond). And("labels.id = ?", l.ID). Exist(ll) diff --git a/pkg/models/label_task.go b/pkg/models/label_task.go index ae6672d65..9de763a28 100644 --- a/pkg/models/label_task.go +++ b/pkg/models/label_task.go @@ -44,7 +44,7 @@ type LabelTask struct { // TableName makes a pretty table name func (LabelTask) TableName() string { - return "label_task" + return "label_tasks" } // Delete deletes a label on a task @@ -159,8 +159,8 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab // multiple times when it is associated to more than one task. // Because of this whole thing, we need this extra switch here to only group by Task IDs if needed. // Probably not the most ideal solution. - var groupBy = "labels.id,label_task.task_id" - var selectStmt = "labels.*, label_task.task_id" + var groupBy = "labels.id,label_tasks.task_id" + var selectStmt = "labels.*, label_tasks.task_id" if opts.GroupByLabelIDsOnly { groupBy = "labels.id" selectStmt = "labels.*" @@ -168,12 +168,12 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab // Get all labels associated with these tasks var labels []*labelWithTaskID - cond := builder.And(builder.NotNull{"label_task.label_id"}) + cond := builder.And(builder.NotNull{"label_tasks.label_id"}) if len(opts.TaskIDs) > 0 && opts.GetForUser == 0 { - cond = builder.And(builder.In("label_task.task_id", opts.TaskIDs), cond) + cond = builder.And(builder.In("label_tasks.task_id", opts.TaskIDs), cond) } if opts.GetForUser != 0 { - cond = builder.And(builder.In("label_task.task_id", + cond = builder.And(builder.In("label_tasks.task_id", builder. Select("id"). From("tasks"). @@ -207,7 +207,7 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab query := s.Table("labels"). Select(selectStmt). - Join("LEFT", "label_task", "label_task.label_id = labels.id"). + Join("LEFT", "label_tasks", "label_tasks.label_id = labels.id"). Where(cond). GroupBy(groupBy). OrderBy("labels.id ASC") @@ -249,7 +249,7 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab // Get the total number of entries totalEntries, err = s.Table("labels"). Select("count(DISTINCT labels.id)"). - Join("LEFT", "label_task", "label_task.label_id = labels.id"). + Join("LEFT", "label_tasks", "label_tasks.label_id = labels.id"). Where(cond). And("labels.title LIKE ?", "%"+opts.Search+"%"). Count(&Label{}) diff --git a/pkg/models/label_task_test.go b/pkg/models/label_task_test.go index 9005a125a..b3154c6f6 100644 --- a/pkg/models/label_task_test.go +++ b/pkg/models/label_task_test.go @@ -215,11 +215,11 @@ func TestLabelTask_Create(t *testing.T) { CRUDable: tt.fields.CRUDable, Rights: tt.fields.Rights, } - allowed, _ := l.CanCreate(s, tt.args.a) + allowed, err := l.CanCreate(s, tt.args.a) if !allowed && !tt.wantForbidden { - t.Errorf("LabelTask.CanCreate() forbidden, want %v", tt.wantForbidden) + t.Errorf("LabelTask.CanCreate() forbidden, want %v, err %v", tt.wantForbidden, err) } - err := l.Create(s, tt.args.a) + err = l.Create(s, tt.args.a) if (err != nil) != tt.wantErr { t.Errorf("LabelTask.Create() error = %v, wantErr %v", err, tt.wantErr) } @@ -227,7 +227,7 @@ func TestLabelTask_Create(t *testing.T) { t.Errorf("LabelTask.Create() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name()) } if !tt.wantErr { - db.AssertExists(t, "label_task", map[string]interface{}{ + db.AssertExists(t, "label_tasks", map[string]interface{}{ "id": l.ID, "task_id": l.TaskID, "label_id": l.LabelID, @@ -326,7 +326,7 @@ func TestLabelTask_Delete(t *testing.T) { t.Errorf("LabelTask.Delete() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name()) } if !tt.wantForbidden { - db.AssertMissing(t, "label_task", map[string]interface{}{ + db.AssertMissing(t, "label_tasks", map[string]interface{}{ "label_id": l.LabelID, "task_id": l.TaskID, }) diff --git a/pkg/models/link_sharing.go b/pkg/models/link_sharing.go index 8b7f4c76e..487d78fff 100644 --- a/pkg/models/link_sharing.go +++ b/pkg/models/link_sharing.go @@ -65,7 +65,7 @@ type LinkSharing struct { // TableName holds the table name func (LinkSharing) TableName() string { - return "link_sharing" + return "link_shares" } // GetID returns the ID of the links sharing object diff --git a/pkg/models/list.go b/pkg/models/list.go index 5ced67d42..6156df8a9 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -80,6 +80,11 @@ type List struct { web.Rights `xorm:"-" json:"-"` } +// TableName returns a better name for the lists table +func (l *List) TableName() string { + return "lists" +} + // ListBackgroundType holds a list background type type ListBackgroundType struct { Type string @@ -292,9 +297,9 @@ func GetListSimplByTaskID(s *xorm.Session, taskID int64) (l *List, err error) { // leading to not finding anything if the id is good, but for example the title is different. var list List exists, err := s. - Select("list.*"). + Select("lists.*"). Table(List{}). - Join("INNER", "tasks", "list.id = tasks.list_id"). + Join("INNER", "tasks", "lists.id = tasks.list_id"). Where("tasks.id = ?", taskID). Get(&list) if err != nil { @@ -336,14 +341,14 @@ func getUserListsStatement(userID int64) *builder.Builder { return builder.Dialect(dialect). Select("l.*"). - From("list", "l"). + From("lists", "l"). Join("INNER", "namespaces n", "l.namespace_id = n.id"). Join("LEFT", "team_namespaces tn", "tn.namespace_id = n.id"). Join("LEFT", "team_members tm", "tm.team_id = tn.team_id"). - Join("LEFT", "team_list tl", "l.id = tl.list_id"). + Join("LEFT", "team_lists tl", "l.id = tl.list_id"). Join("LEFT", "team_members tm2", "tm2.team_id = tl.team_id"). - Join("LEFT", "users_list ul", "ul.list_id = l.id"). - Join("LEFT", "users_namespace un", "un.namespace_id = l.namespace_id"). + Join("LEFT", "users_lists ul", "ul.list_id = l.id"). + Join("LEFT", "users_namespaces un", "un.namespace_id = l.namespace_id"). Where(builder.Or( builder.Eq{"tm.user_id": userID}, builder.Eq{"tm2.user_id": userID}, @@ -373,15 +378,17 @@ func getRawListsForUser(s *xorm.Session, opts *listOptions) (lists []*List, resu limit, start := getLimitFromPageIndex(opts.page, opts.perPage) var filterCond builder.Cond - vals := strings.Split(opts.search, ",") ids := []int64{} - for _, val := range vals { - v, err := strconv.ParseInt(val, 10, 64) - if err != nil { - log.Debugf("List search string part '%s' is not a number: %s", val, err) - continue + if opts.search != "" { + vals := strings.Split(opts.search, ",") + for _, val := range vals { + v, err := strconv.ParseInt(val, 10, 64) + if err != nil { + log.Debugf("List search string part '%s' is not a number: %s", val, err) + continue + } + ids = append(ids, v) } - ids = append(ids, v) } if len(ids) > 0 { @@ -486,9 +493,9 @@ func (l *List) CheckIsArchived(s *xorm.Session) (err error) { nl := &NamespaceList{} exists, err := s. - Table("list"). - Join("LEFT", "namespaces", "list.namespace_id = namespaces.id"). - Where("list.id = ? AND (list.is_archived = true OR namespaces.is_archived = true)", l.ID). + Table("lists"). + Join("LEFT", "namespaces", "lists.namespace_id = namespaces.id"). + Where("lists.id = ? AND (lists.is_archived = true OR namespaces.is_archived = true)", l.ID). Get(nl) if err != nil { return diff --git a/pkg/models/list_rights.go b/pkg/models/list_rights.go index 3ac5d07b5..f263ab7fd 100644 --- a/pkg/models/list_rights.go +++ b/pkg/models/list_rights.go @@ -222,16 +222,16 @@ func (l *List) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bool, i r := &allListRights{} var maxRight = 0 exists, err := s. - Table("list"). + Table("lists"). Alias("l"). // User stuff - Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id"). - Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id"). + Join("LEFT", []string{"users_namespaces", "un"}, "un.namespace_id = l.namespace_id"). + Join("LEFT", []string{"users_lists", "ul"}, "ul.list_id = l.id"). Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id"). // Team stuff Join("LEFT", []string{"team_namespaces", "tn"}, " l.namespace_id = tn.namespace_id"). Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id"). - Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id"). + Join("LEFT", []string{"team_lists", "tl"}, "l.id = tl.list_id"). Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id"). // The actual condition Where(builder.And( diff --git a/pkg/models/list_team.go b/pkg/models/list_team.go index 3362d7d1d..fee411c35 100644 --- a/pkg/models/list_team.go +++ b/pkg/models/list_team.go @@ -47,7 +47,7 @@ type TeamList struct { // TableName makes beautiful table names func (TeamList) TableName() string { - return "team_list" + return "team_lists" } // TeamWithRight represents a team, combined with rights. @@ -196,8 +196,8 @@ func (tl *TeamList) ReadAll(s *xorm.Session, a web.Auth, search string, page int all := []*TeamWithRight{} query := s. Table("teams"). - Join("INNER", "team_list", "team_id = teams.id"). - Where("team_list.list_id = ?", tl.ListID). + Join("INNER", "team_lists", "team_id = teams.id"). + Where("team_lists.list_id = ?", tl.ListID). Where("teams.name LIKE ?", "%"+search+"%") if limit > 0 { query = query.Limit(limit, start) @@ -219,8 +219,8 @@ func (tl *TeamList) ReadAll(s *xorm.Session, a web.Auth, search string, page int totalItems, err = s. Table("teams"). - Join("INNER", "team_list", "team_id = teams.id"). - Where("team_list.list_id = ?", tl.ListID). + Join("INNER", "team_lists", "team_id = teams.id"). + Where("team_lists.list_id = ?", tl.ListID). Where("teams.name LIKE ?", "%"+search+"%"). Count(&TeamWithRight{}) if err != nil { diff --git a/pkg/models/list_team_test.go b/pkg/models/list_team_test.go index 31c48a561..83cb64809 100644 --- a/pkg/models/list_team_test.go +++ b/pkg/models/list_team_test.go @@ -99,7 +99,7 @@ func TestTeamList_Create(t *testing.T) { assert.NoError(t, err) err = s.Commit() assert.NoError(t, err) - db.AssertExists(t, "team_list", map[string]interface{}{ + db.AssertExists(t, "team_lists", map[string]interface{}{ "team_id": 1, "list_id": 1, "right": RightAdmin, @@ -171,7 +171,7 @@ func TestTeamList_Delete(t *testing.T) { assert.NoError(t, err) err = s.Commit() assert.NoError(t, err) - db.AssertMissing(t, "team_list", map[string]interface{}{ + db.AssertMissing(t, "team_lists", map[string]interface{}{ "team_id": 1, "list_id": 3, }) @@ -279,7 +279,7 @@ func TestTeamList_Update(t *testing.T) { err = s.Commit() assert.NoError(t, err) if !tt.wantErr { - db.AssertExists(t, "team_list", map[string]interface{}{ + db.AssertExists(t, "team_lists", map[string]interface{}{ "list_id": tt.fields.ListID, "team_id": tt.fields.TeamID, "right": tt.fields.Right, diff --git a/pkg/models/list_test.go b/pkg/models/list_test.go index a3d64044d..c80355a59 100644 --- a/pkg/models/list_test.go +++ b/pkg/models/list_test.go @@ -45,7 +45,7 @@ func TestList_CreateOrUpdate(t *testing.T) { assert.NoError(t, err) err = s.Commit() assert.NoError(t, err) - db.AssertExists(t, "list", map[string]interface{}{ + db.AssertExists(t, "lists", map[string]interface{}{ "id": list.ID, "title": list.Title, "description": list.Description, @@ -105,7 +105,7 @@ func TestList_CreateOrUpdate(t *testing.T) { assert.NoError(t, err) err = s.Commit() assert.NoError(t, err) - db.AssertExists(t, "list", map[string]interface{}{ + db.AssertExists(t, "lists", map[string]interface{}{ "id": list.ID, "title": list.Title, "description": list.Description, @@ -129,7 +129,7 @@ func TestList_CreateOrUpdate(t *testing.T) { assert.NoError(t, err) err = s.Commit() assert.NoError(t, err) - db.AssertExists(t, "list", map[string]interface{}{ + db.AssertExists(t, "lists", map[string]interface{}{ "id": list.ID, "title": list.Title, "description": list.Description, @@ -176,7 +176,7 @@ func TestList_Delete(t *testing.T) { assert.NoError(t, err) err = s.Commit() assert.NoError(t, err) - db.AssertMissing(t, "list", map[string]interface{}{ + db.AssertMissing(t, "lists", map[string]interface{}{ "id": 1, }) } diff --git a/pkg/models/list_users.go b/pkg/models/list_users.go index 06909eb6e..13576805f 100644 --- a/pkg/models/list_users.go +++ b/pkg/models/list_users.go @@ -50,7 +50,7 @@ type ListUser struct { // TableName is the table name for ListUser func (ListUser) TableName() string { - return "users_list" + return "users_lists" } // UserWithRight represents a user in combination with the right it can have on a list/namespace @@ -202,8 +202,8 @@ func (lu *ListUser) ReadAll(s *xorm.Session, a web.Auth, search string, page int // Get all users all := []*UserWithRight{} query := s. - Join("INNER", "users_list", "user_id = users.id"). - Where("users_list.list_id = ?", lu.ListID). + Join("INNER", "users_lists", "user_id = users.id"). + Where("users_lists.list_id = ?", lu.ListID). Where("users.username LIKE ?", "%"+search+"%") if limit > 0 { query = query.Limit(limit, start) @@ -219,8 +219,8 @@ func (lu *ListUser) ReadAll(s *xorm.Session, a web.Auth, search string, page int } numberOfTotalItems, err = s. - Join("INNER", "users_list", "user_id = users.id"). - Where("users_list.list_id = ?", lu.ListID). + Join("INNER", "users_lists", "user_id = users.id"). + Where("users_lists.list_id = ?", lu.ListID). Where("users.username LIKE ?", "%"+search+"%"). Count(&UserWithRight{}) diff --git a/pkg/models/list_users_test.go b/pkg/models/list_users_test.go index 9825f4b55..d7b1620d9 100644 --- a/pkg/models/list_users_test.go +++ b/pkg/models/list_users_test.go @@ -133,7 +133,7 @@ func TestListUser_Create(t *testing.T) { assert.NoError(t, err) if !tt.wantErr { - db.AssertExists(t, "users_list", map[string]interface{}{ + db.AssertExists(t, "users_lists", map[string]interface{}{ "user_id": ul.UserID, "list_id": tt.fields.ListID, }, false) @@ -323,7 +323,7 @@ func TestListUser_Update(t *testing.T) { assert.NoError(t, err) if !tt.wantErr { - db.AssertExists(t, "users_list", map[string]interface{}{ + db.AssertExists(t, "users_lists", map[string]interface{}{ "list_id": tt.fields.ListID, "user_id": lu.UserID, "right": tt.fields.Right, @@ -405,7 +405,7 @@ func TestListUser_Delete(t *testing.T) { assert.NoError(t, err) if !tt.wantErr { - db.AssertMissing(t, "users_list", map[string]interface{}{ + db.AssertMissing(t, "users_lists", map[string]interface{}{ "user_id": tt.fields.UserID, "list_id": tt.fields.ListID, }) diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index 69cced677..044be2bcf 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -250,10 +250,10 @@ func getNamespacesWithLists(s *xorm.Session, namespaces *map[int64]*NamespaceWit Table("namespaces"). Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id"). Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id"). - Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id"). + Join("LEFT", "users_namespaces", "users_namespaces.namespace_id = namespaces.id"). Where("team_members.user_id = ?", userID). Or("namespaces.owner_id = ?", userID). - Or("users_namespace.user_id = ?", userID). + Or("users_namespaces.user_id = ?", userID). GroupBy("namespaces.id"). Where(filterCond). Where(isArchivedCond) @@ -269,10 +269,10 @@ func getNamespacesWithLists(s *xorm.Session, namespaces *map[int64]*NamespaceWit Table("namespaces"). Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id"). Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id"). - Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id"). + Join("LEFT", "users_namespaces", "users_namespaces.namespace_id = namespaces.id"). Where("team_members.user_id = ?", userID). Or("namespaces.owner_id = ?", userID). - Or("users_namespace.user_id = ?", userID). + Or("users_namespaces.user_id = ?", userID). And("namespaces.is_archived = false"). GroupBy("namespaces.id"). Where(filterCond). @@ -332,11 +332,11 @@ func getSharedListsInNamespace(s *xorm.Session, archived bool, doer *user.User) // Get all lists individually shared with our user (not via a namespace) individualLists := []*List{} iListQuery := s.Select("l.*"). - Table("list"). + Table("lists"). Alias("l"). - Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id"). + Join("LEFT", []string{"team_lists", "tl"}, "l.id = tl.list_id"). Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tl.team_id"). - Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id"). + Join("LEFT", []string{"users_lists", "ul"}, "ul.list_id = l.id"). Where(builder.And( builder.Eq{"tm.user_id": doer.ID}, builder.Neq{"l.owner_id": doer.ID}, @@ -390,8 +390,8 @@ func getFavoriteLists(s *xorm.Session, lists []*List, namespaceIDs []int64, doer // Check if we have any favorites or favorited lists and remove the favorites namespace from the list if not var favoriteCount int64 favoriteCount, err = s. - Join("INNER", "list", "tasks.list_id = list.id"). - Join("INNER", "namespaces", "list.namespace_id = namespaces.id"). + Join("INNER", "lists", "tasks.list_id = lists.id"). + Join("INNER", "namespaces", "lists.namespace_id = namespaces.id"). Where(builder.And(builder.Eq{"tasks.is_favorite": true}, builder.In("namespaces.id", namespaceIDs))). Count(&Task{}) if err != nil { diff --git a/pkg/models/namespace_rights.go b/pkg/models/namespace_rights.go index 97cb6f045..0d03202cb 100644 --- a/pkg/models/namespace_rights.go +++ b/pkg/models/namespace_rights.go @@ -83,7 +83,7 @@ func (n *Namespace) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bo The following loop creates an sql condition like this one: namespaces.owner_id = 1 OR - (users_namespace.user_id = 1 AND users_namespace.right = 1) OR + (users_namespaces.user_id = 1 AND users_namespaces.right = 1) OR (team_members.user_id = 1 AND team_namespaces.right = 1) OR @@ -97,8 +97,8 @@ func (n *Namespace) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bo // User conditions // If the namespace was shared directly with the user and the user has the right conds = append(conds, builder.And( - builder.Eq{"users_namespace.user_id": a.GetID()}, - builder.Eq{"users_namespace.right": r}, + builder.Eq{"users_namespaces.user_id": a.GetID()}, + builder.Eq{"users_namespaces.right": r}, )) // Team rights @@ -120,7 +120,7 @@ func (n *Namespace) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bo Select("*"). Table("namespaces"). // User stuff - Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id"). + Join("LEFT", "users_namespaces", "users_namespaces.namespace_id = namespaces.id"). // Teams stuff Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id"). Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id"). diff --git a/pkg/models/namespace_users.go b/pkg/models/namespace_users.go index e912695f2..61375794d 100644 --- a/pkg/models/namespace_users.go +++ b/pkg/models/namespace_users.go @@ -49,7 +49,7 @@ type NamespaceUser struct { // TableName is the table name for NamespaceUser func (NamespaceUser) TableName() string { - return "users_namespace" + return "users_namespaces" } // Create creates a new namespace <-> user relation @@ -189,8 +189,8 @@ func (nu *NamespaceUser) ReadAll(s *xorm.Session, a web.Auth, search string, pag limit, start := getLimitFromPageIndex(page, perPage) query := s. - Join("INNER", "users_namespace", "user_id = users.id"). - Where("users_namespace.namespace_id = ?", nu.NamespaceID). + Join("INNER", "users_namespaces", "user_id = users.id"). + Where("users_namespaces.namespace_id = ?", nu.NamespaceID). Where("users.username LIKE ?", "%"+search+"%") if limit > 0 { query = query.Limit(limit, start) @@ -206,8 +206,8 @@ func (nu *NamespaceUser) ReadAll(s *xorm.Session, a web.Auth, search string, pag } numberOfTotalItems, err = s. - Join("INNER", "users_namespace", "user_id = users.id"). - Where("users_namespace.namespace_id = ?", nu.NamespaceID). + Join("INNER", "users_namespaces", "user_id = users.id"). + Where("users_namespaces.namespace_id = ?", nu.NamespaceID). Where("users.username LIKE ?", "%"+search+"%"). Count(&UserWithRight{}) diff --git a/pkg/models/namespace_users_test.go b/pkg/models/namespace_users_test.go index 0d557124b..d3dcf1452 100644 --- a/pkg/models/namespace_users_test.go +++ b/pkg/models/namespace_users_test.go @@ -132,7 +132,7 @@ func TestNamespaceUser_Create(t *testing.T) { assert.NoError(t, err) if !tt.wantErr { - db.AssertExists(t, "users_namespace", map[string]interface{}{ + db.AssertExists(t, "users_namespaces", map[string]interface{}{ "user_id": tt.fields.UserID, "namespace_id": tt.fields.NamespaceID, }, false) @@ -326,7 +326,7 @@ func TestNamespaceUser_Update(t *testing.T) { assert.NoError(t, err) if !tt.wantErr { - db.AssertExists(t, "users_namespace", map[string]interface{}{ + db.AssertExists(t, "users_namespaces", map[string]interface{}{ "user_id": tt.fields.UserID, "namespace_id": tt.fields.NamespaceID, "right": tt.fields.Right, @@ -407,7 +407,7 @@ func TestNamespaceUser_Delete(t *testing.T) { assert.NoError(t, err) if !tt.wantErr { - db.AssertMissing(t, "users_namespace", map[string]interface{}{ + db.AssertMissing(t, "users_namespaces", map[string]interface{}{ "user_id": tt.fields.UserID, "namespace_id": tt.fields.NamespaceID, }) diff --git a/pkg/models/subscription.go b/pkg/models/subscription.go index 26b01d253..f9e1510ad 100644 --- a/pkg/models/subscription.go +++ b/pkg/models/subscription.go @@ -186,7 +186,7 @@ func getSubscriberCondForEntity(entityType SubscriptionEntityType, entityID int6 builder.And( builder.Eq{"entity_id": builder. Select("namespace_id"). - From("list"). + From("lists"). Where(builder.Eq{"id": entityID}), }, builder.Eq{"entity_type": SubscriptionEntityNamespace}, @@ -203,8 +203,8 @@ func getSubscriberCondForEntity(entityType SubscriptionEntityType, entityID int6 builder.And( builder.Eq{"entity_id": builder. Select("namespace_id"). - From("list"). - Join("INNER", "tasks", "list.id = tasks.list_id"). + From("lists"). + Join("INNER", "tasks", "lists.id = tasks.list_id"). Where(builder.Eq{"tasks.id": entityID}), }, builder.Eq{"entity_type": SubscriptionEntityNamespace}, diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 8295f64b3..e329e2738 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -397,7 +397,7 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO } if len(labelFilters) > 0 { - filters = append(filters, getFilterCondForSeparateTable("label_task", opts.filterConcat, labelFilters)) + filters = append(filters, getFilterCondForSeparateTable("label_tasks", opts.filterConcat, labelFilters)) } if len(namespaceFilters) > 0 { @@ -413,7 +413,7 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO "list_id", builder. Select("id"). - From("list"). + From("lists"). Where(filtercond), ) filters = append(filters, cond) diff --git a/pkg/models/unit_tests.go b/pkg/models/unit_tests.go index 9e6ba4b2c..51c034aa3 100644 --- a/pkg/models/unit_tests.go +++ b/pkg/models/unit_tests.go @@ -39,10 +39,10 @@ func SetupTests() { err = db.InitTestFixtures( "files", - "label_task", + "label_tasks", "labels", - "link_sharing", - "list", + "link_shares", + "lists", "namespaces", "task_assignees", "task_attachments", @@ -50,13 +50,13 @@ func SetupTests() { "task_relations", "task_reminders", "tasks", - "team_list", + "team_lists", "team_members", "team_namespaces", "teams", "users", - "users_list", - "users_namespace", + "users_lists", + "users_namespaces", "buckets", "saved_filters", "subscriptions", diff --git a/pkg/models/user_list.go b/pkg/models/user_list.go index b5d369b08..ae3550952 100644 --- a/pkg/models/user_list.go +++ b/pkg/models/user_list.go @@ -44,16 +44,16 @@ func ListUsersFromList(s *xorm.Session, l *List, search string) (users []*user.U n.owner_id as nOwner, tm.user_id as tnUID, tm2.user_id as tlUID`). - Table("list"). + Table("lists"). Alias("l"). // User stuff - Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id"). - Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id"). + Join("LEFT", []string{"users_namespaces", "un"}, "un.namespace_id = l.namespace_id"). + Join("LEFT", []string{"users_lists", "ul"}, "ul.list_id = l.id"). Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id"). // Team stuff Join("LEFT", []string{"team_namespaces", "tn"}, " l.namespace_id = tn.namespace_id"). Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id"). - Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id"). + Join("LEFT", []string{"team_lists", "tl"}, "l.id = tl.list_id"). Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id"). // The actual condition Where( diff --git a/pkg/modules/migration/create_from_structure_test.go b/pkg/modules/migration/create_from_structure_test.go index 148195cc4..bdd4d6aa9 100644 --- a/pkg/modules/migration/create_from_structure_test.go +++ b/pkg/modules/migration/create_from_structure_test.go @@ -117,7 +117,7 @@ func TestInsertFromStructure(t *testing.T) { "title": testStructure[0].Namespace.Title, "description": testStructure[0].Namespace.Description, }, false) - db.AssertExists(t, "list", map[string]interface{}{ + db.AssertExists(t, "lists", map[string]interface{}{ "title": testStructure[0].Lists[0].Title, "description": testStructure[0].Lists[0].Description, }, false) From 8a7baaed1995d0ad62ea9891a6fe7799179c25ae Mon Sep 17 00:00:00 2001 From: renovate Date: Thu, 1 Apr 2021 16:31:27 +0000 Subject: [PATCH 25/35] Update module go-sql-driver/mysql to v1.6.0 (#826) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/826 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 307b1c630..33f0fa8f1 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/getsentry/sentry-go v0.10.0 github.com/go-errors/errors v1.1.1 // indirect github.com/go-redis/redis/v8 v8.6.0 - github.com/go-sql-driver/mysql v1.5.0 + github.com/go-sql-driver/mysql v1.6.0 github.com/go-testfixtures/testfixtures/v3 v3.5.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/golang/snappy v0.0.2 // indirect diff --git a/go.sum b/go.sum index bf5617bd6..08b02bd12 100644 --- a/go.sum +++ b/go.sum @@ -248,6 +248,8 @@ github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZp github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-testfixtures/testfixtures/v3 v3.5.0 h1:fFJGHhFdcwy48oTLHvr0WRQ09rGiZE+as9ElvbRWS+c= github.com/go-testfixtures/testfixtures/v3 v3.5.0/go.mod h1:P4L3WxgOsCLbAeUC50qX5rdj1ULZfUMqgCbqah3OH5U= From 9b7eef985e3d8bdcef3fbac489430d40a5d469a7 Mon Sep 17 00:00:00 2001 From: renovate Date: Sat, 3 Apr 2021 11:08:49 +0000 Subject: [PATCH 26/35] Update golang.org/x/oauth2 commit hash to 2e8d934 (#827) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/827 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 33f0fa8f1..6063f7a33 100644 --- a/go.mod +++ b/go.mod @@ -78,7 +78,7 @@ require ( golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect - golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 + golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 golang.org/x/text v0.3.5 // indirect diff --git a/go.sum b/go.sum index 08b02bd12..d3c7516d9 100644 --- a/go.sum +++ b/go.sum @@ -1016,6 +1016,8 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 h1:duBc5zuJsmJXYOVVE/6Pxe golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 h1:D7nTwh4J0i+5mW4Zjzn5omvlr6YBcWywE6KOcatyNxY= golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 0b8173c1c3c35acc2f60954cd6de3f35695ecb62 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 3 Apr 2021 16:49:20 +0200 Subject: [PATCH 27/35] Fix not able to make saved filters favorite --- .gitignore | 1 + pkg/migration/20210403145503.go | 43 +++++++++++++++++++++++++++++++++ pkg/models/list.go | 19 +++++++++++++++ pkg/models/list_rights.go | 11 +++++++++ pkg/models/namespace.go | 41 +++++++++++++++---------------- pkg/models/saved_filters.go | 29 ++++++++++++++++++++-- pkg/swagger/docs.go | 4 +++ pkg/swagger/swagger.json | 4 +++ pkg/swagger/swagger.yaml | 3 +++ 9 files changed, 131 insertions(+), 24 deletions(-) create mode 100644 pkg/migration/20210403145503.go diff --git a/.gitignore b/.gitignore index 8862180fb..7cf9e4a85 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ files/ vikunja-dump* vendor/ os-packages/ +mage_output_file.go diff --git a/pkg/migration/20210403145503.go b/pkg/migration/20210403145503.go new file mode 100644 index 000000000..9cd4b116c --- /dev/null +++ b/pkg/migration/20210403145503.go @@ -0,0 +1,43 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type savedFilters20210403145503 struct { + IsFavorite bool `xorm:"default false" json:"is_favorite"` +} + +func (savedFilters20210403145503) TableName() string { + return "saved_filters" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20210403145503", + Description: "", + Migrate: func(tx *xorm.Engine) error { + return tx.Sync2(savedFilters20210403145503{}) + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/models/list.go b/pkg/models/list.go index 6156df8a9..ebcd54ed8 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -586,6 +586,25 @@ func CreateOrUpdateList(s *xorm.Session, list *List, auth web.Auth) (err error) // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{id} [post] func (l *List) Update(s *xorm.Session, a web.Auth) (err error) { + fid := getSavedFilterIDFromListID(l.ID) + if fid > 0 { + f, err := getSavedFilterSimpleByID(s, fid) + if err != nil { + return err + } + + f.IsFavorite = l.IsFavorite + f.Title = l.Title + f.Description = l.Description + err = f.Update(s, a) + if err != nil { + return err + } + + *l = *f.toList() + return nil + } + err = CreateOrUpdateList(s, l, a) if err != nil { return err diff --git a/pkg/models/list_rights.go b/pkg/models/list_rights.go index f263ab7fd..5b4144f77 100644 --- a/pkg/models/list_rights.go +++ b/pkg/models/list_rights.go @@ -115,6 +115,17 @@ func (l *List) CanUpdate(s *xorm.Session, a web.Auth) (canUpdate bool, err error if l.ID == FavoritesPseudoList.ID { return false, nil } + + fid := getSavedFilterIDFromListID(l.ID) + if fid > 0 { + sf, err := getSavedFilterSimpleByID(s, fid) + if err != nil { + return false, err + } + + return sf.CanUpdate(s, a) + } + canUpdate, err = l.CanWrite(s, a) // If the list is archived and the user tries to un-archive it, let the request through if IsErrListIsArchived(err) && !l.IsArchived { diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index 044be2bcf..ebe5cd142 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -434,14 +434,10 @@ func getSavedFilters(s *xorm.Session, doer *user.User) (savedFiltersNamespace *N } for _, filter := range savedFilters { - savedFiltersNamespace.Lists = append(savedFiltersNamespace.Lists, &List{ - ID: getListIDFromSavedFilterID(filter.ID), - Title: filter.Title, - Description: filter.Description, - Created: filter.Created, - Updated: filter.Updated, - Owner: doer, - }) + filterList := filter.toList() + filterList.NamespaceID = savedFiltersNamespace.ID + filterList.Owner = doer + savedFiltersNamespace.Lists = append(savedFiltersNamespace.Lists, filterList) } return @@ -521,6 +517,19 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int lists = append(lists, sharedListsNamespace.Lists...) } + ///////////////// + // Saved Filters + + savedFiltersNamespace, err := getSavedFilters(s, doer) + if err != nil { + return nil, 0, 0, err + } + + if savedFiltersNamespace != nil { + namespaces[savedFiltersNamespace.ID] = savedFiltersNamespace + lists = append(lists, savedFiltersNamespace.Lists...) + } + ///////////////// // Favorite lists @@ -533,18 +542,6 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int namespaces[favoritesNamespace.ID] = favoritesNamespace } - ///////////////// - // Saved Filters - - savedFiltersNamespace, err := getSavedFilters(s, doer) - if err != nil { - return nil, 0, 0, err - } - - if savedFiltersNamespace != nil { - namespaces[savedFiltersNamespace.ID] = savedFiltersNamespace - } - ////////////////////// // Put it all together @@ -554,8 +551,8 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int } for _, list := range lists { - if list.NamespaceID == SharedListsPseudoNamespace.ID { - // Shared lists are already in the namespace + if list.NamespaceID == SharedListsPseudoNamespace.ID || list.NamespaceID == SavedFiltersPseudoNamespace.ID { + // Shared lists and filtered lists are already in the namespace continue } namespaces[list.NamespaceID].Lists = append(namespaces[list.NamespaceID].Lists, list) diff --git a/pkg/models/saved_filters.go b/pkg/models/saved_filters.go index 1d93a5290..819d1937e 100644 --- a/pkg/models/saved_filters.go +++ b/pkg/models/saved_filters.go @@ -29,7 +29,7 @@ type SavedFilter struct { // The unique numeric id of this saved filter ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"filter"` // The actual filters this filter contains - Filters *TaskCollection `xorm:"JSON not null" json:"filters"` + Filters *TaskCollection `xorm:"JSON not null" json:"filters" valid:"required"` // The title of the filter. Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"` // The description of the filter @@ -39,6 +39,9 @@ type SavedFilter struct { // The user who owns this filter Owner *user.User `xorm:"-" json:"owner" valid:"-"` + // True if the filter is a favorite. Favorite filters show up in a separate namespace together with favorite lists. + IsFavorite bool `xorm:"default false" json:"is_favorite"` + // A timestamp when this filter was created. You cannot change this value. Created time.Time `xorm:"created not null" json:"created"` // A timestamp when this filter was last updated. You cannot change this value. @@ -90,6 +93,18 @@ func getSavedFiltersForUser(s *xorm.Session, auth web.Auth) (filters []*SavedFil return } +func (sf *SavedFilter) toList() *List { + return &List{ + ID: getListIDFromSavedFilterID(sf.ID), + Title: sf.Title, + Description: sf.Description, + IsFavorite: sf.IsFavorite, + Created: sf.Created, + Updated: sf.Updated, + Owner: sf.Owner, + } +} + // Create creates a new saved filter // @Summary Creates a new saved filter // @Description Creates a new saved filter @@ -154,12 +169,22 @@ func (sf *SavedFilter) ReadOne(s *xorm.Session, a web.Auth) error { // @Failure 500 {object} models.Message "Internal error" // @Router /filters/{id} [post] func (sf *SavedFilter) Update(s *xorm.Session, a web.Auth) error { - _, err := s. + origFilter, err := getSavedFilterSimpleByID(s, sf.ID) + if err != nil { + return err + } + + if sf.Filters == nil { + sf.Filters = origFilter.Filters + } + + _, err = s. Where("id = ?", sf.ID). Cols( "title", "description", "filters", + "is_favorite", ). Update(sf) return err diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 3dad52f1d..4d3d7b1f0 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -7770,6 +7770,10 @@ var doc = `{ "description": "The unique numeric id of this saved filter", "type": "integer" }, + "is_favorite": { + "description": "True if the filter is a favorite. Favorite filters show up in a separate namespace together with favorite lists.", + "type": "boolean" + }, "owner": { "description": "The user who owns this filter", "$ref": "#/definitions/user.User" diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index 218bb867b..d75e776ce 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -7753,6 +7753,10 @@ "description": "The unique numeric id of this saved filter", "type": "integer" }, + "is_favorite": { + "description": "True if the filter is a favorite. Favorite filters show up in a separate namespace together with favorite lists.", + "type": "boolean" + }, "owner": { "description": "The user who owns this filter", "$ref": "#/definitions/user.User" diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index f55d98cbf..b0240baac 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -524,6 +524,9 @@ definitions: id: description: The unique numeric id of this saved filter type: integer + is_favorite: + description: True if the filter is a favorite. Favorite filters show up in a separate namespace together with favorite lists. + type: boolean owner: $ref: '#/definitions/user.User' description: The user who owns this filter From b2bbb03e48d15ac0122e008222b3ea6118f7c2f7 Mon Sep 17 00:00:00 2001 From: renovate Date: Wed, 7 Apr 2021 09:15:09 +0000 Subject: [PATCH 28/35] Update golang.org/x/term commit hash to 72f3dc4 (#828) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/828 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 6063f7a33..3a3c7c53c 100644 --- a/go.mod +++ b/go.mod @@ -80,7 +80,7 @@ require ( golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 + golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72 golang.org/x/text v0.3.5 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/go.sum b/go.sum index d3c7516d9..9c8a72f10 100644 --- a/go.sum +++ b/go.sum @@ -1108,6 +1108,8 @@ golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+ golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs= golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72 h1:VqE9gduFZ4dbR7XoL77lHFp0/DyDUBKSXK7CMFkVcV0= +golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From b7d832891a899d4831b10d324c92ae2816179473 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 7 Apr 2021 12:27:51 +0200 Subject: [PATCH 29/35] Switch telegram notifications to matrix notifications --- .drone1.yml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/.drone1.yml b/.drone1.yml index d1af9d7cc..617987b07 100644 --- a/.drone1.yml +++ b/.drone1.yml @@ -772,21 +772,23 @@ trigger: - "refs/tags/**" depends_on: + - testing - release + - deploy-docs + - docker-arm-release + - docker-amd64-release - docker-manifest steps: - - name: telegram - image: appleboy/drone-telegram:1-linux-amd64 + - name: notify + image: plugins/matrix settings: - token: - from_secret: TELEGRAM_TOKEN - to: - from_secret: TELEGRAM_TO - message: > - {{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}} - {{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}` - Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}. + homeserver: https://matrix.org + roomid: vikunja-dev:matrix.org + username: + from_secret: matrix_username + password: + from_secret: matrix_password when: status: - success From 84291679cc84e8441abac109b42c6da63d89ff81 Mon Sep 17 00:00:00 2001 From: konrad Date: Wed, 7 Apr 2021 12:44:39 +0000 Subject: [PATCH 30/35] Add names for link shares (#829) Co-authored-by: kolaente Reviewed-on: https://kolaente.dev/vikunja/api/pulls/829 Co-authored-by: konrad Co-committed-by: konrad --- pkg/db/fixtures/buckets.yml | 6 +++ pkg/db/fixtures/task_attachments.yml | 5 ++ pkg/db/fixtures/task_comments.yml | 6 +++ pkg/db/fixtures/tasks.yml | 10 +++- pkg/integrations/kanban_test.go | 26 +++++++++ pkg/integrations/link_sharing_test.go | 2 +- pkg/integrations/task_comment_test.go | 26 +++++++++ pkg/integrations/task_test.go | 26 +++++++++ pkg/migration/20210403220653.go | 43 +++++++++++++++ pkg/models/kanban.go | 15 +++--- pkg/models/kanban_test.go | 14 +++++ pkg/models/link_sharing.go | 41 ++++++++++++++ pkg/models/task_attachment.go | 29 +++++----- pkg/models/task_attachment_test.go | 7 ++- pkg/models/task_collection_test.go | 21 ++++++++ pkg/models/task_comments.go | 32 +++++------ pkg/models/task_comments_test.go | 14 +++++ pkg/models/task_relation.go | 8 ++- pkg/models/tasks.go | 21 +++----- pkg/models/tasks_test.go | 13 +++++ pkg/models/users.go | 78 +++++++++++++++++++++++++++ pkg/swagger/docs.go | 4 ++ pkg/swagger/swagger.json | 4 ++ pkg/swagger/swagger.yaml | 3 ++ 24 files changed, 398 insertions(+), 56 deletions(-) create mode 100644 pkg/migration/20210403220653.go create mode 100644 pkg/models/users.go diff --git a/pkg/db/fixtures/buckets.yml b/pkg/db/fixtures/buckets.yml index 7f2940260..c1ddcd94f 100644 --- a/pkg/db/fixtures/buckets.yml +++ b/pkg/db/fixtures/buckets.yml @@ -209,3 +209,9 @@ created_by_id: 1 created: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52 +- id: 35 + title: testbucket35 + list_id: 23 + created_by_id: -2 + created: 2020-04-18 21:13:52 + updated: 2020-04-18 21:13:52 diff --git a/pkg/db/fixtures/task_attachments.yml b/pkg/db/fixtures/task_attachments.yml index 9bbbcb0ed..c203d5b34 100644 --- a/pkg/db/fixtures/task_attachments.yml +++ b/pkg/db/fixtures/task_attachments.yml @@ -9,3 +9,8 @@ file_id: 9999 created_by_id: 1 created: 2018-12-01 15:13:12 +- id: 3 + task_id: 1 + file_id: 1 + created_by_id: -2 + created: 2018-12-01 15:13:12 diff --git a/pkg/db/fixtures/task_comments.yml b/pkg/db/fixtures/task_comments.yml index 16ce99a73..83163ed74 100644 --- a/pkg/db/fixtures/task_comments.yml +++ b/pkg/db/fixtures/task_comments.yml @@ -94,3 +94,9 @@ task_id: 36 created: 2020-02-19 18:07:06 updated: 2020-02-19 18:07:06 +- id: 17 + comment: comment 17 + author_id: -2 + task_id: 35 + created: 2020-02-19 18:07:06 + updated: 2020-02-19 18:07:06 diff --git a/pkg/db/fixtures/tasks.yml b/pkg/db/fixtures/tasks.yml index 0fb2a9864..73d392b64 100644 --- a/pkg/db/fixtures/tasks.yml +++ b/pkg/db/fixtures/tasks.yml @@ -338,5 +338,11 @@ bucket_id: 20 created: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04 - - +- id: 37 + title: 'task #37' + done: false + created_by_id: -2 + list_id: 2 + index: 2 + created: 2018-12-01 01:12:04 + updated: 2018-12-01 01:12:04 diff --git a/pkg/integrations/kanban_test.go b/pkg/integrations/kanban_test.go index ac90457c4..d2291aa41 100644 --- a/pkg/integrations/kanban_test.go +++ b/pkg/integrations/kanban_test.go @@ -19,6 +19,8 @@ package integrations import ( "testing" + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" @@ -33,6 +35,20 @@ func TestBucket(t *testing.T) { }, t: t, } + testHandlerLinkShareWrite := webHandlerTest{ + linkShare: &models.LinkSharing{ + ID: 2, + Hash: "test2", + ListID: 2, + Right: models.RightWrite, + SharingType: models.SharingTypeWithoutPassword, + SharedByID: 1, + }, + strFunc: func() handler.CObject { + return &models.Bucket{} + }, + t: t, + } t.Run("ReadAll", func(t *testing.T) { t.Run("Normal", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(nil, map[string]string{"list": "1"}) @@ -297,5 +313,15 @@ func TestBucket(t *testing.T) { assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) }) }) + t.Run("Link Share", func(t *testing.T) { + rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"Lorem Ipsum"}`) + assert.NoError(t, err) + assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) + db.AssertExists(t, "buckets", map[string]interface{}{ + "list_id": 2, + "created_by_id": -2, + "title": "Lorem Ipsum", + }, false) + }) }) } diff --git a/pkg/integrations/link_sharing_test.go b/pkg/integrations/link_sharing_test.go index 4e01f829d..97d3bd6f1 100644 --- a/pkg/integrations/link_sharing_test.go +++ b/pkg/integrations/link_sharing_test.go @@ -553,7 +553,7 @@ func TestLinkSharing(t *testing.T) { rec, err := testHandlerTaskWriteCollection.testReadAllWithLinkShare(nil, nil) assert.NoError(t, err) assert.NotContains(t, rec.Body.String(), `task #2`) - assert.NotContains(t, rec.Body.String(), `task #3`) + assert.NotContains(t, rec.Body.String(), `task #3"`) assert.NotContains(t, rec.Body.String(), `task #4`) assert.NotContains(t, rec.Body.String(), `task #5`) assert.NotContains(t, rec.Body.String(), `task #6`) diff --git a/pkg/integrations/task_comment_test.go b/pkg/integrations/task_comment_test.go index 59e3a92d1..5aa9bb5f3 100644 --- a/pkg/integrations/task_comment_test.go +++ b/pkg/integrations/task_comment_test.go @@ -19,6 +19,8 @@ package integrations import ( "testing" + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" @@ -33,6 +35,20 @@ func TestTaskComments(t *testing.T) { }, t: t, } + testHandlerLinkShareWrite := webHandlerTest{ + linkShare: &models.LinkSharing{ + ID: 2, + Hash: "test2", + ListID: 2, + Right: models.RightWrite, + SharingType: models.SharingTypeWithoutPassword, + SharedByID: 1, + }, + strFunc: func() handler.CObject { + return &models.TaskComment{} + }, + t: t, + } // Only run specific nested tests: // ^TestTaskComments$/^Update$/^Update_task_items$/^Removing_Assignees_null$ t.Run("Update", func(t *testing.T) { @@ -281,5 +297,15 @@ func TestTaskComments(t *testing.T) { assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`) }) }) + t.Run("Link Share", func(t *testing.T) { + rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"task": "13"}, `{"comment":"Lorem Ipsum"}`) + assert.NoError(t, err) + assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`) + db.AssertExists(t, "task_comments", map[string]interface{}{ + "task_id": 13, + "comment": "Lorem Ipsum", + "author_id": -2, + }, false) + }) }) } diff --git a/pkg/integrations/task_test.go b/pkg/integrations/task_test.go index bc3a71dc6..1a09f92c9 100644 --- a/pkg/integrations/task_test.go +++ b/pkg/integrations/task_test.go @@ -19,6 +19,8 @@ package integrations import ( "testing" + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" @@ -33,6 +35,20 @@ func TestTask(t *testing.T) { }, t: t, } + testHandlerLinkShareWrite := webHandlerTest{ + linkShare: &models.LinkSharing{ + ID: 2, + Hash: "test2", + ListID: 2, + Right: models.RightWrite, + SharingType: models.SharingTypeWithoutPassword, + SharedByID: 1, + }, + strFunc: func() handler.CObject { + return &models.Task{} + }, + t: t, + } // Only run specific nested tests: // ^TestTask$/^Update$/^Update_task_items$/^Removing_Assignees_null$ t.Run("Update", func(t *testing.T) { @@ -489,5 +505,15 @@ func TestTask(t *testing.T) { assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist) }) }) + t.Run("Link Share", func(t *testing.T) { + rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"Lorem Ipsum"}`) + assert.NoError(t, err) + assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) + db.AssertExists(t, "tasks", map[string]interface{}{ + "list_id": 2, + "title": "Lorem Ipsum", + "created_by_id": -2, + }, false) + }) }) } diff --git a/pkg/migration/20210403220653.go b/pkg/migration/20210403220653.go new file mode 100644 index 000000000..1ce91a4a3 --- /dev/null +++ b/pkg/migration/20210403220653.go @@ -0,0 +1,43 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type linkShares20210403220653 struct { + Name string `xorm:"text null" json:"name"` +} + +func (linkShares20210403220653) TableName() string { + return "link_shares" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20210403220653", + Description: "Add the name column to link shares", + Migrate: func(tx *xorm.Engine) error { + return tx.Sync2(linkShares20210403220653{}) + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go index 94d28d545..1e827a510 100644 --- a/pkg/models/kanban.go +++ b/pkg/models/kanban.go @@ -135,12 +135,9 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int } // Get all users - users := make(map[int64]*user.User) - if len(userIDs) > 0 { - err = s.In("id", userIDs).Find(&users) - if err != nil { - return - } + users, err := getUsersOrLinkSharesFromIDs(s, userIDs) + if err != nil { + return } for _, bb := range buckets { @@ -234,7 +231,11 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{id}/buckets [put] func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) { - b.CreatedByID = a.GetID() + b.CreatedBy, err = getUserOrLinkShareUser(s, a) + if err != nil { + return + } + b.CreatedByID = b.CreatedBy.ID _, err = s.Insert(b) return diff --git a/pkg/models/kanban_test.go b/pkg/models/kanban_test.go index 6dc6e6ba7..99b099d4b 100644 --- a/pkg/models/kanban_test.go +++ b/pkg/models/kanban_test.go @@ -89,6 +89,20 @@ func TestBucket_ReadAll(t *testing.T) { assert.Len(t, buckets, 3) assert.Equal(t, int64(2), buckets[0].Tasks[0].ID) }) + t.Run("link share", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + testuser := &user.User{ID: 1} + b := &Bucket{ListID: 23} + result, _, _, err := b.ReadAll(s, testuser, "", 0, 0) + assert.NoError(t, err) + buckets, _ := result.([]*Bucket) + assert.Len(t, buckets, 1) + assert.NotNil(t, buckets[0].CreatedBy) + assert.Equal(t, int64(-2), buckets[0].CreatedByID) + }) } func TestBucket_Delete(t *testing.T) { diff --git a/pkg/models/link_sharing.go b/pkg/models/link_sharing.go index 487d78fff..e8835e2d6 100644 --- a/pkg/models/link_sharing.go +++ b/pkg/models/link_sharing.go @@ -42,6 +42,8 @@ type LinkSharing struct { ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"share"` // The public id to get this shared list Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash"` + // The name of this link share. All actions someone takes while being authenticated with that link will appear with that name. + Name string `xorm:"text null" json:"name"` // The ID of the shared list ListID int64 `xorm:"bigint not null" json:"-" param:"list"` // The right this list is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. @@ -84,6 +86,25 @@ func GetLinkShareFromClaims(claims jwt.MapClaims) (share *LinkSharing, err error return } +func (share *LinkSharing) getUserID() int64 { + return share.ID * -1 +} + +func (share *LinkSharing) toUser() *user.User { + suffix := "Link Share" + if share.Name != "" { + suffix = " (" + suffix + ")" + } + + return &user.User{ + ID: share.getUserID(), + Name: share.Name + suffix, + Username: share.Name, + Created: share.Created, + Updated: share.Updated, + } +} + // Create creates a new link share for a given list // @Summary Share a list via link // @Description Share a list via link. The user needs to have write-access to the list to be able do this. @@ -246,3 +267,23 @@ func GetListByShareHash(s *xorm.Session, hash string) (list *List, err error) { list, err = GetListSimpleByID(s, share.ListID) return } + +// GetLinkShareByID returns a link share by its id. +func GetLinkShareByID(s *xorm.Session, id int64) (share *LinkSharing, err error) { + share = &LinkSharing{} + has, err := s.Where("id = ?", id).Get(share) + if err != nil { + return + } + if !has { + return share, ErrListShareDoesNotExist{ID: id} + } + return +} + +// GetLinkSharesByIDs returns all link shares from a slice of ids +func GetLinkSharesByIDs(s *xorm.Session, ids []int64) (shares map[int64]*LinkSharing, err error) { + shares = make(map[int64]*LinkSharing) + err = s.In("id", ids).Find(&shares) + return +} diff --git a/pkg/models/task_attachment.go b/pkg/models/task_attachment.go index 457d16d2f..8ab8f56e6 100644 --- a/pkg/models/task_attachment.go +++ b/pkg/models/task_attachment.go @@ -64,7 +64,17 @@ func (ta *TaskAttachment) NewAttachment(s *xorm.Session, f io.ReadCloser, realna // Add an entry to the db ta.FileID = file.ID - ta.CreatedByID = a.GetID() + + ta.CreatedBy, err = getUserOrLinkShareUser(s, a) + if err != nil { + // remove the uploaded file if adding it to the db fails + if err2 := file.Delete(); err2 != nil { + return err2 + } + return err + } + ta.CreatedByID = ta.CreatedBy.ID + _, err = s.Insert(ta) if err != nil { // remove the uploaded file if adding it to the db fails @@ -74,8 +84,6 @@ func (ta *TaskAttachment) NewAttachment(s *xorm.Session, f io.ReadCloser, realna return err } - ta.CreatedBy, _ = user.GetFromAuth(a) // Ignoring cases where the auth is not a user - return nil } @@ -145,19 +153,19 @@ func (ta *TaskAttachment) ReadAll(s *xorm.Session, a web.Auth, search string, pa return nil, 0, 0, err } - us := make(map[int64]*user.User) - err = s.In("id", userIDs).Find(&us) + users, err := getUsersOrLinkSharesFromIDs(s, userIDs) if err != nil { return nil, 0, 0, err } for _, r := range attachments { + r.CreatedBy = users[r.CreatedByID] + // If the actual file does not exist, don't try to load it as that would fail with nil panic if _, exists := fs[r.FileID]; !exists { continue } r.File = fs[r.FileID] - r.CreatedBy = us[r.CreatedByID] } numberOfTotalItems, err = s. @@ -231,12 +239,9 @@ func getTaskAttachmentsByTaskIDs(s *xorm.Session, taskIDs []int64) (attachments return } - users := make(map[int64]*user.User) - if len(userIDs) > 0 { - err = s.In("id", userIDs).Find(&users) - if err != nil { - return - } + users, err := getUsersOrLinkSharesFromIDs(s, userIDs) + if err != nil { + return nil, err } // Obfuscate all user emails diff --git a/pkg/models/task_attachment_test.go b/pkg/models/task_attachment_test.go index 7d6dbad56..47650deea 100644 --- a/pkg/models/task_attachment_test.go +++ b/pkg/models/task_attachment_test.go @@ -146,8 +146,13 @@ func TestTaskAttachment_ReadAll(t *testing.T) { as, _, _, err := ta.ReadAll(s, &user.User{ID: 1}, "", 0, 50) attachments, _ := as.([]*TaskAttachment) assert.NoError(t, err) - assert.Len(t, attachments, 2) + assert.Len(t, attachments, 3) assert.Equal(t, "test", attachments[0].File.Name) + for _, a := range attachments { + assert.NotNil(t, a.CreatedBy) + } + assert.Equal(t, int64(-2), attachments[2].CreatedByID) + assert.Equal(t, int64(-2), attachments[2].CreatedBy.ID) } func TestTaskAttachment_Delete(t *testing.T) { diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 2f50fecf7..f195cb12d 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -59,6 +59,12 @@ func TestTaskCollection_ReadAll(t *testing.T) { Created: testCreatedTime, Updated: testUpdatedTime, } + linkShareUser2 := &user.User{ + ID: -2, + Name: "Link Share", + Created: testCreatedTime, + Updated: testUpdatedTime, + } loc := config.GetTimeZone() @@ -124,6 +130,21 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, Created: testCreatedTime, }, + { + ID: 3, + TaskID: 1, + FileID: 1, + CreatedByID: -2, + CreatedBy: linkShareUser2, + Created: testCreatedTime, + File: &files.File{ + ID: 1, + Name: "test", + Size: 100, + Created: time.Unix(1570998791, 0).In(loc), + CreatedByID: 1, + }, + }, }, Created: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc), diff --git a/pkg/models/task_comments.go b/pkg/models/task_comments.go index 1924712c4..d213ef884 100644 --- a/pkg/models/task_comments.go +++ b/pkg/models/task_comments.go @@ -67,24 +67,22 @@ func (tc *TaskComment) Create(s *xorm.Session, a web.Auth) (err error) { return err } - tc.AuthorID = a.GetID() + tc.Author, err = getUserOrLinkShareUser(s, a) + if err != nil { + return err + } + tc.AuthorID = tc.Author.ID + _, err = s.Insert(tc) if err != nil { return } - doer, _ := user.GetFromAuth(a) - err = events.Dispatch(&TaskCommentCreatedEvent{ + return events.Dispatch(&TaskCommentCreatedEvent{ Task: &task, Comment: tc, - Doer: doer, + Doer: tc.Author, }) - if err != nil { - return err - } - - tc.Author, err = user.GetUserByID(s, a.GetID()) - return } // Delete removes a task comment @@ -215,14 +213,12 @@ func (tc *TaskComment) ReadAll(s *xorm.Session, auth web.Auth, search string, pa return } - // Get all authors - authors := make(map[int64]*user.User) - err = s. - Select("users.*"). - Table("task_comments"). - Where("task_id = ? AND comment like ?", tc.TaskID, "%"+search+"%"). - Join("INNER", "users", "users.id = task_comments.author_id"). - Find(&authors) + var authorIDs []int64 + for _, comment := range comments { + authorIDs = append(authorIDs, comment.AuthorID) + } + + authors, err := getUsersOrLinkSharesFromIDs(s, authorIDs) if err != nil { return } diff --git a/pkg/models/task_comments_test.go b/pkg/models/task_comments_test.go index 3ad3abf8d..ecc16ae07 100644 --- a/pkg/models/task_comments_test.go +++ b/pkg/models/task_comments_test.go @@ -184,4 +184,18 @@ func TestTaskComment_ReadAll(t *testing.T) { assert.Error(t, err) assert.True(t, IsErrGenericForbidden(err)) }) + t.Run("comment from link share", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + tc := &TaskComment{TaskID: 35} + u := &user.User{ID: 1} + result, _, _, err := tc.ReadAll(s, u, "", 0, -1) + comments := result.([]*TaskComment) + assert.NoError(t, err) + assert.Len(t, comments, 2) + assert.Equal(t, int64(-2), comments[1].AuthorID) + assert.NotNil(t, comments[1].Author) + }) } diff --git a/pkg/models/task_relation.go b/pkg/models/task_relation.go index 9f787405b..b0bdd98bb 100644 --- a/pkg/models/task_relation.go +++ b/pkg/models/task_relation.go @@ -144,13 +144,17 @@ func (rel *TaskRelation) Create(s *xorm.Session, a web.Auth) error { } } - rel.CreatedByID = a.GetID() + rel.CreatedBy, err = getUserOrLinkShareUser(s, a) + if err != nil { + return err + } + rel.CreatedByID = rel.CreatedBy.ID // Build up the other relation (see the comment above for explanation) otherRelation := &TaskRelation{ TaskID: rel.OtherTaskID, OtherTaskID: rel.TaskID, - CreatedByID: a.GetID(), + CreatedByID: rel.CreatedByID, } switch rel.RelationKind { diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index e329e2738..14c6431eb 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -670,7 +670,7 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task) (err error) { return } - users, err := user.GetUsersByIDs(s, userIDs) + users, err := getUsersOrLinkSharesFromIDs(s, userIDs) if err != nil { return } @@ -817,17 +817,11 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err return err } - if _, is := a.(*LinkSharing); is { - // A negative user id indicates user share links - t.CreatedByID = a.GetID() * -1 - } else { - u, err := user.GetUserByID(s, a.GetID()) - if err != nil { - return err - } - t.CreatedByID = u.ID - t.CreatedBy = u + createdBy, err := getUserOrLinkShareUser(s, a) + if err != nil { + return err } + t.CreatedByID = createdBy.ID // Generate a uuid if we don't already have one if t.UID == "" { @@ -856,6 +850,8 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err return err } + t.CreatedBy = createdBy + // Update the assignees if updateAssignees { if err := t.updateTaskAssignees(s, t.Assignees, a); err != nil { @@ -870,10 +866,9 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err t.setIdentifier(l) - doer, _ := user.GetFromAuth(a) err = events.Dispatch(&TaskCreatedEvent{ Task: t, - Doer: doer, + Doer: createdBy, }) if err != nil { return err diff --git a/pkg/models/tasks_test.go b/pkg/models/tasks_test.go index 725c2c402..ff1e692d1 100644 --- a/pkg/models/tasks_test.go +++ b/pkg/models/tasks_test.go @@ -579,4 +579,17 @@ func TestTask_ReadOne(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, task.Subscription) }) + t.Run("created by link share", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + task := &Task{ID: 37} + err := task.ReadOne(s, u) + assert.NoError(t, err) + assert.Equal(t, "task #37", task.Title) + assert.Equal(t, int64(-2), task.CreatedByID) + assert.NotNil(t, task.CreatedBy) + assert.Equal(t, int64(-2), task.CreatedBy.ID) + }) } diff --git a/pkg/models/users.go b/pkg/models/users.go new file mode 100644 index 000000000..84fd38110 --- /dev/null +++ b/pkg/models/users.go @@ -0,0 +1,78 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package models + +import ( + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/web" + "xorm.io/xorm" +) + +// Returns either a user or a link share disguised as a user. +func getUserOrLinkShareUser(s *xorm.Session, a web.Auth) (uu *user.User, err error) { + if u, is := a.(*user.User); is { + uu, err = user.GetUserByID(s, u.ID) + return + } + + if ls, is := a.(*LinkSharing); is { + l, err := GetLinkShareByID(s, ls.ID) + if err != nil { + return nil, err + } + return l.toUser(), nil + } + + return +} + +// Returns all users or pseudo link shares from a slice of ids. ids < 0 are considered to be a link share in that case. +func getUsersOrLinkSharesFromIDs(s *xorm.Session, ids []int64) (users map[int64]*user.User, err error) { + users = make(map[int64]*user.User) + var userIDs []int64 + var linkShareIDs []int64 + for _, id := range ids { + if id < 0 { + linkShareIDs = append(linkShareIDs, id*-1) + continue + } + + userIDs = append(userIDs, id) + } + + if len(userIDs) > 0 { + users, err = user.GetUsersByIDs(s, userIDs) + if err != nil { + return + } + } + + if len(linkShareIDs) == 0 { + return + } + + shares, err := GetLinkSharesByIDs(s, linkShareIDs) + if err != nil { + return nil, err + } + + for _, share := range shares { + users[share.ID*-1] = share.toUser() + } + + return +} diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 4d3d7b1f0..91f3669a5 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -7489,6 +7489,10 @@ var doc = `{ "description": "The ID of the shared thing", "type": "integer" }, + "name": { + "description": "The name of this link share. All actions someone takes while being authenticated with that link will appear with that name.", + "type": "string" + }, "right": { "description": "The right this list is shared with. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.", "type": "integer", diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index d75e776ce..d6ab5fe85 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -7472,6 +7472,10 @@ "description": "The ID of the shared thing", "type": "integer" }, + "name": { + "description": "The name of this link share. All actions someone takes while being authenticated with that link will appear with that name.", + "type": "string" + }, "right": { "description": "The right this list is shared with. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.", "type": "integer", diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index b0240baac..cdd914760 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -310,6 +310,9 @@ definitions: id: description: The ID of the shared thing type: integer + name: + description: The name of this link share. All actions someone takes while being authenticated with that link will appear with that name. + type: string right: default: 0 description: The right this list is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. From 325dcc5795b3048c67bb5d58c69487273db3d19a Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 7 Apr 2021 14:46:40 +0200 Subject: [PATCH 31/35] Fix matrix notify room id --- .drone1.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone1.yml b/.drone1.yml index 617987b07..8d43ff89f 100644 --- a/.drone1.yml +++ b/.drone1.yml @@ -784,7 +784,7 @@ steps: image: plugins/matrix settings: homeserver: https://matrix.org - roomid: vikunja-dev:matrix.org + roomid: WqBDCxzghKcNflkErL:matrix.org username: from_secret: matrix_username password: From cb0df3ebbc1b2fd6985ed2542255486b108f33f0 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 7 Apr 2021 14:56:44 +0200 Subject: [PATCH 32/35] Show empty avatar when the user was not found --- pkg/routes/api/v1/avatar.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/routes/api/v1/avatar.go b/pkg/routes/api/v1/avatar.go index 488eb0e93..03c9e0576 100644 --- a/pkg/routes/api/v1/avatar.go +++ b/pkg/routes/api/v1/avatar.go @@ -62,14 +62,13 @@ func GetAvatar(c echo.Context) error { // Get the user u, err := user.GetUserWithEmail(s, &user.User{Username: username}) - if err != nil { + if err != nil && !user.IsErrUserDoesNotExist(err) { log.Errorf("Error getting user for avatar: %v", err) return handler.HandleHTTPError(err, c) } - // Initialize the avatar provider - // For now, we only have one avatar provider, in the future there could be multiple which - // could be changed based on user settings etc. + found := !(err != nil && user.IsErrUserDoesNotExist(err)) + var avatarProvider avatar.Provider switch u.AvatarProvider { case "gravatar": @@ -82,6 +81,10 @@ func GetAvatar(c echo.Context) error { avatarProvider = &empty.Provider{} } + if !found { + avatarProvider = &empty.Provider{} + } + size := c.QueryParam("size") var sizeInt int64 = 250 // Default size of 250 if size != "" { From 8ddc00bd29e1c67a514e2c9c314edf422a1b575b Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 7 Apr 2021 15:02:57 +0200 Subject: [PATCH 33/35] Fix getting user info from /user endpoint for link shares --- pkg/models/kanban.go | 2 +- pkg/models/task_attachment.go | 2 +- pkg/models/task_comments.go | 2 +- pkg/models/task_relation.go | 2 +- pkg/models/tasks.go | 2 +- pkg/models/users.go | 4 ++-- pkg/routes/api/v1/user_show.go | 14 +++++--------- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go index 1e827a510..d9dbd6efa 100644 --- a/pkg/models/kanban.go +++ b/pkg/models/kanban.go @@ -231,7 +231,7 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{id}/buckets [put] func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) { - b.CreatedBy, err = getUserOrLinkShareUser(s, a) + b.CreatedBy, err = GetUserOrLinkShareUser(s, a) if err != nil { return } diff --git a/pkg/models/task_attachment.go b/pkg/models/task_attachment.go index 8ab8f56e6..70708feb4 100644 --- a/pkg/models/task_attachment.go +++ b/pkg/models/task_attachment.go @@ -65,7 +65,7 @@ func (ta *TaskAttachment) NewAttachment(s *xorm.Session, f io.ReadCloser, realna // Add an entry to the db ta.FileID = file.ID - ta.CreatedBy, err = getUserOrLinkShareUser(s, a) + ta.CreatedBy, err = GetUserOrLinkShareUser(s, a) if err != nil { // remove the uploaded file if adding it to the db fails if err2 := file.Delete(); err2 != nil { diff --git a/pkg/models/task_comments.go b/pkg/models/task_comments.go index d213ef884..7d0c73939 100644 --- a/pkg/models/task_comments.go +++ b/pkg/models/task_comments.go @@ -67,7 +67,7 @@ func (tc *TaskComment) Create(s *xorm.Session, a web.Auth) (err error) { return err } - tc.Author, err = getUserOrLinkShareUser(s, a) + tc.Author, err = GetUserOrLinkShareUser(s, a) if err != nil { return err } diff --git a/pkg/models/task_relation.go b/pkg/models/task_relation.go index b0bdd98bb..b0d66536e 100644 --- a/pkg/models/task_relation.go +++ b/pkg/models/task_relation.go @@ -144,7 +144,7 @@ func (rel *TaskRelation) Create(s *xorm.Session, a web.Auth) error { } } - rel.CreatedBy, err = getUserOrLinkShareUser(s, a) + rel.CreatedBy, err = GetUserOrLinkShareUser(s, a) if err != nil { return err } diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 14c6431eb..9c81d5bed 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -817,7 +817,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err return err } - createdBy, err := getUserOrLinkShareUser(s, a) + createdBy, err := GetUserOrLinkShareUser(s, a) if err != nil { return err } diff --git a/pkg/models/users.go b/pkg/models/users.go index 84fd38110..b29bb0c49 100644 --- a/pkg/models/users.go +++ b/pkg/models/users.go @@ -22,8 +22,8 @@ import ( "xorm.io/xorm" ) -// Returns either a user or a link share disguised as a user. -func getUserOrLinkShareUser(s *xorm.Session, a web.Auth) (uu *user.User, err error) { +// GetUserOrLinkShareUser returns either a user or a link share disguised as a user. +func GetUserOrLinkShareUser(s *xorm.Session, a web.Auth) (uu *user.User, err error) { if u, is := a.(*user.User); is { uu, err = user.GetUserByID(s, u.ID) return diff --git a/pkg/routes/api/v1/user_show.go b/pkg/routes/api/v1/user_show.go index 512f0c0b2..2265089b1 100644 --- a/pkg/routes/api/v1/user_show.go +++ b/pkg/routes/api/v1/user_show.go @@ -19,9 +19,11 @@ package v1 import ( "net/http" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/auth" + "code.vikunja.io/api/pkg/db" - user2 "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" ) @@ -38,7 +40,7 @@ import ( // @Failure 500 {object} models.Message "Internal server error." // @Router /user [get] func UserShow(c echo.Context) error { - userInfos, err := user2.GetCurrentUser(c) + a, err := auth.GetAuthFromClaims(c) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Error getting current user.") } @@ -46,14 +48,8 @@ func UserShow(c echo.Context) error { s := db.NewSession() defer s.Close() - user, err := user2.GetUserByID(s, userInfos.ID) + user, err := models.GetUserOrLinkShareUser(s, a) if err != nil { - _ = s.Rollback() - return handler.HandleHTTPError(err, c) - } - - if err := s.Commit(); err != nil { - _ = s.Rollback() return handler.HandleHTTPError(err, c) } From 126f3acdc829a7c8a14c233c71b45c7b7b8295eb Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 7 Apr 2021 18:28:58 +0200 Subject: [PATCH 34/35] Enable searching users by full email or name --- pkg/cmd/user.go | 2 +- pkg/db/fixtures/users.yml | 4 +++ pkg/integrations/user_list_test.go | 6 +--- pkg/migration/20210407170753.go | 44 ++++++++++++++++++++++++ pkg/models/users_list_test.go | 4 +++ pkg/routes/api/v1/user_list.go | 4 +-- pkg/routes/api/v1/user_settings.go | 8 ++++- pkg/routes/api/v1/user_show.go | 21 ++++++++++-- pkg/swagger/docs.go | 12 +++++-- pkg/swagger/swagger.json | 12 +++++-- pkg/swagger/swagger.yaml | 10 ++++-- pkg/user/user.go | 5 +++ pkg/user/user_test.go | 55 +++++++++++++++++++++++++++++- pkg/user/users_list.go | 46 ++++++++++++------------- 14 files changed, 191 insertions(+), 42 deletions(-) create mode 100644 pkg/migration/20210407170753.go diff --git a/pkg/cmd/user.go b/pkg/cmd/user.go index 3837e3aa1..e7c9a295f 100644 --- a/pkg/cmd/user.go +++ b/pkg/cmd/user.go @@ -120,7 +120,7 @@ var userListCmd = &cobra.Command{ s := db.NewSession() defer s.Close() - users, err := user.ListUsers(s, "") + users, err := user.ListAllUsers(s) if err != nil { _ = s.Rollback() log.Fatalf("Error getting users: %s", err) diff --git a/pkg/db/fixtures/users.yml b/pkg/db/fixtures/users.yml index 9c152be64..5f5153e59 100644 --- a/pkg/db/fixtures/users.yml +++ b/pkg/db/fixtures/users.yml @@ -58,6 +58,7 @@ email: 'user7@example.com' is_active: true issuer: local + discoverable_by_email: true updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - id: 8 @@ -86,6 +87,7 @@ created: 2018-12-01 15:13:12 - id: 11 username: 'user11' + name: 'Some one else' password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 email: 'user11@example.com' is_active: true @@ -94,10 +96,12 @@ created: 2018-12-01 15:13:12 - id: 12 username: 'user12' + name: 'Name with spaces' password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 email: 'user12@example.com' is_active: true issuer: local + discoverable_by_name: true updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - id: 13 diff --git a/pkg/integrations/user_list_test.go b/pkg/integrations/user_list_test.go index bdcf140e3..6850fa199 100644 --- a/pkg/integrations/user_list_test.go +++ b/pkg/integrations/user_list_test.go @@ -28,11 +28,7 @@ func TestUserList(t *testing.T) { t.Run("Normal test", func(t *testing.T) { rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserList, &testuser1, "", nil, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `user1`) - assert.Contains(t, rec.Body.String(), `user2`) - assert.Contains(t, rec.Body.String(), `user3`) - assert.Contains(t, rec.Body.String(), `user4`) - assert.Contains(t, rec.Body.String(), `user5`) + assert.Equal(t, "null\n", rec.Body.String()) }) t.Run("Search for user3", func(t *testing.T) { rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserList, &testuser1, "", map[string][]string{"s": {"user3"}}, nil) diff --git a/pkg/migration/20210407170753.go b/pkg/migration/20210407170753.go new file mode 100644 index 000000000..5865f5c78 --- /dev/null +++ b/pkg/migration/20210407170753.go @@ -0,0 +1,44 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type users20210407170753 struct { + DiscoverableByName bool `xorm:"bool default false index" json:"-"` + DiscoverableByEmail bool `xorm:"bool default false index" json:"-"` +} + +func (users20210407170753) TableName() string { + return "users" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20210407170753", + Description: "Add discoverable by email or name columns", + Migrate: func(tx *xorm.Engine) error { + return tx.Sync2(users20210407170753{}) + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/models/users_list_test.go b/pkg/models/users_list_test.go index 7c11b4bc7..5ea9889cd 100644 --- a/pkg/models/users_list_test.go +++ b/pkg/models/users_list_test.go @@ -93,6 +93,7 @@ func TestListUsersFromList(t *testing.T) { IsActive: true, Issuer: "local", EmailRemindersEnabled: true, + DiscoverableByEmail: true, Created: testCreatedTime, Updated: testUpdatedTime, } @@ -129,6 +130,7 @@ func TestListUsersFromList(t *testing.T) { testuser11 := &user.User{ ID: 11, Username: "user11", + Name: "Some one else", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, Issuer: "local", @@ -139,10 +141,12 @@ func TestListUsersFromList(t *testing.T) { testuser12 := &user.User{ ID: 12, Username: "user12", + Name: "Name with spaces", Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", IsActive: true, Issuer: "local", EmailRemindersEnabled: true, + DiscoverableByName: true, Created: testCreatedTime, Updated: testUpdatedTime, } diff --git a/pkg/routes/api/v1/user_list.go b/pkg/routes/api/v1/user_list.go index 1f4d4581e..28bae9ef1 100644 --- a/pkg/routes/api/v1/user_list.go +++ b/pkg/routes/api/v1/user_list.go @@ -31,11 +31,11 @@ import ( // UserList gets all information about a user // @Summary Get users -// @Description Lists all users (without emailadresses). Also possible to search for a specific user. +// @Description Search for a user by its username, name or full email. Name (not username) or email require that the user has enabled this in their settings. // @tags user // @Accept json // @Produce json -// @Param s query string false "Search for a user by its name." +// @Param s query string false "The search criteria." // @Security JWTKeyAuth // @Success 200 {array} user.User "All (found) users." // @Failure 400 {object} web.HTTPError "Something's invalid." diff --git a/pkg/routes/api/v1/user_settings.go b/pkg/routes/api/v1/user_settings.go index 08fc34e47..8c5571961 100644 --- a/pkg/routes/api/v1/user_settings.go +++ b/pkg/routes/api/v1/user_settings.go @@ -38,7 +38,11 @@ type UserSettings struct { // The new name of the current user. Name string `json:"name"` // If enabled, sends email reminders of tasks to the user. - EmailRemindersEnabled bool `xorm:"bool default false" json:"email_reminders_enabled"` + EmailRemindersEnabled bool `json:"email_reminders_enabled"` + // If true, this user can be found by their name or parts of it when searching for it. + DiscoverableByName bool `json:"discoverable_by_name"` + // If true, the user can be found when searching for their exact email. + DiscoverableByEmail bool `json:"discoverable_by_email"` } // GetUserAvatarProvider returns the currently set user avatar @@ -161,6 +165,8 @@ func UpdateGeneralUserSettings(c echo.Context) error { user.Name = us.Name user.EmailRemindersEnabled = us.EmailRemindersEnabled + user.DiscoverableByEmail = us.DiscoverableByEmail + user.DiscoverableByName = us.DiscoverableByName _, err = user2.UpdateUser(s, user) if err != nil { diff --git a/pkg/routes/api/v1/user_show.go b/pkg/routes/api/v1/user_show.go index 2265089b1..266281a42 100644 --- a/pkg/routes/api/v1/user_show.go +++ b/pkg/routes/api/v1/user_show.go @@ -19,6 +19,8 @@ package v1 import ( "net/http" + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/auth" @@ -28,6 +30,11 @@ import ( "github.com/labstack/echo/v4" ) +type userWithSettings struct { + user.User + Settings *UserSettings `json:"settings"` +} + // UserShow gets all informations about the current user // @Summary Get user information // @Description Returns the current user object. @@ -48,10 +55,20 @@ func UserShow(c echo.Context) error { s := db.NewSession() defer s.Close() - user, err := models.GetUserOrLinkShareUser(s, a) + u, err := models.GetUserOrLinkShareUser(s, a) if err != nil { return handler.HandleHTTPError(err, c) } - return c.JSON(http.StatusOK, user) + us := &userWithSettings{ + User: *u, + Settings: &UserSettings{ + Name: u.Name, + EmailRemindersEnabled: u.EmailRemindersEnabled, + DiscoverableByName: u.DiscoverableByName, + DiscoverableByEmail: u.DiscoverableByEmail, + }, + } + + return c.JSON(http.StatusOK, us) } diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 91f3669a5..6f4005a67 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -6976,7 +6976,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Lists all users (without emailadresses). Also possible to search for a specific user.", + "description": "Search for a user by its username, name or full email. Name (not username) or email require that the user has enabled this in their settings.", "consumes": [ "application/json" ], @@ -6990,7 +6990,7 @@ var doc = `{ "parameters": [ { "type": "string", - "description": "Search for a user by its name.", + "description": "The search criteria.", "name": "s", "in": "query" } @@ -8534,6 +8534,14 @@ var doc = `{ "v1.UserSettings": { "type": "object", "properties": { + "discoverable_by_email": { + "description": "If true, the user can be found when searching for their exact email.", + "type": "boolean" + }, + "discoverable_by_name": { + "description": "If true, this user can be found by their name or parts of it when searching for it.", + "type": "boolean" + }, "email_reminders_enabled": { "description": "If enabled, sends email reminders of tasks to the user.", "type": "boolean" diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index d6ab5fe85..f5c42c025 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -6959,7 +6959,7 @@ "JWTKeyAuth": [] } ], - "description": "Lists all users (without emailadresses). Also possible to search for a specific user.", + "description": "Search for a user by its username, name or full email. Name (not username) or email require that the user has enabled this in their settings.", "consumes": [ "application/json" ], @@ -6973,7 +6973,7 @@ "parameters": [ { "type": "string", - "description": "Search for a user by its name.", + "description": "The search criteria.", "name": "s", "in": "query" } @@ -8517,6 +8517,14 @@ "v1.UserSettings": { "type": "object", "properties": { + "discoverable_by_email": { + "description": "If true, the user can be found when searching for their exact email.", + "type": "boolean" + }, + "discoverable_by_name": { + "description": "If true, this user can be found by their name or parts of it when searching for it.", + "type": "boolean" + }, "email_reminders_enabled": { "description": "If enabled, sends email reminders of tasks to the user.", "type": "boolean" diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index cdd914760..26b9da991 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -1079,6 +1079,12 @@ definitions: type: object v1.UserSettings: properties: + discoverable_by_email: + description: If true, the user can be found when searching for their exact email. + type: boolean + discoverable_by_name: + description: If true, this user can be found by their name or parts of it when searching for it. + type: boolean email_reminders_enabled: description: If enabled, sends email reminders of tasks to the user. type: boolean @@ -5662,9 +5668,9 @@ paths: get: consumes: - application/json - description: Lists all users (without emailadresses). Also possible to search for a specific user. + description: Search for a user by its username, name or full email. Name (not username) or email require that the user has enabled this in their settings. parameters: - - description: Search for a user by its name. + - description: The search criteria. in: query name: s type: string diff --git a/pkg/user/user.go b/pkg/user/user.go index bbf397f2e..de7fd8547 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -70,6 +70,9 @@ type User struct { // If enabled, sends email reminders of tasks to the user. EmailRemindersEnabled bool `xorm:"bool default true" json:"-"` + DiscoverableByName bool `xorm:"bool default false index" json:"-"` + DiscoverableByEmail bool `xorm:"bool default false index" json:"-"` + // A timestamp when this task was created. You cannot change this value. Created time.Time `xorm:"created not null" json:"created"` // A timestamp when this task was last updated. You cannot change this value. @@ -366,6 +369,8 @@ func UpdateUser(s *xorm.Session, user *User) (updatedUser *User, err error) { "is_active", "name", "email_reminders_enabled", + "discoverable_by_name", + "discoverable_by_email", ). Update(user) if err != nil { diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go index aef3033ef..495870a52 100644 --- a/pkg/user/user_test.go +++ b/pkg/user/user_test.go @@ -373,10 +373,63 @@ func TestListUsers(t *testing.T) { s := db.NewSession() defer s.Close() - all, err := ListUsers(s, "") + all, err := ListAllUsers(s) assert.NoError(t, err) assert.Len(t, all, 14) }) + t.Run("no search term", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + all, err := ListUsers(s, "") + assert.NoError(t, err) + assert.Len(t, all, 0) + }) + t.Run("not discoverable by email", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + all, err := ListUsers(s, "user1@example.com") + assert.NoError(t, err) + assert.Len(t, all, 0) + db.AssertExists(t, "users", map[string]interface{}{ + "email": "user1@example.com", + }, false) + }) + t.Run("not discoverable by name", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + all, err := ListUsers(s, "one else") + assert.NoError(t, err) + assert.Len(t, all, 0) + db.AssertExists(t, "users", map[string]interface{}{ + "name": "Some one else", + }, false) + }) + t.Run("discoverable by email", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + all, err := ListUsers(s, "user7@example.com") + assert.NoError(t, err) + assert.Len(t, all, 1) + assert.Equal(t, int64(7), all[0].ID) + }) + t.Run("discoverable by partial name", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + all, err := ListUsers(s, "with space") + assert.NoError(t, err) + assert.Len(t, all, 1) + assert.Equal(t, int64(12), all[0].ID) + }) } func TestUserPasswordReset(t *testing.T) { diff --git a/pkg/user/users_list.go b/pkg/user/users_list.go index 50db5f53d..2d72e6a37 100644 --- a/pkg/user/users_list.go +++ b/pkg/user/users_list.go @@ -17,42 +17,40 @@ package user import ( - "strconv" "strings" + "xorm.io/builder" "xorm.io/xorm" - - "code.vikunja.io/api/pkg/log" ) // ListUsers returns a list with all users, filtered by an optional searchstring -func ListUsers(s *xorm.Session, searchterm string) (users []*User, err error) { +func ListUsers(s *xorm.Session, search string) (users []*User, err error) { - vals := strings.Split(searchterm, ",") - ids := []int64{} - for _, val := range vals { - v, err := strconv.ParseInt(val, 10, 64) - if err != nil { - log.Debugf("User search string part '%s' is not a number: %s", val, err) - continue - } - ids = append(ids, v) - } + // Prevent searching for placeholders + search = strings.ReplaceAll(search, "%", "") - if len(ids) > 0 { - err = s. - In("id", ids). - Find(&users) - return - } - - if searchterm == "" { - err = s.Find(&users) + if search == "" || strings.ReplaceAll(search, " ", "") == "" { return } err = s. - Where("username LIKE ?", "%"+searchterm+"%"). + Where(builder.Or( + builder.Like{"username", "%" + search + "%"}, + builder.And( + builder.Eq{"email": search}, + builder.Eq{"discoverable_by_email": true}, + ), + builder.And( + builder.Like{"name", "%" + search + "%"}, + builder.Eq{"discoverable_by_name": true}, + ), + )). Find(&users) return } + +// ListAllUsers returns all users +func ListAllUsers(s *xorm.Session) (users []*User, err error) { + err = s.Find(&users) + return +} From 6a927c07031de7faff907730e5d0a97e9f047ee9 Mon Sep 17 00:00:00 2001 From: renovate Date: Thu, 8 Apr 2021 13:01:29 +0000 Subject: [PATCH 35/35] Update module labstack/echo/v4 to v4.2.2 (#830) Reviewed-on: https://kolaente.dev/vikunja/api/pulls/830 Co-authored-by: renovate Co-committed-by: renovate --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3a3c7c53c..58fe9b7df 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/imdario/mergo v0.3.12 github.com/jgautheron/goconst v1.4.0 // indirect github.com/kr/text v0.2.0 // indirect - github.com/labstack/echo/v4 v4.2.1 + github.com/labstack/echo/v4 v4.2.2 github.com/labstack/gommon v0.3.0 github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef github.com/lib/pq v1.10.0 diff --git a/go.sum b/go.sum index 9c8a72f10..ce7ee0bbd 100644 --- a/go.sum +++ b/go.sum @@ -504,6 +504,8 @@ github.com/labstack/echo/v4 v4.2.0 h1:jkCSsjXmBmapVXF6U4BrSz/cgofWM0CU3Q74wQvXkI github.com/labstack/echo/v4 v4.2.0/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/echo/v4 v4.2.1 h1:LF5Iq7t/jrtUuSutNuiEWtB5eiHfZ5gSe2pcu5exjQw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/echo/v4 v4.2.2 h1:bq2fdZCionY1jck8rzUpQEu2YSmI8QbX6LHrCa60IVs= +github.com/labstack/echo/v4 v4.2.2/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4=