Favorite lists (#654)
Add tests Remove the favorites pseudo namespace if there are no lists or tasks favorited Regenerate swagger docs Fix favorite lists not being updated because of nonexisting users (the favorite list does not have one) Make the pseudo favorites list always favorited Add favorited lists to the favorites pseudo namespace Add favorite field to list Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/654
This commit is contained in:
parent
e5559137dd
commit
6bdddd462a
9 changed files with 124 additions and 14 deletions
|
@ -199,3 +199,13 @@
|
||||||
is_archived: 1
|
is_archived: 1
|
||||||
updated: 2018-12-02 15:13:12
|
updated: 2018-12-02 15:13:12
|
||||||
created: 2018-12-01 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
|
||||||
|
|
|
@ -82,3 +82,9 @@
|
||||||
is_archived: 1
|
is_archived: 1
|
||||||
updated: 2018-12-02 15:13:12
|
updated: 2018-12-02 15:13:12
|
||||||
created: 2018-12-01 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
|
||||||
|
|
43
pkg/migration/20200905232458.go
Normal file
43
pkg/migration/20200905232458.go
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -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
|
// 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"`
|
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.
|
// A timestamp when this list was created. You cannot change this value.
|
||||||
Created time.Time `xorm:"created not null" json:"created"`
|
Created time.Time `xorm:"created not null" json:"created"`
|
||||||
// A timestamp when this list was last updated. You cannot change this value.
|
// A timestamp when this list was last updated. You cannot change this value.
|
||||||
|
@ -80,6 +83,7 @@ var FavoritesPseudoList = List{
|
||||||
Title: "Favorites",
|
Title: "Favorites",
|
||||||
Description: "This list has all tasks marked as favorites.",
|
Description: "This list has all tasks marked as favorites.",
|
||||||
NamespaceID: FavoritesPseudoNamespace.ID,
|
NamespaceID: FavoritesPseudoNamespace.ID,
|
||||||
|
IsFavorite: true,
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
Updated: time.Now(),
|
Updated: time.Now(),
|
||||||
}
|
}
|
||||||
|
@ -464,7 +468,7 @@ func GenerateListIdentifier(l *List, sess *xorm.Engine) (err error) {
|
||||||
func CreateOrUpdateList(list *List) (err error) {
|
func CreateOrUpdateList(list *List) (err error) {
|
||||||
|
|
||||||
// Check if the namespace exists
|
// Check if the namespace exists
|
||||||
if list.NamespaceID != 0 {
|
if list.NamespaceID != 0 && list.NamespaceID != FavoritesPseudoNamespace.ID {
|
||||||
_, err = GetNamespaceByID(list.NamespaceID)
|
_, err = GetNamespaceByID(list.NamespaceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -503,6 +507,7 @@ func CreateOrUpdateList(list *List) (err error) {
|
||||||
"is_archived",
|
"is_archived",
|
||||||
"identifier",
|
"identifier",
|
||||||
"hex_color",
|
"hex_color",
|
||||||
|
"is_favorite",
|
||||||
}
|
}
|
||||||
if list.Description != "" {
|
if list.Description != "" {
|
||||||
colsToUpdate = append(colsToUpdate, "description")
|
colsToUpdate = append(colsToUpdate, "description")
|
||||||
|
|
|
@ -292,19 +292,6 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r
|
||||||
all = append(all[:1], all[2:]...)
|
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
|
// More details for the lists
|
||||||
err = AddListDetails(lists)
|
err = AddListDetails(lists)
|
||||||
if err != nil {
|
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 {
|
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)
|
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.
|
numberOfTotalItems, err = x.
|
||||||
Table("namespaces").
|
Table("namespaces").
|
||||||
Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id").
|
Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id").
|
||||||
|
|
|
@ -136,6 +136,8 @@ func TestNamespace_Delete(t *testing.T) {
|
||||||
|
|
||||||
func TestNamespace_ReadAll(t *testing.T) {
|
func TestNamespace_ReadAll(t *testing.T) {
|
||||||
user1 := &user.User{ID: 1}
|
user1 := &user.User{ID: 1}
|
||||||
|
user11 := &user.User{ID: 11}
|
||||||
|
user12 := &user.User{ID: 12}
|
||||||
|
|
||||||
t.Run("normal", func(t *testing.T) {
|
t.Run("normal", func(t *testing.T) {
|
||||||
n := &Namespace{}
|
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(-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
|
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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6545,6 +6545,10 @@ var doc = `{
|
||||||
"description": "Whether or not a list is archived.",
|
"description": "Whether or not a list is archived.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"is_favorite": {
|
||||||
|
"description": "True if a list is a favorite. Favorite lists show up in a separate namespace.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"namespace_id": {
|
"namespace_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
|
|
@ -6528,6 +6528,10 @@
|
||||||
"description": "Whether or not a list is archived.",
|
"description": "Whether or not a list is archived.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"is_favorite": {
|
||||||
|
"description": "True if a list is a favorite. Favorite lists show up in a separate namespace.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"namespace_id": {
|
"namespace_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
|
|
@ -288,6 +288,9 @@ definitions:
|
||||||
is_archived:
|
is_archived:
|
||||||
description: Whether or not a list is archived.
|
description: Whether or not a list is archived.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
is_favorite:
|
||||||
|
description: True if a list is a favorite. Favorite lists show up in a separate namespace.
|
||||||
|
type: boolean
|
||||||
namespace_id:
|
namespace_id:
|
||||||
type: integer
|
type: integer
|
||||||
owner:
|
owner:
|
||||||
|
|
Loading…
Reference in a new issue