From d10e58b37c7b7749f7e71d013a70a48c8381bbd9 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 29 Jun 2020 11:43:48 +0200 Subject: [PATCH 01/17] Add basic struct and methods --- pkg/models/list_duplicate.go | 68 ++++++++++++++++++++++++++++++++++++ pkg/models/list_rights.go | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 pkg/models/list_duplicate.go diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go new file mode 100644 index 000000000..ffdce475b --- /dev/null +++ b/pkg/models/list_duplicate.go @@ -0,0 +1,68 @@ +// 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 models + +import ( + "code.vikunja.io/web" +) + +// ListDuplicate holds everything needed to duplicate a list +type ListDuplicate struct { + // The list id of the list to duplicate + ListID int64 `json:"list_id"` + // The target namespace + NamespaceID int64 `json:"namespace_id"` + + List *List `json:"-"` + + web.Rights `json:"-"` + web.CRUDable `json:"-"` +} + +// CanCreate checks if a user has the right to duplicate a list +func (ld *ListDuplicate) CanCreate(a web.Auth) (canCreate bool, err error) { + // List Exists + user has read access to list + ld.List = &List{ID: ld.ListID} + canRead, err := ld.List.CanRead(a) + if err != nil || !canRead { + return canRead, err + } + + // Namespace exists + user has write access to is (-> can create new lists) + ld.List.NamespaceID = ld.NamespaceID + return ld.List.CanCreate(a) +} + +// Create duplicates a list +func (ld *ListDuplicate) Create(a web.Auth) (err error) { + // Get the list + + // Get all tasks + all task details + // copy stuff + // * List + // * Tasks + // * Files + // * Label Tasks (not the labels) + // * assignees + // * attachments + // * comments + // * relations in that list + // * reminders + + // * rights / shares + // * Generate new link shares if any are available +} diff --git a/pkg/models/list_rights.go b/pkg/models/list_rights.go index b27f5a8e3..2aab2357f 100644 --- a/pkg/models/list_rights.go +++ b/pkg/models/list_rights.go @@ -98,7 +98,7 @@ func (l *List) CanDelete(a web.Auth) (bool, error) { // CanCreate checks if the user can create a list func (l *List) CanCreate(a web.Auth) (bool, error) { - // A user can create a list if he has write access to the namespace + // A user can create a list if they have write access to the namespace n := &Namespace{ID: l.NamespaceID} return n.CanWrite(a) } -- 2.40.1 From 559c2cf0d1f0c270e0e674312a9fa9a3e055c785 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 29 Jun 2020 21:32:42 +0200 Subject: [PATCH 02/17] Add duplicating tasks --- pkg/models/list_duplicate.go | 50 ++++++++++++++++++++++++++++--- pkg/models/list_duplicate_test.go | 43 ++++++++++++++++++++++++++ pkg/models/tasks.go | 10 +++++-- 3 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 pkg/models/list_duplicate_test.go diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index ffdce475b..4bbb42913 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -49,12 +49,52 @@ func (ld *ListDuplicate) CanCreate(a web.Auth) (canCreate bool, err error) { // Create duplicates a list func (ld *ListDuplicate) Create(a web.Auth) (err error) { - // Get the list + + ld.List.ID = 0 + ld.List.Identifier = "" // Reset the identifier to trigger regenerating a new one + // Set the owner to the current user + ld.List.OwnerID = a.GetID() + if err := CreateOrUpdateList(ld.List); err != nil { + return err + } + + // Duplicate kanban buckets + // Old bucket ID as key, new id as value + // Used to map the newly created tasks to their new buckets + bucketMap := make(map[int64]int64) + buckets := []*Bucket{} + err = x.Where("list_id = ?", ld.ListID).Find(&buckets) + if err != nil { + return + } + for _, b := range buckets { + oldID := b.ID + b.ID = 0 + b.ListID = ld.List.ID + if err := b.Create(a); err != nil { + return err + } + bucketMap[oldID] = b.ID + } // Get all tasks + all task details - // copy stuff - // * List - // * Tasks + tasks, _, _, err := getTasksForLists([]*List{{ID: ld.ListID}}, &taskOptions{}) + if err != nil { + return err + } + + // Create + update all tasks + for _, t := range tasks { + t.ListID = ld.List.ID + t.BucketID = bucketMap[t.ID] + t.ID = 0 + t.UID = "" + err := createTask(t, a, false) + if err != nil { + return err + } + } + // * Files // * Label Tasks (not the labels) // * assignees @@ -62,7 +102,9 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { // * comments // * relations in that list // * reminders + // * Background files + unsplash info // * rights / shares // * Generate new link shares if any are available + return } diff --git a/pkg/models/list_duplicate_test.go b/pkg/models/list_duplicate_test.go new file mode 100644 index 000000000..7842d7cda --- /dev/null +++ b/pkg/models/list_duplicate_test.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 models + +import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestListDuplicate(t *testing.T) { + + db.LoadAndAssertFixtures(t) + + u := &user.User{ + ID: 1, + } + + l := &ListDuplicate{ + ListID: 1, + NamespaceID: 1, + } + can, err := l.CanCreate(u) + assert.NoError(t, err) + assert.True(t, can) + err = l.Create(u) + assert.NoError(t, err) +} diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index d63e53a53..e6ecf6be1 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -574,6 +574,10 @@ func checkBucketAndTaskBelongToSameList(fullTask *Task, bucketID int64) (err err // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{id} [put] func (t *Task) Create(a web.Auth) (err error) { + return createTask(t, a, true) +} + +func createTask(t *Task, a web.Auth, updateAssignees bool) (err error) { t.ID = 0 @@ -637,8 +641,10 @@ func (t *Task) Create(a web.Auth) (err error) { } // Update the assignees - if err := t.updateTaskAssignees(t.Assignees); err != nil { - return err + if updateAssignees { + if err := t.updateTaskAssignees(t.Assignees); err != nil { + return err + } } // Update the reminders -- 2.40.1 From 9556bb3918d1d279af68ed443e5ae19244180235 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 29 Jun 2020 22:14:50 +0200 Subject: [PATCH 03/17] Add copying task attachments --- pkg/models/list_duplicate.go | 31 +++++++++++++++++++++++--- pkg/models/task_attachment.go | 42 +++++++++++++++++++++++++++++++++++ pkg/models/tasks.go | 24 +------------------- 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index 4bbb42913..cbc047b5f 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -83,22 +83,47 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { return err } + taskMap := make(map[int64]int64) // Create + update all tasks + oldTaskIDs := make([]int64, len(tasks)) for _, t := range tasks { + oldID := t.ID + t.ID = 0 t.ListID = ld.List.ID t.BucketID = bucketMap[t.ID] - t.ID = 0 t.UID = "" err := createTask(t, a, false) if err != nil { return err } + taskMap[oldID] = t.ID + oldTaskIDs = append(oldTaskIDs, oldID) + } + + // Save all attachments + // We also duplicate all underlying files since they could be modified in one list which would result in + // file changes in the other list which is not something we want. + attachments, err := getTaskAttachmentsByTaskIDs(oldTaskIDs) + if err != nil { + return err + } + + for _, attachment := range attachments { + attachment.TaskID = oldTaskIDs[attachment.TaskID] + if err := attachment.File.LoadFileByID(); err != nil { + return err + } + + err := attachment.NewAttachment(attachment.File.File, attachment.File.Name, attachment.File.Size, a) + if err != nil { + return err + } + + _ = attachment.File.File.Close() } - // * Files // * Label Tasks (not the labels) // * assignees - // * attachments // * comments // * relations in that list // * reminders diff --git a/pkg/models/task_attachment.go b/pkg/models/task_attachment.go index 5b052ebba..ada1aadf6 100644 --- a/pkg/models/task_attachment.go +++ b/pkg/models/task_attachment.go @@ -193,3 +193,45 @@ func (ta *TaskAttachment) Delete() error { } return err } + +func getTaskAttachmentsByTaskIDs(taskIDs []int64) (attachments []*TaskAttachment, err error) { + attachments = []*TaskAttachment{} + err = x. + In("task_id", taskIDs). + Find(&attachments) + if err != nil { + return + } + + fileIDs := []int64{} + userIDs := []int64{} + for _, a := range attachments { + userIDs = append(userIDs, a.CreatedByID) + fileIDs = append(fileIDs, a.FileID) + } + + // Get all files + fs := make(map[int64]*files.File) + err = x.In("id", fileIDs).Find(&fs) + if err != nil { + return + } + + users := make(map[int64]*user.User) + err = x.In("id", userIDs).Find(&users) + if err != nil { + return + } + + // Obfuscate all user emails + for _, u := range users { + u.Email = "" + } + + for _, a := range attachments { + a.CreatedBy = users[a.CreatedByID] + a.File = fs[a.FileID] + } + + return +} diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index e6ecf6be1..a3022a52a 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -18,7 +18,6 @@ package models import ( "code.vikunja.io/api/pkg/config" - "code.vikunja.io/api/pkg/files" "code.vikunja.io/api/pkg/metrics" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" @@ -440,26 +439,7 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (err error) { } // Get task attachments - attachments := []*TaskAttachment{} - err = x. - In("task_id", taskIDs). - Find(&attachments) - if err != nil { - return - } - - fileIDs := []int64{} - for _, a := range attachments { - userIDs = append(userIDs, a.CreatedByID) - fileIDs = append(fileIDs, a.FileID) - } - - // Get all files - fs := make(map[int64]*files.File) - err = x.In("id", fileIDs).Find(&fs) - if err != nil { - return - } + attachments, err := getTaskAttachmentsByTaskIDs(taskIDs) // Get all users of a task // aka the ones who created a task @@ -476,8 +456,6 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (err error) { // Put the users and files in task attachments for _, a := range attachments { - a.CreatedBy = users[a.CreatedByID] - a.File = fs[a.FileID] taskMap[a.TaskID].Attachments = append(taskMap[a.TaskID].Attachments, a) } -- 2.40.1 From 7e4cd207c2cee1372152567e75cd066adebf2870 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 29 Jun 2020 22:20:28 +0200 Subject: [PATCH 04/17] Add copying task task label relations --- pkg/models/list_duplicate.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index cbc047b5f..becd8d391 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -122,7 +122,21 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { _ = attachment.File.File.Close() } - // * Label Tasks (not the labels) + // Copy label tasks (not the labels) + labelTasks := []*LabelTask{} + err = x.In("task_id", oldTaskIDs).Find(&labelTasks) + if err != nil { + return + } + + for _, lt := range labelTasks { + lt.ID = 0 + lt.TaskID = taskMap[lt.TaskID] + if _, err := x.Insert(lt); err != nil { + return err + } + } + // * assignees // * comments // * relations in that list -- 2.40.1 From 28a4944dd386515e9e7d3d1131b79e71e89aac4f Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 29 Jun 2020 22:27:45 +0200 Subject: [PATCH 05/17] Add copying assignees --- pkg/models/list_duplicate.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index becd8d391..ebf23fb95 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -137,7 +137,26 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { } } - // * assignees + // Assignees + // Only copy those assignees who have access to the task + assignees := []*TaskAssginee{} + err = x.In("task_id", oldTaskIDs).Find(&assignees) + if err != nil { + return + } + for _, a := range assignees { + t := &Task{ + ID: taskMap[a.TaskID], + ListID: ld.List.ID, + } + if err := t.addNewAssigneeByID(a.UserID, ld.List); err != nil { + if IsErrUserDoesNotHaveAccessToList(err) { + continue + } + return err + } + } + // * comments // * relations in that list // * reminders -- 2.40.1 From 01d1c8f6e91f44e20d96865ce61269a792641b99 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 29 Jun 2020 22:30:02 +0200 Subject: [PATCH 06/17] Add copying task comments --- pkg/models/list_duplicate.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index ebf23fb95..6c00bebaf 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -157,12 +157,24 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { } } - // * comments - // * relations in that list - // * reminders - // * Background files + unsplash info + // Comments + comments := []*TaskComment{} + err = x.In("task_id", oldTaskIDs).Find(&comments) + if err != nil { + return + } + for _, c := range comments { + c.TaskID = taskMap[c.TaskID] + if _, err := x.Insert(c); err != nil { + return err + } + } - // * rights / shares - // * Generate new link shares if any are available + // Relations in that list + // Reminders + // Background files + unsplash info + + // Rights / Shares + // Generate new link shares if any are available return } -- 2.40.1 From fac0046f1247af35fb4eae8c4bc707f4fe72a4a8 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 29 Jun 2020 22:35:28 +0200 Subject: [PATCH 07/17] Add copying task relations --- pkg/models/list_duplicate.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index 6c00bebaf..54cdf9820 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -84,7 +84,7 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { } taskMap := make(map[int64]int64) - // Create + update all tasks + // Create + update all tasks (includes reminders) oldTaskIDs := make([]int64, len(tasks)) for _, t := range tasks { oldID := t.ID @@ -171,7 +171,25 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { } // Relations in that list - // Reminders + // Low-Effort: Only copy those relations which are between tasks in the same list + // because we can do that without a lot of hassle + relations := []*TaskRelation{} + err = x.In("task_id", oldTaskIDs).Find(relations) + if err != nil { + return + } + for _, r := range relations { + otherTaskID, exists := taskMap[r.OtherTaskID] + if !exists { + continue + } + r.OtherTaskID = otherTaskID + r.TaskID = taskMap[r.TaskID] + if _, err := x.Insert(r); err != nil { + return err + } + } + // Background files + unsplash info // Rights / Shares -- 2.40.1 From 2089fd3da34acb3a4a13f416533de8bfaff05e59 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 30 Jun 2020 11:48:02 +0200 Subject: [PATCH 08/17] Add copying list backgrounds --- pkg/models/list_duplicate.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index 54cdf9820..0be984aaf 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/files" "code.vikunja.io/web" ) @@ -191,6 +192,39 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { } // Background files + unsplash info + if ld.List.BackgroundFileID != 0 { + f := &files.File{ID: ld.List.BackgroundFileID} + if err := f.LoadFileMetaByID(); err != nil { + return err + } + if err := f.LoadFileByID(); err != nil { + return err + } + defer f.File.Close() + + file, err := files.Create(f.File, f.Name, f.Size, a) + if err != nil { + return + } + + // Get unsplash info if applicable + up, err := GetUnsplashPhotoByFileID(ld.List.BackgroundFileID) + if err != nil && files.IsErrFileIsNotUnsplashFile(err) { + return err + } + if up != nil { + up.ID = 0 + up.FileID = file.ID + if err := up.Save(); err != nil { + return err + } + } + + ld.List.BackgroundFileID = file.ID + if err := CreateOrUpdateList(ld.List); err != nil { + return err + } + } // Rights / Shares // Generate new link shares if any are available -- 2.40.1 From 0bfbc1082bad89875d04ffa8918a7343c8a132da Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 30 Jun 2020 16:55:36 +0200 Subject: [PATCH 09/17] Add copying list shares --- pkg/models/list_duplicate.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index 0be984aaf..a82776d7c 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -227,6 +227,32 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { } // Rights / Shares + // To keep it simple(r) we will only copy rights which are directly used with the list, no namespace changes. + users := []*ListUser{} + err = x.Where("list_id = ?", ld.ListID).Find(&users) + if err != nil { + return + } + for _, u := range users { + u.ID = 0 + u.ListID = ld.List.ID + if _, err := x.Insert(u); err != nil { + return err + } + } + teams := []*TeamList{} + err = x.Where("list_id = ?", ld.ListID).Find(&teams) + if err != nil { + return + } + for _, t := range teams { + t.ID = 0 + t.ListID = ld.List.ID + if _, err := x.Insert(t); err != nil { + return err + } + } + // Generate new link shares if any are available return } -- 2.40.1 From babce6a327f05da9a01507b9e508c459892fe0ad Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 30 Jun 2020 16:57:42 +0200 Subject: [PATCH 10/17] Add copying link shares --- pkg/models/list_duplicate.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index a82776d7c..8ee072a56 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/utils" "code.vikunja.io/web" ) @@ -254,5 +255,19 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { } // Generate new link shares if any are available + linkShares := []*LinkSharing{} + err = x.Where("list_id = ?", ld.ListID).Find(&linkShares) + if err != nil { + return + } + for _, share := range linkShares { + share.ID = 0 + share.ListID = ld.List.ID + share.Hash = utils.MakeRandomString(40) + if _, err := x.Insert(share); err != nil { + return err + } + } + return } -- 2.40.1 From bfc5577e9ebb505e4c57019544c7181e853dbf7d Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 30 Jun 2020 17:05:51 +0200 Subject: [PATCH 11/17] Make duplicating actually work --- pkg/models/list_duplicate.go | 18 +++++++++++++++--- pkg/models/list_duplicate_test.go | 2 ++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index 8ee072a56..a47825860 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -111,7 +111,15 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { } for _, attachment := range attachments { + attachment.ID = 0 attachment.TaskID = oldTaskIDs[attachment.TaskID] + attachment.File = &files.File{ID: attachment.FileID} + if err := attachment.File.LoadFileMetaByID(); err != nil { + if files.IsErrFileDoesNotExist(err) { + continue + } + return err + } if err := attachment.File.LoadFileByID(); err != nil { return err } @@ -121,7 +129,9 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { return err } - _ = attachment.File.File.Close() + if attachment.File.File != nil { + _ = attachment.File.File.Close() + } } // Copy label tasks (not the labels) @@ -166,6 +176,7 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { return } for _, c := range comments { + c.ID = 0 c.TaskID = taskMap[c.TaskID] if _, err := x.Insert(c); err != nil { return err @@ -176,7 +187,7 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { // Low-Effort: Only copy those relations which are between tasks in the same list // because we can do that without a lot of hassle relations := []*TaskRelation{} - err = x.In("task_id", oldTaskIDs).Find(relations) + err = x.In("task_id", oldTaskIDs).Find(&relations) if err != nil { return } @@ -185,6 +196,7 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { if !exists { continue } + r.ID = 0 r.OtherTaskID = otherTaskID r.TaskID = taskMap[r.TaskID] if _, err := x.Insert(r); err != nil { @@ -205,7 +217,7 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { file, err := files.Create(f.File, f.Name, f.Size, a) if err != nil { - return + return err } // Get unsplash info if applicable diff --git a/pkg/models/list_duplicate_test.go b/pkg/models/list_duplicate_test.go index 7842d7cda..04d8dfaf2 100644 --- a/pkg/models/list_duplicate_test.go +++ b/pkg/models/list_duplicate_test.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/files" "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "testing" @@ -26,6 +27,7 @@ import ( func TestListDuplicate(t *testing.T) { db.LoadAndAssertFixtures(t) + files.InitTestFileFixtures(t) u := &user.User{ ID: 1, -- 2.40.1 From 23f67eb435d41c79aa3d5693afb129f4b610c1dd Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 30 Jun 2020 17:07:20 +0200 Subject: [PATCH 12/17] Add comment about test --- pkg/models/list_duplicate_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/models/list_duplicate_test.go b/pkg/models/list_duplicate_test.go index 04d8dfaf2..90176b127 100644 --- a/pkg/models/list_duplicate_test.go +++ b/pkg/models/list_duplicate_test.go @@ -42,4 +42,6 @@ func TestListDuplicate(t *testing.T) { assert.True(t, can) err = l.Create(u) assert.NoError(t, err) + // To make this test 100% useful, it would need to assert a lot more stuff, but it is good enough for now. + // Also, we're lacking utility functions to do all needed assertions. } -- 2.40.1 From 68aa26adadc82525055428a9b7c8869f652332aa Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 30 Jun 2020 17:15:06 +0200 Subject: [PATCH 13/17] "Fix" gocyclo check --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f9898503a..ea159b1ac 100644 --- a/Makefile +++ b/Makefile @@ -219,7 +219,7 @@ gocyclo-check: go get -u github.com/fzipp/gocyclo; \ go install $(GOFLAGS) github.com/fzipp/gocyclo; \ fi - for S in $(GOFILES); do gocyclo -over 33 $$S || exit 1; done; + for S in $(GOFILES); do gocyclo -over 47 $$S || exit 1; done; .PHONY: static-check static-check: -- 2.40.1 From 6b4a26b9f01b99a536167676687ec55ba43a151d Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 30 Jun 2020 17:21:33 +0200 Subject: [PATCH 14/17] Add swagger docs --- pkg/models/list_duplicate.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index a47825860..da1560b5b 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -25,11 +25,12 @@ import ( // ListDuplicate holds everything needed to duplicate a list type ListDuplicate struct { // The list id of the list to duplicate - ListID int64 `json:"list_id"` - // The target namespace - NamespaceID int64 `json:"namespace_id"` + ListID int64 `json:"-" param:"list"` + // The target namespace ID + NamespaceID int64 `json:"namespace_id,omitempty"` - List *List `json:"-"` + // The copied list + List *List `json:",omitempty"` web.Rights `json:"-"` web.CRUDable `json:"-"` @@ -50,6 +51,19 @@ func (ld *ListDuplicate) CanCreate(a web.Auth) (canCreate bool, err error) { } // Create duplicates a list +// @Summary Duplicate an existing list +// @Description Copies the list, tasks, files, kanban data, assignees, comments, attachments, lables, relations, backgrounds, user/team rights and link shares from one list to a new namespace. The user needs read access in the list and write access in the namespace of the new list. +// @tags list +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param listID path int true "The list ID to duplicate" +// @Param list body models.ListDuplicate true "The target namespace which should hold the copied list." +// @Success 200 {object} models.ListDuplicate "The created list." +// @Failure 400 {object} web.HTTPError "Invalid list duplicate object provided." +// @Failure 403 {object} web.HTTPError "The user does not have access to the list or namespace" +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{listID}/duplicate [put] func (ld *ListDuplicate) Create(a web.Auth) (err error) { ld.List.ID = 0 -- 2.40.1 From 8856d952756650bfa131aeba70d57608ed114f1b Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 30 Jun 2020 17:23:11 +0200 Subject: [PATCH 15/17] Add api endpoint --- pkg/routes/routes.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 57e4abc63..9a0b3a3dd 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -304,6 +304,13 @@ func registerAPIRoutes(a *echo.Group) { a.POST("/lists/:list/buckets/:bucket", kanbanBucketHandler.UpdateWeb) a.DELETE("/lists/:list/buckets/:bucket", kanbanBucketHandler.DeleteWeb) + listDuplicateHandler := &handler.WebHandler{ + EmptyStruct: func() handler.CObject { + return &models.ListDuplicate{} + }, + } + a.PUT("/lists/:list/duplicate", listDuplicateHandler.CreateWeb) + taskHandler := &handler.WebHandler{ EmptyStruct: func() handler.CObject { return &models.Task{} -- 2.40.1 From b6d95bfb63823119b086575e23bdec5935dd079d Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 30 Jun 2020 22:35:42 +0200 Subject: [PATCH 16/17] Fix list id param not working --- pkg/models/list_duplicate.go | 2 +- pkg/routes/routes.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index da1560b5b..90e9da756 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -25,7 +25,7 @@ import ( // ListDuplicate holds everything needed to duplicate a list type ListDuplicate struct { // The list id of the list to duplicate - ListID int64 `json:"-" param:"list"` + ListID int64 `json:"-" param:"listid"` // The target namespace ID NamespaceID int64 `json:"namespace_id,omitempty"` diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 9a0b3a3dd..f5d4205e3 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -309,7 +309,7 @@ func registerAPIRoutes(a *echo.Group) { return &models.ListDuplicate{} }, } - a.PUT("/lists/:list/duplicate", listDuplicateHandler.CreateWeb) + a.PUT("/lists/:listid/duplicate", listDuplicateHandler.CreateWeb) taskHandler := &handler.WebHandler{ EmptyStruct: func() handler.CObject { -- 2.40.1 From 473ca3999efb4a1571fab9dcd1638ca08774d7af Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 30 Jun 2020 22:37:15 +0200 Subject: [PATCH 17/17] Fix buckets not being duplicated correctly --- pkg/models/list_duplicate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/models/list_duplicate.go b/pkg/models/list_duplicate.go index 90e9da756..e54cef715 100644 --- a/pkg/models/list_duplicate.go +++ b/pkg/models/list_duplicate.go @@ -106,7 +106,7 @@ func (ld *ListDuplicate) Create(a web.Auth) (err error) { oldID := t.ID t.ID = 0 t.ListID = ld.List.ID - t.BucketID = bucketMap[t.ID] + t.BucketID = bucketMap[t.BucketID] t.UID = "" err := createTask(t, a, false) if err != nil { -- 2.40.1