diff --git a/pkg/db/fixtures/list.yml b/pkg/db/fixtures/list.yml index 0e89157869..20ea7b6eec 100644 --- a/pkg/db/fixtures/list.yml +++ b/pkg/db/fixtures/list.yml @@ -199,3 +199,13 @@ is_archived: 1 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 +- + id: 23 + title: Test23 + description: Lorem Ipsum + identifier: test23 + owner_id: 12 + namespace_id: 17 + is_favorite: true + updated: 2018-12-02 15:13:12 + created: 2018-12-01 15:13:12 diff --git a/pkg/db/fixtures/namespaces.yml b/pkg/db/fixtures/namespaces.yml index 34c8d7b937..41f996e033 100644 --- a/pkg/db/fixtures/namespaces.yml +++ b/pkg/db/fixtures/namespaces.yml @@ -82,3 +82,9 @@ is_archived: 1 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 +- id: 17 + title: testnamespace17 + description: Lorem Ipsum + owner_id: 12 + updated: 2018-12-02 15:13:12 + created: 2018-12-01 15:13:12 diff --git a/pkg/migration/20200905232458.go b/pkg/migration/20200905232458.go new file mode 100644 index 0000000000..a11d0a8938 --- /dev/null +++ b/pkg/migration/20200905232458.go @@ -0,0 +1,43 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2020 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package migration + +import ( + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type list20200905232458 struct { + IsFavorite bool `xorm:"default false" json:"is_favorite"` +} + +func (list20200905232458) TableName() string { + return "list" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20200905232458", + Description: "Add is_favorite field to lists", + Migrate: func(tx *xorm.Engine) error { + return tx.Sync2(list20200905232458{}) + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/models/list.go b/pkg/models/list.go index 8499f308cb..e18f1bc438 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -57,6 +57,9 @@ type List struct { // Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background BackgroundInformation interface{} `xorm:"-" json:"background_information"` + // True if a list is a favorite. Favorite lists show up in a separate namespace. + IsFavorite bool `xorm:"default false" json:"is_favorite"` + // A timestamp when this list was created. You cannot change this value. Created time.Time `xorm:"created not null" json:"created"` // A timestamp when this list was last updated. You cannot change this value. @@ -80,6 +83,7 @@ var FavoritesPseudoList = List{ Title: "Favorites", Description: "This list has all tasks marked as favorites.", NamespaceID: FavoritesPseudoNamespace.ID, + IsFavorite: true, Created: time.Now(), Updated: time.Now(), } @@ -464,7 +468,7 @@ func GenerateListIdentifier(l *List, sess *xorm.Engine) (err error) { func CreateOrUpdateList(list *List) (err error) { // Check if the namespace exists - if list.NamespaceID != 0 { + if list.NamespaceID != 0 && list.NamespaceID != FavoritesPseudoNamespace.ID { _, err = GetNamespaceByID(list.NamespaceID) if err != nil { return err @@ -503,6 +507,7 @@ func CreateOrUpdateList(list *List) (err error) { "is_archived", "identifier", "hex_color", + "is_favorite", } if list.Description != "" { colsToUpdate = append(colsToUpdate, "description") diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index 83e030a422..68decb34de 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -292,19 +292,6 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r all = append(all[:1], all[2:]...) } - // Check if we have any favorites and remove the favorites namespace from the list if not - favoriteCount, err := x. - Join("INNER", "list", "tasks.list_id = list.id"). - Join("INNER", "namespaces", "list.namespace_id = namespaces.id"). - Where(builder.And(builder.Eq{"is_favorite": true}, builder.In("namespaces.id", namespaceids))). - Count(&Task{}) - if err != nil { - return nil, 0, 0, err - } - if favoriteCount == 0 { - all = append(all[:0], all[1:]...) - } - // More details for the lists err = AddListDetails(lists) if err != nil { @@ -323,9 +310,38 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r } for _, list := range lists { + if list.IsFavorite { + nMap[pseudoFavoriteNamespace.ID].Lists = append(nMap[pseudoFavoriteNamespace.ID].Lists, list) + } nMap[list.NamespaceID].Lists = append(nMap[list.NamespaceID].Lists, list) } + // Check if we have any favorites or favorited lists and remove the favorites namespace from the list if not + var favoriteCount int64 + favoriteCount, err = x. + Join("INNER", "list", "tasks.list_id = list.id"). + Join("INNER", "namespaces", "list.namespace_id = namespaces.id"). + Where(builder.And(builder.Eq{"tasks.is_favorite": true}, builder.In("namespaces.id", namespaceids))). + Count(&Task{}) + if err != nil { + return nil, 0, 0, err + } + + // If we don't have any favorites in the favorites pseudo list, remove that pseudo list from the namespace + if favoriteCount == 0 { + for in, l := range nMap[pseudoFavoriteNamespace.ID].Lists { + if l.ID == FavoritesPseudoList.ID { + nMap[pseudoFavoriteNamespace.ID].Lists = append(nMap[pseudoFavoriteNamespace.ID].Lists[:in], nMap[pseudoFavoriteNamespace.ID].Lists[in+1:]...) + break + } + } + } + + // If we don't have any favorites in the namespace, remove it + if len(nMap[pseudoFavoriteNamespace.ID].Lists) == 0 { + all = append(all[:0], all[1:]...) + } + numberOfTotalItems, err = x. Table("namespaces"). Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id"). diff --git a/pkg/models/namespace_test.go b/pkg/models/namespace_test.go index 1c23cf7296..7afcac5784 100644 --- a/pkg/models/namespace_test.go +++ b/pkg/models/namespace_test.go @@ -136,6 +136,8 @@ func TestNamespace_Delete(t *testing.T) { func TestNamespace_ReadAll(t *testing.T) { user1 := &user.User{ID: 1} + user11 := &user.User{ID: 11} + user12 := &user.User{ID: 12} t.Run("normal", func(t *testing.T) { n := &Namespace{} @@ -166,4 +168,21 @@ func TestNamespace_ReadAll(t *testing.T) { assert.Equal(t, int64(-2), namespaces[0].ID) // The first one should be the one with favorites assert.Equal(t, int64(-1), namespaces[1].ID) // The second one should be the one with the shared namespaces }) + t.Run("no favorites", func(t *testing.T) { + n := &Namespace{} + nn, _, _, err := n.ReadAll(user11, "", 1, -1) + namespaces := nn.([]*NamespaceWithLists) + assert.NoError(t, err) + // Assert the first namespace is not the favorites namespace + assert.NotEqual(t, FavoritesPseudoNamespace.ID, namespaces[0].ID) + }) + t.Run("no favorite tasks but namespace", func(t *testing.T) { + n := &Namespace{} + nn, _, _, err := n.ReadAll(user12, "", 1, -1) + namespaces := nn.([]*NamespaceWithLists) + assert.NoError(t, err) + // Assert the first namespace is the favorites namespace and contains lists + assert.Equal(t, FavoritesPseudoNamespace.ID, namespaces[0].ID) + assert.NotEqual(t, 0, namespaces[0].Lists) + }) } diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 65b1a11a0f..75c69b7fff 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -6545,6 +6545,10 @@ var doc = `{ "description": "Whether or not a list is archived.", "type": "boolean" }, + "is_favorite": { + "description": "True if a list is a favorite. Favorite lists show up in a separate namespace.", + "type": "boolean" + }, "namespace_id": { "type": "integer" }, diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index 71101f8e74..14bebf0795 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -6528,6 +6528,10 @@ "description": "Whether or not a list is archived.", "type": "boolean" }, + "is_favorite": { + "description": "True if a list is a favorite. Favorite lists show up in a separate namespace.", + "type": "boolean" + }, "namespace_id": { "type": "integer" }, diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index c3104072d8..e7c17a3013 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -288,6 +288,9 @@ definitions: is_archived: description: Whether or not a list is archived. type: boolean + is_favorite: + description: True if a list is a favorite. Favorite lists show up in a separate namespace. + type: boolean namespace_id: type: integer owner: