Migration Improvements (#122)

Update swagger docs

Update docs

Let the wunderlist migrator use the registerRoutes function

Add migration status table

Add migration status

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#122
This commit is contained in:
konrad 2020-01-20 19:48:46 +00:00
parent 0654ead831
commit 8c33e24e92
12 changed files with 349 additions and 5 deletions

View File

@ -35,6 +35,9 @@ type Migrator interface {
// The use case for this are Oauth flows, where the server token should remain hidden and not // The use case for this are Oauth flows, where the server token should remain hidden and not
// known to the frontend. // known to the frontend.
AuthURL() string AuthURL() string
// Name holds the name of the migration.
// This is used to show the name to users and to keep track of users who already migrated.
Name() string
} }
``` ```
@ -43,6 +46,9 @@ type Migrator interface {
Once your migrator implements the migration interface, it becomes possible to use the helper http handlers. Once your migrator implements the migration interface, it becomes possible to use the helper http handlers.
Their usage is very similar to the [general web handler](https://kolaente.dev/vikunja/web#user-content-defining-routes-using-the-standard-web-handler): Their usage is very similar to the [general web handler](https://kolaente.dev/vikunja/web#user-content-defining-routes-using-the-standard-web-handler):
The `RegisterRoutes(m)` method registers all routes with the scheme `/[MigratorName]/(auth|migrate|status)` for the
authUrl, Status and Migrate methods.
```go ```go
// This is an example for the Wunderlist migrator // This is an example for the Wunderlist migrator
if config.MigrationWunderlistEnable.GetBool() { if config.MigrationWunderlistEnable.GetBool() {
@ -51,8 +57,7 @@ if config.MigrationWunderlistEnable.GetBool() {
return &wunderlist.Migration{} return &wunderlist.Migration{}
}, },
} }
m.GET("/wunderlist/auth", wunderlistMigrationHandler.AuthURL) wunderlistMigrationHandler.RegisterRoutes(m)
m.POST("/wunderlist/migrate", wunderlistMigrationHandler.Migrate)
} }
``` ```

View File

@ -23,6 +23,7 @@ import (
"code.vikunja.io/api/pkg/mail" "code.vikunja.io/api/pkg/mail"
"code.vikunja.io/api/pkg/migration" "code.vikunja.io/api/pkg/migration"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
migrator "code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/red" "code.vikunja.io/api/pkg/red"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -79,6 +80,10 @@ func initialize() {
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
} }
err = migrator.InitDB()
if err != nil {
log.Fatal(err.Error())
}
// Initialize the files handler // Initialize the files handler
files.InitFileHandler() files.InitFileHandler()

View File

@ -0,0 +1,47 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018-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 migration
import (
"github.com/go-xorm/xorm"
"src.techknowlogick.com/xormigrate"
)
type status20200120201756 struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
UserID int64 `xorm:"int(11) not null" json:"user_id"`
MigratorName string `xorm:"varchar(255)" json:"migrator_name"`
CreatedUnix int64 `xorm:"created not null"`
}
func (s status20200120201756) TableName() string {
return "migration_status"
}
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20200120201756",
Description: "Add migration status table",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync2(status20200120201756{})
},
Rollback: func(tx *xorm.Engine) error {
return dropTableColum(tx, "migration_status", "index")
},
})
}

View File

@ -0,0 +1,49 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018-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 migration
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"github.com/go-xorm/xorm"
)
var x *xorm.Engine
// InitDB sets up the database connection to use in this module
func InitDB() (err error) {
x, err = db.CreateDBEngine()
if err != nil {
log.Criticalf("Could not connect to db: %v", err.Error())
return
}
// Cache
if config.CacheEnabled.GetBool() && config.CacheType.GetString() == "redis" {
db.RegisterTableStructsForCache(GetTables())
}
return nil
}
// GetTables returns all structs which are also a table.
func GetTables() []interface{} {
return []interface{}{
&Status{},
}
}

View File

@ -34,6 +34,14 @@ type AuthURL struct {
URL string `json:"url"` URL string `json:"url"`
} }
// RegisterRoutes registers all routes for migration
func (mw *MigrationWeb) RegisterRoutes(g *echo.Group) {
ms := mw.MigrationStruct()
g.GET("/"+ms.Name()+"/auth", mw.AuthURL)
g.GET("/"+ms.Name()+"/status", mw.Status)
g.POST("/"+ms.Name()+"/migrate", mw.Migrate)
}
// AuthURL is the web handler to get the auth url // AuthURL is the web handler to get the auth url
func (mw *MigrationWeb) AuthURL(c echo.Context) error { func (mw *MigrationWeb) AuthURL(c echo.Context) error {
ms := mw.MigrationStruct() ms := mw.MigrationStruct()
@ -62,5 +70,27 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error {
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
} }
err = migration.SetMigrationStatus(ms, user)
if err != nil {
return handler.HandleHTTPError(err, c)
}
return c.JSON(http.StatusOK, models.Message{Message: "Everything was migrated successfully."}) return c.JSON(http.StatusOK, models.Message{Message: "Everything was migrated successfully."})
} }
// Status returns whether or not a user has already done this migration
func (mw *MigrationWeb) Status(c echo.Context) error {
ms := mw.MigrationStruct()
user, err := models.GetCurrentUser(c)
if err != nil {
return handler.HandleHTTPError(err, c)
}
status, err := migration.GetMigrationStatus(ms, user)
if err != nil {
return handler.HandleHTTPError(err, c)
}
return c.JSON(http.StatusOK, status)
}

View File

@ -0,0 +1,49 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018-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 migration
import "code.vikunja.io/api/pkg/models"
// Status represents this migration status
type Status struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
UserID int64 `xorm:"int(11) not null" json:"-"`
MigratorName string `xorm:"varchar(255)" json:"migrator_name"`
CreatedUnix int64 `xorm:"created not null" json:"time_unix"`
}
// TableName holds the table name for the migration status table
func (s *Status) TableName() string {
return "migration_status"
}
// SetMigrationStatus sets the migration status for a user
func SetMigrationStatus(m Migrator, u *models.User) (err error) {
status := &Status{
UserID: u.ID,
MigratorName: m.Name(),
}
_, err = x.Insert(status)
return
}
// GetMigrationStatus returns the migration status for a migration and a user
func GetMigrationStatus(m Migrator, u *models.User) (status *Status, err error) {
status = &Status{}
_, err = x.Where("user_id = ? and migrator_name = ?", u.ID, m.Name()).Desc("id").Get(status)
return
}

View File

@ -28,4 +28,7 @@ type Migrator interface {
// The use case for this are Oauth flows, where the server token should remain hidden and not // The use case for this are Oauth flows, where the server token should remain hidden and not
// known to the frontend. // known to the frontend.
AuthURL() string AuthURL() string
// Name holds the name of the migration.
// This is used to show the name to users and to keep track of users who already migrated.
Name() string
} }

View File

@ -480,3 +480,16 @@ func (w *Migration) AuthURL() string {
config.MigrationWunderlistRedirectURL.GetString() + config.MigrationWunderlistRedirectURL.GetString() +
"&state=" + utils.MakeRandomString(32) "&state=" + utils.MakeRandomString(32)
} }
// Name is used to get the name of the wunderlist migration
// @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/wunderlist/status [get]
func (w *Migration) Name() string {
return "wunderlist"
}

View File

@ -386,8 +386,7 @@ func registerAPIRoutes(a *echo.Group) {
return &wunderlist.Migration{} return &wunderlist.Migration{}
}, },
} }
m.GET("/wunderlist/auth", wunderlistMigrationHandler.AuthURL) wunderlistMigrationHandler.RegisterRoutes(m)
m.POST("/wunderlist/migrate", wunderlistMigrationHandler.Migrate)
} }
} }

View File

@ -1,6 +1,6 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at // This file was generated by swaggo/swag at
// 2020-01-19 16:18:04.294790395 +0100 CET m=+0.176548843 // 2020-01-20 20:27:54.414478978 +0100 CET m=+0.171577588
package swagger package swagger
@ -1692,6 +1692,37 @@ var doc = `{
} }
} }
}, },
"/migration/wunderlist/status": {
"get": {
"security": [
{
"JWTKeyAuth": []
}
],
"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.",
"produces": [
"application/json"
],
"tags": [
"migration"
],
"summary": "Get migration status",
"responses": {
"200": {
"description": "The migration status",
"schema": {
"$ref": "#/definitions/migration.Status"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/namespace/{id}": { "/namespace/{id}": {
"post": { "post": {
"security": [ "security": [
@ -4548,6 +4579,20 @@ var doc = `{
} }
} }
}, },
"migration.Status": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"migrator_name": {
"type": "string"
},
"time_unix": {
"type": "integer"
}
}
},
"models.APIUserPassword": { "models.APIUserPassword": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -5596,6 +5641,12 @@ var doc = `{
"v1.vikunjaInfos": { "v1.vikunjaInfos": {
"type": "object", "type": "object",
"properties": { "properties": {
"available_migrators": {
"type": "array",
"items": {
"type": "string"
}
},
"frontend_url": { "frontend_url": {
"type": "string" "type": "string"
}, },
@ -5608,6 +5659,9 @@ var doc = `{
"motd": { "motd": {
"type": "string" "type": "string"
}, },
"registration_enabled": {
"type": "boolean"
},
"version": { "version": {
"type": "string" "type": "string"
} }

View File

@ -1674,6 +1674,37 @@
} }
} }
}, },
"/migration/wunderlist/status": {
"get": {
"security": [
{
"JWTKeyAuth": []
}
],
"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.",
"produces": [
"application/json"
],
"tags": [
"migration"
],
"summary": "Get migration status",
"responses": {
"200": {
"description": "The migration status",
"schema": {
"$ref": "#/definitions/migration.Status"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/namespace/{id}": { "/namespace/{id}": {
"post": { "post": {
"security": [ "security": [
@ -4529,6 +4560,20 @@
} }
} }
}, },
"migration.Status": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"migrator_name": {
"type": "string"
},
"time_unix": {
"type": "integer"
}
}
},
"models.APIUserPassword": { "models.APIUserPassword": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -5577,6 +5622,12 @@
"v1.vikunjaInfos": { "v1.vikunjaInfos": {
"type": "object", "type": "object",
"properties": { "properties": {
"available_migrators": {
"type": "array",
"items": {
"type": "string"
}
},
"frontend_url": { "frontend_url": {
"type": "string" "type": "string"
}, },
@ -5589,6 +5640,9 @@
"motd": { "motd": {
"type": "string" "type": "string"
}, },
"registration_enabled": {
"type": "boolean"
},
"version": { "version": {
"type": "string" "type": "string"
} }

View File

@ -18,6 +18,15 @@ definitions:
url: url:
type: string type: string
type: object type: object
migration.Status:
properties:
id:
type: integer
migrator_name:
type: string
time_unix:
type: integer
type: object
models.APIUserPassword: models.APIUserPassword:
properties: properties:
email: email:
@ -854,6 +863,10 @@ definitions:
type: object type: object
v1.vikunjaInfos: v1.vikunjaInfos:
properties: properties:
available_migrators:
items:
type: string
type: array
frontend_url: frontend_url:
type: string type: string
link_sharing_enabled: link_sharing_enabled:
@ -862,6 +875,8 @@ definitions:
type: string type: string
motd: motd:
type: string type: string
registration_enabled:
type: boolean
version: version:
type: string type: string
type: object type: object
@ -1997,6 +2012,27 @@ paths:
summary: Migrate all lists, tasks etc. from wunderlist summary: Migrate all lists, tasks etc. from wunderlist
tags: tags:
- migration - migration
/migration/wunderlist/status:
get:
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.
produces:
- application/json
responses:
"200":
description: The migration status
schema:
$ref: '#/definitions/migration.Status'
"500":
description: Internal server error
schema:
$ref: '#/definitions/models.Message'
security:
- JWTKeyAuth: []
summary: Get migration status
tags:
- migration
/namespace/{id}: /namespace/{id}:
post: post:
consumes: consumes: