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:
parent
0d044997df
commit
f5a33478f2
3 changed files with 74 additions and 59 deletions
|
@ -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()
|
|
||||||
|
allTasks := []*tickTickTask{}
|
||||||
|
line := 0
|
||||||
|
for {
|
||||||
|
|
||||||
|
record, err := r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Debugf("[TickTick Migration] CSV parse error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
allTasks := make([]*tickTickTask, 0, len(records))
|
line++
|
||||||
for line, record := range records {
|
if line <= 4 {
|
||||||
if line <= 3 {
|
|
||||||
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,
|
|
||||||
DueDate: dueDate,
|
|
||||||
Reminder: reminder,
|
Reminder: reminder,
|
||||||
Repeat: record[9],
|
Repeat: record[9],
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
Status: record[11],
|
Status: record[11],
|
||||||
CreatedTime: createdTime,
|
|
||||||
CompletedTime: completedTime,
|
|
||||||
Order: order,
|
Order: order,
|
||||||
TaskID: taskID,
|
TaskID: taskID,
|
||||||
ParentID: parentID,
|
ParentID: parentID,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if record[6] != "" {
|
||||||
|
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)
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue