diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index 95ab32185..1bf8f16f1 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -21,6 +21,7 @@ import ( "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/imdario/mergo" + "sort" "time" "xorm.io/builder" ) @@ -53,8 +54,8 @@ type Namespace struct { web.Rights `xorm:"-" json:"-"` } -// PseudoNamespace is a pseudo namespace used to hold shared lists -var PseudoNamespace = Namespace{ +// SharedListsPseudoNamespace is a pseudo namespace used to hold shared lists +var SharedListsPseudoNamespace = Namespace{ ID: -1, Title: "Shared Lists", Description: "Lists of other users shared with you via teams or directly.", @@ -71,6 +72,15 @@ var FavoritesPseudoNamespace = Namespace{ Updated: time.Now(), } +// SavedFiltersPseudoNamespace is a pseudo namespace used to hold saved filters +var SavedFiltersPseudoNamespace = Namespace{ + ID: -3, + Title: "Filters", + Description: "Saved filters.", + Created: time.Now(), + Updated: time.Now(), +} + // TableName makes beautiful table names func (Namespace) TableName() string { return "namespaces" @@ -84,7 +94,7 @@ func (n *Namespace) GetSimpleByID() (err error) { // Get the namesapce with shared lists if n.ID == -1 { - *n = PseudoNamespace + *n = SharedListsPseudoNamespace return } @@ -179,13 +189,18 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r return nil, 0, 0, ErrGenericForbidden{} } + // This map will hold all namespaces and their lists. The key is usually the id of the namespace. + // We're using a map here because it makes a few things like adding lists or removing pseudo namespaces easier. + namespaces := make(map[int64]*NamespaceWithLists) + + ////////////////////////////// + // Lists with their namespaces + doer, err := user.GetFromAuth(a) if err != nil { return nil, 0, 0, err } - all := []*NamespaceWithLists{} - // Adding a 1=1 condition by default here because xorm always needs a condition and cannot handle nil conditions var isArchivedCond builder.Cond = builder.Eq{"1": 1} if !n.IsArchived { @@ -194,26 +209,7 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r ) } - // Create our pseudo namespace with favorite lists - // We want this one at the beginning, which is why we create it here - pseudoFavoriteNamespace := FavoritesPseudoNamespace - pseudoFavoriteNamespace.Owner = doer - all = append(all, &NamespaceWithLists{ - Namespace: pseudoFavoriteNamespace, - Lists: []*List{{}}, - }) - *all[0].Lists[0] = FavoritesPseudoList // Copying the list to be able to modify it later - - // Create our pseudo namespace to hold the shared lists - pseudonamespace := PseudoNamespace - pseudonamespace.Owner = doer - all = append(all, &NamespaceWithLists{ - pseudonamespace, - []*List{}, - }) - limit, start := getLimitFromPageIndex(page, perPage) - query := x.Select("namespaces.*"). Table("namespaces"). Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id"). @@ -228,15 +224,15 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r if limit > 0 { query = query.Limit(limit, start) } - err = query.Find(&all) + err = query.Find(&namespaces) if err != nil { - return all, 0, 0, err + return nil, 0, 0, err } // Make a list of namespace ids var namespaceids []int64 var userIDs []int64 - for _, nsp := range all { + for _, nsp := range namespaces { namespaceids = append(namespaceids, nsp.ID) userIDs = append(userIDs, nsp.OwnerID) } @@ -245,7 +241,7 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r userMap := make(map[int64]*user.User) err = x.In("id", userIDs).Find(&userMap) if err != nil { - return all, 0, 0, err + return nil, 0, 0, err } // Get all lists @@ -258,7 +254,18 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r } err = listQuery.Find(&lists) if err != nil { - return all, 0, 0, err + return nil, 0, 0, err + } + + /////////////// + // Shared Lists + + // Create our pseudo namespace to hold the shared lists + sharedListsPseudonamespace := SharedListsPseudoNamespace + sharedListsPseudonamespace.Owner = doer + namespaces[sharedListsPseudonamespace.ID] = &NamespaceWithLists{ + sharedListsPseudonamespace, + []*List{}, } // Get all lists individually shared with our user (not via a namespace) @@ -287,9 +294,9 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r lists = append(lists, l) } - // Remove the pseudonamespace if we don't have any shared lists + // Remove the sharedListsPseudonamespace if we don't have any shared lists if len(individualLists) == 0 { - all = append(all[:1], all[2:]...) + delete(namespaces, sharedListsPseudonamespace.ID) } // More details for the lists @@ -298,22 +305,23 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r return nil, 0, 0, err } - nMap := make(map[int64]*NamespaceWithLists, len(all)) + ///////////////// + // Favorite lists - // Put objects in our namespace list - for _, n := range all { - - // Users - n.Owner = userMap[n.OwnerID] - - nMap[n.ID] = n + // Create our pseudo namespace with favorite lists + pseudoFavoriteNamespace := FavoritesPseudoNamespace + pseudoFavoriteNamespace.Owner = doer + namespaces[pseudoFavoriteNamespace.ID] = &NamespaceWithLists{ + Namespace: pseudoFavoriteNamespace, + Lists: []*List{{}}, } + *namespaces[pseudoFavoriteNamespace.ID].Lists[0] = FavoritesPseudoList // Copying the list to be able to modify it later for _, list := range lists { if list.IsFavorite { - nMap[pseudoFavoriteNamespace.ID].Lists = append(nMap[pseudoFavoriteNamespace.ID].Lists, list) + namespaces[pseudoFavoriteNamespace.ID].Lists = append(namespaces[pseudoFavoriteNamespace.ID].Lists, list) } - nMap[list.NamespaceID].Lists = append(nMap[list.NamespaceID].Lists, list) + namespaces[list.NamespaceID].Lists = append(namespaces[list.NamespaceID].Lists, list) } // Check if we have any favorites or favorited lists and remove the favorites namespace from the list if not @@ -329,19 +337,24 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r // 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 { + for in, l := range namespaces[pseudoFavoriteNamespace.ID].Lists { if l.ID == FavoritesPseudoList.ID { - nMap[pseudoFavoriteNamespace.ID].Lists = append(nMap[pseudoFavoriteNamespace.ID].Lists[:in], nMap[pseudoFavoriteNamespace.ID].Lists[in+1:]...) + namespaces[pseudoFavoriteNamespace.ID].Lists = append(namespaces[pseudoFavoriteNamespace.ID].Lists[:in], namespaces[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:]...) + if len(namespaces[pseudoFavoriteNamespace.ID].Lists) == 0 { + delete(namespaces, pseudoFavoriteNamespace.ID) } + ///////////////// + // Saved Filters + + // TODO + numberOfTotalItems, err = x. Table("namespaces"). Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id"). @@ -355,9 +368,20 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r Where("namespaces.title LIKE ?", "%"+search+"%"). Count(&NamespaceWithLists{}) if err != nil { - return all, 0, 0, err + return nil, 0, 0, err } + ////////////////////// + // Put it all together + all := make([]*NamespaceWithLists, 0, len(namespaces)) + for _, n := range namespaces { + n.Owner = userMap[n.OwnerID] + all = append(all, n) + } + sort.Slice(all, func(i, j int) bool { + return all[i].ID < all[j].ID + }) + return all, len(all), numberOfTotalItems, nil } diff --git a/pkg/models/saved_filters.go b/pkg/models/saved_filters.go index b2e232909..483f0ef95 100644 --- a/pkg/models/saved_filters.go +++ b/pkg/models/saved_filters.go @@ -86,7 +86,13 @@ func (s *SavedFilter) Create(auth web.Auth) error { } func getSavedFilterSimpleByID(id int64) (s *SavedFilter, err error) { - _, err = x.Where("id = ?", id).Get(s) + exists, err := x.Where("id = ?", id).Get(s) + if err != nil { + return nil, err + } + if !exists { + return nil, ErrSavedFilterDoesNotExist{SavedFilterID: id} + } return } diff --git a/pkg/routes/api/v1/list_by_namespace.go b/pkg/routes/api/v1/list_by_namespace.go index 3142b1d98..a09b3110c 100644 --- a/pkg/routes/api/v1/list_by_namespace.go +++ b/pkg/routes/api/v1/list_by_namespace.go @@ -69,7 +69,7 @@ func getNamespace(c echo.Context) (namespace *models.Namespace, err error) { } if namespaceID == -1 { - namespace = &models.PseudoNamespace + namespace = &models.SharedListsPseudoNamespace return }