mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-07-16 14:52:35 -05:00
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).
195 lines
5.9 KiB
Go
195 lines
5.9 KiB
Go
// 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 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 Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package csv
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"code.vikunja.io/api/pkg/models"
|
|
"code.vikunja.io/api/pkg/modules/migration"
|
|
user2 "code.vikunja.io/api/pkg/user"
|
|
"github.com/labstack/echo/v5"
|
|
)
|
|
|
|
// MigratorWeb handles CSV migration HTTP routes
|
|
type MigratorWeb struct{}
|
|
|
|
// RegisterRoutes registers all CSV migration routes
|
|
func (c *MigratorWeb) RegisterRoutes(g *echo.Group) {
|
|
g.GET("/csv/status", c.Status)
|
|
g.PUT("/csv/detect", c.Detect)
|
|
g.PUT("/csv/preview", c.Preview)
|
|
g.PUT("/csv/migrate", c.Migrate)
|
|
}
|
|
|
|
// Status returns the migration status
|
|
// @Summary Get CSV migration status
|
|
// @Description Returns if the current user already did the CSV migration or not.
|
|
// @tags migration
|
|
// @Produce json
|
|
// @Security JWTKeyAuth
|
|
// @Success 200 {object} migration.Status "The migration status"
|
|
// @Failure 500 {object} models.Message "Internal server error"
|
|
// @Router /migration/csv/status [get]
|
|
func (c *MigratorWeb) Status(ctx *echo.Context) error {
|
|
u, err := user2.GetCurrentUser(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m := &Migrator{}
|
|
s, err := migration.GetMigrationStatus(m, u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, s)
|
|
}
|
|
|
|
// Detect analyzes a CSV file and returns detection results
|
|
// @Summary Detect CSV structure
|
|
// @Description Analyzes a CSV file and returns auto-detected columns, delimiter, quote character, and date format with suggested column mappings.
|
|
// @tags migration
|
|
// @Accept multipart/form-data
|
|
// @Produce json
|
|
// @Security JWTKeyAuth
|
|
// @Param import formData file true "The CSV file to analyze"
|
|
// @Success 200 {object} DetectionResult "Detection results with suggested mappings"
|
|
// @Failure 400 {object} models.Message "Invalid CSV file"
|
|
// @Failure 500 {object} models.Message "Internal server error"
|
|
// @Router /migration/csv/detect [put]
|
|
func (c *MigratorWeb) Detect(ctx *echo.Context) error {
|
|
_, err := user2.GetCurrentUser(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
file, err := ctx.FormFile("import")
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "No file provided")
|
|
}
|
|
|
|
src, err := file.Open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer src.Close()
|
|
|
|
result, err := DetectCSVStructure(src, file.Size)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// Preview generates a preview of the import
|
|
// @Summary Preview CSV import
|
|
// @Description Generates a preview of the first 5 tasks that would be imported with the given configuration.
|
|
// @tags migration
|
|
// @Accept multipart/form-data
|
|
// @Produce json
|
|
// @Security JWTKeyAuth
|
|
// @Param import formData file true "The CSV file to preview"
|
|
// @Param config formData string true "The import configuration JSON"
|
|
// @Success 200 {object} PreviewResult "Preview of tasks to import"
|
|
// @Failure 400 {object} models.Message "Invalid CSV file or configuration"
|
|
// @Failure 500 {object} models.Message "Internal server error"
|
|
// @Router /migration/csv/preview [put]
|
|
func (c *MigratorWeb) Preview(ctx *echo.Context) error {
|
|
_, err := user2.GetCurrentUser(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
file, err := ctx.FormFile("import")
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "No file provided")
|
|
}
|
|
|
|
configStr := ctx.FormValue("config")
|
|
if configStr == "" {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "No configuration provided")
|
|
}
|
|
|
|
var config ImportConfig
|
|
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid configuration: "+err.Error())
|
|
}
|
|
|
|
src, err := file.Open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer src.Close()
|
|
|
|
result, err := PreviewImport(src, file.Size, &config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// Migrate imports the CSV file
|
|
// @Summary Import CSV file
|
|
// @Description Imports tasks from a CSV file into Vikunja with the provided configuration.
|
|
// @tags migration
|
|
// @Accept multipart/form-data
|
|
// @Produce json
|
|
// @Security JWTKeyAuth
|
|
// @Param import formData file true "The CSV file to import"
|
|
// @Param config formData string true "The import configuration JSON"
|
|
// @Success 200 {object} models.Message "A message telling you everything was migrated successfully."
|
|
// @Failure 400 {object} models.Message "Invalid CSV file or configuration"
|
|
// @Failure 500 {object} models.Message "Internal server error"
|
|
// @Router /migration/csv/migrate [put]
|
|
func (c *MigratorWeb) Migrate(ctx *echo.Context) error {
|
|
u, err := user2.GetCurrentUser(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
file, err := ctx.FormFile("import")
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "No file provided")
|
|
}
|
|
|
|
configStr := ctx.FormValue("config")
|
|
if configStr == "" {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "No configuration provided")
|
|
}
|
|
|
|
var config ImportConfig
|
|
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid configuration: "+err.Error())
|
|
}
|
|
|
|
src, err := file.Open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer src.Close()
|
|
|
|
if err := RunMigration(u, src, file.Size, &config); err != nil {
|
|
return err
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, models.Message{Message: "Everything was migrated successfully."})
|
|
}
|