refactor(zone): remove legacy route domain storage

第二阶段清理:删除 of_managed_domains 与 of_proxy_routes 冗余域名/证书列,
移除 ManagedDomain 模型与 API、路由侧 legacy 字段维护,以及前端 WebsiteService。
ImportLegacy 在旧列/旧表缺失时跳过对应源,保持幂等。
This commit is contained in:
ryan
2026-07-12 15:31:01 +08:00
parent fb3dd5afe6
commit 5b52acdd6c
26 changed files with 272 additions and 915 deletions
@@ -75,7 +75,7 @@ func TestBuildSnapshotReadsZoneDomainCertificates(t *testing.T) {
second, err := oftls.CreateCertificate(ctx, oftls.CertificateInput{Name: "second", CertPEM: secondCertPEM, KeyPEM: secondKeyPEM})
require.NoError(t, err)
route := &model.ProxyRoute{SiteName: "tls-site", Domain: "legacy.invalid", OriginURL: "http://origin:8080", Upstreams: `["http://origin:8080"]`, Enabled: true, EnableHTTPS: true}
route := &model.ProxyRoute{SiteName: "tls-site", OriginURL: "http://origin:8080", Upstreams: `["http://origin:8080"]`, Enabled: true, EnableHTTPS: true}
require.NoError(t, model.CreateProxyRouteRecord(ctx, route))
zone := &model.Zone{Domain: "example.com"}
require.NoError(t, db.DB(ctx).Create(zone).Error)
@@ -96,8 +96,6 @@ func TestPublishConfigVersionCreatesVersion(t *testing.T) {
route := &model.ProxyRoute{
SiteName: "publish-site",
Domain: "publish.example.com",
Domains: `["publish.example.com"]`,
OriginURL: "http://origin.publish.example.com:8080",
Upstreams: `["http://origin.publish.example.com:8080"]`,
Enabled: true,
@@ -142,8 +140,6 @@ func TestBuildSnapshotWAFDocumentUsesNormalizedSiteNames(t *testing.T) {
route := &model.ProxyRoute{
SiteName: "example.com",
Domain: "Example.COM",
Domains: `["example.com","www.example.com"]`,
OriginURL: "http://origin.example.com:8080",
Upstreams: `["http://origin.example.com:8080"]`,
Enabled: true,
@@ -204,8 +200,6 @@ func TestBuildCurrentConfigBundleEnablesGlobalPoWWithoutExplicitBinding(t *testi
route := &model.ProxyRoute{
SiteName: "pow-global.example.com",
Domain: "pow-global.example.com",
Domains: `["pow-global.example.com"]`,
OriginURL: "http://origin.example.com:8080",
Upstreams: `["http://origin.example.com:8080"]`,
Enabled: true,
@@ -45,8 +45,6 @@ func TestBuildSnapshotRoutesPages(t *testing.T) {
route := &model.ProxyRoute{
SiteName: "speedtest",
Domain: "speedtest.arctel.net",
Domains: `["speedtest.arctel.net"]`,
OriginURL: "openflare-pages://project/1",
Upstreams: `["openflare-pages://project/1"]`,
Enabled: true,
@@ -145,8 +145,6 @@ func applyProxyRouteUpstreamType(ctx context.Context, route *model.ProxyRoute, u
func updateProxyRouteRecord(tx *gorm.DB, route *model.ProxyRoute) error {
return tx.Model(&model.ProxyRoute{}).Where("id = ?", route.ID).Updates(map[string]any{
"site_name": route.SiteName, "origin_id": route.OriginID, "origin_url": route.OriginURL,
"domain": route.Domain, "domains": route.Domains, "cert_id": route.CertID,
"cert_ids": route.CertIDs, "domain_cert_ids": route.DomainCertIDs,
"origin_host": route.OriginHost, "upstreams": route.Upstreams, "enabled": route.Enabled,
"enable_https": route.EnableHTTPS, "redirect_http": route.RedirectHTTP,
"limit_conn_per_server": route.LimitConnPerServer, "limit_conn_per_ip": route.LimitConnPerIP,
@@ -5,7 +5,6 @@ package proxy_route
import (
"context"
"encoding/json"
"errors"
"strings"
"time"
@@ -253,43 +252,12 @@ func buildProxyRoute(ctx context.Context, route *model.ProxyRoute, input Input)
limitRate,
upstreamType,
)
// Preserve legacy columns until the second-phase schema cleanup. They are
// derived solely from ZoneDomain bindings and are not exposed by this API.
populateLegacyZoneDomainFields(route, domains)
if err := applyProxyRouteUpstreamType(ctx, route, upstreamType, input); err != nil {
return nil, nil, err
}
return route, domains, nil
}
func mustMarshalProxyRouteLegacy(value any) string {
encoded, err := json.Marshal(value)
if err != nil {
panic(err)
}
return string(encoded)
}
func populateLegacyZoneDomainFields(route *model.ProxyRoute, domains []model.ZoneDomain) {
legacyDomains := make([]string, 0, len(domains))
legacyCertIDs := make([]uint, 0, len(domains))
for _, domain := range domains {
legacyDomains = append(legacyDomains, domain.Domain)
if domain.CertID != nil {
legacyCertIDs = append(legacyCertIDs, *domain.CertID)
}
}
route.Domain = legacyDomains[0]
route.Domains = mustMarshalProxyRouteLegacy(legacyDomains)
route.CertIDs = mustMarshalProxyRouteLegacy(legacyCertIDs)
if len(legacyCertIDs) > 0 {
route.CertID = &legacyCertIDs[0]
} else {
route.CertID = nil
}
route.DomainCertIDs = route.CertIDs
}
func buildProxyRouteViews(ctx context.Context, routes []*model.ProxyRoute) ([]*View, error) {
views := make([]*View, 0, len(routes))
for _, route := range routes {
-5
View File
@@ -17,11 +17,6 @@ const (
errCertificateFilesRequired = "certificate file and key file cannot be empty"
errCertificatePEMInvalid = "证书 PEM 内容不合法"
errManagedDomainRequired = "域名不能为空"
errManagedDomainInvalid = "域名格式不合法"
errManagedDomainWildcardInvalid = "通配符域名仅支持 *.example.com 格式"
errManagedDomainExists = "域名已存在"
errManagedDomainCertNotFound = "所选证书不存在"
errDNSAccountInUse = "该 DNS 账号已被证书使用,无法删除"
)
@@ -42,7 +42,6 @@ func setupTLSTestDB(t *testing.T) func() {
&model.TLSCertificate{},
&model.Zone{},
&model.ZoneDomain{},
&model.ManagedDomain{},
&model.DNSAccount{},
&model.AcmeAccount{},
&model.TaskExecution{}, // 异步任务执行记录也需要 migrate
@@ -111,54 +110,6 @@ func generateTestCertificatePair(t *testing.T, dnsNames []string) (string, strin
return string(certPEM), string(keyPEM)
}
func TestCreateManagedDomain(t *testing.T) {
cleanup := setupTLSTestDB(t)
defer cleanup()
ctx := context.Background()
certPEM, keyPEM := generateTestCertificatePair(t, []string{"api.example.com"})
certificate, err := CreateCertificate(ctx, CertificateInput{
Name: "api-cert",
CertPEM: certPEM,
KeyPEM: keyPEM,
})
require.NoError(t, err)
certID := certificate.ID
domain, err := CreateManagedDomain(ctx, ManagedDomainInput{
Domain: "api.example.com",
CertID: &certID,
Enabled: true,
Remark: "primary api",
})
require.NoError(t, err)
assert.NotZero(t, domain.ID)
assert.Equal(t, "api.example.com", domain.Domain)
assert.Equal(t, certID, *domain.CertID)
assert.True(t, domain.Enabled)
assert.Equal(t, "primary api", domain.Remark)
_, err = CreateManagedDomain(ctx, ManagedDomainInput{
Domain: "api.example.com",
Enabled: true,
})
require.Error(t, err)
assert.Equal(t, errManagedDomainExists, err.Error())
}
func TestCreateManagedDomainRejectsInvalidWildcard(t *testing.T) {
cleanup := setupTLSTestDB(t)
defer cleanup()
ctx := context.Background()
_, err := CreateManagedDomain(ctx, ManagedDomainInput{
Domain: "*.*.example.com",
Enabled: true,
})
require.Error(t, err)
assert.Equal(t, errManagedDomainWildcardInvalid, err.Error())
}
func TestCreateCertificateEncryptsPrivateKey(t *testing.T) {
cleanup := setupTLSTestDB(t)
defer cleanup()
@@ -1,242 +0,0 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package tls
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"unicode"
"github.com/Rain-kl/Wavelet/internal/model"
)
const (
managedDomainMatchTypeExact = "exact"
managedDomainMatchTypeWildcard = "wildcard"
maxManagedDomainLength = 253
minManagedDomainLabelCount = 2
)
// ManagedDomainInput 托管域名创建/更新请求。
type ManagedDomainInput struct {
Domain string `json:"domain"`
CertID *uint `json:"cert_id"`
Enabled bool `json:"enabled"`
Remark string `json:"remark"`
}
// ManagedDomainMatchCandidate 证书匹配候选。
type ManagedDomainMatchCandidate struct {
ManagedDomainID uint `json:"managed_domain_id"`
Domain string `json:"domain"`
MatchType string `json:"match_type"`
CertificateID uint `json:"certificate_id"`
CertificateName string `json:"certificate_name"`
}
// ManagedDomainMatchResult 证书匹配结果。
type ManagedDomainMatchResult struct {
Domain string `json:"domain"`
Matched bool `json:"matched"`
Candidate *ManagedDomainMatchCandidate `json:"candidate,omitempty"`
Candidates []ManagedDomainMatchCandidate `json:"candidates"`
}
// ListManagedDomains 列出托管域名。
func ListManagedDomains(ctx context.Context) ([]model.ManagedDomain, error) {
return model.ListManagedDomains(ctx)
}
// CreateManagedDomain 创建托管域名。
func CreateManagedDomain(ctx context.Context, input ManagedDomainInput) (*model.ManagedDomain, error) {
domain, err := buildManagedDomain(ctx, nil, input)
if err != nil {
return nil, err
}
if err = model.CreateManagedDomainRecord(ctx, domain); err != nil {
if isUniqueConstraintError(err) {
return nil, errors.New(errManagedDomainExists)
}
return nil, err
}
return domain, nil
}
// UpdateManagedDomain 更新托管域名。
func UpdateManagedDomain(ctx context.Context, id uint, input ManagedDomainInput) (*model.ManagedDomain, error) {
domain, err := model.GetManagedDomainByID(ctx, id)
if err != nil {
return nil, err
}
domain, err = buildManagedDomain(ctx, domain, input)
if err != nil {
return nil, err
}
if err = model.SaveManagedDomain(ctx, domain); err != nil {
if isUniqueConstraintError(err) {
return nil, errors.New(errManagedDomainExists)
}
return nil, err
}
return domain, nil
}
// DeleteManagedDomain 删除托管域名。
func DeleteManagedDomain(ctx context.Context, id uint) error {
if _, err := model.GetManagedDomainByID(ctx, id); err != nil {
return err
}
return model.DeleteManagedDomainRecord(ctx, id)
}
// MatchManagedDomainCertificate 为域名匹配证书。
func MatchManagedDomainCertificate(ctx context.Context, rawDomain string) (*ManagedDomainMatchResult, error) {
domain := normalizeManagedDomain(rawDomain)
if err := validateManagedDomainPattern(domain); err != nil {
return nil, err
}
managedDomains, err := model.ListEnabledManagedDomainsWithCertificate(ctx)
if err != nil {
return nil, err
}
candidates := make([]ManagedDomainMatchCandidate, 0)
for _, item := range managedDomains {
if item.CertID == nil || *item.CertID == 0 {
continue
}
matchType := detectManagedDomainMatchType(item.Domain, domain)
if matchType == "" {
continue
}
certificate, err := model.GetTLSCertificateByID(ctx, *item.CertID)
if err != nil {
return nil, fmt.Errorf("托管域名 %s 关联证书不存在", item.Domain)
}
candidates = append(candidates, ManagedDomainMatchCandidate{
ManagedDomainID: item.ID,
Domain: item.Domain,
MatchType: matchType,
CertificateID: certificate.ID,
CertificateName: certificate.Name,
})
}
sortManagedDomainCandidates(candidates)
result := &ManagedDomainMatchResult{
Domain: domain,
Matched: len(candidates) > 0,
Candidates: candidates,
}
if len(candidates) > 0 {
candidate := candidates[0]
result.Candidate = &candidate
}
return result, nil
}
func buildManagedDomain(ctx context.Context, existing *model.ManagedDomain, input ManagedDomainInput) (*model.ManagedDomain, error) {
domain := normalizeManagedDomain(input.Domain)
remark := strings.TrimSpace(input.Remark)
if err := validateManagedDomainPattern(domain); err != nil {
return nil, err
}
if input.CertID != nil && *input.CertID != 0 {
if _, err := model.GetTLSCertificateByID(ctx, *input.CertID); err != nil {
return nil, errors.New(errManagedDomainCertNotFound)
}
} else {
input.CertID = nil
}
if existing == nil {
existing = &model.ManagedDomain{}
}
existing.Domain = domain
existing.CertID = input.CertID
existing.Enabled = input.Enabled
existing.Remark = remark
return existing, nil
}
func normalizeManagedDomain(domain string) string {
return strings.ToLower(strings.TrimSpace(domain))
}
func validateManagedDomainPattern(domain string) error {
if domain == "" {
return errors.New(errManagedDomainRequired)
}
if strings.Contains(domain, "://") || strings.Contains(domain, "/") {
return errors.New(errManagedDomainInvalid)
}
if strings.Contains(domain, "*") {
if !strings.HasPrefix(domain, "*.") || strings.Count(domain, "*") != 1 {
return errors.New(errManagedDomainWildcardInvalid)
}
return validateHostname(strings.TrimPrefix(domain, "*."))
}
return validateHostname(domain)
}
func validateHostname(domain string) error {
if domain == "" {
return errors.New(errManagedDomainRequired)
}
if len(domain) > maxManagedDomainLength {
return errors.New(errManagedDomainInvalid)
}
labels := strings.Split(domain, ".")
if len(labels) < minManagedDomainLabelCount {
return errors.New(errManagedDomainInvalid)
}
for _, label := range labels {
if len(label) == 0 || len(label) > 63 {
return errors.New(errManagedDomainInvalid)
}
if label[0] == '-' || label[len(label)-1] == '-' {
return errors.New(errManagedDomainInvalid)
}
for _, r := range label {
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-' {
continue
}
return errors.New(errManagedDomainInvalid)
}
}
return nil
}
func detectManagedDomainMatchType(pattern string, domain string) string {
if pattern == domain {
return managedDomainMatchTypeExact
}
if !strings.HasPrefix(pattern, "*.") {
return ""
}
suffix := strings.TrimPrefix(pattern, "*.")
if !strings.HasSuffix(domain, "."+suffix) {
return ""
}
prefix := strings.TrimSuffix(domain, "."+suffix)
if prefix == "" || strings.Contains(prefix, ".") {
return ""
}
return managedDomainMatchTypeWildcard
}
func sortManagedDomainCandidates(candidates []ManagedDomainMatchCandidate) {
sort.Slice(candidates, func(i int, j int) bool {
left := candidates[i]
right := candidates[j]
if left.MatchType != right.MatchType {
return left.MatchType == managedDomainMatchTypeExact
}
if len(left.Domain) != len(right.Domain) {
return len(left.Domain) > len(right.Domain)
}
return left.ManagedDomainID < right.ManagedDomainID
})
}
+4 -125
View File
@@ -4,12 +4,11 @@
package tls
import (
"net/http"
"strings"
"net/http"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/apiutil"
"github.com/Rain-kl/Wavelet/internal/common/response"
"github.com/gin-gonic/gin"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/apiutil"
"github.com/Rain-kl/Wavelet/internal/common/response"
"github.com/gin-gonic/gin"
)
@@ -328,126 +327,6 @@ func RenewCertificateHandler(c *gin.Context) {
c.JSON(http.StatusOK, response.OK(certificate))
}
// GetManagedDomains 列出托管域名。
// @Summary 列出托管域名
// @Description 返回全部托管域名及关联证书,需要管理员权限
// @Tags openflare-tls
// @Produce json
// @Security SessionCookie
// @Success 200 {object} response.Any{data=[]model.ManagedDomain} "托管域名列表"
// @Failure 400 {object} response.Any "参数错误"
// @Failure 401 {object} response.Any "未登录"
// @Failure 404 {object} response.Any "无权限或不存在"
// @Failure 500 {object} response.Any "内部错误"
func GetManagedDomains(c *gin.Context) {
domains, err := ListManagedDomains(c.Request.Context())
if handleLogicError(c, err) {
return
}
c.JSON(http.StatusOK, response.OK(domains))
}
// CreateManagedDomainHandler 创建托管域名。
// @Summary 创建托管域名
// @Description 创建新的托管域名记录,需要管理员权限
// @Tags openflare-tls
// @Accept json
// @Produce json
// @Security SessionCookie
// @Param request body tls.ManagedDomainInput true "托管域名参数"
// @Success 200 {object} response.Any{data=model.ManagedDomain} "创建成功的托管域名"
// @Failure 400 {object} response.Any "参数错误"
// @Failure 401 {object} response.Any "未登录"
// @Failure 404 {object} response.Any "无权限或不存在"
// @Failure 500 {object} response.Any "内部错误"
func CreateManagedDomainHandler(c *gin.Context) {
var input ManagedDomainInput
if !apiutil.BindJSON(c, &input) {
return
}
domain, err := CreateManagedDomain(c.Request.Context(), input)
if handleLogicError(c, err) {
return
}
c.JSON(http.StatusOK, response.OK(domain))
}
// UpdateManagedDomainHandler 更新托管域名。
// @Summary 更新托管域名
// @Description 按 ID 更新托管域名,需要管理员权限
// @Tags openflare-tls
// @Accept json
// @Produce json
// @Security SessionCookie
// @Param id path int true "托管域名 ID"
// @Param request body tls.ManagedDomainInput true "托管域名参数"
// @Success 200 {object} response.Any{data=model.ManagedDomain} "更新后的托管域名"
// @Failure 400 {object} response.Any "参数错误"
// @Failure 401 {object} response.Any "未登录"
// @Failure 404 {object} response.Any "无权限或不存在"
// @Failure 404 {object} response.Any "记录不存在"
// @Failure 500 {object} response.Any "内部错误"
func UpdateManagedDomainHandler(c *gin.Context) {
id, ok := apiutil.IDParam(c)
if !ok {
return
}
var input ManagedDomainInput
if !apiutil.BindJSON(c, &input) {
return
}
domain, err := UpdateManagedDomain(c.Request.Context(), id, input)
if handleLogicError(c, err) {
return
}
c.JSON(http.StatusOK, response.OK(domain))
}
// DeleteManagedDomainHandler 删除托管域名。
// @Summary 删除托管域名
// @Description 按 ID 删除托管域名,需要管理员权限
// @Tags openflare-tls
// @Produce json
// @Security SessionCookie
// @Param id path int true "托管域名 ID"
// @Success 200 {object} response.Any "删除成功"
// @Failure 400 {object} response.Any "参数错误"
// @Failure 401 {object} response.Any "未登录"
// @Failure 404 {object} response.Any "无权限或不存在"
// @Failure 404 {object} response.Any "记录不存在"
// @Failure 500 {object} response.Any "内部错误"
func DeleteManagedDomainHandler(c *gin.Context) {
id, ok := apiutil.IDParam(c)
if !ok {
return
}
if err := DeleteManagedDomain(c.Request.Context(), id); handleLogicError(c, err) {
return
}
c.JSON(http.StatusOK, response.OKNil())
}
// MatchManagedDomainCertificateHandler 匹配域名证书。
// @Summary 匹配托管域名证书
// @Description 按域名查询可用的证书匹配候选,需要管理员权限
// @Tags openflare-tls
// @Produce json
// @Security SessionCookie
// @Param domain query string true "域名"
// @Success 200 {object} response.Any{data=tls.ManagedDomainMatchResult} "证书匹配结果"
// @Failure 400 {object} response.Any "参数错误"
// @Failure 401 {object} response.Any "未登录"
// @Failure 404 {object} response.Any "无权限或不存在"
// @Failure 500 {object} response.Any "内部错误"
func MatchManagedDomainCertificateHandler(c *gin.Context) {
domain := strings.TrimSpace(c.Query("domain"))
result, err := MatchManagedDomainCertificate(c.Request.Context(), domain)
if handleLogicError(c, err) {
return
}
c.JSON(http.StatusOK, response.OK(result))
}
// GetDNSAccounts 列出 DNS 账号。
// @Summary 列出 DNS 账号
// @Description 返回全部 DNS 提供商账号,需要管理员权限
@@ -200,24 +200,18 @@ func TestSyncToUptimeKumaSuccess(t *testing.T) {
routeA := &model.ProxyRoute{
SiteName: "site-a",
Domain: "site-a.com",
Domains: `["site-a.com"]`,
OriginURL: "http://10.0.0.1",
Enabled: true,
EnableHTTPS: false,
}
routeB := &model.ProxyRoute{
SiteName: "site-b",
Domain: "site-b.com",
Domains: `["site-b.com"]`,
OriginURL: "https://10.0.0.2",
Enabled: true,
EnableHTTPS: true,
}
routeC := &model.ProxyRoute{
SiteName: "site-c",
Domain: "site-c.com",
Domains: `["site-c.com"]`,
OriginURL: "http://10.0.0.3",
Enabled: false,
EnableHTTPS: false,
@@ -315,16 +309,12 @@ func TestSyncToUptimeKumaSelectedScope(t *testing.T) {
routeA := &model.ProxyRoute{
SiteName: "site-a",
Domain: "site-a.com",
Domains: `["site-a.com"]`,
OriginURL: "http://10.0.0.1",
Enabled: true,
EnableHTTPS: false,
}
routeB := &model.ProxyRoute{
SiteName: "site-b",
Domain: "site-b.com",
Domains: `["site-b.com"]`,
OriginURL: "http://10.0.0.2",
Enabled: true,
EnableHTTPS: false,
+58 -26
View File
@@ -36,8 +36,24 @@ type legacyDomain struct {
Remark string
}
// ImportLegacy imports legacy proxy-route names first, and managed domains only when routes contain no domains.
// ImportLegacy imports legacy records atomically after collecting validation conflicts.
// legacyRouteRow reads pre-cleanup of_proxy_routes columns via raw scan.
type legacyRouteRow struct {
ID uint `gorm:"column:id"`
Domain string `gorm:"column:domain"`
Domains string `gorm:"column:domains"`
DomainCertIDs string `gorm:"column:domain_cert_ids"`
Remark string `gorm:"column:remark"`
}
// legacyManagedRow reads of_managed_domains while the table still exists.
type legacyManagedRow struct {
Domain string `gorm:"column:domain"`
CertID *uint `gorm:"column:cert_id"`
Remark string `gorm:"column:remark"`
}
// ImportLegacy imports legacy proxy-route / managed-domain rows into Zone tables.
// After the phase-2 schema cleanup, missing legacy columns or tables are skipped.
//
//nolint:cyclop // the transactional importer intentionally validates every legacy source in one pass.
func ImportLegacy(ctx context.Context) (report ImportReport, resultErr error) {
@@ -46,40 +62,50 @@ func ImportLegacy(ctx context.Context) (report ImportReport, resultErr error) {
return report, fmt.Errorf("database is not initialized")
}
resultErr = conn.Transaction(func(tx *gorm.DB) error {
var routes []model.ProxyRoute
if err := tx.Find(&routes).Error; err != nil {
return err
}
items := make([]legacyDomain, 0)
hasRouteDomains := false
for _, route := range routes {
domains, err := routeidentity.DecodeDomains(route.Domains, route.Domain)
if err != nil {
report.Conflicts = append(report.Conflicts, fmt.Sprintf("route %d: %v", route.ID, err))
continue
if tx.Migrator().HasColumn("of_proxy_routes", "domain") &&
tx.Migrator().HasColumn("of_proxy_routes", "domains") {
var routes []legacyRouteRow
if err := tx.Table("of_proxy_routes").
Select("id, domain, domains, domain_cert_ids, remark").
Find(&routes).Error; err != nil {
return err
}
if len(domains) > 0 {
hasRouteDomains = true
}
certIDs := decodeLegacyCertIDs(route.DomainCertIDs, len(domains))
for i, domain := range domains {
var certID *uint
if i < len(certIDs) && certIDs[i] > 0 {
v := certIDs[i]
certID = &v
for _, route := range routes {
domains, err := routeidentity.DecodeDomains(route.Domains, route.Domain)
if err != nil {
report.Conflicts = append(report.Conflicts, fmt.Sprintf("route %d: %v", route.ID, err))
continue
}
if len(domains) > 0 {
hasRouteDomains = true
}
certIDs := decodeLegacyCertIDs(route.DomainCertIDs, len(domains))
for i, domain := range domains {
var certID *uint
if i < len(certIDs) && certIDs[i] > 0 {
v := certIDs[i]
certID = &v
}
items = append(items, legacyDomain{Domain: domain, CertID: certID, Remark: route.Remark})
}
items = append(items, legacyDomain{Domain: domain, CertID: certID, Remark: route.Remark})
}
}
if !hasRouteDomains {
var legacy []model.ManagedDomain
if err := tx.Find(&legacy).Error; err != nil {
if !hasRouteDomains && tx.Migrator().HasTable("of_managed_domains") {
var legacy []legacyManagedRow
if err := tx.Table("of_managed_domains").
Select("domain, cert_id, remark").
Find(&legacy).Error; err != nil {
return err
}
for _, item := range legacy {
items = append(items, legacyDomain{Domain: item.Domain, CertID: item.CertID, Remark: item.Remark})
items = append(items, legacyDomain(item))
}
}
for _, item := range items {
domain, err := normalizeDomain(item.Domain)
if err != nil {
@@ -121,7 +147,12 @@ func ImportLegacy(ctx context.Context) (report ImportReport, resultErr error) {
continue
}
}
if err = tx.Create(&model.ZoneDomain{ZoneID: zone.ID, Domain: domain, CertID: item.CertID, Remark: item.Remark}).Error; err != nil {
if err = tx.Create(&model.ZoneDomain{
ZoneID: zone.ID,
Domain: domain,
CertID: item.CertID,
Remark: item.Remark,
}).Error; err != nil {
return err
}
report.Domains++
@@ -144,4 +175,5 @@ func decodeLegacyCertIDs(raw string, count int) []uint {
}
return values
}
func isNotFound(err error) bool { return err == gorm.ErrRecordNotFound }