WIP: Add Kanboard migrator #1607
|
@ -18,7 +18,7 @@ The interface makes it possible to use helper methods which handle http and focu
|
|||
|
||||
There are two ways of migrating data from another service:
|
||||
|
||||
1. Through the auth-based flow where the user gives you access to their data at the third-party service through an oauth flow. You can then call the service's api on behalf of your user to get all the data. The Todoist, Trello and Microsoft To-Do Migrators use this pattern.
|
||||
1. Through the auth-based flow where the user gives you access to their data at the third-party service through an oauth flow. You can then call the service's api on behalf of your user to get all the data. The Todoist, Kanboard, Trello and Microsoft To-Do Migrators use this pattern.
|
||||
2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need to parse the file and create the projects, tasks etc. The Vikunja File Import uses this pattern.
|
||||
|
||||
To differentiate the two, there are two different interfaces you must implement.
|
||||
|
|
|
@ -1022,6 +1022,15 @@ Full path: `migration.trello`
|
|||
Environment path: `VIKUNJA_MIGRATION_TRELLO`
|
||||
|
||||
|
||||
### kanboard
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
Full path: `migration.kanboard`
|
||||
|
||||
Environment path: `VIKUNJA_MIGRATION_KANBOARD`
|
||||
|
||||
|
||||
### microsofttodo
|
||||
|
||||
Default: `<empty>`
|
||||
|
|
1
go.mod
1
go.mod
|
@ -164,6 +164,7 @@ require (
|
|||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/ybbus/jsonrpc/v2 v2.1.7 // indirect
|
||||
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 // indirect
|
||||
go.opentelemetry.io/otel v1.15.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.15.0 // indirect
|
||||
|
|
3
go.sum
3
go.sum
|
@ -595,6 +595,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
|
|||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
||||
|
@ -769,6 +770,8 @@ github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT
|
|||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/ybbus/jsonrpc/v2 v2.1.7 h1:QjoXuZhkXZ3oLBkrONBe2avzFkYeYLorpeA+d8175XQ=
|
||||
github.com/ybbus/jsonrpc/v2 v2.1.7/go.mod h1:rIuG1+ORoiqocf9xs/v+ecaAVeo3zcZHQgInyKFMeg0=
|
||||
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 h1:0sw0nJM544SpsihWx1bkXdYLQDlzRflMgFJQ4Yih9ts=
|
||||
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4/go.mod h1:+ccdNT0xMY1dtc5XBxumbYfOUhmduiGudqaDgD2rVRE=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
|
|
|
@ -142,6 +142,8 @@ const (
|
|||
MigrationMicrosoftTodoClientID Key = `migration.microsofttodo.clientid`
|
||||
MigrationMicrosoftTodoClientSecret Key = `migration.microsofttodo.clientsecret`
|
||||
MigrationMicrosoftTodoRedirectURL Key = `migration.microsofttodo.redirecturl`
|
||||
MigrationKanboardEnable Key = `migration.kanboard.enable`
|
||||
MigrationKanboardApiInsecure Key = `migration.kanboard.insecure`
|
||||
|
||||
CorsEnable Key = `cors.enable`
|
||||
CorsOrigins Key = `cors.origins`
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-present 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 Affero General Public Licensee 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 Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package kanboard
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"github.com/ybbus/jsonrpc/v2"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
)
|
||||
|
||||
// Migration represents the kanboard migration struct
|
||||
type Migration struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type kanboardBoard struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
IsActive string `json:"is_active"`
|
||||
Token string `json:"token"`
|
||||
LastModified string `json:"last_modified"`
|
||||
IsPublic string `json:"is_public"`
|
||||
IsPrivate string `json:"is_private"`
|
||||
DefaultSwimlane string `json:"default_swimlane"`
|
||||
ShowDefaultSwimlane string `json:"show_default_swimlane"`
|
||||
Description interface{} `json:"description"`
|
||||
Identifier string `json:"identifier"`
|
||||
URL struct {
|
||||
Board string `json:"board"`
|
||||
Calendar string `json:"calendar"`
|
||||
List string `json:"list"`
|
||||
} `json:"url"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
// Name is used to get the name of the kanboard migration - we're using the docs here to annotate the status route.
|
||||
// @Summary Get migration status
|
||||
// @Description Returns if the current user already did the migation or not. This is useful to show a confirmation message in the frontend if the user is trying to do the same migration again.
|
||||
// @tags migration
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} migration.Status "The migration status"
|
||||
// @Failure 500 {object} models.Message "Internal server error"
|
||||
// @Router /migration/kanboard/status [get]
|
||||
func (m *Migration) Name() string {
|
||||
return "kanboard"
|
||||
}
|
||||
|
||||
// AuthURL returns the url users need to authenticate against
|
||||
// @Summary Get the auth url from kanboard
|
||||
// @Description Returns the auth url where the user needs to get its auth code. This code can then be used to migrate everything from kanboard to Vikunja.
|
||||
// @tags migration
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} handler.AuthURL "The auth url."
|
||||
// @Failure 500 {object} models.Message "Internal server error"
|
||||
// @Router /migration/kanboard/auth [get]
|
||||
func (m *Migration) AuthURL() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func basicAuth(username, password string) string {
|
||||
auth := username + ":" + password
|
||||
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
}
|
||||
|
||||
func getKanboardData(endpoint string, username string, password string, allowInsecure bool) (kanboardData []kanboardBoard, err error) {
|
||||
|
||||
var tr *http.Transport
|
||||
|
||||
if allowInsecure {
|
||||
tr = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
} else {
|
||||
tr = &http.Transport{}
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
auth := make(map[string]string)
|
||||
auth["Authorization"] = "Basic " + basicAuth(username, password)
|
||||
|
||||
opts := jsonrpc.RPCClientOpts{
|
||||
HTTPClient: client,
|
||||
CustomHeaders: auth,
|
||||
}
|
||||
|
||||
rpcClient := jsonrpc.NewClientWithOpts(endpoint, &opts)
|
||||
|
||||
err = rpcClient.CallFor(&kanboardData, "getAllProjects")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Converts all previously obtained data from kanboard into the vikunja format.
|
||||
// `kanboardData` should contain all boards with their projects and cards respectively.
|
||||
func convertKanboardDataToVikunja(kanboardData []kanboardBoard) (fullVikunjaHierachie []*models.ProjectWithTasksAndBuckets, err error) {
|
||||
|
||||
log.Debugf("[Kanboard Migration] ")
|
||||
|
||||
fullVikunjaHierachie = []*models.ProjectWithTasksAndBuckets{
|
||||
{
|
||||
Project: models.Project{
|
||||
Title: "Imported from Kanboard",
|
||||
},
|
||||
ChildProjects: []*models.ProjectWithTasksAndBuckets{},
|
||||
},
|
||||
}
|
||||
|
||||
log.Debugf("[Kanboard Migration] Converting %d boards to vikunja projects", len(kanboardData))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Migrate gets all tasks from kanboard for a user and puts them into vikunja
|
||||
// @Summary Migrate all projects, tasks etc. from kanboard
|
||||
// @Description Migrates all projects, tasks, notes, reminders, subtasks and files from kanboard to vikunja.
|
||||
// @tags migration
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param migrationCode body kanboard.Migration true "The auth token previously obtained from the auth url. See the docs for /migration/kanboard/auth."
|
||||
// @Success 200 {object} models.Message "A message telling you everything was migrated successfully."
|
||||
// @Failure 500 {object} models.Message "Internal server error"
|
||||
// @Router /migration/kanboard/migrate [post]
|
||||
func (m *Migration) Migrate(u *user.User) (err error) {
|
||||
log.Debugf("[Kanboard Migration] Starting migration for user %d", u.ID)
|
||||
log.Debugf("[Kanboard Migration] Getting all kanboard data for user %d", u.ID)
|
||||
|
||||
kanboardData, err := getKanboardData(
|
||||
m.Endpoint,
|
||||
m.Username,
|
||||
m.Password,
|
||||
config.MigrationKanboardApiInsecure.GetBool(),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Kanboard Migration] Got all kanboard data for user %d", u.ID)
|
||||
log.Debugf("[Kanboard Migration] Start converting kanboard data for user %d", u.ID)
|
||||
|
||||
fullVikunjaHierachie, err := convertKanboardDataToVikunja(kanboardData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Kanboard Migration] Done migrating kanboard data for user %d", u.ID)
|
||||
log.Debugf("[Kanboard Migration] Start inserting kanboard data for user %d", u.ID)
|
||||
|
||||
err = migration.InsertFromStructure(fullVikunjaHierachie, u)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Kanboard Migration] Done inserting kanboard data for user %d", u.ID)
|
||||
log.Debugf("[Kanboard Migration] Migration done for user %d", u.ID)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -22,6 +22,7 @@ import (
|
|||
"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/migration/kanboard"
|
||||
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"
|
||||
|
@ -134,7 +135,10 @@ func Info(c echo.Context) error {
|
|||
m := µsofttodo.Migration{}
|
||||
info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
|
||||
}
|
||||
|
||||
if config.MigrationKanboardEnable.GetBool() {
|
||||
m := &kanboard.Migration{}
|
||||
info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
|
||||
}
|
||||
if config.BackgroundsEnabled.GetBool() {
|
||||
if config.BackgroundsUploadEnabled.GetBool() {
|
||||
info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "upload")
|
||||
|
|
|
@ -69,6 +69,7 @@ import (
|
|||
"code.vikunja.io/api/pkg/modules/background/upload"
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
migrationHandler "code.vikunja.io/api/pkg/modules/migration/handler"
|
||||
"code.vikunja.io/api/pkg/modules/migration/kanboard"
|
||||
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"
|
||||
|
@ -597,6 +598,16 @@ func registerMigrations(m *echo.Group) {
|
|||
trelloMigrationHandler.RegisterRoutes(m)
|
||||
}
|
||||
|
||||
// Kanboard
|
||||
if config.MigrationKanboardEnable.GetBool() {
|
||||
kanboardMigrationHandler := &migrationHandler.MigrationWeb{
|
||||
MigrationStruct: func() migration.Migrator {
|
||||
return &kanboard.Migration{}
|
||||
},
|
||||
}
|
||||
kanboardMigrationHandler.RegisterRoutes(m)
|
||||
}
|
||||
|
||||
// Microsoft Todo
|
||||
if config.MigrationMicrosoftTodoEnable.GetBool() {
|
||||
microsoftTodoMigrationHandler := &migrationHandler.MigrationWeb{
|
||||
|
|
Loading…
Reference in New Issue