diff --git a/pkg/integrations/list_test.go b/pkg/integrations/list_test.go index 400a11229..7b0e50f63 100644 --- a/pkg/integrations/list_test.go +++ b/pkg/integrations/list_test.go @@ -42,8 +42,8 @@ func TestList(t *testing.T) { assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_list assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace assert.NotContains(t, rec.Body.String(), `Test5`) - assert.NotContains(t, rec.Body.String(), `Test21`) - assert.NotContains(t, rec.Body.String(), `Test22`) + assert.NotContains(t, rec.Body.String(), `Test21`) // Archived through namespace + assert.NotContains(t, rec.Body.String(), `Test22`) // Archived directly }) t.Run("Search", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"Test1"}}, nil) @@ -54,6 +54,17 @@ func TestList(t *testing.T) { assert.NotContains(t, rec.Body.String(), `Test4`) assert.NotContains(t, rec.Body.String(), `Test5`) }) + t.Run("Normal with archived lists", func(t *testing.T) { + rec, err := testHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil) + assert.NoError(t, err) + assert.Contains(t, rec.Body.String(), `Test1`) + assert.NotContains(t, rec.Body.String(), `Test2"`) + assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_list + assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace + assert.NotContains(t, rec.Body.String(), `Test5`) + assert.Contains(t, rec.Body.String(), `Test21`) // Archived through namespace + assert.Contains(t, rec.Body.String(), `Test22`) // Archived directly + }) }) t.Run("ReadOne", func(t *testing.T) { t.Run("Normal", func(t *testing.T) { diff --git a/pkg/models/label.go b/pkg/models/label.go index bfdcd2c77..fab3280d3 100644 --- a/pkg/models/label.go +++ b/pkg/models/label.go @@ -203,7 +203,10 @@ func getLabelByIDSimple(labelID int64) (*Label, error) { func getUserTaskIDs(u *user.User) (taskIDs []int64, err error) { // Get all lists - lists, _, _, err := getRawListsForUser("", u, -1, 0) + lists, _, _, err := getRawListsForUser(&listOptions{ + user: u, + page: -1, + }) if err != nil { return nil, err } diff --git a/pkg/models/list.go b/pkg/models/list.go index bb46f8922..3ff82c3fe 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -21,6 +21,7 @@ import ( "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" + "xorm.io/builder" ) // List represents a list of tasks @@ -44,7 +45,7 @@ type List struct { Tasks []*Task `xorm:"-" json:"-"` // Whether or not a list is archived. - IsArchived bool `xorm:"not null default false" json:"is_archived"` + IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"` // A timestamp when this list was created. You cannot change this value. Created timeutil.TimeStamp `xorm:"created not null" json:"created"` @@ -97,6 +98,7 @@ func GetListsByNamespaceID(nID int64, doer *user.User) (lists []*List, err error // @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search lists by title." +// @Param is_archived query bool false "If true, also returns all archived lists." // @Security JWTKeyAuth // @Success 200 {array} models.List "The lists" // @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" @@ -116,7 +118,13 @@ func (l *List) ReadAll(a web.Auth, search string, page int, perPage int) (result return lists, 0, 0, err } - lists, resultCount, totalItems, err := getRawListsForUser(search, &user.User{ID: a.GetID()}, page, perPage) + lists, resultCount, totalItems, err := getRawListsForUser(&listOptions{ + search: search, + user: &user.User{ID: a.GetID()}, + page: page, + perPage: perPage, + isArchived: l.IsArchived, + }) if err != nil { return nil, 0, 0, err } @@ -188,13 +196,30 @@ func GetListSimplByTaskID(taskID int64) (l *List, err error) { return &list, nil } +type listOptions struct { + search string + user *user.User + page int + perPage int + isArchived bool +} + // Gets the lists only, without any tasks or so -func getRawListsForUser(search string, u *user.User, page int, perPage int) (lists []*List, resultCount int, totalItems int64, err error) { - fullUser, err := user.GetUserByID(u.ID) +func getRawListsForUser(opts *listOptions) (lists []*List, resultCount int, totalItems int64, err error) { + fullUser, err := user.GetUserByID(opts.user.ID) if err != nil { return nil, 0, 0, err } + // 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 !opts.isArchived { + isArchivedCond = builder.And( + builder.Eq{"l.is_archived": false}, + builder.Eq{"n.is_archived": false}, + ) + } + // 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 err = x.Select("l.*"). @@ -213,10 +238,9 @@ func getRawListsForUser(search string, u *user.User, page int, perPage int) (lis Or("ul.user_id = ?", fullUser.ID). Or("un.user_id = ?", fullUser.ID). GroupBy("l.id"). - Limit(getLimitFromPageIndex(page, perPage)). - Where("l.title LIKE ?", "%"+search+"%"). - Where("l.is_archived = false"). - Where("n.is_archived = false"). + Limit(getLimitFromPageIndex(opts.page, opts.perPage)). + Where("l.title LIKE ?", "%"+opts.search+"%"). + Where(isArchivedCond). Find(&lists) if err != nil { return nil, 0, 0, err @@ -238,10 +262,9 @@ func getRawListsForUser(search string, u *user.User, page int, perPage int) (lis Or("ul.user_id = ?", fullUser.ID). Or("un.user_id = ?", fullUser.ID). GroupBy("l.id"). - Limit(getLimitFromPageIndex(page, perPage)). - Where("l.title LIKE ?", "%"+search+"%"). - Where("l.is_archived = false"). - Where("n.is_archived = false"). + Limit(getLimitFromPageIndex(opts.page, opts.perPage)). + Where("l.title LIKE ?", "%"+opts.search+"%"). + Where(isArchivedCond). Count(&List{}) return lists, len(lists), totalItems, err } diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index 30955053a..69ba194ba 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -109,7 +109,10 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i // If the list ID is not set, we get all tasks for the user. // This allows to use this function in Task.ReadAll with a possibility to deprecate the latter at some point. if tf.ListID == 0 { - tf.Lists, _, _, err = getRawListsForUser("", &user.User{ID: a.GetID()}, -1, 0) + tf.Lists, _, _, err = getRawListsForUser(&listOptions{ + user: &user.User{ID: a.GetID()}, + page: -1, + }) if err != nil { return nil, 0, 0, err }