refactor(migration): extract file/CSV migrate orchestration into shared funcs
Pull the StartMigration -> Migrate -> FinishMigration orchestration out of the v1 echo handlers into handler.RunFileMigration and csv.RunMigration so the v2 API can reuse the exact same business logic. v1 is refactored onto them and stays byte-identical on the wire. Also tag the CSV detect/preview/config DTOs with doc:/enum: so they carry descriptions in the v2 OpenAPI schema (ignored by v1 swaggo/xorm).
This commit is contained in:
parent
3af5eb8208
commit
a881246e80
|
|
@ -107,28 +107,28 @@ var AllTaskAttributes = []TaskAttribute{
|
|||
|
||||
// ColumnMapping represents a mapping from a CSV column to a task attribute
|
||||
type ColumnMapping struct {
|
||||
ColumnIndex int `json:"column_index"`
|
||||
ColumnName string `json:"column_name"`
|
||||
Attribute TaskAttribute `json:"attribute"`
|
||||
ColumnIndex int `json:"column_index" doc:"The zero-based index of the CSV column this mapping applies to."`
|
||||
ColumnName string `json:"column_name" doc:"The header name of the CSV column, for display."`
|
||||
Attribute TaskAttribute `json:"attribute" enum:"title,description,due_date,start_date,end_date,done,priority,labels,project,reminder,ignore" doc:"The task attribute the column maps to. Use \"ignore\" to drop the column."`
|
||||
}
|
||||
|
||||
// DetectionResult contains the auto-detected CSV structure
|
||||
type DetectionResult struct {
|
||||
Columns []string `json:"columns"`
|
||||
Delimiter string `json:"delimiter"`
|
||||
QuoteChar string `json:"quote_char"`
|
||||
DateFormat string `json:"date_format"`
|
||||
SuggestedMapping []ColumnMapping `json:"suggested_mapping"`
|
||||
PreviewRows [][]string `json:"preview_rows"`
|
||||
Columns []string `json:"columns" doc:"The detected column header names, in order."`
|
||||
Delimiter string `json:"delimiter" doc:"The detected field delimiter (one of \",\", \";\", tab, \"|\")."`
|
||||
QuoteChar string `json:"quote_char" doc:"The detected quote character."`
|
||||
DateFormat string `json:"date_format" doc:"The detected Go reference date layout used to parse date columns."`
|
||||
SuggestedMapping []ColumnMapping `json:"suggested_mapping" doc:"A best-guess column-to-attribute mapping; the client may edit it before previewing or migrating."`
|
||||
PreviewRows [][]string `json:"preview_rows" doc:"The first few raw rows of the file, for the client to render a preview."`
|
||||
}
|
||||
|
||||
// ImportConfig contains the configuration for CSV import
|
||||
type ImportConfig struct {
|
||||
Delimiter string `json:"delimiter"`
|
||||
QuoteChar string `json:"quote_char"`
|
||||
DateFormat string `json:"date_format"`
|
||||
SkipRows int `json:"skip_rows"`
|
||||
Mapping []ColumnMapping `json:"mapping"`
|
||||
Delimiter string `json:"delimiter" doc:"The field delimiter to parse with. Defaults to comma when empty."`
|
||||
QuoteChar string `json:"quote_char" doc:"The quote character to parse with."`
|
||||
DateFormat string `json:"date_format" doc:"The Go reference date layout used to parse date columns."`
|
||||
SkipRows int `json:"skip_rows" doc:"Number of leading rows to skip (e.g. a header row) before importing."`
|
||||
Mapping []ColumnMapping `json:"mapping" doc:"The column-to-attribute mappings that drive the import."`
|
||||
}
|
||||
|
||||
// PreviewTask represents a task preview before import
|
||||
|
|
@ -146,8 +146,8 @@ type PreviewTask struct {
|
|||
|
||||
// PreviewResult contains preview data before import
|
||||
type PreviewResult struct {
|
||||
Tasks []PreviewTask `json:"tasks"`
|
||||
TotalRows int `json:"total_rows"`
|
||||
Tasks []PreviewTask `json:"tasks" doc:"The first few tasks that would be imported with the given config."`
|
||||
TotalRows int `json:"total_rows" doc:"The total number of data rows in the file."`
|
||||
}
|
||||
|
||||
// stripBOM removes the UTF-8 BOM from the beginning of a reader
|
||||
|
|
@ -557,6 +557,22 @@ func (m *Migrator) Migrate(_ *user.User, _ io.ReaderAt, _ int64) error {
|
|||
return &migration.ErrCSVConfigRequired{}
|
||||
}
|
||||
|
||||
// RunMigration records the migration's start, imports the CSV with the given
|
||||
// config and records its finish. Shared by the v1 and v2 HTTP layers so the
|
||||
// status bookkeeping around MigrateWithConfig lives in one place.
|
||||
func RunMigration(u *user.User, file io.ReaderAt, size int64, config *ImportConfig) error {
|
||||
status, err := migration.StartMigration(&Migrator{}, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := MigrateWithConfig(u, file, size, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return migration.FinishMigration(status)
|
||||
}
|
||||
|
||||
// MigrateWithConfig imports CSV data into Vikunja with the provided configuration
|
||||
func MigrateWithConfig(u *user.User, file io.ReaderAt, size int64, config *ImportConfig) error {
|
||||
if size == 0 {
|
||||
|
|
|
|||
|
|
@ -186,19 +186,7 @@ func (c *MigratorWeb) Migrate(ctx *echo.Context) error {
|
|||
}
|
||||
defer src.Close()
|
||||
|
||||
m := &Migrator{}
|
||||
status, err := migration.StartMigration(m, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = MigrateWithConfig(u, src, file.Size, &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = migration.FinishMigration(status)
|
||||
if err != nil {
|
||||
if err := RunMigration(u, src, file.Size, &config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
|
@ -36,6 +37,22 @@ func (fw *FileMigratorWeb) RegisterRoutes(g *echo.Group) {
|
|||
g.PUT("/"+ms.Name()+"/migrate", fw.Migrate)
|
||||
}
|
||||
|
||||
// RunFileMigration records the migration's start, runs the file migrator and
|
||||
// records its finish. Shared by the v1 and v2 HTTP layers so the orchestration
|
||||
// lives in one place; the caller supplies the already-opened upload.
|
||||
func RunFileMigration(ms migration.FileMigrator, u *user2.User, file io.ReaderAt, size int64) error {
|
||||
m, err := migration.StartMigration(ms, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ms.Migrate(u, file, size); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return migration.FinishMigration(m)
|
||||
}
|
||||
|
||||
// Migrate calls the migration method
|
||||
func (fw *FileMigratorWeb) Migrate(c *echo.Context) error {
|
||||
ms := fw.MigrationStruct()
|
||||
|
|
@ -56,19 +73,7 @@ func (fw *FileMigratorWeb) Migrate(c *echo.Context) error {
|
|||
}
|
||||
defer src.Close()
|
||||
|
||||
m, err := migration.StartMigration(ms, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Do the migration
|
||||
err = ms.Migrate(user, src, file.Size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = migration.FinishMigration(m)
|
||||
if err != nil {
|
||||
if err := RunFileMigration(ms, user, src, file.Size); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue