// 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) { 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 tasks, _, _, err := getTasksForLists([]*List{{ID: ld.ListID}}, &taskOptions{}) if err != nil { 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.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() } // 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 // * reminders // * Background files + unsplash info // * rights / shares // * Generate new link shares if any are available return }