Smail/backend/controllers/database_migration.go
smail f60d3b0880
Some checks are pending
Security Checks / security (push) Waiting to run
整理前后端与测试构建目录并完善配置和安全引导
统一 frontend、backend、test、test-log 和 out 目录,更新构建、部署及测试入口;纳入配置管理兼容与首次安全引导修复;排除本机配置、密钥和测试产物。
2026-09-05 19:19:52 +08:00

103 lines
3.3 KiB
Go

package controllers
import (
"net/http"
"strings"
"git.pcux.cn/xuans/Smail/config"
"git.pcux.cn/xuans/Smail/dto/response"
"git.pcux.cn/xuans/Smail/services/access"
"git.pcux.cn/xuans/Smail/services/dbmigration"
appcontext "git.pcux.cn/xuans/Smail/utils/context"
)
type databaseMigrationPreviewRequest struct {
TargetName string `json:"targetName"`
ExpectedGeneration uint64 `json:"expectedGeneration"`
}
type databaseMigrationExecuteRequest struct {
ID string `json:"id"`
Token string `json:"token"`
}
// PreviewDatabaseMigration 校验只改数据库名称的迁移计划,不连接或修改目标数据库。
func PreviewDatabaseMigration(ctx *appcontext.Context, w http.ResponseWriter, req *http.Request) {
if err := authorizeCapability(ctx, access.CapabilityDatabaseMigrate, "", false); err != nil {
writeConfigAccessError(w, err)
return
}
manager, ok := configManagerOrError(w)
if !ok {
return
}
service := dbmigration.Current()
if service == nil {
writeConfigError(w, dbmigration.ErrExecutorUnavailable)
return
}
var input databaseMigrationPreviewRequest
if err := decodeJSONRequest(w, req, &input); err != nil || input.ExpectedGeneration == 0 {
writeConfigError(w, dbmigration.ErrInvalidRequest)
return
}
status := manager.Status()
if input.ExpectedGeneration != status.ActiveGeneration {
writeConfigError(w, config.ErrGenerationConflict)
return
}
record, token, err := service.Preview(req.Context(), manager.Snapshot().Root.Database, status.ActiveGeneration, input.TargetName)
if err != nil {
writeConfigError(w, err)
return
}
response.NewSuccessResponse(map[string]any{"migration": record, "token": token}).FPrint(w)
}
// ExecuteDatabaseMigration 执行预览票据对应的固定迁移计划,不接受 DSN、SQL 或连接参数。
func ExecuteDatabaseMigration(ctx *appcontext.Context, w http.ResponseWriter, req *http.Request) {
if err := authorizeCapability(ctx, access.CapabilityDatabaseMigrate, "", false); err != nil {
writeConfigAccessError(w, err)
return
}
if dbmigration.Current() == nil {
writeConfigError(w, dbmigration.ErrExecutorUnavailable)
return
}
var input databaseMigrationExecuteRequest
if err := decodeJSONRequest(w, req, &input); err != nil {
writeConfigError(w, dbmigration.ErrInvalidRequest)
return
}
record, err := dbmigration.Current().ExecuteAuthorized(req.Context(), input.ID, input.Token, configActor(ctx, req))
if err != nil {
writeConfigError(w, err)
return
}
response.NewSuccessResponse(record).FPrint(w)
}
// GetDatabaseMigration 返回迁移状态,不返回密码、密钥或底层数据库错误。
func GetDatabaseMigration(ctx *appcontext.Context, w http.ResponseWriter, req *http.Request) {
if err := authorizeCapability(ctx, access.CapabilityDatabaseMigrate, "", false); err != nil {
writeConfigAccessError(w, err)
return
}
service := dbmigration.Current()
if service == nil {
writeConfigError(w, dbmigration.ErrExecutorUnavailable)
return
}
parts := strings.Split(strings.Trim(req.URL.Path, "/"), "/")
if len(parts) != 5 || parts[0] != "api" || parts[1] != "v1" || parts[2] != "database" || parts[3] != "migrations" || strings.TrimSpace(parts[4]) == "" {
writeConfigError(w, dbmigration.ErrInvalidRequest)
return
}
record, err := service.Get(parts[4])
if err != nil {
writeConfigError(w, err)
return
}
response.NewSuccessResponse(record).FPrint(w)
}