vikunja/pkg/modules/migration/wunderlist/wunderlist.go
kolaente 5acf3a6e82
Some checks failed
continuous-integration/drone/pr Build is failing
Add done and done at to wunderlist migrator
2020-01-18 23:11:20 +01:00

257 lines
7.3 KiB
Go

// Vikunja is a todo-list application to facilitate your life.
// Copyright 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 <https://www.gnu.org/licenses/>.
package wunderlist
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/utils"
"time"
)
// Migration represents the implementation of the migration for wunderlist
type Migration struct {
}
type task struct {
ID int `json:"id"`
AssigneeID int `json:"assignee_id"`
CreatedAt time.Time `json:"created_at"`
CreatedByID int `json:"created_by_id"`
DueDate time.Time `json:"due_date"`
ListID int `json:"list_id"`
Revision int `json:"revision"`
Starred bool `json:"starred"`
Title string `json:"title"`
Completed bool `json:"completed"`
CompletedAt time.Time `json:"completed_at"`
}
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"`
}
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.List {
l := &models.List{
Title: list.Title,
Created: list.CreatedAt.Unix(),
}
// Find all tasks belonging to this list and put them in
for _, t := range content.tasks {
if t.ListID == listID {
newTask := &models.Task{
Text: t.Title,
DueDateUnix: t.DueDate.Unix(),
Created: t.CreatedAt.Unix(),
Done: t.Completed,
DoneAtUnix: t.CompletedAt.Unix(),
}
// Find related notes
for _, n := range content.notes {
if n.TaskID == t.ID {
newTask.Description = n.Content
}
}
// Attachments
//for _, f := range content.files {
// TODO
//}
// 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{
Text: s.Title,
})
}
}
// Reminders
for _, r := range content.reminders {
if r.TaskID == t.ID {
newTask.RemindersUnix = append(newTask.RemindersUnix, r.Date.Unix())
}
}
l.Tasks = append(l.Tasks, newTask)
}
}
return l
}
func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierachie []*models.NamespaceWithLists, 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.NamespaceWithLists{
Namespace: models.Namespace{
Name: folder.Title,
Created: folder.CreatedAt.Unix(),
Updated: folder.UpdatedAt.Unix(),
},
}
// Then find all lists for that folder
for _, listID := range folder.ListIds {
if list, exists := listMap[listID]; exists {
l := convertListForFolder(listID, list, content)
namespace.Lists = append(namespace.Lists, l)
// And remove the list so we don't iterate over it again
delete(listMap, listID)
}
}
// 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.NamespaceWithLists{
Namespace: models.Namespace{
Name: "Migrated from wunderlist",
},
}
for _, list := range listMap {
l := convertListForFolder(list.ID, list, content)
newNamespace.Lists = append(newNamespace.Lists, l)
// And remove the list so we don't iterate over it again
delete(listMap, list.ID)
}
fullVikunjaHierachie = append(fullVikunjaHierachie, newNamespace)
}
return
}
// Migrate migrates a user's wunderlist lists, tasks, etc.
func (w *Migration) Migrate(user models.User) error {
// 0. Get api token from oauth user token
// 1. Get all folders
// 2. Get all lists
// 3. Get all tasks for each list
// 3. Get all done tasks for each list
// 4. Get all notes for all lists
// 5. Get all files for all lists
// 6. Get all reminders for all lists
// 7. Get all subtasks for all lists
return nil
}
// AuthURL returns the url users need to authenticate against
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)
}