diff --git a/config.yml.sample b/config.yml.sample index 6c9031b14f..6b3225b941 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -191,21 +191,6 @@ files: maxsize: 20MB migration: - # These are the settings for the wunderlist migrator - wunderlist: - # Wheter to enable the wunderlist migrator or not - enable: false - # The client id, required for making requests to the wunderlist api - # You need to register your vikunja instance at https://developer.wunderlist.com/apps/new to get this - clientid: - # The client secret, also required for making requests to the wunderlist api - clientsecret: - # The url where clients are redirected after they authorized Vikunja to access their wunderlist stuff. - # This needs to match the url you entered when registering your Vikunja instance at wunderlist. - # This is usually the frontend url where the frontend then makes a request to /migration/wunderlist/migrate - # with the code obtained from the wunderlist api. - # Note that the vikunja frontend expects this to be /migrate/wunderlist - redirecturl: todoist: # Wheter to enable the todoist migrator or not enable: false diff --git a/docs/content/doc/setup/config.md b/docs/content/doc/setup/config.md index 5825ca80e8..da62f45788 100644 --- a/docs/content/doc/setup/config.md +++ b/docs/content/doc/setup/config.md @@ -969,17 +969,6 @@ Environment path: `VIKUNJA_FILES_MAXSIZE` -### wunderlist - -These are the settings for the wunderlist migrator - -Default: `` - -Full path: `migration.wunderlist` - -Environment path: `VIKUNJA_MIGRATION_WUNDERLIST` - - ### todoist Default: `` diff --git a/pkg/config/config.go b/pkg/config/config.go index db5d076068..1f2193f73f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -128,10 +128,6 @@ const ( FilesBasePath Key = `files.basepath` FilesMaxSize Key = `files.maxsize` - MigrationWunderlistEnable Key = `migration.wunderlist.enable` - MigrationWunderlistClientID Key = `migration.wunderlist.clientid` - MigrationWunderlistClientSecret Key = `migration.wunderlist.clientsecret` - MigrationWunderlistRedirectURL Key = `migration.wunderlist.redirecturl` MigrationTodoistEnable Key = `migration.todoist.enable` MigrationTodoistClientID Key = `migration.todoist.clientid` MigrationTodoistClientSecret Key = `migration.todoist.clientsecret` @@ -369,7 +365,6 @@ func InitDefaultConfig() { CorsOrigins.setDefault([]string{"*"}) CorsMaxAge.setDefault(0) // Migration - MigrationWunderlistEnable.setDefault(false) MigrationTodoistEnable.setDefault(false) MigrationTrelloEnable.setDefault(false) MigrationMicrosoftTodoEnable.setDefault(false) diff --git a/pkg/modules/migration/wunderlist/testimage.jpg b/pkg/modules/migration/testimage.jpg similarity index 100% rename from pkg/modules/migration/wunderlist/testimage.jpg rename to pkg/modules/migration/testimage.jpg diff --git a/pkg/modules/migration/todoist/todoist_test.go b/pkg/modules/migration/todoist/todoist_test.go index ddfec24f91..28da2f054e 100644 --- a/pkg/modules/migration/todoist/todoist_test.go +++ b/pkg/modules/migration/todoist/todoist_test.go @@ -46,7 +46,7 @@ func TestConvertTodoistToVikunja(t *testing.T) { dueTimeWithTime = dueTimeWithTime.In(config.GetTimeZone()) nilTime, err := time.Parse(time.RFC3339Nano, "0001-01-01T00:00:00Z") assert.NoError(t, err) - exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg") + exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/testimage.jpg") assert.NoError(t, err) makeTestItem := func(id, projectId string, hasDueDate, hasLabels, done bool) *item { diff --git a/pkg/modules/migration/trello/trello_test.go b/pkg/modules/migration/trello/trello_test.go index e34b5de7ec..3be909f5f4 100644 --- a/pkg/modules/migration/trello/trello_test.go +++ b/pkg/modules/migration/trello/trello_test.go @@ -36,7 +36,7 @@ func TestConvertTrelloToVikunja(t *testing.T) { time1, err := time.Parse(time.RFC3339Nano, "2014-09-26T08:25:05Z") assert.NoError(t, err) - exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg") + exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/testimage.jpg") assert.NoError(t, err) trelloData := []*trello.Board{ diff --git a/pkg/modules/migration/wunderlist/wunderlist.go b/pkg/modules/migration/wunderlist/wunderlist.go deleted file mode 100644 index fea6722e07..0000000000 --- a/pkg/modules/migration/wunderlist/wunderlist.go +++ /dev/null @@ -1,512 +0,0 @@ -// Vikunja is a to-do list application to facilitate your life. -// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details. -// -// You should have received a copy of the GNU Affero General Public Licensee -// along with this program. If not, see . - -package wunderlist - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strconv" - "time" - - "code.vikunja.io/api/pkg/config" - "code.vikunja.io/api/pkg/files" - "code.vikunja.io/api/pkg/log" - "code.vikunja.io/api/pkg/models" - "code.vikunja.io/api/pkg/modules/migration" - "code.vikunja.io/api/pkg/user" - "code.vikunja.io/api/pkg/utils" -) - -// Migration represents the implementation of the migration for wunderlist -type Migration struct { - // Code is the code used to get a user api token - Code string `query:"code" json:"code"` -} - -// This represents all necessary fields for getting an api token for the wunderlist api from a code -type wunderlistAuthRequest struct { - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - Code string `json:"code"` -} - -type wunderlistAuthToken struct { - AccessToken string `json:"access_token"` -} - -type task struct { - AssigneeID int `json:"assignee_id"` - CreatedAt time.Time `json:"created_at"` - CreatedByID int `json:"created_by_id"` - Completed bool `json:"completed"` - CompletedAt time.Time `json:"completed_at"` - DueDate string `json:"due_date"` - ID int `json:"id"` - ListID int `json:"list_id"` - Revision int `json:"revision"` - Starred bool `json:"starred"` - Title string `json:"title"` -} - -type list struct { - ID int `json:"id"` - CreatedAt time.Time `json:"created_at"` - Title string `json:"title"` - ListType string `json:"list_type"` - Type string `json:"type"` - Revision int `json:"revision"` - - Migrated bool `json:"-"` -} - -type folder struct { - ID int `json:"id"` - Title string `json:"title"` - ListIds []int `json:"list_ids"` - CreatedAt time.Time `json:"created_at"` - CreatedByRequestID string `json:"created_by_request_id"` - UpdatedAt time.Time `json:"updated_at"` - Type string `json:"type"` - Revision int `json:"revision"` -} - -type note struct { - ID int `json:"id"` - TaskID int `json:"task_id"` - Content string `json:"content"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Revision int `json:"revision"` -} - -type file struct { - ID int `json:"id"` - URL string `json:"url"` - TaskID int `json:"task_id"` - ListID int `json:"list_id"` - UserID int `json:"user_id"` - FileName string `json:"file_name"` - ContentType string `json:"content_type"` - FileSize int `json:"file_size"` - LocalCreatedAt time.Time `json:"local_created_at"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Type string `json:"type"` - Revision int `json:"revision"` -} - -type reminder struct { - ID int `json:"id"` - Date time.Time `json:"date"` - TaskID int `json:"task_id"` - Revision int `json:"revision"` - Type string `json:"type"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -type subtask struct { - ID int `json:"id"` - TaskID int `json:"task_id"` - CreatedAt time.Time `json:"created_at"` - CreatedByID int `json:"created_by_id"` - Revision int `json:"revision"` - Title string `json:"title"` -} - -type wunderlistContents struct { - tasks []*task - lists []*list - folders []*folder - notes []*note - files []*file - reminders []*reminder - subtasks []*subtask -} - -func convertListForFolder(listID int, list *list, content *wunderlistContents) (*models.ListWithTasksAndBuckets, error) { - - l := &models.ListWithTasksAndBuckets{ - List: models.List{ - Title: list.Title, - Created: list.CreatedAt, - }, - } - - // Find all tasks belonging to this list and put them in - for _, t := range content.tasks { - if t.ListID == listID { - newTask := &models.Task{ - Title: t.Title, - Created: t.CreatedAt, - Done: t.Completed, - } - - // Set Done At - if newTask.Done { - newTask.DoneAt = t.CompletedAt.In(config.GetTimeZone()) - } - - // Parse the due date - if t.DueDate != "" { - dueDate, err := time.Parse("2006-01-02", t.DueDate) - if err != nil { - return nil, err - } - newTask.DueDate = dueDate.In(config.GetTimeZone()) - } - - // Find related notes - for _, n := range content.notes { - if n.TaskID == t.ID { - newTask.Description = n.Content - } - } - - // Attachments - for _, f := range content.files { - if f.TaskID == t.ID { - // Download the attachment and put it in the file - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, f.URL, nil) - if err != nil { - return nil, err - } - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - buf := &bytes.Buffer{} - _, err = buf.ReadFrom(resp.Body) - if err != nil { - return nil, err - } - - newTask.Attachments = append(newTask.Attachments, &models.TaskAttachment{ - File: &files.File{ - Name: f.FileName, - Mime: f.ContentType, - Size: uint64(f.FileSize), - Created: f.CreatedAt, - // We directly pass the file contents here to have a way to link the attachment to the file later. - // Because we don't have an ID for our task at this point of the migration, we cannot just throw all - // attachments in a slice and do the work of downloading and properly storing them later. - FileContent: buf.Bytes(), - }, - Created: f.CreatedAt, - }) - } - } - - // Subtasks - for _, s := range content.subtasks { - if s.TaskID == t.ID { - if newTask.RelatedTasks[models.RelationKindSubtask] == nil { - newTask.RelatedTasks = make(models.RelatedTaskMap) - } - newTask.RelatedTasks[models.RelationKindSubtask] = append(newTask.RelatedTasks[models.RelationKindSubtask], &models.Task{ - Title: s.Title, - }) - } - } - - // Reminders - for _, r := range content.reminders { - if r.TaskID == t.ID { - newTask.Reminders = append(newTask.Reminders, r.Date.In(config.GetTimeZone())) - } - } - - l.Tasks = append(l.Tasks, &models.TaskWithComments{Task: *newTask}) - } - } - return l, nil -} - -func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) { - - // Make a map from the list with the key being list id for easier handling - listMap := make(map[int]*list, len(content.lists)) - for _, l := range content.lists { - listMap[l.ID] = l - } - - // First, we look through all folders and create namespaces for them. - for _, folder := range content.folders { - namespace := &models.NamespaceWithListsAndTasks{ - Namespace: models.Namespace{ - Title: folder.Title, - Created: folder.CreatedAt, - Updated: folder.UpdatedAt, - }, - } - - // Then find all lists for that folder - for _, listID := range folder.ListIds { - if list, exists := listMap[listID]; exists { - l, err := convertListForFolder(listID, list, content) - if err != nil { - return nil, err - } - namespace.Lists = append(namespace.Lists, l) - // And mark the list as migrated so we don't iterate over it again - list.Migrated = true - } - } - - // And then finally put the namespace (which now has all the details) back in the full array. - fullVikunjaHierachie = append(fullVikunjaHierachie, namespace) - } - - // At the end, loop over all lists which don't belong to a namespace and put them in a default namespace - if len(listMap) > 0 { - newNamespace := &models.NamespaceWithListsAndTasks{ - Namespace: models.Namespace{ - Title: "Migrated from wunderlist", - }, - } - - for _, list := range listMap { - - if list.Migrated { - continue - } - - l, err := convertListForFolder(list.ID, list, content) - if err != nil { - return nil, err - } - newNamespace.Lists = append(newNamespace.Lists, l) - } - - fullVikunjaHierachie = append(fullVikunjaHierachie, newNamespace) - } - - return -} - -func makeAuthGetRequest(token *wunderlistAuthToken, urlPart string, v interface{}, urlParams url.Values) error { - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://a.wunderlist.com/api/v1/"+urlPart, nil) - if err != nil { - return err - } - req.Header.Set("X-Access-Token", token.AccessToken) - req.Header.Set("X-Client-ID", config.MigrationWunderlistClientID.GetString()) - req.URL.RawQuery = urlParams.Encode() - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - buf := &bytes.Buffer{} - _, err = buf.ReadFrom(resp.Body) - if err != nil { - return err - } - - if resp.StatusCode > 399 { - return fmt.Errorf("wunderlist API Error: Status Code: %d, Response was: %s", resp.StatusCode, buf.String()) - } - - // If the response is an empty json array, we need to exit here, otherwise this breaks the json parser since it - // expects a null for an empty slice - str := buf.String() - if str == "[]" { - return nil - } - - return json.Unmarshal(buf.Bytes(), v) -} - -// Migrate migrates a user's wunderlist lists, tasks, etc. -// @Summary Migrate all lists, tasks etc. from wunderlist -// @Description Migrates all folders, lists, tasks, notes, reminders, subtasks and files from wunderlist to vikunja. -// @tags migration -// @Accept json -// @Produce json -// @Security JWTKeyAuth -// @Param migrationCode body wunderlist.Migration true "The auth code previously obtained from the auth url. See the docs for /migration/wunderlist/auth." -// @Success 200 {object} models.Message "A message telling you everything was migrated successfully." -// @Failure 500 {object} models.Message "Internal server error" -// @Router /migration/wunderlist/migrate [post] -func (w *Migration) Migrate(user *user.User) (err error) { - - log.Debugf("[Wunderlist migration] Starting wunderlist migration for user %d", user.ID) - - // Struct init - wContent := &wunderlistContents{ - tasks: []*task{}, - lists: []*list{}, - folders: []*folder{}, - notes: []*note{}, - files: []*file{}, - reminders: []*reminder{}, - subtasks: []*subtask{}, - } - - // 0. Get api token from oauth user token - authRequest := wunderlistAuthRequest{ - ClientID: config.MigrationWunderlistClientID.GetString(), - ClientSecret: config.MigrationWunderlistClientSecret.GetString(), - Code: w.Code, - } - jsonAuth, err := json.Marshal(authRequest) - if err != nil { - return - } - req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "https://www.wunderlist.com/oauth/access_token", bytes.NewBuffer(jsonAuth)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - resp, err := http.DefaultClient.Do(req) - if err != nil { - return - } - defer resp.Body.Close() - - authToken := &wunderlistAuthToken{} - err = json.NewDecoder(resp.Body).Decode(authToken) - if err != nil { - return - } - - log.Debugf("[Wunderlist migration] Start getting all data from wunderlist for user %d", user.ID) - - // 1. Get all folders - err = makeAuthGetRequest(authToken, "folders", &wContent.folders, nil) - if err != nil { - return - } - - // 2. Get all lists - err = makeAuthGetRequest(authToken, "lists", &wContent.lists, nil) - if err != nil { - return - } - - for _, l := range wContent.lists { - - listQueryParam := url.Values{"list_id": []string{strconv.Itoa(l.ID)}} - - // 3. Get all tasks for each list - tasks := []*task{} - err = makeAuthGetRequest(authToken, "tasks", &tasks, listQueryParam) - if err != nil { - return - } - wContent.tasks = append(wContent.tasks, tasks...) - - // 3. Get all done tasks for each list - doneTasks := []*task{} - err = makeAuthGetRequest(authToken, "tasks", &doneTasks, url.Values{"list_id": []string{strconv.Itoa(l.ID)}, "completed": []string{"true"}}) - if err != nil { - return - } - wContent.tasks = append(wContent.tasks, doneTasks...) - - // 4. Get all notes for all lists - notes := []*note{} - err = makeAuthGetRequest(authToken, "notes", ¬es, listQueryParam) - if err != nil { - return - } - wContent.notes = append(wContent.notes, notes...) - - // 5. Get all files for all lists - fils := []*file{} - err = makeAuthGetRequest(authToken, "files", &fils, listQueryParam) - if err != nil { - return - } - wContent.files = append(wContent.files, fils...) - - // 6. Get all reminders for all lists - reminders := []*reminder{} - err = makeAuthGetRequest(authToken, "reminders", &reminders, listQueryParam) - if err != nil { - return - } - wContent.reminders = append(wContent.reminders, reminders...) - - // 7. Get all subtasks for all lists - subtasks := []*subtask{} - err = makeAuthGetRequest(authToken, "subtasks", &subtasks, listQueryParam) - if err != nil { - return - } - wContent.subtasks = append(wContent.subtasks, subtasks...) - } - - log.Debugf("[Wunderlist migration] Got all data from wunderlist for user %d", user.ID) - log.Debugf("[Wunderlist migration] Migrating data to vikunja format for user %d", user.ID) - - // Convert + Insert everything - fullVikunjaHierachie, err := convertWunderlistToVikunja(wContent) - if err != nil { - return - } - - log.Debugf("[Wunderlist migration] Done migrating data to vikunja format for user %d", user.ID) - log.Debugf("[Wunderlist migration] Insert data into db for user %d", user.ID) - - err = migration.InsertFromStructure(fullVikunjaHierachie, user) - if err != nil { - return err - } - - log.Debugf("[Wunderlist migration] Done inserting data into db for user %d", user.ID) - log.Debugf("[Wunderlist migration] Wunderlist migration for user %d done", user.ID) - - return nil -} - -// AuthURL returns the url users need to authenticate against -// @Summary Get the auth url from wunderlist -// @Description Returns the auth url where the user needs to get its auth code. This code can then be used to migrate everything from wunderlist to Vikunja. -// @tags migration -// @Produce json -// @Security JWTKeyAuth -// @Success 200 {object} handler.AuthURL "The auth url." -// @Failure 500 {object} models.Message "Internal server error" -// @Router /migration/wunderlist/auth [get] -func (w *Migration) AuthURL() string { - return "https://www.wunderlist.com/oauth/authorize?client_id=" + - config.MigrationWunderlistClientID.GetString() + - "&redirect_uri=" + - config.MigrationWunderlistRedirectURL.GetString() + - "&state=" + utils.MakeRandomString(32) -} - -// Name is used to get the name of the wunderlist migration -// @Summary Get migration status -// @Description Returns if the current user already did the migation or not. This is useful to show a confirmation message in the frontend if the user is trying to do the same migration again. -// @tags migration -// @Produce json -// @Security JWTKeyAuth -// @Success 200 {object} migration.Status "The migration status" -// @Failure 500 {object} models.Message "Internal server error" -// @Router /migration/wunderlist/status [get] -func (w *Migration) Name() string { - return "wunderlist" -} diff --git a/pkg/modules/migration/wunderlist/wunderlist_test.go b/pkg/modules/migration/wunderlist/wunderlist_test.go deleted file mode 100644 index 2b27260d01..0000000000 --- a/pkg/modules/migration/wunderlist/wunderlist_test.go +++ /dev/null @@ -1,386 +0,0 @@ -// Vikunja is a to-do list application to facilitate your life. -// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details. -// -// You should have received a copy of the GNU Affero General Public Licensee -// along with this program. If not, see . - -package wunderlist - -import ( - "os" - "strconv" - "testing" - "time" - - "code.vikunja.io/api/pkg/config" - "code.vikunja.io/api/pkg/files" - "code.vikunja.io/api/pkg/models" - "github.com/stretchr/testify/assert" - "gopkg.in/d4l3k/messagediff.v1" -) - -func TestWunderlistParsing(t *testing.T) { - - config.InitConfig() - - time1, err := time.Parse(time.RFC3339Nano, "2013-08-30T08:29:46.203Z") - assert.NoError(t, err) - time1 = time1.In(config.GetTimeZone()) - time2, err := time.Parse(time.RFC3339Nano, "2013-08-30T08:36:13.273Z") - assert.NoError(t, err) - time2 = time2.In(config.GetTimeZone()) - time3, err := time.Parse(time.RFC3339Nano, "2013-09-05T08:36:13.273Z") - assert.NoError(t, err) - time3 = time3.In(config.GetTimeZone()) - time4, err := time.Parse(time.RFC3339Nano, "2013-08-02T11:58:55Z") - assert.NoError(t, err) - time4 = time4.In(config.GetTimeZone()) - - exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg") - assert.NoError(t, err) - - createTestTask := func(id, listID int, done bool) *task { - completedAt, err := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00Z") - assert.NoError(t, err) - if done { - completedAt = time1 - } - completedAt = completedAt.In(config.GetTimeZone()) - return &task{ - ID: id, - AssigneeID: 123, - CreatedAt: time1, - DueDate: "2013-09-05", - ListID: listID, - Title: "Ipsum" + strconv.Itoa(id), - Completed: done, - CompletedAt: completedAt, - } - } - - createTestNote := func(id, taskID int) *note { - return ¬e{ - ID: id, - TaskID: taskID, - Content: "Lorem Ipsum dolor sit amet", - CreatedAt: time3, - UpdatedAt: time2, - } - } - - fixtures := &wunderlistContents{ - folders: []*folder{ - { - ID: 123, - Title: "Lorem Ipsum", - ListIds: []int{1, 2, 3, 4}, - CreatedAt: time1, - UpdatedAt: time2, - }, - }, - lists: []*list{ - { - ID: 1, - CreatedAt: time1, - Title: "Lorem1", - }, - { - ID: 2, - CreatedAt: time1, - Title: "Lorem2", - }, - { - ID: 3, - CreatedAt: time1, - Title: "Lorem3", - }, - { - ID: 4, - CreatedAt: time1, - Title: "Lorem4", - }, - { - ID: 5, - CreatedAt: time4, - Title: "List without a namespace", - }, - }, - tasks: []*task{ - createTestTask(1, 1, false), - createTestTask(2, 1, false), - createTestTask(3, 2, true), - createTestTask(4, 2, false), - createTestTask(5, 3, false), - createTestTask(6, 3, true), - createTestTask(7, 3, true), - createTestTask(8, 3, false), - createTestTask(9, 4, true), - createTestTask(10, 4, true), - }, - notes: []*note{ - createTestNote(1, 1), - createTestNote(2, 2), - createTestNote(3, 3), - }, - files: []*file{ - { - ID: 1, - URL: "https://vikunja.io/testimage.jpg", // Using an image which we are hosting, so it'll still be up - TaskID: 1, - ListID: 1, - FileName: "file.md", - ContentType: "text/plain", - FileSize: 12345, - CreatedAt: time2, - UpdatedAt: time4, - }, - { - ID: 2, - URL: "https://vikunja.io/testimage.jpg", - TaskID: 3, - ListID: 2, - FileName: "file2.md", - ContentType: "text/plain", - FileSize: 12345, - CreatedAt: time3, - UpdatedAt: time4, - }, - }, - reminders: []*reminder{ - { - ID: 1, - Date: time4, - TaskID: 1, - CreatedAt: time4, - UpdatedAt: time4, - }, - { - ID: 2, - Date: time3, - TaskID: 4, - CreatedAt: time3, - UpdatedAt: time3, - }, - }, - subtasks: []*subtask{ - { - ID: 1, - TaskID: 2, - CreatedAt: time4, - Title: "LoremSub1", - }, - { - ID: 2, - TaskID: 2, - CreatedAt: time4, - Title: "LoremSub2", - }, - { - ID: 3, - TaskID: 4, - CreatedAt: time4, - Title: "LoremSub3", - }, - }, - } - - expectedHierachie := []*models.NamespaceWithListsAndTasks{ - { - Namespace: models.Namespace{ - Title: "Lorem Ipsum", - Created: time1, - Updated: time2, - }, - Lists: []*models.ListWithTasksAndBuckets{ - { - List: models.List{ - Created: time1, - Title: "Lorem1", - }, - Tasks: []*models.TaskWithComments{ - { - Task: models.Task{ - Title: "Ipsum1", - DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()), - Created: time1, - Description: "Lorem Ipsum dolor sit amet", - Attachments: []*models.TaskAttachment{ - { - File: &files.File{ - Name: "file.md", - Mime: "text/plain", - Size: 12345, - Created: time2, - FileContent: exampleFile, - }, - Created: time2, - }, - }, - Reminders: []time.Time{time4}, - }, - }, - { - Task: models.Task{ - Title: "Ipsum2", - DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()), - Created: time1, - Description: "Lorem Ipsum dolor sit amet", - RelatedTasks: map[models.RelationKind][]*models.Task{ - models.RelationKindSubtask: { - { - Title: "LoremSub1", - }, - { - Title: "LoremSub2", - }, - }, - }, - }, - }, - }, - }, - { - List: models.List{ - Created: time1, - Title: "Lorem2", - }, - Tasks: []*models.TaskWithComments{ - { - Task: models.Task{ - Title: "Ipsum3", - Done: true, - DoneAt: time1, - DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()), - Created: time1, - Description: "Lorem Ipsum dolor sit amet", - Attachments: []*models.TaskAttachment{ - { - File: &files.File{ - Name: "file2.md", - Mime: "text/plain", - Size: 12345, - Created: time3, - FileContent: exampleFile, - }, - Created: time3, - }, - }, - }, - }, - { - Task: models.Task{ - Title: "Ipsum4", - DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()), - Created: time1, - Reminders: []time.Time{time3}, - RelatedTasks: map[models.RelationKind][]*models.Task{ - models.RelationKindSubtask: { - { - Title: "LoremSub3", - }, - }, - }, - }, - }, - }, - }, - { - List: models.List{ - Created: time1, - Title: "Lorem3", - }, - Tasks: []*models.TaskWithComments{ - { - Task: models.Task{ - Title: "Ipsum5", - DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()), - Created: time1, - }, - }, - { - Task: models.Task{ - Title: "Ipsum6", - DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()), - Created: time1, - Done: true, - DoneAt: time1, - }, - }, - { - Task: models.Task{ - Title: "Ipsum7", - DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()), - Created: time1, - Done: true, - DoneAt: time1, - }, - }, - { - Task: models.Task{ - Title: "Ipsum8", - DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()), - Created: time1, - }, - }, - }, - }, - { - List: models.List{ - Created: time1, - Title: "Lorem4", - }, - Tasks: []*models.TaskWithComments{ - { - Task: models.Task{ - Title: "Ipsum9", - DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()), - Created: time1, - Done: true, - DoneAt: time1, - }, - }, - { - Task: models.Task{ - Title: "Ipsum10", - DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()), - Created: time1, - Done: true, - DoneAt: time1, - }, - }, - }, - }, - }, - }, - { - Namespace: models.Namespace{ - Title: "Migrated from wunderlist", - }, - Lists: []*models.ListWithTasksAndBuckets{ - { - List: models.List{ - Created: time4, - Title: "List without a namespace", - }, - }, - }, - }, - } - - hierachie, err := convertWunderlistToVikunja(fixtures) - assert.NoError(t, err) - assert.NotNil(t, hierachie) - if diff, equal := messagediff.PrettyDiff(hierachie, expectedHierachie); !equal { - t.Errorf("converted wunderlist data = %v, want %v, diff: %v", hierachie, expectedHierachie, diff) - } -} diff --git a/pkg/routes/api/v1/info.go b/pkg/routes/api/v1/info.go index 37f63b7f76..e6da630d25 100644 --- a/pkg/routes/api/v1/info.go +++ b/pkg/routes/api/v1/info.go @@ -27,7 +27,6 @@ import ( "code.vikunja.io/api/pkg/modules/migration/todoist" "code.vikunja.io/api/pkg/modules/migration/trello" vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file" - "code.vikunja.io/api/pkg/modules/migration/wunderlist" "code.vikunja.io/api/pkg/version" "github.com/labstack/echo/v4" @@ -121,10 +120,6 @@ func Info(c echo.Context) error { info.AuthInfo.OpenIDConnect.Providers = providers // Migrators - if config.MigrationWunderlistEnable.GetBool() { - m := &wunderlist.Migration{} - info.AvailableMigrators = append(info.AvailableMigrators, m.Name()) - } if config.MigrationTodoistEnable.GetBool() { m := &todoist.Migration{} info.AvailableMigrators = append(info.AvailableMigrators, m.Name()) diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index ed73ef40ba..53eabe88c8 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -69,7 +69,6 @@ import ( "code.vikunja.io/api/pkg/modules/migration/todoist" "code.vikunja.io/api/pkg/modules/migration/trello" vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file" - "code.vikunja.io/api/pkg/modules/migration/wunderlist" apiv1 "code.vikunja.io/api/pkg/routes/api/v1" "code.vikunja.io/api/pkg/routes/caldav" _ "code.vikunja.io/api/pkg/swagger" // To generate swagger docs @@ -590,16 +589,6 @@ func registerAPIRoutes(a *echo.Group) { } func registerMigrations(m *echo.Group) { - // Wunderlist - if config.MigrationWunderlistEnable.GetBool() { - wunderlistMigrationHandler := &migrationHandler.MigrationWeb{ - MigrationStruct: func() migration.Migrator { - return &wunderlist.Migration{} - }, - } - wunderlistMigrationHandler.RegisterRoutes(m) - } - // Todoist if config.MigrationTodoistEnable.GetBool() { todoistMigrationHandler := &migrationHandler.MigrationWeb{ diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 55a0640067..90b59f570d 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -3061,113 +3061,6 @@ const docTemplate = `{ } } }, - "/migration/wunderlist/auth": { - "get": { - "security": [ - { - "JWTKeyAuth": [] - } - ], - "description": "Returns the auth url where the user needs to get its auth code. This code can then be used to migrate everything from wunderlist to Vikunja.", - "produces": [ - "application/json" - ], - "tags": [ - "migration" - ], - "summary": "Get the auth url from wunderlist", - "responses": { - "200": { - "description": "The auth url.", - "schema": { - "$ref": "#/definitions/handler.AuthURL" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.Message" - } - } - } - } - }, - "/migration/wunderlist/migrate": { - "post": { - "security": [ - { - "JWTKeyAuth": [] - } - ], - "description": "Migrates all folders, lists, tasks, notes, reminders, subtasks and files from wunderlist to vikunja.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "migration" - ], - "summary": "Migrate all lists, tasks etc. from wunderlist", - "parameters": [ - { - "description": "The auth code previously obtained from the auth url. See the docs for /migration/wunderlist/auth.", - "name": "migrationCode", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/wunderlist.Migration" - } - } - ], - "responses": { - "200": { - "description": "A message telling you everything was migrated successfully.", - "schema": { - "$ref": "#/definitions/models.Message" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.Message" - } - } - } - } - }, - "/migration/wunderlist/status": { - "get": { - "security": [ - { - "JWTKeyAuth": [] - } - ], - "description": "Returns if the current user already did the migation or not. This is useful to show a confirmation message in the frontend if the user is trying to do the same migration again.", - "produces": [ - "application/json" - ], - "tags": [ - "migration" - ], - "summary": "Get migration status", - "responses": { - "200": { - "description": "The migration status", - "schema": { - "$ref": "#/definitions/migration.Status" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.Message" - } - } - } - } - }, "/namespace/{id}": { "post": { "security": [ @@ -9489,15 +9382,6 @@ const docTemplate = `{ "type": "string" } } - }, - "wunderlist.Migration": { - "type": "object", - "properties": { - "code": { - "description": "Code is the code used to get a user api token", - "type": "string" - } - } } }, "securityDefinitions": { diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index f27a45af3c..bf9681b551 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -3052,113 +3052,6 @@ } } }, - "/migration/wunderlist/auth": { - "get": { - "security": [ - { - "JWTKeyAuth": [] - } - ], - "description": "Returns the auth url where the user needs to get its auth code. This code can then be used to migrate everything from wunderlist to Vikunja.", - "produces": [ - "application/json" - ], - "tags": [ - "migration" - ], - "summary": "Get the auth url from wunderlist", - "responses": { - "200": { - "description": "The auth url.", - "schema": { - "$ref": "#/definitions/handler.AuthURL" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.Message" - } - } - } - } - }, - "/migration/wunderlist/migrate": { - "post": { - "security": [ - { - "JWTKeyAuth": [] - } - ], - "description": "Migrates all folders, lists, tasks, notes, reminders, subtasks and files from wunderlist to vikunja.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "migration" - ], - "summary": "Migrate all lists, tasks etc. from wunderlist", - "parameters": [ - { - "description": "The auth code previously obtained from the auth url. See the docs for /migration/wunderlist/auth.", - "name": "migrationCode", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/wunderlist.Migration" - } - } - ], - "responses": { - "200": { - "description": "A message telling you everything was migrated successfully.", - "schema": { - "$ref": "#/definitions/models.Message" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.Message" - } - } - } - } - }, - "/migration/wunderlist/status": { - "get": { - "security": [ - { - "JWTKeyAuth": [] - } - ], - "description": "Returns if the current user already did the migation or not. This is useful to show a confirmation message in the frontend if the user is trying to do the same migration again.", - "produces": [ - "application/json" - ], - "tags": [ - "migration" - ], - "summary": "Get migration status", - "responses": { - "200": { - "description": "The migration status", - "schema": { - "$ref": "#/definitions/migration.Status" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.Message" - } - } - } - } - }, "/namespace/{id}": { "post": { "security": [ @@ -9480,15 +9373,6 @@ "type": "string" } } - }, - "wunderlist.Migration": { - "type": "object", - "properties": { - "code": { - "description": "Code is the code used to get a user api token", - "type": "string" - } - } } }, "securityDefinitions": { diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index 692a9f3c7a..393d2054b9 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -1401,12 +1401,6 @@ definitions: message: type: string type: object - wunderlist.Migration: - properties: - code: - description: Code is the code used to get a user api token - type: string - type: object info: contact: email: hello@vikunja.io @@ -3484,77 +3478,6 @@ paths: summary: Get migration status tags: - migration - /migration/wunderlist/auth: - get: - description: Returns the auth url where the user needs to get its auth code. - This code can then be used to migrate everything from wunderlist to Vikunja. - produces: - - application/json - responses: - "200": - description: The auth url. - schema: - $ref: '#/definitions/handler.AuthURL' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.Message' - security: - - JWTKeyAuth: [] - summary: Get the auth url from wunderlist - tags: - - migration - /migration/wunderlist/migrate: - post: - consumes: - - application/json - description: Migrates all folders, lists, tasks, notes, reminders, subtasks - and files from wunderlist to vikunja. - parameters: - - description: The auth code previously obtained from the auth url. See the - docs for /migration/wunderlist/auth. - in: body - name: migrationCode - required: true - schema: - $ref: '#/definitions/wunderlist.Migration' - produces: - - application/json - responses: - "200": - description: A message telling you everything was migrated successfully. - schema: - $ref: '#/definitions/models.Message' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.Message' - security: - - JWTKeyAuth: [] - summary: Migrate all lists, tasks etc. from wunderlist - tags: - - migration - /migration/wunderlist/status: - get: - description: Returns if the current user already did the migation or not. This - is useful to show a confirmation message in the frontend if the user is trying - to do the same migration again. - produces: - - application/json - responses: - "200": - description: The migration status - schema: - $ref: '#/definitions/migration.Status' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.Message' - security: - - JWTKeyAuth: [] - summary: Get migration status - tags: - - migration /namespace/{id}: post: consumes: