fix(migration): make sure importing works when the csv file has errors and don't try to parse empty values as dates

This commit is contained in:
kolaente 2022-10-09 19:23:23 +02:00
parent 0d044997df
commit f5a33478f2
No known key found for this signature in database
GPG key ID: F40E70337AB24C9B
3 changed files with 74 additions and 59 deletions

View file

@ -18,6 +18,7 @@ package ticktick
import ( import (
"encoding/csv" "encoding/csv"
"errors"
"io" "io"
"regexp" "regexp"
"sort" "sort"
@ -25,6 +26,8 @@ import (
"strings" "strings"
"time" "time"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration" "code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
@ -62,6 +65,10 @@ var durationRegex = regexp.MustCompile(`P([\d\.]+Y)?([\d\.]+M)?([\d\.]+D)?T?([\d
func parseDuration(str string) time.Duration { func parseDuration(str string) time.Duration {
matches := durationRegex.FindStringSubmatch(str) matches := durationRegex.FindStringSubmatch(str)
if len(matches) == 0 {
return 0
}
years := parseDurationPart(matches[1], time.Hour*24*365) years := parseDurationPart(matches[1], time.Hour*24*365)
months := parseDurationPart(matches[2], time.Hour*24*30) months := parseDurationPart(matches[2], time.Hour*24*30)
days := parseDurationPart(matches[3], time.Hour*24) days := parseDurationPart(matches[3], time.Hour*24)
@ -69,7 +76,7 @@ func parseDuration(str string) time.Duration {
minutes := parseDurationPart(matches[5], time.Second*60) minutes := parseDurationPart(matches[5], time.Second*60)
seconds := parseDurationPart(matches[6], time.Second) seconds := parseDurationPart(matches[6], time.Second)
return time.Duration(years + months + days + hours + minutes + seconds) return years + months + days + hours + minutes + seconds
} }
func parseDurationPart(value string, unit time.Duration) time.Duration { func parseDurationPart(value string, unit time.Duration) time.Duration {
@ -170,38 +177,30 @@ func (m *Migrator) Name() string {
// @Failure 500 {object} models.Message "Internal server error" // @Failure 500 {object} models.Message "Internal server error"
// @Router /migration/ticktick/migrate [post] // @Router /migration/ticktick/migrate [post]
func (m *Migrator) Migrate(user *user.User, file io.ReaderAt, size int64) error { func (m *Migrator) Migrate(user *user.User, file io.ReaderAt, size int64) error {
fr := io.NewSectionReader(file, 0, 0) fr := io.NewSectionReader(file, 0, size)
r := csv.NewReader(fr) r := csv.NewReader(fr)
records, err := r.ReadAll()
if err != nil {
return err
}
allTasks := make([]*tickTickTask, 0, len(records)) allTasks := []*tickTickTask{}
for line, record := range records { line := 0
if line <= 3 { for {
record, err := r.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
log.Debugf("[TickTick Migration] CSV parse error: %s", err)
}
line++
if line <= 4 {
continue continue
} }
startDate, err := time.Parse(timeISO, record[6])
if err != nil {
return err
}
dueDate, err := time.Parse(timeISO, record[7])
if err != nil {
return err
}
priority, err := strconv.Atoi(record[10]) priority, err := strconv.Atoi(record[10])
if err != nil { if err != nil {
return err return err
} }
createdTime, err := time.Parse(timeISO, record[12])
if err != nil {
return err
}
completedTime, err := time.Parse(timeISO, record[13])
if err != nil {
return err
}
order, err := strconv.ParseFloat(record[14], 64) order, err := strconv.ParseFloat(record[14], 64)
if err != nil { if err != nil {
return err return err
@ -217,24 +216,47 @@ func (m *Migrator) Migrate(user *user.User, file io.ReaderAt, size int64) error
reminder := parseDuration(record[8]) reminder := parseDuration(record[8])
allTasks = append(allTasks, &tickTickTask{ t := &tickTickTask{
ListName: record[1], ListName: record[1],
Title: record[2], Title: record[2],
Tags: strings.Split(record[3], ", "), Tags: strings.Split(record[3], ", "),
Content: record[4], Content: record[4],
IsChecklist: record[5] == "Y", IsChecklist: record[5] == "Y",
StartDate: startDate, Reminder: reminder,
DueDate: dueDate, Repeat: record[9],
Reminder: reminder, Priority: priority,
Repeat: record[9], Status: record[11],
Priority: priority, Order: order,
Status: record[11], TaskID: taskID,
CreatedTime: createdTime, ParentID: parentID,
CompletedTime: completedTime, }
Order: order,
TaskID: taskID, if record[6] != "" {
ParentID: parentID, t.StartDate, err = time.Parse(timeISO, record[6])
}) if err != nil {
return err
}
}
if record[7] != "" {
t.DueDate, err = time.Parse(timeISO, record[7])
if err != nil {
return err
}
}
if record[12] != "" {
t.StartDate, err = time.Parse(timeISO, record[12])
if err != nil {
return err
}
}
if record[13] != "" {
t.CompletedTime, err = time.Parse(timeISO, record[13])
if err != nil {
return err
}
}
allTasks = append(allTasks, t)
} }
vikunjaTasks := convertTickTickToVikunja(allTasks) vikunjaTasks := convertTickTickToVikunja(allTasks)

View file

@ -17,22 +17,19 @@
package v1 package v1
import ( import (
"code.vikunja.io/api/pkg/modules/migration/ticktick"
"net/http" "net/http"
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo"
"code.vikunja.io/api/pkg/modules/migration/trello"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/modules/auth/openid" "code.vikunja.io/api/pkg/modules/auth/openid"
microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo"
"code.vikunja.io/api/pkg/modules/migration/ticktick"
"code.vikunja.io/api/pkg/modules/migration/todoist" "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/modules/migration/wunderlist"
"code.vikunja.io/api/pkg/version" "code.vikunja.io/api/pkg/version"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )

View file

@ -47,19 +47,12 @@
package routes package routes
import ( import (
"code.vikunja.io/api/pkg/modules/migration/ticktick"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"strings" "strings"
"time" "time"
"code.vikunja.io/api/pkg/modules/migration/ticktick"
"github.com/ulule/limiter/v3"
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
"code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
@ -73,8 +66,10 @@ import (
"code.vikunja.io/api/pkg/modules/migration" "code.vikunja.io/api/pkg/modules/migration"
migrationHandler "code.vikunja.io/api/pkg/modules/migration/handler" migrationHandler "code.vikunja.io/api/pkg/modules/migration/handler"
microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo" microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo"
"code.vikunja.io/api/pkg/modules/migration/ticktick"
"code.vikunja.io/api/pkg/modules/migration/todoist" "code.vikunja.io/api/pkg/modules/migration/todoist"
"code.vikunja.io/api/pkg/modules/migration/trello" "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/modules/migration/wunderlist"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1" apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/routes/caldav" "code.vikunja.io/api/pkg/routes/caldav"
@ -89,6 +84,7 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
elog "github.com/labstack/gommon/log" elog "github.com/labstack/gommon/log"
"github.com/ulule/limiter/v3"
) )
// NewEcho registers a new Echo instance // NewEcho registers a new Echo instance