fix(migration): use Todoist v9 api to migrate tasks from them

Discussion: https://community.vikunja.io/t/importing-tasks-from-todoist/322/7
This commit is contained in:
kolaente 2022-12-18 20:38:58 +01:00
parent a79b1de2d0
commit 6a97a214a3
Signed by untrusted user: konrad
GPG Key ID: F40E70337AB24C9B
2 changed files with 220 additions and 232 deletions

View File

@ -45,28 +45,25 @@ type apiTokenResponse struct {
} }
type label struct { type label struct {
ID int64 `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Color int64 `json:"color"` Color string `json:"color"`
ItemOrder int64 `json:"item_order"` ItemOrder int64 `json:"item_order"`
IsDeleted int64 `json:"is_deleted"` IsDeleted bool `json:"is_deleted"`
IsFavorite int64 `json:"is_favorite"` IsFavorite bool `json:"is_favorite"`
} }
type project struct { type project struct {
ID int64 `json:"id"` ID string `json:"id"`
LegacyID int64 `json:"legacy_id"` Name string `json:"name"`
Name string `json:"name"` Color string `json:"color"`
Color int64 `json:"color"` ParentID string `json:"parent_id"`
ParentID int64 `json:"parent_id"` ChildOrder int64 `json:"child_order"`
ChildOrder int64 `json:"child_order"` Collapsed bool `json:"collapsed"`
Collapsed int64 `json:"collapsed"` Shared bool `json:"shared"`
Shared bool `json:"shared"` IsDeleted bool `json:"is_deleted"`
LegacyParentID int64 `json:"legacy_parent_id"` IsArchived bool `json:"is_archived"`
SyncID int64 `json:"sync_id"` IsFavorite bool `json:"is_favorite"`
IsDeleted int64 `json:"is_deleted"`
IsArchived int64 `json:"is_archived"`
IsFavorite int64 `json:"is_favorite"`
} }
type dueDate struct { type dueDate struct {
@ -78,31 +75,26 @@ type dueDate struct {
} }
type item struct { type item struct {
ID int64 `json:"id"` ID string `json:"id"`
LegacyID int64 `json:"legacy_id"` LegacyID string `json:"legacy_id"`
UserID int64 `json:"user_id"` UserID string `json:"user_id"`
ProjectID int64 `json:"project_id"` ProjectID string `json:"project_id"`
LegacyProjectID int64 `json:"legacy_project_id"` Content string `json:"content"`
Content string `json:"content"` Priority int64 `json:"priority"`
Priority int64 `json:"priority"` Due *dueDate `json:"due"`
Due *dueDate `json:"due"` ParentID string `json:"parent_id"`
ParentID int64 `json:"parent_id"` ChildOrder int64 `json:"child_order"`
LegacyParentID int64 `json:"legacy_parent_id"` SectionID string `json:"section_id"`
ChildOrder int64 `json:"child_order"` Children interface{} `json:"children"`
SectionID int64 `json:"section_id"` Labels []string `json:"labels"`
DayOrder int64 `json:"day_order"` AddedByUID string `json:"added_by_uid"`
Collapsed int64 `json:"collapsed"` AssignedByUID string `json:"assigned_by_uid"`
Children interface{} `json:"children"` ResponsibleUID string `json:"responsible_uid"`
Labels []int64 `json:"labels"` Checked bool `json:"checked"`
AddedByUID int64 `json:"added_by_uid"` IsDeleted bool `json:"is_deleted"`
AssignedByUID int64 `json:"assigned_by_uid"` DateAdded time.Time `json:"added_at"`
ResponsibleUID int64 `json:"responsible_uid"` HasMoreNotes bool `json:"has_more_notes"`
Checked int64 `json:"checked"` DateCompleted time.Time `json:"completed_at"`
InHistory int64 `json:"in_history"`
IsDeleted int64 `json:"is_deleted"`
DateAdded time.Time `json:"date_added"`
HasMoreNotes bool `json:"has_more_notes"`
DateCompleted time.Time `json:"date_completed"`
} }
type itemWrapper struct { type itemWrapper struct {
@ -110,12 +102,11 @@ type itemWrapper struct {
} }
type doneItem struct { type doneItem struct {
CompletedDate time.Time `json:"completed_date"` CompletedDate time.Time `json:"completed_at"`
Content string `json:"content"` Content string `json:"content"`
ID int64 `json:"id"` ID string `json:"id"`
ProjectID int64 `json:"project_id"` ProjectID string `json:"project_id"`
TaskID int64 `json:"task_id"` TaskID string `json:"task_id"`
UserID int `json:"user_id"`
} }
type doneItemSync struct { type doneItemSync struct {
@ -132,18 +123,14 @@ type fileAttachment struct {
} }
type note struct { type note struct {
ID int64 `json:"id"` ID string `json:"id"`
LegacyID int64 `json:"legacy_id"` PostedUID int64 `json:"posted_uid"`
PostedUID int64 `json:"posted_uid"` ProjectID string `json:"project_id"`
ProjectID int64 `json:"project_id"` ItemID string `json:"item_id"`
LegacyProjectID int64 `json:"legacy_project_id"` Content string `json:"content"`
ItemID int64 `json:"item_id"` FileAttachment *fileAttachment `json:"file_attachment"`
LegacyItemID int64 `json:"legacy_item_id"` IsDeleted bool `json:"is_deleted"`
Content string `json:"content"` Posted time.Time `json:"posted_at"`
FileAttachment *fileAttachment `json:"file_attachment"`
UidsToNotify []int64 `json:"uids_to_notify"`
IsDeleted int64 `json:"is_deleted"`
Posted time.Time `json:"posted"`
} }
type projectNote struct { type projectNote struct {
@ -153,15 +140,13 @@ type projectNote struct {
IsDeleted int64 `json:"is_deleted"` IsDeleted int64 `json:"is_deleted"`
Posted time.Time `json:"posted"` Posted time.Time `json:"posted"`
PostedUID int64 `json:"posted_uid"` PostedUID int64 `json:"posted_uid"`
ProjectID int64 `json:"project_id"` ProjectID string `json:"project_id"`
UidsToNotify []int64 `json:"uids_to_notify"` UidsToNotify []int64 `json:"uids_to_notify"`
} }
type reminder struct { type reminder struct {
ID int64 `json:"id"` ID string `json:"id"`
NotifyUID int64 `json:"notify_uid"` ItemID string `json:"item_id"`
ItemID int64 `json:"item_id"`
Service string `json:"service"`
Type string `json:"type"` Type string `json:"type"`
Due *dueDate `json:"due"` Due *dueDate `json:"due"`
MmOffset int64 `json:"mm_offset"` MmOffset int64 `json:"mm_offset"`
@ -169,11 +154,11 @@ type reminder struct {
} }
type section struct { type section struct {
ID int64 `json:"id"` ID string `json:"id"`
DateAdded time.Time `json:"date_added"` DateAdded time.Time `json:"added_at"`
IsDeleted bool `json:"is_deleted"` IsDeleted bool `json:"is_deleted"`
Name string `json:"name"` Name string `json:"name"`
ProjectID int64 `json:"project_id"` ProjectID string `json:"project_id"`
SectionOrder int64 `json:"section_order"` SectionOrder int64 `json:"section_order"`
} }
@ -187,32 +172,32 @@ type sync struct {
Sections []*section `json:"sections"` Sections []*section `json:"sections"`
} }
var todoistColors = map[int64]string{} var todoistColors = map[string]string{}
func init() { func init() {
todoistColors = make(map[int64]string, 19) todoistColors = make(map[string]string, 19)
// The todoists colors are static, taken from https://developer.todoist.com/sync/v8/#colors // The todoists colors are static, taken from https://developer.todoist.com/guides/#colors
todoistColors = map[int64]string{ todoistColors = map[string]string{
30: "b8256f", "berry_red": "b8256f",
31: "db4035", "red": "db4035",
32: "ff9933", "orange": "ff9933",
33: "fad000", "yellow": "fad000",
34: "afb83b", "olive_green": "afb83b",
35: "7ecc49", "lime_green": "7ecc49",
36: "299438", "green": "299438",
37: "6accbc", "mint_green": "6accbc",
38: "158fad", "teal": "158fad",
39: "14aaf5", "sky_blue": "14aaf5",
40: "96c3eb", "light_blue": "96c3eb",
41: "4073ff", "blue": "4073ff",
42: "884dff", "grape": "884dff",
43: "af38eb", "violet": "af38eb",
44: "eb96eb", "lavender": "eb96eb",
45: "e05194", "magenta": "e05194",
46: "ff8d85", "salmon": "ff8d85",
47: "808080", "charcoal": "808080",
48: "b8b8b8", "grey": "b8b8b8",
49: "ccac93", "taupe": "ccac93",
} }
} }
@ -266,7 +251,7 @@ func parseDate(dateString string) (date time.Time, err error) {
return date, err return date, err
} }
func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) { func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
newNamespace := &models.NamespaceWithListsAndTasks{ newNamespace := &models.NamespaceWithListsAndTasks{
Namespace: models.Namespace{ Namespace: models.Namespace{
@ -275,20 +260,22 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
} }
// A map for all vikunja lists with the project id they're coming from as key // A map for all vikunja lists with the project id they're coming from as key
lists := make(map[int64]*models.ListWithTasksAndBuckets, len(sync.Projects)) lists := make(map[string]*models.ListWithTasksAndBuckets, len(sync.Projects))
// A map for all vikunja tasks with the todoist task id as key to find them easily and add more data // A map for all vikunja tasks with the todoist task id as key to find them easily and add more data
tasks := make(map[int64]*models.TaskWithComments, len(sync.Items)) tasks := make(map[string]*models.TaskWithComments, len(sync.Items))
// A map for all vikunja labels with the todoist id as key to find them easier // A map for all vikunja labels with the todoist id as key to find them easier
labels := make(map[int64]*models.Label, len(sync.Labels)) labels := make(map[string]*models.Label, len(sync.Labels))
sections := make(map[string]int64)
for _, p := range sync.Projects { for _, p := range sync.Projects {
list := &models.ListWithTasksAndBuckets{ list := &models.ListWithTasksAndBuckets{
List: models.List{ List: models.List{
Title: p.Name, Title: p.Name,
HexColor: todoistColors[p.Color], HexColor: todoistColors[p.Color],
IsArchived: p.IsArchived == 1, IsArchived: p.IsArchived,
}, },
} }
@ -301,20 +288,22 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
return sync.Sections[i].SectionOrder < sync.Sections[j].SectionOrder return sync.Sections[i].SectionOrder < sync.Sections[j].SectionOrder
}) })
var fabricatedSectionID int64 = 1
for _, section := range sync.Sections { for _, section := range sync.Sections {
if section.IsDeleted || section.ProjectID == 0 { if section.IsDeleted || section.ProjectID == "" {
continue continue
} }
lists[section.ProjectID].Buckets = append(lists[section.ProjectID].Buckets, &models.Bucket{ lists[section.ProjectID].Buckets = append(lists[section.ProjectID].Buckets, &models.Bucket{
ID: section.ID, ID: fabricatedSectionID,
Title: section.Name, Title: section.Name,
Created: section.DateAdded, Created: section.DateAdded,
}) })
sections[section.ID] = fabricatedSectionID
} }
for _, label := range sync.Labels { for _, label := range sync.Labels {
labels[label.ID] = &models.Label{ labels[label.Name] = &models.Label{
Title: label.Name, Title: label.Name,
HexColor: todoistColors[label.Color], HexColor: todoistColors[label.Color],
} }
@ -325,8 +314,8 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
Task: models.Task{ Task: models.Task{
Title: i.Content, Title: i.Content,
Created: i.DateAdded.In(config.GetTimeZone()), Created: i.DateAdded.In(config.GetTimeZone()),
Done: i.Checked == 1, Done: i.Checked,
BucketID: i.SectionID, BucketID: sections[i.SectionID],
}, },
} }
@ -357,29 +346,31 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
} }
// Put all labels together from earlier // Put all labels together from earlier
for _, lID := range i.Labels { for _, lName := range i.Labels {
task.Labels = append(task.Labels, labels[lID]) task.Labels = append(task.Labels, labels[lName])
} }
tasks[i.ID] = task tasks[i.ID] = task
if _, exists := lists[i.ProjectID]; !exists { if _, exists := lists[i.ProjectID]; !exists {
log.Debugf("[Todoist Migration] Tried to put item %d in project %d but the project does not exist", i.ID, i.ProjectID) log.Debugf("[Todoist Migration] Tried to put item %s in project %s but the project does not exist", i.ID, i.ProjectID)
continue continue
} }
lists[i.ProjectID].Tasks = append(lists[i.ProjectID].Tasks, task) lists[i.ProjectID].Tasks = append(lists[i.ProjectID].Tasks, task)
fabricatedSectionID++
} }
// If the parenId of a task is not 0, create a task relation // If the parenId of a task is not 0, create a task relation
// We're looping again here to make sure we have seem all tasks before and have them in our map // We're looping again here to make sure we have seem all tasks before and have them in our map
for _, i := range sync.Items { for _, i := range sync.Items {
if i.ParentID == 0 { if i.ParentID == "" {
continue continue
} }
if _, exists := tasks[i.ParentID]; !exists { if _, exists := tasks[i.ParentID]; !exists {
log.Debugf("[Todoist Migration] Could not find task %d in tasks map while trying to get resolve subtasks for task %d", i.ParentID, i.ID) log.Debugf("[Todoist Migration] Could not find task %s in tasks map while trying to get resolve subtasks for task %s", i.ParentID, i.ID)
continue continue
} }
@ -407,7 +398,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
// FIXME: Should be comments // FIXME: Should be comments
for _, n := range sync.Notes { for _, n := range sync.Notes {
if _, exists := tasks[n.ItemID]; !exists { if _, exists := tasks[n.ItemID]; !exists {
log.Debugf("[Todoist Migration] Could not find task %d for note %d", n.ItemID, n.ID) log.Debugf("[Todoist Migration] Could not find task %s for note %s", n.ItemID, n.ID)
continue continue
} }
@ -460,7 +451,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
} }
if _, exists := tasks[r.ItemID]; !exists { if _, exists := tasks[r.ItemID]; !exists {
log.Debugf("Could not find task %d for reminder %d while trying to resolve reminders", r.ItemID, r.ID) log.Debugf("Could not find task %s for reminder %s while trying to resolve reminders", r.ItemID, r.ID)
continue continue
} }
@ -537,7 +528,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
"sync_token": []string{"*"}, "sync_token": []string{"*"},
"resource_types": []string{"[\"all\"]"}, "resource_types": []string{"[\"all\"]"},
} }
resp, err := migration.DoPost("https://api.todoist.com/sync/v8/sync", form) resp, err := migration.DoPost("https://api.todoist.com/sync/v9/sync", form)
if err != nil { if err != nil {
return return
} }
@ -553,10 +544,10 @@ func (m *Migration) Migrate(u *user.User) (err error) {
// Get all done tasks and projects // Get all done tasks and projects
offset := 0 offset := 0
doneItems := make(map[int64]*doneItem) doneItems := make(map[string]*doneItem)
for { for {
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/completed/get_all?limit=200&offset="+strconv.Itoa(offset), form) resp, err = migration.DoPost("https://api.todoist.com/sync/v9/completed/get_all?limit=200&offset="+strconv.Itoa(offset), form)
if err != nil { if err != nil {
return return
} }
@ -580,9 +571,9 @@ func (m *Migration) Migrate(u *user.User) (err error) {
doneItems[i.TaskID] = i doneItems[i.TaskID] = i
// need to get done item data // need to get done item data
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/items/get", url.Values{ resp, err = migration.DoPost("https://api.todoist.com/sync/v9/items/get", url.Values{
"token": []string{token}, "token": []string{token},
"item_id": []string{strconv.FormatInt(i.TaskID, 10)}, "item_id": []string{i.TaskID},
}) })
if err != nil { if err != nil {
return return
@ -594,7 +585,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
if err != nil { if err != nil {
return return
} }
log.Debugf("[Todoist Migration] Retrieved full task data for done task %d", i.TaskID) log.Debugf("[Todoist Migration] Retrieved full task data for done task %s", i.TaskID)
syncResponse.Items = append(syncResponse.Items, doneI.Item) syncResponse.Items = append(syncResponse.Items, doneI.Item)
} }
@ -609,7 +600,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
log.Debugf("[Todoist Migration] Getting archived projects for user %d", u.ID) log.Debugf("[Todoist Migration] Getting archived projects for user %d", u.ID)
// Get all archived projects // Get all archived projects
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/projects/get_archived", form) resp, err = migration.DoPost("https://api.todoist.com/sync/v9/projects/get_archived", form)
if err != nil { if err != nil {
return return
} }
@ -626,9 +617,8 @@ func (m *Migration) Migrate(u *user.User) (err error) {
log.Debugf("[Todoist Migration] Getting data for archived projects for user %d", u.ID) log.Debugf("[Todoist Migration] Getting data for archived projects for user %d", u.ID)
// Project data is not included in the regular sync for archived projects so we need to get all of those by hand // Project data is not included in the regular sync for archived projects so we need to get all of those by hand
//https://api.todoist.com/sync/v8/projects/get_data\?project_id\=2269005399
for _, p := range archivedProjects { for _, p := range archivedProjects {
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/projects/get_data?project_id="+strconv.FormatInt(p.ID, 10), form) resp, err = migration.DoPost("https://api.todoist.com/sync/v9/projects/get_data?project_id="+p.ID, form)
if err != nil { if err != nil {
return return
} }

View File

@ -18,7 +18,6 @@ package todoist
import ( import (
"os" "os"
"strconv"
"testing" "testing"
"time" "time"
@ -50,30 +49,29 @@ func TestConvertTodoistToVikunja(t *testing.T) {
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg") exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
assert.NoError(t, err) assert.NoError(t, err)
makeTestItem := func(id, projectId int64, hasDueDate, hasLabels, done bool) *item { makeTestItem := func(id, projectId string, hasDueDate, hasLabels, done bool) *item {
item := &item{ item := &item{
ID: id, ID: id,
UserID: 1855589, UserID: "1855589",
ProjectID: projectId, ProjectID: projectId,
Content: "Task" + strconv.FormatInt(id, 10), Content: "Task" + id,
Priority: 1, Priority: 1,
ParentID: 0,
ChildOrder: 1, ChildOrder: 1,
DateAdded: time1, DateAdded: time1,
DateCompleted: nilTime, DateCompleted: nilTime,
} }
if done { if done {
item.Checked = 1 item.Checked = true
item.DateCompleted = time3 item.DateCompleted = time3
} }
if hasLabels { if hasLabels {
item.Labels = []int64{ item.Labels = []string{
80000, "Label1",
80001, "Label2",
80002, "Label3",
80003, "Label4",
} }
} }
@ -91,163 +89,163 @@ func TestConvertTodoistToVikunja(t *testing.T) {
testSync := &sync{ testSync := &sync{
Projects: []*project{ Projects: []*project{
{ {
ID: 396936926, ID: "396936926",
Name: "Project1", Name: "Project1",
Color: 30, Color: "berry_red",
ChildOrder: 1, ChildOrder: 1,
Collapsed: 0, Collapsed: false,
Shared: false, Shared: false,
IsDeleted: 0, IsDeleted: false,
IsArchived: 0, IsArchived: false,
IsFavorite: 0, IsFavorite: false,
}, },
{ {
ID: 396936927, ID: "396936927",
Name: "Project2", Name: "Project2",
Color: 37, Color: "mint_green",
ChildOrder: 1, ChildOrder: 1,
Collapsed: 0, Collapsed: false,
Shared: false, Shared: false,
IsDeleted: 0, IsDeleted: false,
IsArchived: 0, IsArchived: false,
IsFavorite: 0, IsFavorite: false,
}, },
{ {
ID: 396936928, ID: "396936928",
Name: "Project3 - Archived", Name: "Project3 - Archived",
Color: 37, Color: "mint_green",
ChildOrder: 1, ChildOrder: 1,
Collapsed: 0, Collapsed: false,
Shared: false, Shared: false,
IsDeleted: 0, IsDeleted: false,
IsArchived: 1, IsArchived: true,
IsFavorite: 0, IsFavorite: false,
}, },
}, },
Items: []*item{ Items: []*item{
makeTestItem(400000000, 396936926, false, false, false), makeTestItem("400000000", "396936926", false, false, false),
makeTestItem(400000001, 396936926, false, false, false), makeTestItem("400000001", "396936926", false, false, false),
makeTestItem(400000002, 396936926, false, false, false), makeTestItem("400000002", "396936926", false, false, false),
makeTestItem(400000003, 396936926, true, true, true), makeTestItem("400000003", "396936926", true, true, true),
makeTestItem(400000004, 396936926, false, true, false), makeTestItem("400000004", "396936926", false, true, false),
makeTestItem(400000005, 396936926, true, false, true), makeTestItem("400000005", "396936926", true, false, true),
makeTestItem(400000006, 396936926, true, false, true), makeTestItem("400000006", "396936926", true, false, true),
{ {
ID: 400000110, ID: "400000110",
UserID: 1855589, UserID: "1855589",
ProjectID: 396936926, ProjectID: "396936926",
Content: "Task with parent", Content: "Task with parent",
Priority: 2, Priority: 2,
ParentID: 400000006, ParentID: "400000006",
ChildOrder: 1, ChildOrder: 1,
Checked: 0, Checked: false,
DateAdded: time1, DateAdded: time1,
}, },
{ {
ID: 400000106, ID: "400000106",
UserID: 1855589, UserID: "1855589",
ProjectID: 396936926, ProjectID: "396936926",
Content: "Task400000106", Content: "Task400000106",
Priority: 1, Priority: 1,
ParentID: 0, ParentID: "",
ChildOrder: 1, ChildOrder: 1,
DateAdded: time1, DateAdded: time1,
Checked: 1, Checked: true,
DateCompleted: time3, DateCompleted: time3,
Due: &dueDate{ Due: &dueDate{
Date: "2021-01-31T19:00:00Z", Date: "2021-01-31T19:00:00Z",
Timezone: nil, Timezone: nil,
IsRecurring: false, IsRecurring: false,
}, },
Labels: []int64{ Labels: []string{
80000, "Label1",
80001, "Label2",
80002, "Label3",
80003, "Label4",
}, },
}, },
makeTestItem(400000107, 396936926, false, false, true), makeTestItem("400000107", "396936926", false, false, true),
makeTestItem(400000108, 396936926, false, false, true), makeTestItem("400000108", "396936926", false, false, true),
{ {
ID: 400000109, ID: "400000109",
UserID: 1855589, UserID: "1855589",
ProjectID: 396936926, ProjectID: "396936926",
Content: "Task400000109", Content: "Task400000109",
Priority: 1, Priority: 1,
ChildOrder: 1, ChildOrder: 1,
Checked: 1, Checked: true,
DateAdded: time1, DateAdded: time1,
DateCompleted: time3, DateCompleted: time3,
SectionID: 1234, SectionID: "1234",
}, },
makeTestItem(400000007, 396936927, true, false, false), makeTestItem("400000007", "396936927", true, false, false),
makeTestItem(400000008, 396936927, true, false, false), makeTestItem("400000008", "396936927", true, false, false),
makeTestItem(400000009, 396936927, false, false, false), makeTestItem("400000009", "396936927", false, false, false),
makeTestItem(400000010, 396936927, false, false, true), makeTestItem("400000010", "396936927", false, false, true),
makeTestItem(400000101, 396936927, false, false, false), makeTestItem("400000101", "396936927", false, false, false),
makeTestItem(400000102, 396936927, true, true, false), makeTestItem("400000102", "396936927", true, true, false),
makeTestItem(400000103, 396936927, false, true, false), makeTestItem("400000103", "396936927", false, true, false),
makeTestItem(400000104, 396936927, false, true, false), makeTestItem("400000104", "396936927", false, true, false),
makeTestItem(400000105, 396936927, true, true, false), makeTestItem("400000105", "396936927", true, true, false),
makeTestItem(400000111, 396936928, false, false, true), makeTestItem("400000111", "396936928", false, false, true),
}, },
Labels: []*label{ Labels: []*label{
{ {
ID: 80000, ID: "80000",
Name: "Label1", Name: "Label1",
Color: 30, Color: "berry_red",
}, },
{ {
ID: 80001, ID: "80001",
Name: "Label2", Name: "Label2",
Color: 31, Color: "red",
}, },
{ {
ID: 80002, ID: "80002",
Name: "Label3", Name: "Label3",
Color: 32, Color: "orange",
}, },
{ {
ID: 80003, ID: "80003",
Name: "Label4", Name: "Label4",
Color: 33, Color: "yellow",
}, },
}, },
Notes: []*note{ Notes: []*note{
{ {
ID: 101476, ID: "101476",
PostedUID: 1855589, PostedUID: 1855589,
ItemID: 400000000, ItemID: "400000000",
Content: "Lorem Ipsum dolor sit amet", Content: "Lorem Ipsum dolor sit amet",
Posted: time1, Posted: time1,
}, },
{ {
ID: 101477, ID: "101477",
PostedUID: 1855589, PostedUID: 1855589,
ItemID: 400000001, ItemID: "400000001",
Content: "Lorem Ipsum dolor sit amet", Content: "Lorem Ipsum dolor sit amet",
Posted: time1, Posted: time1,
}, },
{ {
ID: 101478, ID: "101478",
PostedUID: 1855589, PostedUID: 1855589,
ItemID: 400000003, ItemID: "400000003",
Content: "Lorem Ipsum dolor sit amet", Content: "Lorem Ipsum dolor sit amet",
Posted: time1, Posted: time1,
}, },
{ {
ID: 101479, ID: "101479",
PostedUID: 1855589, PostedUID: 1855589,
ItemID: 400000010, ItemID: "400000010",
Content: "Lorem Ipsum dolor sit amet", Content: "Lorem Ipsum dolor sit amet",
Posted: time1, Posted: time1,
}, },
{ {
ID: 101480, ID: "101480",
PostedUID: 1855589, PostedUID: 1855589,
ItemID: 400000101, ItemID: "400000101",
Content: "Lorem Ipsum dolor sit amet", Content: "Lorem Ipsum dolor sit amet",
FileAttachment: &fileAttachment{ FileAttachment: &fileAttachment{
FileName: "file.md", FileName: "file.md",
@ -263,43 +261,43 @@ func TestConvertTodoistToVikunja(t *testing.T) {
{ {
ID: 102000, ID: 102000,
Content: "Lorem Ipsum dolor sit amet", Content: "Lorem Ipsum dolor sit amet",
ProjectID: 396936926, ProjectID: "396936926",
Posted: time3, Posted: time3,
PostedUID: 1855589, PostedUID: 1855589,
}, },
{ {
ID: 102001, ID: 102001,
Content: "Lorem Ipsum dolor sit amet 2", Content: "Lorem Ipsum dolor sit amet 2",
ProjectID: 396936926, ProjectID: "396936926",
Posted: time3, Posted: time3,
PostedUID: 1855589, PostedUID: 1855589,
}, },
{ {
ID: 102002, ID: 102002,
Content: "Lorem Ipsum dolor sit amet 3", Content: "Lorem Ipsum dolor sit amet 3",
ProjectID: 396936926, ProjectID: "396936926",
Posted: time3, Posted: time3,
PostedUID: 1855589, PostedUID: 1855589,
}, },
{ {
ID: 102003, ID: 102003,
Content: "Lorem Ipsum dolor sit amet 4", Content: "Lorem Ipsum dolor sit amet 4",
ProjectID: 396936927, ProjectID: "396936927",
Posted: time3, Posted: time3,
PostedUID: 1855589, PostedUID: 1855589,
}, },
{ {
ID: 102004, ID: 102004,
Content: "Lorem Ipsum dolor sit amet 5", Content: "Lorem Ipsum dolor sit amet 5",
ProjectID: 396936927, ProjectID: "396936927",
Posted: time3, Posted: time3,
PostedUID: 1855589, PostedUID: 1855589,
}, },
}, },
Reminders: []*reminder{ Reminders: []*reminder{
{ {
ID: 103000, ID: "103000",
ItemID: 400000000, ItemID: "400000000",
Due: &dueDate{ Due: &dueDate{
Date: "2020-06-15", Date: "2020-06-15",
IsRecurring: false, IsRecurring: false,
@ -307,40 +305,40 @@ func TestConvertTodoistToVikunja(t *testing.T) {
MmOffset: 180, MmOffset: 180,
}, },
{ {
ID: 103001, ID: "103001",
ItemID: 400000000, ItemID: "400000000",
Due: &dueDate{ Due: &dueDate{
Date: "2020-06-16T07:00:00", Date: "2020-06-16T07:00:00",
IsRecurring: false, IsRecurring: false,
}, },
}, },
{ {
ID: 103002, ID: "103002",
ItemID: 400000002, ItemID: "400000002",
Due: &dueDate{ Due: &dueDate{
Date: "2020-07-15T07:00:00Z", Date: "2020-07-15T07:00:00Z",
IsRecurring: true, IsRecurring: true,
}, },
}, },
{ {
ID: 103003, ID: "103003",
ItemID: 400000003, ItemID: "400000003",
Due: &dueDate{ Due: &dueDate{
Date: "2020-06-15T07:00:00", Date: "2020-06-15T07:00:00",
IsRecurring: false, IsRecurring: false,
}, },
}, },
{ {
ID: 103004, ID: "103004",
ItemID: 400000005, ItemID: "400000005",
Due: &dueDate{ Due: &dueDate{
Date: "2020-06-15T07:00:00", Date: "2020-06-15T07:00:00",
IsRecurring: false, IsRecurring: false,
}, },
}, },
{ {
ID: 103006, ID: "103006",
ItemID: 400000009, ItemID: "400000009",
Due: &dueDate{ Due: &dueDate{
Date: "2020-06-15T07:00:00", Date: "2020-06-15T07:00:00",
IsRecurring: false, IsRecurring: false,
@ -349,9 +347,9 @@ func TestConvertTodoistToVikunja(t *testing.T) {
}, },
Sections: []*section{ Sections: []*section{
{ {
ID: 1234, ID: "1234",
Name: "Some Bucket", Name: "Some Bucket",
ProjectID: 396936926, ProjectID: "396936926",
}, },
}, },
} }
@ -359,19 +357,19 @@ func TestConvertTodoistToVikunja(t *testing.T) {
vikunjaLabels := []*models.Label{ vikunjaLabels := []*models.Label{
{ {
Title: "Label1", Title: "Label1",
HexColor: todoistColors[30], HexColor: todoistColors["berry_red"],
}, },
{ {
Title: "Label2", Title: "Label2",
HexColor: todoistColors[31], HexColor: todoistColors["red"],
}, },
{ {
Title: "Label3", Title: "Label3",
HexColor: todoistColors[32], HexColor: todoistColors["orange"],
}, },
{ {
Title: "Label4", Title: "Label4",
HexColor: todoistColors[33], HexColor: todoistColors["yellow"],
}, },
} }
@ -385,11 +383,11 @@ func TestConvertTodoistToVikunja(t *testing.T) {
List: models.List{ List: models.List{
Title: "Project1", Title: "Project1",
Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3", Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3",
HexColor: todoistColors[30], HexColor: todoistColors["berry_red"],
}, },
Buckets: []*models.Bucket{ Buckets: []*models.Bucket{
{ {
ID: 1234, ID: 1,
Title: "Some Bucket", Title: "Some Bucket",
}, },
}, },
@ -510,7 +508,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
Done: true, Done: true,
Created: time1, Created: time1,
DoneAt: time3, DoneAt: time3,
BucketID: 1234, BucketID: 1,
}, },
}, },
}, },
@ -519,7 +517,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
List: models.List{ List: models.List{
Title: "Project2", Title: "Project2",
Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5", Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5",
HexColor: todoistColors[37], HexColor: todoistColors["mint_green"],
}, },
Tasks: []*models.TaskWithComments{ Tasks: []*models.TaskWithComments{
{ {
@ -616,7 +614,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
{ {
List: models.List{ List: models.List{
Title: "Project3 - Archived", Title: "Project3 - Archived",
HexColor: todoistColors[37], HexColor: todoistColors["mint_green"],
IsArchived: true, IsArchived: true,
}, },
Tasks: []*models.TaskWithComments{ Tasks: []*models.TaskWithComments{
@ -634,7 +632,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
}, },
} }
doneItems := make(map[int64]*doneItem) doneItems := make(map[string]*doneItem)
hierachie, err := convertTodoistToVikunja(testSync, doneItems) hierachie, err := convertTodoistToVikunja(testSync, doneItems)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, hierachie) assert.NotNil(t, hierachie)