257 lines
7.3 KiB
Go
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)
|
|
}
|